mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-20 01:07:18 +03:00
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:
parent
aa231edc07
commit
2b89f6beea
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -39,5 +39,5 @@ class ConfigAdaptor {
|
||||
}
|
||||
|
||||
def authService(participantConfig: ParticipantConfig): AuthService =
|
||||
participantConfig.authentication.create()
|
||||
participantConfig.authentication.create(participantConfig.jwtTimestampLeeway)
|
||||
}
|
||||
|
@ -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] =
|
||||
|
@ -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,
|
||||
|
@ -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"),
|
||||
|
@ -161,7 +161,7 @@ class LedgerApiServer(
|
||||
ledgerFeatures = ledgerFeatures,
|
||||
participantId = participantId,
|
||||
authService = authService,
|
||||
jwtTimestampLeeway = participantConfig.authentication.jwtTimestampLeeway,
|
||||
jwtTimestampLeeway = participantConfig.jwtTimestampLeeway,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
|
@ -326,22 +326,25 @@ ledger {
|
||||
|
||||
# JWKS URL for jwt-rs-256-jwks algorithm
|
||||
# url = "https://localhost/jwks.json"
|
||||
|
||||
#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
|
||||
#
|
||||
# # Set a specific leeway window in seconds in which the Expires At ("exp") Claim will still be valid.
|
||||
# expires-at = 5
|
||||
#
|
||||
# # Set a specific leeway window in seconds in which the Issued At ("iat") Claim will still be valid.
|
||||
# issued-at = 5
|
||||
#
|
||||
# # Set a specific leeway window in seconds in which the Not Before ("nbf") Claim will still be valid.
|
||||
# not-before = 5
|
||||
#}
|
||||
}
|
||||
|
||||
# jwt-timestamp-leeway {
|
||||
# # Enables leeway for JWT tokens with provided configuration below.
|
||||
# enabled = true
|
||||
|
||||
# # Define the default window in seconds in which the Not Before, Issued At and Expires At Claims will still be valid.
|
||||
# default = 500
|
||||
#
|
||||
# # Set a specific leeway window in seconds in which the Expires At ("exp") Claim will still be valid.
|
||||
# expires-at = 5
|
||||
#
|
||||
# # Set a specific leeway window in seconds in which the Issued At ("iat") Claim will still be valid.
|
||||
# issued-at = 5
|
||||
#
|
||||
# # 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
|
||||
# source properties.
|
||||
|
@ -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
|
||||
|
@ -259,13 +259,14 @@ object SandboxOnXRunner {
|
||||
extra: BridgeConfig,
|
||||
): Unit = {
|
||||
val apiServerConfig = participantConfig.apiServer
|
||||
val authentication = participantConfig.authentication.create() match {
|
||||
case _: AuthServiceJWT => "JWT-based authentication"
|
||||
case AuthServiceNone => "none authenticated"
|
||||
case _: AuthServiceStatic => "static authentication"
|
||||
case AuthServiceWildcard => "all unauthenticated allowed"
|
||||
case other => other.getClass.getSimpleName
|
||||
}
|
||||
val authentication =
|
||||
participantConfig.authentication.create(participantConfig.jwtTimestampLeeway) match {
|
||||
case _: AuthServiceJWT => "JWT-based authentication"
|
||||
case AuthServiceNone => "none authenticated"
|
||||
case _: AuthServiceStatic => "static authentication"
|
||||
case AuthServiceWildcard => "all unauthenticated allowed"
|
||||
case other => other.getClass.getSimpleName
|
||||
}
|
||||
|
||||
val ledgerDetails =
|
||||
Seq[(String, String)](
|
||||
|
Loading…
Reference in New Issue
Block a user