mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-20 01:07:18 +03:00
trigger reach auth on internal network (#10844)
In many network setups, there will be a more direct route for the trigger service to contact the auth middleware than going back through the frontend's public IP address (and possibly thus also through intermediaries like an nginx reverse proxy etc.). In _some_ network setups, it may not even be possible for the trigger service to reach the auth middleware through its externally-visible address. This PR caters to these cases by allowing the trigger service to use two separate addresses for the auth middleware, an internal one the trigger service uses when it needs to talk to the auth middleware, and an external one used in generating URLs for external clients. This is backwards-compatible: if the old option is used, we simply use the same value for both. CHANGELOG_BEGIN - The Trigger Service can now accept separate `--auth-internal` and `--auth-external` CLI arguments, where `--auth-internal` is the address used by the Trigger Service to reach the Auth Middleware directly, and `--auth-external` is the address the Trigger Service uses in generated URLs sent back to the client. The `--auth` option remains and keeps working as before, setting both internal and external addresses to the same given value. CHANGELOG_END
This commit is contained in:
parent
79080839c1
commit
b4750a495c
@ -218,7 +218,7 @@ class Client(config: Client.Config) {
|
||||
if (redirect) { Some(callbackUri) }
|
||||
else { None }
|
||||
appendToUri(
|
||||
config.authMiddlewareUri,
|
||||
config.authMiddlewareExternalUri,
|
||||
Path./("login"),
|
||||
Request.Login(redirectUri, claims, requestId.map(_.toString)).toQuery,
|
||||
)
|
||||
@ -292,13 +292,13 @@ class Client(config: Client.Config) {
|
||||
|
||||
def authUri(claims: Request.Claims): Uri =
|
||||
appendToUri(
|
||||
config.authMiddlewareUri,
|
||||
config.authMiddlewareInternalUri,
|
||||
Path./("auth"),
|
||||
Request.Auth(claims).toQuery,
|
||||
)
|
||||
|
||||
val refreshUri: Uri =
|
||||
appendToUri(config.authMiddlewareUri, Path./("refresh"))
|
||||
appendToUri(config.authMiddlewareInternalUri, Path./("refresh"))
|
||||
}
|
||||
|
||||
object Client {
|
||||
@ -326,7 +326,8 @@ object Client {
|
||||
}
|
||||
|
||||
case class Config(
|
||||
authMiddlewareUri: Uri,
|
||||
authMiddlewareInternalUri: Uri,
|
||||
authMiddlewareExternalUri: Uri,
|
||||
redirectToLogin: RedirectToLogin,
|
||||
maxAuthCallbacks: Int,
|
||||
authCallbackTimeout: FiniteDuration,
|
||||
|
@ -118,13 +118,15 @@ trait TestFixture
|
||||
),
|
||||
)
|
||||
)
|
||||
authUri = Uri()
|
||||
.withScheme("http")
|
||||
.withAuthority(
|
||||
middlewareBinding.localAddress.getHostName,
|
||||
middlewareBinding.localAddress.getPort,
|
||||
)
|
||||
middlewareClientConfig = Client.Config(
|
||||
authMiddlewareUri = Uri()
|
||||
.withScheme("http")
|
||||
.withAuthority(
|
||||
middlewareBinding.localAddress.getHostName,
|
||||
middlewareBinding.localAddress.getPort,
|
||||
),
|
||||
authMiddlewareInternalUri = authUri,
|
||||
authMiddlewareExternalUri = authUri,
|
||||
redirectToLogin = redirectToLogin,
|
||||
maxAuthCallbacks = maxClientAuthCallbacks,
|
||||
authCallbackTimeout = FiniteDuration(1, duration.MINUTES),
|
||||
|
@ -605,7 +605,8 @@ class TestMiddlewareClientLoginCallbackUri
|
||||
with ScalatestRouteTest {
|
||||
private val client = Client(
|
||||
Client.Config(
|
||||
authMiddlewareUri = Uri("http://auth.domain"),
|
||||
authMiddlewareInternalUri = Uri("http://auth.internal"),
|
||||
authMiddlewareExternalUri = Uri("http://auth.external"),
|
||||
redirectToLogin = Client.RedirectToLogin.Yes,
|
||||
maxAuthCallbacks = 1000,
|
||||
authCallbackTimeout = FiniteDuration(1, duration.MINUTES),
|
||||
@ -622,7 +623,7 @@ class TestMiddlewareClientLoginCallbackUri
|
||||
val claims = Request.Claims(actAs = List(Party("Alice")))
|
||||
routes.loginUri(claims = claims) shouldBe
|
||||
Uri(
|
||||
s"http://auth.domain/login?claims=${claims.toQueryString()}&redirect_uri=http://client.domain/cb"
|
||||
s"http://auth.external/login?claims=${claims.toQueryString()}&redirect_uri=http://client.domain/cb"
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -635,7 +636,7 @@ class TestMiddlewareClientLoginCallbackUri
|
||||
complete(routes.loginUri(claims).toString)
|
||||
} ~> check {
|
||||
responseAs[String] shouldEqual
|
||||
s"http://auth.domain/login?claims=${claims.toQueryString()}&redirect_uri=http://client.domain/cb"
|
||||
s"http://auth.external/login?claims=${claims.toQueryString()}&redirect_uri=http://client.domain/cb"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -646,7 +647,7 @@ class TestMiddlewareClientLoginCallbackUri
|
||||
import akka.http.scaladsl.server.directives.RouteDirectives._
|
||||
Get() ~> routes { routes => complete(routes.loginUri(claims).toString) } ~> check {
|
||||
responseAs[String] shouldEqual
|
||||
s"http://auth.domain/login?claims=${claims.toQueryString()}&redirect_uri=http://client.domain/cb"
|
||||
s"http://auth.external/login?claims=${claims.toQueryString()}&redirect_uri=http://client.domain/cb"
|
||||
}
|
||||
}
|
||||
"be from request when relative" in {
|
||||
@ -657,7 +658,7 @@ class TestMiddlewareClientLoginCallbackUri
|
||||
complete(routes.loginUri(claims).toString)
|
||||
} ~> check {
|
||||
responseAs[String] shouldEqual
|
||||
s"http://auth.domain/login?claims=${claims.toQueryString()}&redirect_uri=http://client.domain/cb"
|
||||
s"http://auth.external/login?claims=${claims.toQueryString()}&redirect_uri=http://client.domain/cb"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -517,11 +517,12 @@ object Server {
|
||||
|
||||
val authClientRoutes = authConfig match {
|
||||
case NoAuth => None
|
||||
case AuthMiddleware(uri) =>
|
||||
case AuthMiddleware(int, ext) =>
|
||||
val client =
|
||||
AuthClient(
|
||||
AuthClient.Config(
|
||||
authMiddlewareUri = uri,
|
||||
authMiddlewareInternalUri = int,
|
||||
authMiddlewareExternalUri = ext,
|
||||
redirectToLogin = authRedirectToLogin,
|
||||
maxAuthCallbacks = maxAuthCallbacks,
|
||||
authCallbackTimeout = authCallbackTimeout,
|
||||
|
@ -23,7 +23,9 @@ private[trigger] final case class ServiceConfig(
|
||||
httpPort: Int,
|
||||
ledgerHost: String,
|
||||
ledgerPort: Int,
|
||||
authUri: Option[Uri],
|
||||
authInternalUri: Option[Uri],
|
||||
authExternalUri: Option[Uri],
|
||||
authBothUri: Option[Uri],
|
||||
authRedirectToLogin: AuthClient.RedirectToLogin,
|
||||
authCallbackUri: Option[Uri],
|
||||
maxInboundMessageSize: Int,
|
||||
@ -90,8 +92,24 @@ private[trigger] object ServiceConfig {
|
||||
|
||||
opt[String]("auth")
|
||||
.optional()
|
||||
.action((t, c) => c.copy(authUri = Some(Uri(t))))
|
||||
.text("Auth middleware URI.")
|
||||
.action((t, c) => c.copy(authBothUri = Some(Uri(t))))
|
||||
.text(
|
||||
"Sets both the internal and external auth URIs. Incompatible with --auth-internal and --auth-external."
|
||||
)
|
||||
|
||||
opt[String]("auth-internal")
|
||||
.optional()
|
||||
.action((t, c) => c.copy(authInternalUri = Some(Uri(t))))
|
||||
.text(
|
||||
"Sets the internal auth URIs (used by the trigger service to connect directly to the middleware). Incompatible with --auth."
|
||||
)
|
||||
|
||||
opt[String]("auth-external")
|
||||
.optional()
|
||||
.action((t, c) => c.copy(authExternalUri = Some(Uri(t))))
|
||||
.text(
|
||||
"Sets the external auth URI (the one returned to the browser). Incompatible with --auth."
|
||||
)
|
||||
|
||||
opt[AuthClient.RedirectToLogin]("auth-redirect")
|
||||
.optional()
|
||||
@ -188,6 +206,16 @@ private[trigger] object ServiceConfig {
|
||||
+ JdbcConfig.help()
|
||||
)
|
||||
|
||||
checkConfig { cfg =>
|
||||
if (
|
||||
(cfg.authBothUri.nonEmpty && (cfg.authInternalUri.nonEmpty || cfg.authExternalUri.nonEmpty))
|
||||
|| (cfg.authInternalUri.nonEmpty != cfg.authExternalUri.nonEmpty)
|
||||
)
|
||||
failure("You must specify either just --auth or both --auth-internal and --auth-external.")
|
||||
else
|
||||
success
|
||||
}
|
||||
|
||||
cmd("init-db")
|
||||
.action((_, c) => c.copy(init = true))
|
||||
.text("Initialize database and terminate.")
|
||||
@ -205,7 +233,9 @@ private[trigger] object ServiceConfig {
|
||||
httpPort = DefaultHttpPort,
|
||||
ledgerHost = null,
|
||||
ledgerPort = 0,
|
||||
authUri = None,
|
||||
authInternalUri = None,
|
||||
authExternalUri = None,
|
||||
authBothUri = None,
|
||||
authRedirectToLogin = AuthClient.RedirectToLogin.No,
|
||||
authCallbackUri = None,
|
||||
maxInboundMessageSize = DefaultMaxInboundMessageSize,
|
||||
|
@ -95,10 +95,19 @@ object ServiceMain {
|
||||
case Left(err) => sys.error(s"Failed to read archive: $err")
|
||||
case Right(dars) => dars.map(_.map(p => p.pkgId -> p.proto))
|
||||
}
|
||||
val authConfig: AuthConfig = config.authUri match {
|
||||
case None => NoAuth
|
||||
case Some(uri) => AuthMiddleware(uri)
|
||||
}
|
||||
val authConfig: AuthConfig =
|
||||
(config.authInternalUri, config.authExternalUri, config.authBothUri) match {
|
||||
case (None, None, None) => NoAuth
|
||||
case (None, None, Some(both)) => AuthMiddleware(both, both)
|
||||
case (Some(int), Some(ext), None) => AuthMiddleware(int, ext)
|
||||
case (int, ext, both) =>
|
||||
// Note that this should never happen, as it should be caucht by
|
||||
// the checkConfig part of our scopt configuration
|
||||
logger.withoutContext.error(
|
||||
s"Must specify either both --auth-internal and --auth-external or just --auth. Got: auth-internal: $int, auth-external: $ext, auth: $both."
|
||||
)
|
||||
sys.exit(1)
|
||||
}
|
||||
val ledgerConfig =
|
||||
LedgerConfig(
|
||||
config.ledgerHost,
|
||||
|
@ -17,7 +17,7 @@ package trigger {
|
||||
|
||||
sealed trait AuthConfig
|
||||
case object NoAuth extends AuthConfig
|
||||
final case class AuthMiddleware(uri: Uri) extends AuthConfig
|
||||
final case class AuthMiddleware(internal: Uri, external: Uri) extends AuthConfig
|
||||
|
||||
import com.daml.auth.middleware.api.Tagged.{AccessToken, RefreshToken}
|
||||
import com.daml.ledger.api.refinements.ApiTypes.ApplicationId
|
||||
|
@ -35,5 +35,38 @@ class ServiceConfigTest extends AnyWordSpec with Matchers with OptionValues {
|
||||
Set("notcustom"),
|
||||
) should ===(None)
|
||||
}
|
||||
"auth and auth-* should not be set together" in {
|
||||
parse(baseOpts ++ Seq("--auth", "http://example.com"), Set()) should !==(None)
|
||||
parse(
|
||||
baseOpts ++ Seq(
|
||||
"--auth-internal",
|
||||
"http://example.com/1",
|
||||
"--auth-external",
|
||||
"http://example.com/2",
|
||||
),
|
||||
Set(),
|
||||
) should !==(None)
|
||||
parse(
|
||||
baseOpts ++ Seq("--auth", "http://example.com", "--auth-internal", "http://example.com/1"),
|
||||
Set(),
|
||||
) should ===(None)
|
||||
parse(
|
||||
baseOpts ++ Seq("--auth", "http://example.com", "--auth-external", "http://example.com/1"),
|
||||
Set(),
|
||||
) should ===(None)
|
||||
parse(baseOpts ++ Seq("--auth-internal", "http://example.com/1"), Set()) should ===(None)
|
||||
parse(baseOpts ++ Seq("--auth-external", "http://example.com/1"), Set()) should ===(None)
|
||||
parse(
|
||||
baseOpts ++ Seq(
|
||||
"--auth",
|
||||
"http://example.com",
|
||||
"--auth-internal",
|
||||
"http://example.com/1",
|
||||
"--auth-external",
|
||||
"http://example.com/2",
|
||||
),
|
||||
Set(),
|
||||
) should ===(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -159,7 +159,7 @@ trait AuthMiddlewareFixture
|
||||
.get
|
||||
jwt.value
|
||||
}
|
||||
protected def authConfig: AuthConfig = AuthMiddleware(authMiddlewareUri)
|
||||
protected def authConfig: AuthConfig = AuthMiddleware(authMiddlewareUri, authMiddlewareUri)
|
||||
protected def authClock: AdjustableClock = resource.value._1
|
||||
protected def authServer: OAuthServer = resource.value._2
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user