Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow secure connection from JSON API to ledger #5555

Merged
merged 9 commits into from
Apr 16, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import com.daml.ledger.api.testing.utils.{
MockMessages,
}
import com.daml.ledger.api.auth.{AuthServiceJWTCodec, AuthServiceJWTPayload}
import com.daml.ledger.api.tls.TlsConfiguration
import com.daml.platform.common.LedgerIdMode
import com.daml.platform.sandbox.{AbstractSandboxFixture, SandboxServer}
import com.daml.platform.sandbox.config.SandboxConfig
Expand Down Expand Up @@ -96,6 +97,7 @@ trait JsonApiFixture
"localhost",
0,
None,
TlsConfiguration(enabled = false, None, None, None),
None,
None)(
jsonApiActorSystem,
Expand Down
9 changes: 9 additions & 0 deletions docs/source/json-api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@ From a DAML project directory:
Optional unique file name where to write the allocated HTTP port number. If process terminates gracefully, this file will be deleted automatically. Used to inform clients in CI about which port HTTP JSON API listens on. Defaults to none, that is, no file gets created.
--application-id <value>
Optional application ID to use for ledger registration. Defaults to HTTP-JSON-API-Gateway
--pem <value>
TLS: The pem file to be used as the private key.
--crt <value>
TLS: The crt file to be used as the cert chain.
Required for client authentication.
--cacrt <value>
TLS: The crt file to be used as the the trusted root CA.
--tls
TLS: Enable tls. This is redundant if --pem, --crt or --cacrt are set
--package-reload-interval <value>
Optional interval to poll for package updates. Examples: 500ms, 5s, 10min, 1h, 1d. Defaults to 5 seconds
--max-inbound-message-size <value>
Expand Down
1 change: 1 addition & 0 deletions extractor/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ da_scala_library(
"//language-support/scala/bindings",
"//ledger-api/rs-grpc-akka",
"//ledger-api/rs-grpc-bridge",
"//ledger-service/cli-opts",
"//ledger-service/lf-value-json",
"//ledger-service/utils",
"//ledger/ledger-api-client",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,16 @@

package com.daml.extractor.config

import java.io.File
import java.nio.file.{Path, Paths}

import com.daml.lf.data.Ref.Party
import com.daml.ledger.api.v1.ledger_offset.LedgerOffset
import com.daml.extractor.targets._
import com.daml.ledger.api.tls.TlsConfiguration
import com.daml.ledger.api.tls.{TlsConfiguration, TlsConfigurationCli}
import CustomScoptReaders._
import com.daml.ports.Port
import scalaz.OneAnd

import scala.util.Try
import scopt.OptionParser

object ConfigParser {
Expand Down Expand Up @@ -45,15 +43,7 @@ object ConfigParser {
templateConfigs: Set[TemplateConfig] = Set.empty,
from: Option[String] = None,
to: Option[String] = None,
tlsPem: Option[String] = None,
tlsCrt: Option[String] = None,
tlsCaCrt: Option[String] = None,
// tlsExplicit is used to handle the --tls flag
// which allows you to enable tls without any special certs,
// i.e., tls without client auth with the default root certs.
// If any certificates are set tls is enabled implicitly and
// tlsExplicit is redundant.
tlsExplicit: Boolean = false,
tlsConfiguration: TlsConfiguration = TlsConfiguration(false, None, None, None),
accessTokenFile: Option[Path] = None,
)

Expand Down Expand Up @@ -226,37 +216,8 @@ object ConfigParser {

note("\nTLS configuration:")

opt[String]("pem")
.optional()
.text("TLS: The pem file to be used as the private key.")
.validate(validatePath(_, "The file specified via --pem does not exist"))
.action { (path, c) =>
c.copy(tlsPem = Some(path))
}
opt[String]("crt")
.optional()
.text(
s"TLS: The crt file to be used as the cert chain.\n${colSpacer}" +
s"Required for client authentication."
)
.validate(validatePath(_, "The file specified via --crt does not exist"))
.action { (path, c) =>
c.copy(tlsCrt = Some(path))
}
opt[String]("cacrt")
.optional()
.text("TLS: The crt file to be used as the the trusted root CA.")
.validate(validatePath(_, "The file specified via --cacrt does not exist"))
.action { (path, c) =>
c.copy(tlsCaCrt = Some(path))
}

opt[Unit]("tls")
.optional()
.text("TLS: Enable tls. This is redundant if --pem, --crt or --cacrt are set")
.action { (_, c) =>
c.copy(tlsExplicit = true)
}
TlsConfigurationCli.parse(this, colSpacer)((f, c) =>
c copy (tlsConfiguration = f(c.tlsConfiguration)))

note("\nAuthentication:")

Expand Down Expand Up @@ -310,12 +271,7 @@ object ConfigParser {
case x => SnapshotEndSetting.Until(x)
}

val tlsConfig = TlsConfiguration(
enabled = cliParams.tlsPem.isDefined || cliParams.tlsCrt.isDefined || cliParams.tlsCaCrt.isDefined || cliParams.tlsExplicit,
keyCertChainFile = cliParams.tlsCrt.map(new File(_)),
keyFile = cliParams.tlsPem.map(new File(_)),
trustCertCollectionFile = cliParams.tlsCaCrt.map(new File(_))
)
val tlsConfig = cliParams.tlsConfiguration

val config = ExtractorConfig(
cliParams.ledgerHost,
Expand Down Expand Up @@ -351,11 +307,6 @@ object ConfigParser {
def showUsage(): Unit =
configParser.showUsage()

private def validatePath(path: String, message: String): Either[String, Unit] = {
val valid = Try(Paths.get(path).toFile.canRead).getOrElse(false)
if (valid) Right(()) else Left(message)
}

private def validateUniqueElements[A](x: Seq[A], message: => String): Either[String, Unit] =
Either.cond(x.size == x.toSet.size, (), message)
}
21 changes: 21 additions & 0 deletions ledger-service/cli-opts/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright (c) 2020 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

load(
"//bazel_tools:scala.bzl",
"da_scala_library",
"lf_scalacopts",
)

da_scala_library(
name = "cli-opts",
srcs = glob(["src/main/scala/**/*.scala"]),
scalacopts = lf_scalacopts,
tags = ["maven_coordinates=com.daml:http-json-cli-opts:__VERSION__"],
visibility = ["//visibility:public"],
deps = [
"//ledger/ledger-api-common",
"@maven//:com_github_scopt_scopt_2_12",
"@maven//:io_netty_netty_handler",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) 2020 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package com.daml.ledger.api.tls

import java.nio.file.Paths

import scala.util.Try

object TlsConfigurationCli {
def parse[C](parser: scopt.OptionParser[C], colSpacer: String)(
setter: (TlsConfiguration => TlsConfiguration, C) => C): Unit = {
def enableSet(tlsUp: TlsConfiguration => TlsConfiguration, c: C) =
setter(tlsc => tlsUp(tlsc copy (enabled = true)), c)

import parser.opt

opt[String]("pem")
.optional()
.text("TLS: The pem file to be used as the private key.")
.validate(validatePath(_, "The file specified via --pem does not exist"))
.action { (path, c) =>
enableSet(_ copy (keyFile = Some(Paths.get(path).toFile)), c)
}

opt[String]("crt")
.optional()
.text(
s"TLS: The crt file to be used as the cert chain.\n${colSpacer}" +
s"Required for client authentication."
)
.validate(validatePath(_, "The file specified via --crt does not exist"))
.action { (path, c) =>
enableSet(_ copy (keyCertChainFile = Some(Paths.get(path).toFile)), c)
}

opt[String]("cacrt")
.optional()
.text("TLS: The crt file to be used as the the trusted root CA.")
.validate(validatePath(_, "The file specified via --cacrt does not exist"))
.action { (path, c) =>
enableSet(_ copy (trustCertCollectionFile = Some(Paths.get(path).toFile)), c)
}

// allows you to enable tls without any special certs,
// i.e., tls without client auth with the default root certs.
// If any certificates are set tls is enabled implicitly and
// this is redundant.
opt[Unit]("tls")
.optional()
.text("TLS: Enable tls. This is redundant if --pem, --crt or --cacrt are set")
.action { (_, c) =>
enableSet(identity, c)
}

()
}

private def validatePath(path: String, message: String): Either[String, Unit] = {
val valid = Try(Paths.get(path).toFile.canRead).getOrElse(false)
if (valid) Right(()) else Left(message)
}
}
2 changes: 2 additions & 0 deletions ledger-service/http-json/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ da_scala_library(
"//daml-lf/transaction",
"//language-support/scala/bindings-akka",
"//ledger-api/rs-grpc-bridge",
"//ledger-service/cli-opts",
"//ledger-service/db-backend",
"//ledger-service/jwt",
"//ledger-service/lf-value-json",
Expand Down Expand Up @@ -117,6 +118,7 @@ da_scala_test(
data = [
":Account.dar",
"//docs:quickstart-model.dar",
"//ledger/test-common/test-certificates",
],
plugins = [
"@maven//:org_spire_math_kind_projector_2_12",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import java.util.concurrent.TimeUnit

import akka.stream.ThrottleMode
import com.daml.util.ExceptionOps._
import com.daml.ledger.api.tls.TlsConfiguration
import com.daml.ledger.api.refinements.ApiTypes.ApplicationId
import scalaz.std.option._
import scalaz.syntax.traverse._
Expand All @@ -27,6 +28,7 @@ private[http] final case class Config(
applicationId: ApplicationId = ApplicationId("HTTP-JSON-API-Gateway"),
packageReloadInterval: FiniteDuration = HttpService.DefaultPackageReloadInterval,
maxInboundMessageSize: Int = HttpService.DefaultMaxInboundMessageSize,
tlsConfig: TlsConfiguration = TlsConfiguration(enabled = false, None, None, None),
jdbcConfig: Option[JdbcConfig] = None,
staticContentConfig: Option[StaticContentConfig] = None,
accessTokenFile: Option[Path] = None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import com.daml.http.util.IdentifierConverters.apiLedgerId
import com.daml.jwt.JwtDecoder
import com.daml.ledger.api.refinements.ApiTypes.ApplicationId
import com.daml.ledger.api.refinements.{ApiTypes => lar}
import com.daml.ledger.api.tls.TlsConfiguration
import com.daml.ledger.client.LedgerClient
import com.daml.ledger.client.configuration.{
CommandClientConfiguration,
Expand Down Expand Up @@ -63,6 +64,7 @@ object HttpService extends StrictLogging {
address: String,
httpPort: Int,
portFile: Option[Path],
tlsConfig: TlsConfiguration,
wsConfig: Option[WebsocketConfig],
accessTokenFile: Option[Path],
contractDao: Option[ContractDao] = None,
Expand All @@ -85,7 +87,7 @@ object HttpService extends StrictLogging {
applicationId = ApplicationId.unwrap(applicationId),
ledgerIdRequirement = LedgerIdRequirement("", enabled = false),
commandClient = CommandClientConfiguration.default,
sslContext = None,
sslContext = tlsConfig.client,
token = tokenHolder.flatMap(_.token),
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import akka.stream.Materializer
import com.daml.grpc.adapter.{AkkaExecutionSequencerPool, ExecutionSequencerFactory}
import com.daml.http.Statement.discard
import com.daml.http.dbbackend.ContractDao
import com.daml.ledger.api.tls.TlsConfigurationCli
import com.daml.ledger.api.refinements.ApiTypes.ApplicationId
import com.typesafe.scalalogging.StrictLogging
import scalaz.{-\/, \/, \/-}
Expand Down Expand Up @@ -49,6 +50,7 @@ object Main extends StrictLogging {
s", applicationId=${config.applicationId.unwrap: String}" +
s", packageReloadInterval=${config.packageReloadInterval.toString}" +
s", maxInboundMessageSize=${config.maxInboundMessageSize: Int}" +
s", tlsConfig=${config.tlsConfig}" +
s", jdbcConfig=${config.jdbcConfig.shows}" +
s", staticContentConfig=${config.staticContentConfig.shows}" +
s", accessTokenFile=${config.accessTokenFile.toString}" +
Expand Down Expand Up @@ -88,6 +90,7 @@ object Main extends StrictLogging {
address = config.address,
httpPort = config.httpPort,
portFile = config.portFile,
tlsConfig = config.tlsConfig,
wsConfig = config.wsConfig,
accessTokenFile = config.accessTokenFile,
contractDao = contractDao,
Expand Down Expand Up @@ -178,6 +181,9 @@ object Main extends StrictLogging {
.text(
s"Optional application ID to use for ledger registration. Defaults to ${Config.Empty.applicationId.unwrap: String}")

TlsConfigurationCli.parse(this, colSpacer = " ")((f, c) =>
c copy (tlsConfig = f(c.tlsConfig)))

opt[Duration]("package-reload-interval")
.action((x, c) => c.copy(packageReloadInterval = FiniteDuration(x.length, x.unit)))
.optional()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import com.daml.api.util.TimestampConversion
import com.daml.bazeltools.BazelRunfiles.requiredResource
import com.daml.lf.data.Ref
import com.daml.grpc.adapter.{AkkaExecutionSequencerPool, ExecutionSequencerFactory}
import com.daml.http.HttpServiceTestFixture.jsonCodecs
import HttpServiceTestFixture.{jsonCodecs, UseTls}
import com.daml.http.domain.ContractId
import com.daml.http.domain.TemplateId.OptionalPkg
import com.daml.http.json.SprayJson.{decode, decode1, objectField}
Expand Down Expand Up @@ -60,6 +60,8 @@ trait AbstractHttpServiceIntegrationTestFuns extends StrictLogging {

def staticContentConfig: Option[StaticContentConfig]

def useTls: UseTls

protected def testId: String = this.getClass.getSimpleName

protected val metdata2: MetadataReader.LfMetadata =
Expand All @@ -86,7 +88,7 @@ trait AbstractHttpServiceIntegrationTestFuns extends StrictLogging {
protected def withHttpServiceAndClient[A]
: ((Uri, DomainJsonEncoder, DomainJsonDecoder, LedgerClient) => Future[A]) => Future[A] =
HttpServiceTestFixture
.withHttpService[A](testId, List(dar1, dar2), jdbcConfig, staticContentConfig)
.withHttpService[A](testId, List(dar1, dar2), jdbcConfig, staticContentConfig, useTls)

protected def withHttpService[A](
f: (Uri, DomainJsonEncoder, DomainJsonDecoder) => Future[A]): Future[A] =
Expand Down Expand Up @@ -400,6 +402,8 @@ abstract class AbstractHttpServiceIntegrationTest
with AbstractHttpServiceIntegrationTestFuns {
import json.JsonProtocol._

override final def useTls = UseTls.NoTls

"query GET empty results" in withHttpService { (uri: Uri, _, _) =>
getRequest(uri = uri.withPath(Uri.Path("/v1/query")))
.flatMap {
Expand Down