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:
Gary Verhaegen 2021-09-14 16:16:42 +02:00 committed by GitHub
parent 79080839c1
commit b4750a495c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 104 additions and 27 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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