trino
trino copied to clipboard
OAuth Token Refresh will cause JWT authentication to fail
Overview
We can enable token refresh with something like the following config:
http-server.authentication.type=PASSWORD,oauth2
http-server.authentication.oauth2.client-id=${ENV:OAUTH_CLIENT_ID}
http-server.authentication.oauth2.client-secret=${ENV:OAUTH_CLIENT_SECRET}
http-server.authentication.oauth2.issuer={server}
http-server.authentication.oauth2.additional-audiences=api://default
http-server.authentication.oauth2.oidc.use-userinfo-endpoint=false
http-server.authentication.oauth2.auth-url={url}
http-server.authentication.oauth2.token-url={url-2}
http-server.authentication.oauth2.jwks-url={url-3}
http-server.authentication.oauth2.scopes=insights_read_write,openid,offline_access
http-server.authentication.oauth2.refresh-tokens=true
http-server.authentication.oauth2.refresh-tokens.issued-token.timeout=1h
This will make Trino use a different implementation of TokenPairSerializer that will encrypt tokens trino fetches through the OAuth Challenge:
https://github.com/trinodb/trino/blob/b3d03cde33583e312f077d53b9133e13487ce92b/core/trino-main/src/main/java/io/trino/server/security/oauth2/JweTokenSerializer.java#L145 and it will also attempt to decrypt all JWT tokens:
(https://github.com/trinodb/trino/blob/b3d03cde33583e312f077d53b9133e13487ce92b/core/trino-main/src/main/java/io/trino/server/security/oauth2/JweTokenSerializer.java#L103).
Issue
Token refresh when only OAuth is enabled works fine, but when we enable both JWT and OAuth, which someone could want to do if they want to support both SSO and JWT for M2M auth, Trino will attempt to decrypt any JWT tokens it receives. This results in the following error:
Error running command: Error starting query at {url} returned an invalid response: JsonResponse{statusCode=500, statusMessage=, headers={content-length=[5842], content-type=[text/plain], date=[Thu, 28 Jul 2022 15:36:48 GMT], strict-transport-security=[max-age=15724800; includeSubDomains]}, hasValue=false} [Error: java.lang.RuntimeException: Authentication error
at io.trino.server.security.AbstractBearerAuthenticator.authenticate(AbstractBearerAuthenticator.java:47)
at io.trino.server.security.AbstractBearerAuthenticator.authenticate(AbstractBearerAuthenticator.java:34)
at io.trino.server.security.AuthenticationFilter.filter(AuthenticationFilter.java:88)
at org.glassfish.jersey.server.ContainerFilteringStage.apply(ContainerFilteringStage.java:132)
at org.glassfish.jersey.server.ContainerFilteringStage.apply(ContainerFilteringStage.java:68)
...
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1034)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.IllegalArgumentException: Malformed jwt token
at io.trino.server.security.oauth2.JweTokenSerializer.deserialize(JweTokenSerializer.java:111)
at io.trino.server.security.oauth2.OAuth2Authenticator.createIdentity(OAuth2Authenticator.java:67)
at io.trino.server.security.AbstractBearerAuthenticator.authenticate(AbstractBearerAuthenticator.java:41)
... 62 more
Caused by: java.text.ParseException: Unexpected number of Base64URL parts, must be five
at com.nimbusds.jose.JWEObject.parse(JWEObject.java:506)
at io.trino.server.security.oauth2.JweTokenSerializer.deserialize(JweTokenSerializer.java:102)
... 64 more
]
Thrown here because in this case, JWT tokens passed in through --access-token are not encrypted, meaning parsing will fail:
https://github.com/trinodb/trino/blob/b3d03cde33583e312f077d53b9133e13487ce92b/core/trino-main/src/main/java/io/trino/server/security/oauth2/JweTokenSerializer.java#L102
trino config to reproduce:
http-server.authentication.type=PASSWORD,oauth2,JWT
http-server.authentication.oauth2.client-id=${ENV:OAUTH_CLIENT_ID}
http-server.authentication.oauth2.client-secret=${ENV:OAUTH_CLIENT_SECRET}
http-server.authentication.oauth2.issuer={server}
http-server.authentication.oauth2.additional-audiences=api://default
http-server.authentication.oauth2.oidc.use-userinfo-endpoint=false
http-server.authentication.oauth2.auth-url={url}
http-server.authentication.oauth2.token-url={url-2}
http-server.authentication.oauth2.jwks-url={url-3}
http-server.authentication.oauth2.scopes=insights_read_write,openid,offline_access
http-server.authentication.oauth2.refresh-tokens=true
http-server.authentication.oauth2.refresh-tokens.issued-token.timeout=1h
http-server.authentication.oauth2.oidc.use-userinfo-endpoint=false
# http-server.authentication.oauth2.oidc.discovery=false
http-server.authentication.jwt.key-file={url-4}
Note that the trino docs suggest that if one form of Auth fails the next authentication type in the list should be attempted, but in this case, Oauth fails and JWT is not attempted, likely due to the un-handled parsing exception.
Workaround
If I change the ordering of the auth types likesso:
http-server.authentication.type=PASSWORD,JWT,oauth2
JWT will be attempted first, resulting in no parsing exceptions.
Suggested Change
If parsing fails, don't throw an error but mark the authentication as failed so we move to the next authentication type in the chain, if any.
cc @kokosing @s2lomon @lukasz-walkiewicz