One leeway config for all autorizers [DPP-1215]. (#14930)

* One leeway config for all autorizers [DPP-1215].

CHANGELOG_BEGIN
CHANGELOG_END
This commit is contained in:
atriantafyllos-da 2022-09-07 15:07:42 +02:00 committed by GitHub
parent aa231edc07
commit 2b89f6beea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 128 additions and 145 deletions

View File

@ -17,7 +17,7 @@ final case class JwtTimestampLeeway(
trait Leeway {
def getVerifier(
algorithm: Algorithm,
mbJwtTimestampLeeway: Option[JwtTimestampLeeway] = None,
jwtTimestampLeeway: Option[JwtTimestampLeeway] = None,
): com.auth0.jwt.interfaces.JWTVerifier = {
def addLeeway(
verification: Verification,
@ -35,7 +35,7 @@ trait Leeway {
}
}
val defaultVerifier = JWT.require(algorithm)
val verification = mbJwtTimestampLeeway.fold(defaultVerifier)(addLeeway(defaultVerifier, _))
val verification = jwtTimestampLeeway.fold(defaultVerifier)(addLeeway(defaultVerifier, _))
verification.build()
}
}

View File

@ -39,5 +39,5 @@ class ConfigAdaptor {
}
def authService(participantConfig: ParticipantConfig): AuthService =
participantConfig.authentication.create()
participantConfig.authentication.create(participantConfig.jwtTimestampLeeway)
}

View File

@ -232,8 +232,8 @@ class PureConfigReaderWriter(secure: Boolean = true) {
implicit val userManagementConfigConvert: ConfigConvert[UserManagementConfig] =
deriveConvert[UserManagementConfig]
implicit val jwtTimestampLeewayConfigConvert: ConfigConvert[JwtTimestampLeeway] =
deriveConvert[JwtTimestampLeeway]
implicit val jwtTimestampLeewayConfigConvert: ConfigConvert[Option[JwtTimestampLeeway]] =
optConvertEnabled(deriveConvert[JwtTimestampLeeway])
implicit val authServiceConfigUnsafeJwtHmac256Reader
: ConfigReader[AuthServiceConfig.UnsafeJwtHmac256] =

View File

@ -199,28 +199,23 @@ object ArbitraryConfig {
val UnsafeJwtHmac256 = for {
secret <- Gen.alphaStr
mbJwtTimestampLeeway <- Gen.option(jwtTimestampLeewayGen)
} yield AuthServiceConfig.UnsafeJwtHmac256(secret, mbJwtTimestampLeeway)
} yield AuthServiceConfig.UnsafeJwtHmac256(secret)
val JwtRs256Crt = for {
certificate <- Gen.alphaStr
mbJwtTimestampLeeway <- Gen.option(jwtTimestampLeewayGen)
} yield AuthServiceConfig.JwtRs256(certificate, mbJwtTimestampLeeway)
} yield AuthServiceConfig.JwtRs256(certificate)
val JwtEs256Crt = for {
certificate <- Gen.alphaStr
mbJwtTimestampLeeway <- Gen.option(jwtTimestampLeewayGen)
} yield AuthServiceConfig.JwtEs256(certificate, mbJwtTimestampLeeway)
} yield AuthServiceConfig.JwtEs256(certificate)
val JwtEs512Crt = for {
certificate <- Gen.alphaStr
mbJwtTimestampLeeway <- Gen.option(jwtTimestampLeewayGen)
} yield AuthServiceConfig.JwtEs512(certificate, mbJwtTimestampLeeway)
} yield AuthServiceConfig.JwtEs512(certificate)
val JwtRs256Jwks = for {
url <- Gen.alphaStr
mbJwtTimestampLeeway <- Gen.option(jwtTimestampLeewayGen)
} yield AuthServiceConfig.JwtRs256Jwks(url, mbJwtTimestampLeeway)
} yield AuthServiceConfig.JwtRs256Jwks(url)
val authServiceConfig = Gen.oneOf(
Gen.const(AuthServiceConfig.Wildcard),
@ -423,10 +418,12 @@ object ArbitraryConfig {
indexService <- indexServiceConfig
indexer <- indexerConfig
lfValueTranslationCache <- lfValueTranslationCache
jwtTimestampLeeway <- Gen.option(jwtTimestampLeewayGen)
} yield ParticipantConfig(
apiServer = apiServer,
authentication = AuthServiceConfig.Wildcard, // hardcoded to wildcard, as otherwise it
// will be redacted and cannot be checked for isomorphism
jwtTimestampLeeway = jwtTimestampLeeway,
dataSourceProperties = dataSourceProperties,
indexService = indexService,
indexer = indexer,

View File

@ -133,6 +133,63 @@ class PureConfigReaderWriterSpec
compare(Duration.ofHours(1), "3600 seconds")
}
behavior of "JwtTimestampLeeway"
it should "read/write against predefined values" in {
def compare(configString: String, expectedValue: Option[JwtTimestampLeeway]) = {
convert(jwtTimestampLeewayConfigConvert, configString).value shouldBe expectedValue
}
compare(
"""
| enabled = true
| default = 1
|""".stripMargin,
Some(JwtTimestampLeeway(Some(1), None, None, None)),
)
compare(
"""
| enabled = true
| expires-at = 2
|""".stripMargin,
Some(JwtTimestampLeeway(None, Some(2), None, None)),
)
compare(
"""
| enabled = true
| issued-at = 3
|""".stripMargin,
Some(JwtTimestampLeeway(None, None, Some(3), None)),
)
compare(
"""
| enabled = true
| not-before = 4
|""".stripMargin,
Some(JwtTimestampLeeway(None, None, None, Some(4))),
)
compare(
"""
| enabled = true
| default = 1
| expires-at = 2
| issued-at = 3
| not-before = 4
|""".stripMargin,
Some(JwtTimestampLeeway(Some(1), Some(2), Some(3), Some(4))),
)
compare(
"""
| enabled = false
| default = 1
| expires-at = 2
| issued-at = 3
| not-before = 4
|""".stripMargin,
None,
)
}
behavior of "PureConfigReaderWriter VersionRange[LanguageVersion]"
it should "read/write against predefined values" in {
@ -354,8 +411,8 @@ class PureConfigReaderWriterSpec
it should "be isomorphic and support redaction" in forAll(ArbitraryConfig.authServiceConfig) {
generatedValue =>
val redacted = generatedValue match {
case AuthServiceConfig.UnsafeJwtHmac256(_, jwtTimestampLeeway) =>
AuthServiceConfig.UnsafeJwtHmac256("<REDACTED>", jwtTimestampLeeway)
case AuthServiceConfig.UnsafeJwtHmac256(_) =>
AuthServiceConfig.UnsafeJwtHmac256("<REDACTED>")
case _ => generatedValue
}
val insecureWriter = new PureConfigReaderWriter(false)
@ -383,78 +440,6 @@ class PureConfigReaderWriterSpec
"type = unsafe-jwt-hmac-256\nsecret=mysecret2",
AuthServiceConfig.UnsafeJwtHmac256("mysecret2"),
)
compare(
"type = unsafe-jwt-hmac-256\nsecret=mysecret3",
AuthServiceConfig.UnsafeJwtHmac256("mysecret3", None),
)
compare(
"""
|type = unsafe-jwt-hmac-256
|secret = mysecret3
|jwt-timestamp-leeway {
| default = 1
|}
|""".stripMargin,
AuthServiceConfig.UnsafeJwtHmac256(
"mysecret3",
Some(JwtTimestampLeeway(Some(1), None, None, None)),
),
)
compare(
"""
|type = unsafe-jwt-hmac-256
|secret = mysecret3
|jwt-timestamp-leeway {
| expires-at = 2
|}
|""".stripMargin,
AuthServiceConfig.UnsafeJwtHmac256(
"mysecret3",
Some(JwtTimestampLeeway(None, Some(2), None, None)),
),
)
compare(
"""
|type = unsafe-jwt-hmac-256
|secret = mysecret3
|jwt-timestamp-leeway {
| issued-at = 3
|}
|""".stripMargin,
AuthServiceConfig.UnsafeJwtHmac256(
"mysecret3",
Some(JwtTimestampLeeway(None, None, Some(3), None)),
),
)
compare(
"""
|type = unsafe-jwt-hmac-256
|secret = mysecret3
|jwt-timestamp-leeway {
| not-before = 4
|}
|""".stripMargin,
AuthServiceConfig.UnsafeJwtHmac256(
"mysecret3",
Some(JwtTimestampLeeway(None, None, None, Some(4))),
),
)
compare(
"""
|type = unsafe-jwt-hmac-256
|secret = mysecret3
|jwt-timestamp-leeway {
| default = 1
| expires-at = 2
| issued-at = 3
| not-before = 4
|}
|""".stripMargin,
AuthServiceConfig.UnsafeJwtHmac256(
"mysecret3",
Some(JwtTimestampLeeway(Some(1), Some(2), Some(3), Some(4))),
),
)
compare(
"type = jwt-rs-256\ncertificate=certfile",
AuthServiceConfig.JwtRs256("certfile"),

View File

@ -161,7 +161,7 @@ class LedgerApiServer(
ledgerFeatures = ledgerFeatures,
participantId = participantId,
authService = authService,
jwtTimestampLeeway = participantConfig.authentication.jwtTimestampLeeway,
jwtTimestampLeeway = participantConfig.jwtTimestampLeeway,
)
}

View File

@ -8,79 +8,73 @@ import com.daml.jwt.{
ECDSAVerifier,
HMAC256Verifier,
JwksVerifier,
RSA256Verifier,
JwtTimestampLeeway,
JwtVerifier,
RSA256Verifier,
}
import com.daml.ledger.api.auth.{AuthService, AuthServiceJWT, AuthServiceWildcard}
sealed trait AuthServiceConfig {
def create(): AuthService
def jwtTimestampLeeway: Option[JwtTimestampLeeway]
def create(jwtTimestampLeeway: Option[JwtTimestampLeeway]): AuthService
}
object AuthServiceConfig {
/** [default] Allows everything */
final object Wildcard extends AuthServiceConfig {
override def create(): AuthService = AuthServiceWildcard
def jwtTimestampLeeway: Option[JwtTimestampLeeway] = None
override def create(jwtTimestampLeeway: Option[JwtTimestampLeeway]): AuthService =
AuthServiceWildcard
}
/** [UNSAFE] Enables JWT-based authorization with shared secret HMAC256 signing: USE THIS EXCLUSIVELY FOR TESTING */
final case class UnsafeJwtHmac256(
secret: String,
jwtTimestampLeeway: Option[JwtTimestampLeeway] = None,
) extends AuthServiceConfig {
private lazy val verifier =
final case class UnsafeJwtHmac256(secret: String) extends AuthServiceConfig {
private def verifier(jwtTimestampLeeway: Option[JwtTimestampLeeway]): JwtVerifier =
HMAC256Verifier(secret, jwtTimestampLeeway).valueOr(err =>
throw new IllegalArgumentException(
s"Failed to create HMAC256 verifier (secret: $secret): $err"
)
)
override def create(): AuthService = AuthServiceJWT(verifier)
def create(jwtTimestampLeeway: Option[JwtTimestampLeeway]): AuthService = AuthServiceJWT(
verifier(jwtTimestampLeeway)
)
}
/** Enables JWT-based authorization, where the JWT is signed by RSA256 with the verifying public key loaded from the given X509 certificate file (.crt) */
final case class JwtRs256(
certificate: String,
jwtTimestampLeeway: Option[JwtTimestampLeeway] = None,
) extends AuthServiceConfig {
private lazy val verifier = RSA256Verifier
final case class JwtRs256(certificate: String) extends AuthServiceConfig {
private def verifier(jwtTimestampLeeway: Option[JwtTimestampLeeway]) = RSA256Verifier
.fromCrtFile(certificate, jwtTimestampLeeway)
.valueOr(err => throw new IllegalArgumentException(s"Failed to create RSA256 verifier: $err"))
override def create(): AuthService = AuthServiceJWT(verifier)
override def create(jwtTimestampLeeway: Option[JwtTimestampLeeway]): AuthService =
AuthServiceJWT(verifier(jwtTimestampLeeway))
}
/** "Enables JWT-based authorization, where the JWT is signed by ECDSA256 with the verifying public key loaded from the given X509 certificate file (.crt)" */
final case class JwtEs256(
certificate: String,
jwtTimestampLeeway: Option[JwtTimestampLeeway] = None,
) extends AuthServiceConfig {
private lazy val verifier = ECDSAVerifier
final case class JwtEs256(certificate: String) extends AuthServiceConfig {
private def verifier(jwtTimestampLeeway: Option[JwtTimestampLeeway]) = ECDSAVerifier
.fromCrtFile(certificate, Algorithm.ECDSA256(_, null), jwtTimestampLeeway)
.valueOr(err =>
throw new IllegalArgumentException(s"Failed to create ECDSA256 verifier: $err")
)
override def create(): AuthService = AuthServiceJWT(verifier)
override def create(jwtTimestampLeeway: Option[JwtTimestampLeeway]): AuthService =
AuthServiceJWT(verifier(jwtTimestampLeeway))
}
/** Enables JWT-based authorization, where the JWT is signed by ECDSA512 with the verifying public key loaded from the given X509 certificate file (.crt) */
final case class JwtEs512(
certificate: String,
jwtTimestampLeeway: Option[JwtTimestampLeeway] = None,
) extends AuthServiceConfig {
private lazy val verifier = ECDSAVerifier
final case class JwtEs512(certificate: String) extends AuthServiceConfig {
private def verifier(jwtTimestampLeeway: Option[JwtTimestampLeeway]) = ECDSAVerifier
.fromCrtFile(certificate, Algorithm.ECDSA512(_, null), jwtTimestampLeeway)
.valueOr(err =>
throw new IllegalArgumentException(s"Failed to create ECDSA512 verifier: $err")
)
override def create(): AuthService = AuthServiceJWT(verifier)
override def create(jwtTimestampLeeway: Option[JwtTimestampLeeway]): AuthService =
AuthServiceJWT(verifier(jwtTimestampLeeway))
}
/** Enables JWT-based authorization, where the JWT is signed by RSA256 with the verifying public key loaded from the given JWKS URL */
final case class JwtRs256Jwks(url: String, jwtTimestampLeeway: Option[JwtTimestampLeeway] = None)
extends AuthServiceConfig {
private lazy val verifier = JwksVerifier(url, jwtTimestampLeeway)
override def create(): AuthService = AuthServiceJWT(verifier)
final case class JwtRs256Jwks(url: String) extends AuthServiceConfig {
private def verifier(jwtTimestampLeeway: Option[JwtTimestampLeeway]) =
JwksVerifier(url, jwtTimestampLeeway)
override def create(jwtTimestampLeeway: Option[JwtTimestampLeeway]): AuthService =
AuthServiceJWT(verifier(jwtTimestampLeeway))
}
}

View File

@ -3,6 +3,7 @@
package com.daml.platform.config
import com.daml.jwt.JwtTimestampLeeway
import com.daml.lf.data.Ref
import com.daml.platform.apiserver.{ApiServerConfig, AuthServiceConfig}
import com.daml.platform.configuration.IndexServiceConfig
@ -15,6 +16,7 @@ import scala.concurrent.duration._
final case class ParticipantConfig(
apiServer: ApiServerConfig = ApiServerConfig(),
authentication: AuthServiceConfig = AuthServiceConfig.Wildcard,
jwtTimestampLeeway: Option[JwtTimestampLeeway] = None,
dataSourceProperties: DataSourceProperties = DataSourceProperties(
connectionPool = ConnectionPoolConfig(
connectionPoolSize = 16,

View File

@ -34,6 +34,7 @@ da_scala_library(
"//daml-lf/language",
"//daml-lf/transaction",
"//language-support/scala/bindings",
"//ledger-service/jwt",
"//ledger/caching",
"//ledger/error",
"//ledger/ledger-api-auth",

View File

@ -326,8 +326,12 @@ ledger {
# JWKS URL for jwt-rs-256-jwks algorithm
# url = "https://localhost/jwks.json"
}
# jwt-timestamp-leeway {
# # Enables leeway for JWT tokens with provided configuration below.
# enabled = true
#jwt-timestamp-leeway {
# # Define the default window in seconds in which the Not Before, Issued At and Expires At Claims will still be valid.
# default = 500
#
@ -340,7 +344,6 @@ ledger {
# # Set a specific leeway window in seconds in which the Not Before ("nbf") Claim will still be valid.
# not-before = 5
#}
}
data-source-properties {
# This section defines connection pool properties for the participant node. The Indexer does have it's own data

View File

@ -6,7 +6,6 @@ package com.daml.ledger.sandbox
import com.daml.ledger.api.auth.{AuthService, AuthServiceWildcard}
import com.daml.ledger.configuration.Configuration
import com.daml.ledger.runner.common.Config
import com.daml.platform.config.{MetricsConfig, ParticipantConfig => _ParticipantConfig}
import com.daml.lf.data.Ref
import com.daml.lf.data.Ref.ParticipantId
import com.daml.lf.engine.{EngineConfig => _EngineConfig}
@ -14,6 +13,7 @@ import com.daml.lf.language.LanguageVersion
import com.daml.platform.apiserver.SeedService.Seeding
import com.daml.platform.apiserver.{AuthServiceConfig, ApiServerConfig => _ApiServerConfig}
import com.daml.platform.config.MetricsConfig.MetricRegistryType
import com.daml.platform.config.{MetricsConfig, ParticipantConfig => _ParticipantConfig}
import com.daml.platform.configuration.{InitialLedgerConfiguration, PartyConfiguration}
import com.daml.platform.indexer.{IndexerConfig => _IndexerConfig}
import com.daml.platform.store.DbSupport.ParticipantDataSourceConfig

View File

@ -259,7 +259,8 @@ object SandboxOnXRunner {
extra: BridgeConfig,
): Unit = {
val apiServerConfig = participantConfig.apiServer
val authentication = participantConfig.authentication.create() match {
val authentication =
participantConfig.authentication.create(participantConfig.jwtTimestampLeeway) match {
case _: AuthServiceJWT => "JWT-based authentication"
case AuthServiceNone => "none authenticated"
case _: AuthServiceStatic => "static authentication"