From 855ecdf1a736e65a2efc6461ec9fa7aa2c803870 Mon Sep 17 00:00:00 2001 From: pbatko-da Date: Mon, 20 Sep 2021 12:17:00 +0200 Subject: [PATCH] [DPP-572] Add ledger API test case for verifying conformance to `--min-tls-version` flag. (#10898) Add CLI flag to select minimum enabled TLS version for participant server CHANGELOG_BEGIN Sandbox: Add CLI flag `--min-tls-version` to select minimum enabled TLS version for participant server. CHANGELOG_END --- ledger/ledger-api-test-tool/BUILD.bazel | 8 ++ .../com/daml/ledger/api/testtool/Cli.scala | 52 +++---- .../com/daml/ledger/api/testtool/Config.scala | 16 ++- .../api/testtool/LedgerApiTestTool.scala | 34 +++-- .../infrastructure/ChannelEndpoint.scala | 27 ++++ .../infrastructure/LedgerSession.scala | 16 ++- .../LedgerTestCasesRunner.scala | 32 +++-- .../infrastructure/LedgerTestContext.scala | 9 +- .../infrastructure/LedgerTestSuite.scala | 1 + .../participant/ParticipantSession.scala | 45 ++++-- .../participant/ParticipantTestContext.scala | 5 +- .../testtool/suites/TLSOnePointThreeIT.scala | 128 ++++++++++++++++++ .../ledger/api/testtool/tests/Tests.scala | 1 + .../platform/apiserver/ApiServices.scala | 1 - .../NonRepudiationProxyConformance.scala | 7 +- 15 files changed, 304 insertions(+), 78 deletions(-) create mode 100644 ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/ChannelEndpoint.scala create mode 100644 ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/suites/TLSOnePointThreeIT.scala diff --git a/ledger/ledger-api-test-tool/BUILD.bazel b/ledger/ledger-api-test-tool/BUILD.bazel index e08a6d5dde..1d3a2a2573 100644 --- a/ledger/ledger-api-test-tool/BUILD.bazel +++ b/ledger/ledger-api-test-tool/BUILD.bazel @@ -91,6 +91,7 @@ da_scala_binary( "//ledger/test-common:model-tests-%s.scala" % lf_version, "//ledger/test-common:dar-files-%s-lib" % lf_version, "//ledger-api/grpc-definitions:ledger_api_proto_scala", + "//ledger/ledger-api-common", "//libs-scala/build-info", "//libs-scala/grpc-utils", "//libs-scala/resources", @@ -124,6 +125,10 @@ da_scala_binary( "//daml-lf/data", "//language-support/scala/bindings", "//ledger/ledger-api-common", + "//ledger/ledger-resources", + "//libs-scala/resources", + "//libs-scala/resources-akka", + "//libs-scala/resources-grpc", "//ledger/test-common:test-common-%s" % lf_version, "//ledger/test-common:package_management-tests-%s.scala" % lf_version, "//ledger/test-common:model-tests-%s.scala" % lf_version, @@ -134,6 +139,8 @@ da_scala_binary( "//libs-scala/timer-utils", "@maven//:io_grpc_grpc_api", "@maven//:io_grpc_grpc_context", + "@maven//:io_grpc_grpc_netty", + "@maven//:io_netty_netty_handler", "@maven//:org_slf4j_slf4j_api", ], ), @@ -245,6 +252,7 @@ conformance_test( # Retired tests will be eventually removed. "--additional=LotsOfPartiesIT", "--additional=TransactionScaleIT", + "--additional=TLSOnePointThreeIT", "--exclude=CommandDeduplicationIT", # Makes sure that deprecated CLI options can still be used to make sure existing CI pipelines are not broken. # This test should fail if any deprecated CLI option has any effect whatsoever -- they are preserved diff --git a/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/Cli.scala b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/Cli.scala index 24b3fb096b..fb9d0f8534 100644 --- a/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/Cli.scala +++ b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/Cli.scala @@ -73,11 +73,13 @@ object Cli { head( """The Ledger API Test Tool is a command line tool for testing the correctness of - |ledger implementations based on Daml and Ledger API.""".stripMargin + |ledger implementations based on Daml and Ledger API.""".stripMargin ) arg[(String, Int)]("[endpoints...]")(endpointRead) - .action((address, config) => config.copy(participants = config.participants :+ address)) + .action((address, config) => + config.copy(participantsEndpoints = config.participantsEndpoints :+ address) + ) .unbounded() .optional() .text("Addresses of the participants to test, specified as `:`.") @@ -105,7 +107,7 @@ object Cli { .optional() .text( """TLS: The crt file to be used as the cert chain. - |Required if any other TLS parameters are set. Applied to all endpoints.""".stripMargin + |Required if any other TLS parameters are set. Applied to all endpoints.""".stripMargin ) .action(crtConfig) @@ -119,9 +121,9 @@ object Cli { .action((v, c) => c.copy(timeoutScaleFactor = v)) .text( """Scale factor for timeouts used in all test suites. Useful to tune timeouts - |depending on the environment and the Ledger implementation under test. - |Defaults to 1.0. Use numbers higher than 1.0 to make test timeouts more lax, - |use numbers lower than 1.0 to make test timeouts more strict.""".stripMargin + |depending on the environment and the Ledger implementation under test. + |Defaults to 1.0. Use numbers higher than 1.0 to make test timeouts more lax, + |use numbers lower than 1.0 to make test timeouts more strict.""".stripMargin ) opt[String](name = "load-scale-factor") @@ -144,16 +146,16 @@ object Cli { .action((_, c) => c.copy(mustFail = true)) .text( """Reverse success status logic of the tool. Use this flag if you expect one or - |more or the scenario tests to fail. If enabled, the tool will succeed when at - |least one test fails, and it will fail when all tests succeed. Defaults to - |false.""".stripMargin + |more or the scenario tests to fail. If enabled, the tool will succeed when at + |least one test fails, and it will fail when all tests succeed. Defaults to + |false.""".stripMargin ) opt[Unit]('x', "extract") .action((_, c) => c.copy(extract = true)) .text( """Extract a DAR necessary to test a Daml ledger and exit without running tests. - |The DAR needs to be manually loaded into a Daml ledger for the tool to work.""".stripMargin + |The DAR needs to be manually loaded into a Daml ledger for the tool to work.""".stripMargin ) opt[Seq[String]]("exclude") @@ -161,8 +163,8 @@ object Cli { .unbounded() .text( """A comma-separated list of exclusion prefixes. Tests whose name start with - |any of the given prefixes will be skipped. Can be specified multiple times, - |i.e. `--exclude=a,b` is the same as `--exclude=a --exclude=b`.""".stripMargin + |any of the given prefixes will be skipped. Can be specified multiple times, + |i.e. `--exclude=a,b` is the same as `--exclude=a --exclude=b`.""".stripMargin ) opt[Seq[String]]("include") @@ -170,10 +172,10 @@ object Cli { .unbounded() .text( """A comma-separated list of inclusion prefixes. If not specified, - |all default tests are included. If specified, only tests that match at least one - |of the given inclusion prefixes (and none of the given exclusion prefixes) will be run. - |Can be specified multiple times, i.e. `--include=a,b` is the same as `--include=a --include=b`. - |Mutually exclusive with `--additional`.""".stripMargin + |all default tests are included. If specified, only tests that match at least one + |of the given inclusion prefixes (and none of the given exclusion prefixes) will be run. + |Can be specified multiple times, i.e. `--include=a,b` is the same as `--include=a --include=b`. + |Mutually exclusive with `--additional`.""".stripMargin ) opt[Seq[String]]("additional") @@ -182,9 +184,9 @@ object Cli { .unbounded() .text( """A comma-separated list of additional prefixes. If specified, also tests that match at least one - |of the given inclusion prefixes (and none of the given exclusion prefixes) will be run. - |Can be specified multiple times, i.e. `--additional=a,b` is the same as `--additional=a --additional=b`. - |Mutually exclusive with `--include`.""".stripMargin + |of the given inclusion prefixes (and none of the given exclusion prefixes) will be run. + |Can be specified multiple times, i.e. `--additional=a,b` is the same as `--additional=a --additional=b`. + |Mutually exclusive with `--include`.""".stripMargin ) opt[Seq[String]]("perf-tests") @@ -209,7 +211,7 @@ object Cli { .action((_, c) => c.copy(shuffleParticipants = true)) .text( """Shuffle the list of participants used in a test. - |By default participants are used in the order they're given.""".stripMargin + |By default participants are used in the order they're given.""".stripMargin ) opt[Unit]("no-wait-for-parties") @@ -221,16 +223,16 @@ object Cli { .action((_, c) => c.copy(partyAllocation = PartyAllocationConfiguration.OpenWorld)) .text( """Do not allocate parties explicitly. - |Instead, expect the ledger to allocate parties dynamically. - |Party names must be their hints.""".stripMargin + |Instead, expect the ledger to allocate parties dynamically. + |Party names must be their hints.""".stripMargin ) opt[Unit]("list") .action((_, c) => c.copy(listTestSuites = true)) .text( """Lists all available test suites that can be used in the include and exclude options. - |Test names always start with their suite name, so using the suite name as a prefix - |matches all tests in a given suite.""".stripMargin + |Test names always start with their suite name, so using the suite name as a prefix + |matches all tests in a given suite.""".stripMargin ) opt[Unit]("list-all") @@ -252,7 +254,7 @@ object Cli { .action((x, c) => c.copy(ledgerClockGranularity = x)) .text( """Specify the largest interval that you will see between clock ticks - |on the ledger under test. The default is \"1s\" (1 second).""".stripMargin + |on the ledger under test. The default is \"1s\" (1 second).""".stripMargin ) opt[Unit]("skip-dar-upload") diff --git a/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/Config.scala b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/Config.scala index 3d268ca3e3..81bab675e3 100644 --- a/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/Config.scala +++ b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/Config.scala @@ -3,16 +3,15 @@ package com.daml.ledger.api.testtool -import java.io.File -import java.nio.file.Path - import com.daml.ledger.api.testtool.infrastructure.PartyAllocationConfiguration import com.daml.ledger.api.tls.TlsConfiguration +import java.io.File +import java.nio.file.Path import scala.concurrent.duration.FiniteDuration final case class Config( - participants: Vector[(String, Int)], + participantsEndpoints: Vector[(String, Int)], maxConnectionAttempts: Int, darPackages: List[File], mustFail: Boolean, @@ -32,11 +31,16 @@ final case class Config( partyAllocation: PartyAllocationConfiguration, ledgerClockGranularity: FiniteDuration, uploadDars: Boolean, -) +) { + def withTlsConfig(modify: TlsConfiguration => TlsConfiguration): Config = { + val base = tlsConfig.getOrElse(TlsConfiguration.Empty) + copy(tlsConfig = Some(modify(base))) + } +} object Config { val default: Config = Config( - participants = Vector.empty, + participantsEndpoints = Vector.empty, maxConnectionAttempts = 10, darPackages = Nil, mustFail = false, diff --git a/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/LedgerApiTestTool.scala b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/LedgerApiTestTool.scala index 5e1f3aab0e..35ea102a00 100644 --- a/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/LedgerApiTestTool.scala +++ b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/LedgerApiTestTool.scala @@ -3,10 +3,6 @@ package com.daml.ledger.api.testtool -import java.io.File -import java.nio.file.{Files, Paths, StandardCopyOption} -import java.util.concurrent.Executors - import com.daml.ledger.api.testtool.infrastructure.Reporter.ColorizedPrintStreamReporter import com.daml.ledger.api.testtool.infrastructure.Result.Excluded import com.daml.ledger.api.testtool.infrastructure._ @@ -17,6 +13,9 @@ import io.grpc.netty.{NegotiationType, NettyChannelBuilder} import org.slf4j.LoggerFactory import scala.collection.compat._ +import java.io.File +import java.nio.file.{Files, Paths, StandardCopyOption} +import java.util.concurrent.Executors import scala.concurrent.duration.DurationInt import scala.concurrent.{ExecutionContext, Future} import scala.util.{Failure, Success} @@ -57,6 +56,7 @@ object LedgerApiTestTool { println() Tests.PerformanceTestsKeys.foreach(println(_)) } + private def printAvailableTestSuites(testSuites: Vector[LedgerTestSuite]): Unit = { println("Listing test suites. Run with --list-all to see individual tests.") printListOfTests(testSuites)(_.name) @@ -126,7 +126,7 @@ object LedgerApiTestTool { sys.exit(0) } - if (config.participants.isEmpty) { + if (config.participantsEndpoints.isEmpty) { println("No participant to test, exiting.") sys.exit(0) } @@ -220,8 +220,11 @@ object LedgerApiTestTool { cases: Vector[LedgerTestCase], concurrentTestRuns: Int, )(implicit executionContext: ExecutionContext): Future[LedgerTestCasesRunner] = { - initializeParticipantChannels(config.participants, config.tlsConfig).asFuture.map( - participants => + initializeParticipantChannels( + config.participantsEndpoints, + config.tlsConfig, + ).asFuture + .map((participants: Vector[ChannelEndpoint]) => new LedgerTestCasesRunner( testCases = cases, participants = participants, @@ -232,8 +235,9 @@ object LedgerApiTestTool { concurrentTestRuns = concurrentTestRuns, uploadDars = config.uploadDars, identifierSuffix = identifierSuffix, + clientTlsConfiguration = config.tlsConfig, ) - ) + ) } private def initializeParticipantChannel( @@ -257,12 +261,18 @@ object LedgerApiTestTool { private def initializeParticipantChannels( participants: Vector[(String, Int)], tlsConfig: Option[TlsConfiguration], - )(implicit executionContext: ExecutionContext): Resource[Vector[Channel]] = { - val participantChannelOwners = + )(implicit executionContext: ExecutionContext): Resource[Vector[ChannelEndpoint]] = { + val channelResources: Seq[Resource[ChannelEndpoint]] = for ((host, port) <- participants) yield { - initializeParticipantChannel(host, port, tlsConfig) + val channelOwner: ResourceOwner[Channel] = + initializeParticipantChannel(host, port, tlsConfig) + channelOwner + .acquire() + .map(channel => + ChannelEndpoint.forRemote(channel = channel, hostname = host, port = port) + ) } - Resource.sequence(participantChannelOwners.map(_.acquire())) + Resource.sequence(channelResources) } } diff --git a/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/ChannelEndpoint.scala b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/ChannelEndpoint.scala new file mode 100644 index 0000000000..abe88460f8 --- /dev/null +++ b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/ChannelEndpoint.scala @@ -0,0 +1,27 @@ +// Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.daml.ledger.api.testtool.infrastructure + +import io.grpc.Channel + +sealed trait Endpoint + +object Endpoint { + + final case object InProcess extends Endpoint + + final case class Remote(hostname: String, port: Int) extends Endpoint + +} + +final case class ChannelEndpoint(channel: Channel, endpoint: Endpoint) + +object ChannelEndpoint { + + def forRemote(channel: Channel, hostname: String, port: Int): ChannelEndpoint = + ChannelEndpoint(channel, Endpoint.Remote(hostname, port)) + + def forInProcess(channel: Channel): ChannelEndpoint = ChannelEndpoint(channel, Endpoint.InProcess) + +} diff --git a/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/LedgerSession.scala b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/LedgerSession.scala index 6c0d72a80c..f84eb2e859 100644 --- a/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/LedgerSession.scala +++ b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/LedgerSession.scala @@ -4,6 +4,7 @@ package com.daml.ledger.api.testtool.infrastructure import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantSession +import com.daml.ledger.api.tls.TlsConfiguration import scala.concurrent.{ExecutionContext, Future} import scala.util.Random @@ -11,6 +12,7 @@ import scala.util.Random private[infrastructure] final class LedgerSession private ( participantSessions: Vector[(String, ParticipantSession)], shuffleParticipants: Boolean, + clientTlsConfiguration: Option[TlsConfiguration], )(implicit val executionContext: ExecutionContext) { private[infrastructure] def createTestContext( applicationId: String, @@ -21,7 +23,12 @@ private[infrastructure] final class LedgerSession private ( else participantSessions Future .traverse(sessions) { case (endpointId, session) => - session.createTestContext(endpointId, applicationId, identifierSuffix) + session.createTestContext( + endpointId, + applicationId, + identifierSuffix, + clientTlsConfiguration, + ) } .map(new LedgerTestContext(_)) } @@ -31,10 +38,15 @@ object LedgerSession { def apply( participantSessions: Vector[ParticipantSession], shuffleParticipants: Boolean, + clientTlsConfiguration: Option[TlsConfiguration], )(implicit executionContext: ExecutionContext): LedgerSession = { val endpointIdProvider = Identification.circularWithIndex(Identification.greekAlphabet) val sessions = participantSessions.map(endpointIdProvider() -> _) - new LedgerSession(sessions, shuffleParticipants) + new LedgerSession( + sessions, + shuffleParticipants, + clientTlsConfiguration, + ) } } diff --git a/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/LedgerTestCasesRunner.scala b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/LedgerTestCasesRunner.scala index 9abb903751..0a81fe0b8b 100644 --- a/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/LedgerTestCasesRunner.scala +++ b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/LedgerTestCasesRunner.scala @@ -3,9 +3,6 @@ package com.daml.ledger.api.testtool.infrastructure -import java.util.concurrent.{ExecutionException, TimeoutException} -import java.util.{Timer, TimerTask} - import akka.actor.ActorSystem import akka.stream.Materializer import akka.stream.scaladsl.{Sink, Source} @@ -16,9 +13,12 @@ import com.daml.ledger.api.testtool.infrastructure.participant.{ ParticipantSession, ParticipantTestContext, } -import io.grpc.{Channel, ClientInterceptor} +import com.daml.ledger.api.tls.TlsConfiguration +import io.grpc.ClientInterceptor import org.slf4j.LoggerFactory +import java.util.concurrent.{ExecutionException, TimeoutException} +import java.util.{Timer, TimerTask} import scala.concurrent.duration.{Duration, DurationInt} import scala.concurrent.{ExecutionContext, Future, Promise} import scala.util.Try @@ -41,7 +41,7 @@ object LedgerTestCasesRunner { final class LedgerTestCasesRunner( testCases: Vector[LedgerTestCase], - participants: Vector[Channel], + participants: Vector[ChannelEndpoint], maxConnectionAttempts: Int = 10, partyAllocation: PartyAllocationConfiguration = ClosedWorldWaitingForAllParticipants, shuffleParticipants: Boolean = false, @@ -50,6 +50,7 @@ final class LedgerTestCasesRunner( uploadDars: Boolean = true, identifierSuffix: String = "test", commandInterceptors: Seq[ClientInterceptor] = Seq.empty, + clientTlsConfiguration: Option[TlsConfiguration], ) { private[this] val verifyRequirements: Try[Unit] = Try { @@ -150,13 +151,18 @@ final class LedgerTestCasesRunner( } private def uploadDarsIfRequired( - sessions: Vector[ParticipantSession] + sessions: Vector[ParticipantSession], + clientTlsConfiguration: Option[TlsConfiguration], )(implicit executionContext: ExecutionContext): Future[Unit] = if (uploadDars) { Future .sequence(sessions.map { session => for { - context <- session.createInitContext("upload-dars", identifierSuffix) + context <- session.createInitContext( + applicationId = "upload-dars", + identifierSuffix = identifierSuffix, + clientTlsConfiguration = clientTlsConfiguration, + ) _ <- Future.sequence(Dars.resources.map(uploadDar(context, _))) } yield () }) @@ -188,18 +194,22 @@ final class LedgerTestCasesRunner( } private def run( - participants: Vector[Channel] + participants: Vector[ChannelEndpoint] )(implicit materializer: Materializer, executionContext: ExecutionContext, ): Future[Vector[LedgerTestSummary]] = { val (concurrentTestCases, sequentialTestCases) = testCases.partition(_.runConcurrently) ParticipantSession(partyAllocation, participants, maxConnectionAttempts, commandInterceptors) - .flatMap { sessions => - val ledgerSession = LedgerSession(sessions, shuffleParticipants) + .flatMap { sessions: Vector[ParticipantSession] => + val ledgerSession = LedgerSession( + sessions, + shuffleParticipants, + clientTlsConfiguration = clientTlsConfiguration, + ) val testResults = for { - _ <- uploadDarsIfRequired(sessions) + _ <- uploadDarsIfRequired(sessions, clientTlsConfiguration) concurrentTestResults <- runTestCases( ledgerSession, concurrentTestCases, diff --git a/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/LedgerTestContext.scala b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/LedgerTestContext.scala index 4050599690..cee21b7620 100644 --- a/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/LedgerTestContext.scala +++ b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/LedgerTestContext.scala @@ -42,10 +42,11 @@ private[testtool] final class LedgerTestContext private[infrastructure] ( val participantAllocations = allocation.partyCounts.map(nextParticipant() -> _) val participantsUnderTest = participantAllocations.map(_._1) Future - .sequence(participantAllocations.map { case (participant, partyCount) => - participant - .preallocateParties(partyCount.count, participantsUnderTest) - .map(parties => Participant(participant, parties: _*)) + .sequence(participantAllocations.map { + case (participant: ParticipantTestContext, partyCount) => + participant + .preallocateParties(partyCount.count, participantsUnderTest) + .map(parties => Participant(participant, parties: _*)) }) .map(allocatedParticipants => Participants(allocatedParticipants: _*)) } diff --git a/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/LedgerTestSuite.scala b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/LedgerTestSuite.scala index ee82d572a3..97aab65a6f 100644 --- a/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/LedgerTestSuite.scala +++ b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/LedgerTestSuite.scala @@ -32,6 +32,7 @@ private[testtool] abstract class LedgerTestSuite { repeated, )((ec: ExecutionContext) => (_: Seq[ParticipantTestContext]) => testCase(ec)) } + protected final def testGivenAllParticipants( shortIdentifier: String, description: String, diff --git a/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/participant/ParticipantSession.scala b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/participant/ParticipantSession.scala index 338c2db974..3bc537e011 100644 --- a/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/participant/ParticipantSession.scala +++ b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/participant/ParticipantSession.scala @@ -4,14 +4,17 @@ package com.daml.ledger.api.testtool.infrastructure.participant import com.daml.ledger.api.testtool.infrastructure.{ + ChannelEndpoint, + Endpoint, Errors, LedgerServices, PartyAllocationConfiguration, } +import com.daml.ledger.api.tls.TlsConfiguration import com.daml.ledger.api.v1.ledger_identity_service.GetLedgerIdentityRequest import com.daml.ledger.api.v1.transaction_service.GetLedgerEndRequest import com.daml.timer.RetryStrategy -import io.grpc.{Channel, ClientInterceptor} +import io.grpc.ClientInterceptor import org.slf4j.LoggerFactory import scala.concurrent.duration.DurationInt @@ -27,28 +30,39 @@ private[infrastructure] final class ParticipantSession private ( // The test tool is designed to run tests in an isolated environment but changing the // global state of the ledger breaks this assumption, no matter what. ledgerId: String, + ledgerEndpoint: Endpoint, )(implicit val executionContext: ExecutionContext) { + private[testtool] def createInitContext( applicationId: String, identifierSuffix: String, + clientTlsConfiguration: Option[TlsConfiguration], ): Future[ParticipantTestContext] = - createTestContext("init", applicationId, identifierSuffix) + createTestContext( + "init", + applicationId, + identifierSuffix, + clientTlsConfiguration = clientTlsConfiguration, + ) private[testtool] def createTestContext( endpointId: String, applicationId: String, identifierSuffix: String, + clientTlsConfiguration: Option[TlsConfiguration], ): Future[ParticipantTestContext] = for { end <- services.transaction.getLedgerEnd(new GetLedgerEndRequest(ledgerId)).map(_.getOffset) } yield new ParticipantTestContext( - ledgerId, - endpointId, - applicationId, - identifierSuffix, - end, - services, - partyAllocation, + ledgerId = ledgerId, + endpointId = endpointId, + applicationId = applicationId, + identifierSuffix = identifierSuffix, + referenceOffset = end, + services = services, + partyAllocation = partyAllocation, + ledgerEndpoint = ledgerEndpoint, + clientTlsConfiguration = clientTlsConfiguration, ) } @@ -57,14 +71,14 @@ object ParticipantSession { def apply( partyAllocation: PartyAllocationConfiguration, - participants: Vector[Channel], + participants: Vector[ChannelEndpoint], maxConnectionAttempts: Int, commandInterceptors: Seq[ClientInterceptor], )(implicit executionContext: ExecutionContext ): Future[Vector[ParticipantSession]] = - Future.traverse(participants) { participant => - val services = new LedgerServices(participant, commandInterceptors) + Future.traverse(participants) { participant: ChannelEndpoint => + val services = new LedgerServices(participant.channel, commandInterceptors) for { ledgerId <- RetryStrategy .exponentialBackoff(attempts = maxConnectionAttempts, 100.millis) { (attempt, wait) => @@ -80,7 +94,12 @@ object ParticipantSession { .recoverWith { case NonFatal(exception) => Future.failed(new Errors.ParticipantConnectionException(exception)) } - } yield new ParticipantSession(partyAllocation, services, ledgerId) + } yield new ParticipantSession( + partyAllocation = partyAllocation, + services = services, + ledgerId = ledgerId, + ledgerEndpoint = participant.endpoint, + ) } } diff --git a/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/participant/ParticipantTestContext.scala b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/participant/ParticipantTestContext.scala index 7146a0ab62..4371a3baf1 100644 --- a/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/participant/ParticipantTestContext.scala +++ b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/infrastructure/participant/ParticipantTestContext.scala @@ -4,15 +4,16 @@ package com.daml.ledger.api.testtool.infrastructure.participant import java.time.{Clock, Instant} - import com.daml.ledger.api.refinements.ApiTypes.TemplateId import com.daml.ledger.api.testtool.infrastructure.Eventually.eventually import com.daml.ledger.api.testtool.infrastructure.ProtobufConverters._ import com.daml.ledger.api.testtool.infrastructure.{ + Endpoint, Identification, LedgerServices, PartyAllocationConfiguration, } +import com.daml.ledger.api.tls.TlsConfiguration import com.daml.ledger.api.v1.active_contracts_service.{ GetActiveContractsRequest, GetActiveContractsResponse, @@ -105,6 +106,8 @@ private[testtool] final class ParticipantTestContext private[participant] ( referenceOffset: LedgerOffset, services: LedgerServices, partyAllocation: PartyAllocationConfiguration, + val ledgerEndpoint: Endpoint, + val clientTlsConfiguration: Option[TlsConfiguration], )(implicit ec: ExecutionContext) { import ParticipantTestContext._ diff --git a/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/suites/TLSOnePointThreeIT.scala b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/suites/TLSOnePointThreeIT.scala new file mode 100644 index 0000000000..b6319ccff1 --- /dev/null +++ b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/suites/TLSOnePointThreeIT.scala @@ -0,0 +1,128 @@ +// Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.daml.ledger.api.testtool.suites + +import com.daml.ledger.api.testtool.infrastructure.Allocation.{NoParties, allocate} +import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantTestContext +import com.daml.ledger.api.testtool.infrastructure.{Endpoint, LedgerTestSuite} +import com.daml.ledger.api.tls.TlsVersion +import com.daml.ledger.api.tls.TlsVersion.TlsVersion +import com.daml.ledger.api.v1.ledger_identity_service.LedgerIdentityServiceGrpc.LedgerIdentityServiceBlockingStub +import com.daml.ledger.api.v1.ledger_identity_service.{ + GetLedgerIdentityRequest, + LedgerIdentityServiceGrpc, +} +import com.daml.ledger.resources.{ResourceContext, ResourceOwner} +import io.grpc.StatusRuntimeException +import io.grpc.netty.NettyChannelBuilder + +import scala.concurrent.Future +import scala.concurrent.duration._ +import scala.util.{Failure, Success, Try} + +/** Verifies that the given participant server correctly handles TLSv1.3 only mode. + * + * It works by creating and exercising a series of client service stubs, each over different TLS version. + * Only TLSv1.3 connection is expected to succeed. + * Connections over lower TLS versions are expected to fail. + */ +final class TLSOnePointThreeIT extends LedgerTestSuite { + + testTlsConnection(clientTlsVersion = TlsVersion.V1_3, assertConnectionOk = true) + testTlsConnection(clientTlsVersion = TlsVersion.V1_2, assertConnectionOk = false) + testTlsConnection(clientTlsVersion = TlsVersion.V1_1, assertConnectionOk = false) + testTlsConnection(clientTlsVersion = TlsVersion.V1, assertConnectionOk = false) + + def testTlsConnection(clientTlsVersion: TlsVersion, assertConnectionOk: Boolean): Unit = { + val (what, assertionOnServerResponse) = + if (assertConnectionOk) + ("accept", assertSuccessfulConnection) + else + ("reject", assertFailedConnection) + + testGivenAllParticipants( + s"ConnectionOnTLSv13FromClientOn${clientTlsVersion.version.replace(".", "")}", + s"A ledger API server should ${what} a ${clientTlsVersion} connection", + allocate(NoParties), + ) { implicit ec => (testContexts: Seq[ParticipantTestContext]) => + { case _ => + // preconditions + assume(testContexts.nonEmpty, "Missing an expected participant test context!") + val firstTextContext = testContexts.head + assume( + firstTextContext.clientTlsConfiguration.isDefined, + "Missing required TLS configuration!", + ) + val tlsConfiguration = firstTextContext.clientTlsConfiguration.get + assume( + tlsConfiguration.enabled, + "TLS configuration is disabled but expected to be enabled!", + ) + assume( + firstTextContext.ledgerEndpoint.isInstanceOf[Endpoint.Remote], + "Expected a remote (i.e. with a hostname and port) ledger endpoint!", + ) + val Endpoint.Remote(ledgerHostname, ledgerPort) = + firstTextContext.ledgerEndpoint.asInstanceOf[Endpoint.Remote] + + // given + val sslContext = tlsConfiguration + .client(enabledProtocols = Seq(clientTlsVersion)) + .getOrElse(throw new IllegalStateException("Missing SslContext!")) + val serviceStubOwner: ResourceOwner[LedgerIdentityServiceBlockingStub] = for { + channel <- ResourceOwner.forChannel( + builder = NettyChannelBuilder + .forAddress(ledgerHostname, ledgerPort) + .useTransportSecurity() + .sslContext(sslContext), + shutdownTimeout = 2.seconds, + ) + } yield LedgerIdentityServiceGrpc.blockingStub(channel) + + // when + val response: Future[String] = serviceStubOwner.use { identityService => + val response = identityService.getLedgerIdentity(new GetLedgerIdentityRequest()) + Future.successful(response.ledgerId) + }(ResourceContext(ec)) + + // then + response.transform[Unit] { + assertionOnServerResponse + } + } + } + } + + private lazy val assertSuccessfulConnection: Try[String] => Try[Unit] = { + case Success(ledgerId) => + Try[Unit] { + assert( + assertion = ledgerId ne null, + message = s"Expected a not null ledger id!", + ) + } + case Failure(exception) => + throw new AssertionError(s"Failed to receive a successful server response!", exception) + } + + private lazy val assertFailedConnection: Try[String] => Try[Unit] = { + case Success(ledgerId) => + Try[Unit] { + assert( + assertion = false, + message = + s"Connection succeeded and returned ledgerId: ${ledgerId} but expected connection failure!", + ) + } + case Failure(_: StatusRuntimeException) => Success[Unit](()) + case Failure(other) => + Try[Unit] { + assert( + assertion = false, + message = s"Unexpected failure: ${other}", + ) + } + } + +} diff --git a/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/tests/Tests.scala b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/tests/Tests.scala index c46a4b79ac..c360cc22eb 100644 --- a/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/tests/Tests.scala +++ b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/tests/Tests.scala @@ -75,6 +75,7 @@ object Tests { new MultiPartySubmissionIT, new ParticipantPruningIT, new MonotonicRecordTimeIT, + new TLSOnePointThreeIT, ) val retired: Vector[LedgerTestSuite] = diff --git a/ledger/participant-integration-api/src/main/scala/platform/apiserver/ApiServices.scala b/ledger/participant-integration-api/src/main/scala/platform/apiserver/ApiServices.scala index bb6c9f48d1..3c01867b82 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/apiserver/ApiServices.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/apiserver/ApiServices.scala @@ -167,7 +167,6 @@ private[daml] object ApiServices { optTimeServiceBackend.map(tsb => new TimeServiceAuthorization(ApiTimeService.create(ledgerId, tsb), authorizer) ) - val writeServiceBackedApiServices = intitializeWriteServiceBackedApiServices( ledgerId, diff --git a/runtime-components/non-repudiation-postgresql/src/test/scala/com/daml/nonrepudiation/postgresql/NonRepudiationProxyConformance.scala b/runtime-components/non-repudiation-postgresql/src/test/scala/com/daml/nonrepudiation/postgresql/NonRepudiationProxyConformance.scala index 7da69fb966..80666f0070 100644 --- a/runtime-components/non-repudiation-postgresql/src/test/scala/com/daml/nonrepudiation/postgresql/NonRepudiationProxyConformance.scala +++ b/runtime-components/non-repudiation-postgresql/src/test/scala/com/daml/nonrepudiation/postgresql/NonRepudiationProxyConformance.scala @@ -3,9 +3,8 @@ package com.daml.nonrepudiation.postgresql -import java.time.{Clock, Duration} - import com.daml.doobie.logging.Slf4jLogHandler +import com.daml.ledger.api.testtool.infrastructure import com.daml.ledger.api.testtool.infrastructure.{ LedgerTestCase, LedgerTestCasesRunner, @@ -34,6 +33,7 @@ import org.scalatest.flatspec.AsyncFlatSpec import org.scalatest.matchers.should.Matchers import org.scalatest.{Inside, OptionValues} +import java.time.{Clock, Duration} import scala.concurrent.duration.DurationInt final class NonRepudiationProxyConformance @@ -109,10 +109,11 @@ final class NonRepudiationProxyConformance proxy.use { _ => val runner = new LedgerTestCasesRunner( testCases = conformanceTestCases, - participants = Vector(proxyChannel), + participants = Vector(infrastructure.ChannelEndpoint.forInProcess(proxyChannel)), commandInterceptors = Seq( SigningInterceptor.signCommands(key, certificate) ), + clientTlsConfiguration = config.tlsConfig, ) runner.runTests.map { summaries =>