[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
This commit is contained in:
pbatko-da 2021-09-20 12:17:00 +02:00 committed by GitHub
parent 3e13e3d87e
commit 855ecdf1a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 304 additions and 78 deletions

View File

@ -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

View File

@ -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 `<host>:<port>`.")
@ -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")

View File

@ -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,

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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,
)
}
}

View File

@ -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,

View File

@ -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: _*))
}

View File

@ -32,6 +32,7 @@ private[testtool] abstract class LedgerTestSuite {
repeated,
)((ec: ExecutionContext) => (_: Seq[ParticipantTestContext]) => testCase(ec))
}
protected final def testGivenAllParticipants(
shortIdentifier: String,
description: String,

View File

@ -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,
)
}
}

View File

@ -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._

View File

@ -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}",
)
}
}
}

View File

@ -75,6 +75,7 @@ object Tests {
new MultiPartySubmissionIT,
new ParticipantPruningIT,
new MonotonicRecordTimeIT,
new TLSOnePointThreeIT,
)
val retired: Vector[LedgerTestSuite] =

View File

@ -167,7 +167,6 @@ private[daml] object ApiServices {
optTimeServiceBackend.map(tsb =>
new TimeServiceAuthorization(ApiTimeService.create(ledgerId, tsb), authorizer)
)
val writeServiceBackedApiServices =
intitializeWriteServiceBackedApiServices(
ledgerId,

View File

@ -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 =>