ajmyyra / ambassador-auth-oidc Goto Github PK
View Code? Open in Web Editor NEWOpenID Connect AuthService for Ambassador API Gateway
License: MIT License
OpenID Connect AuthService for Ambassador API Gateway
License: MIT License
Also implement a blacklist with regular updates from Redis for logged out JWTs.
Currently only tokens in a cookie are supported.
Will it be possible to:
Storing cookies to the users browsers seems unneeded in some use-cases.
Logging is currently all over the place and repeats itself on many lines. Create a separate logger to handle the cases.
Staring it against https keycloak i get this error.
2019/01/17 11:26:54 OIDC provider setup failed: Get https://sso-keycloak-sso.cloudapps02.euan-hume-02-ocp.svcs.dxc.com/auth/realms/kubeflow/.well-known/openid-configuration: x509: certificate signed by unknown authority
As an env, so we can easily return 200 to Ambassador on those endpoints that don't need authentication enabled.
Hello,
First a little description of my setup :)
In a kubernetes cluster i'm running the following:
Ambassador v: 0.72.0
Keycloak v: 5.0.0
and a echo-http-test service serving a "hello world page"
I'm trying to get ambassador-auth-oidc to work as middle-ware between ambassador and keycloak.
All services are up and running. Keycloak is configured with realm, client and user.
I can navigate to my echo-http-test uri, and it will redirect to keycloaks login page as expected.
I login, and i get redirected to ambassador-auth-oidc service "/login/oidc"
Here I keep on getting error-code response from ambassador-auth-oidc:
Failed to exchange token.
The full logs of the ambassador service contains:
2019/06/27 15:16:06 Failed to exchange token: oauth2: cannot fetch token: 500 Internal Server Error
Response:
And the logs of from Keycloak say:
ERROR [org.keycloak.services.error.KeycloakErrorHandler] (default task-160) Uncaught server error: java.lang.NullPointerException
at org.keycloak.protocol.oidc.endpoints.TokenEndpoint.codeToToken(TokenEndpoint.java:300)
at org.keycloak.protocol.oidc.endpoints.TokenEndpoint.processGrantRequest(TokenEndpoint.java:183)
at sun.reflect.GeneratedMethodAccessor751.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:139)
at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:509)
at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:399)
at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$0(ResourceMethodInvoker.java:363)
at org.jboss.resteasy.core.interception.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:355)
at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:365)
at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:337)
at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:137)
at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:106)
at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:132)
at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:100)
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:439)
at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:229)
at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:135)
at org.jboss.resteasy.core.interception.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:355)
at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:138)
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:215)
at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:227)
at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56)
at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:51)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:791)
at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)
at org.keycloak.services.filters.KeycloakSessionServletFilter.doFilter(KeycloakSessionServletFilter.java:90)
at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
at org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132)
at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
at io.undertow.security.handlers.NotificationReceiverHandler.handleRequest(NotificationReceiverHandler.java:50)
at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at org.wildfly.extension.undertow.deployment.GlobalRequestControllerHandler.handleRequest(GlobalRequestControllerHandler.java:68)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:292)
at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:81)
at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:138)
at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135)
at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
at org.wildfly.extension.undertow.security.SecurityContextThreadSetupAction.lambda$create$0(SecurityContextThreadSetupAction.java:105)
at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1502)
at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1502)
at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1502)
at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1502)
at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:272)
at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:81)
at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:104)
at io.undertow.server.Connectors.executeRootHandler(Connectors.java:360)
at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)
at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1985)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1487)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1378)
Now initially i thought this would be a error on keycloaks side, however i did a bit of a search around stack-overflow and found that the error is caused by a missing redirect_uri parameter in the request between oidc and keycloak.
I think it would be best-practice to use UTC time explicitly as the dex codebase does:
https://github.com/dexidp/dex/search?q=UTC&unscoped_q=UTC
Otherwise the code will just use whatever timezone the server is using.
I hit this when using a dex setup with a local client and this auth service running in the server. The server was on UTC and my local wasn't. So tokens generated on my local were rejected by the server, which was doing a check on when it was issued and thinking it was being used before it was even issued. It is a fringe use-case and we were able to address it by changing the local to use UTC. But it highlights why making UTC explicit might be a better practice.
Currently every user able to login at OIDC endpoint will be let in. Make it possible to limit users, for example with email domain or if their email_verified is set to true.
I have discovered that there is some confusion around the userinfo_endpoint and whether it's mandatory or not.
Dex, a big project around OIDC, doesn't seem to implement it yet and I have found references to the spec that characterize it as RECOMMENDED.
Because of the Dex use-case and also the fact that the userinfo endpoint doesn't seem to be mandatory, it makes sense to provide a flag to allow getting the claims from the id_token instead of the userinfo endpoint.
cc @ajmyyra
Have you considered an option to have this available without Redis and have the JWT token only be stored in the cookie? You will not be able to token blacklisting on logout, but this could be ok with short lived tokens and would simplify deployment?
Hi
I'm trying to connect Ambassador GW to Keycloak using this auth-oidc middleware.
The only change I made in the auth-deployment.yaml and auth-service.yaml was to change from port 8080 to 9090.
I'm using MacOS
Here is the K8S deployments:
NAME READY STATUS RESTARTS AGE
pod/ambassador-558b7b6cb7-8gzq9 1/1 Running 8 28h
pod/ambassador-558b7b6cb7-c8vd8 1/1 Running 9 28h
pod/ambassador-558b7b6cb7-m9jdp 1/1 Running 8 28h
pod/flinckr-events-7b97db9d64-pc2tz 1/1 Running 2 20h
pod/keycloak-0 1/1 Running 2 17d
pod/oidc-auth-6cb698d467-ljncs 2/2 Running 5 20h
pod/postgres-kc-postgresql-0 1/1 Running 2 18d
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/ambassador LoadBalancer 10.99.201.29 localhost 80:32393/TCP,443:30066/TCP 4d3h
service/ambassador-admin ClusterIP 10.99.238.140 <none> 8877/TCP 4d3h
service/ambassador-oidc-auth ClusterIP 10.105.50.239 <none> 9090/TCP 20h
service/flinckr-events ClusterIP 10.101.31.137 <none> 9000/TCP 20h
service/keycloak-headless ClusterIP None <none> 8080/TCP,8443/TCP 17d
service/keycloak-http LoadBalancer 10.107.104.233 localhost 8080:32156/TCP,8443:32712/TCP 5d15h
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 18d
service/oidc-auth ClusterIP 10.102.175.104 <none> 9090/TCP 20h
service/postgres-kc-postgresql ClusterIP 10.103.204.109 <none> 5432/TCP 18d
service/postgres-kc-postgresql-headless ClusterIP None <none> 5432/TCP 18d
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/ambassador 3/3 3 3 4d3h
deployment.apps/flinckr-events 1/1 1 1 20h
deployment.apps/oidc-auth 1/1 1 1 20h
NAME DESIRED CURRENT READY AGE
replicaset.apps/ambassador-558b7b6cb7 3 3 3 4d3h
replicaset.apps/flinckr-events-7b97db9d64 1 1 1 20h
replicaset.apps/oidc-auth-6cb698d467 1 1 1 20h
NAME READY AGE
statefulset.apps/keycloak 1/1 17d
statefulset.apps/postgres-kc-postgresql 1/1 18d
The task is to make Ambassador to redirect to Keycloak in case the user is not authenticated, or in case the session is expired.
Here are the secrets I configured:
kubectl create secret generic ambassador-auth-jwt-key --from-literal=jwt-key=$(openssl rand -base64 64|tr -d '\n ')
kubectl create secret generic ambassador-auth-redis-password --from-literal=redis-password=$(openssl rand -base64 20)
kubectl create secret generic ambassador-auth-oidc-provider --from-literal=oidc-provider="http://keycloak-http:8080/auth/realms/flinckr_realm"
kubectl create secret generic ambassador-auth-self-url --from-literal=self-url="http://localhost"
kubectl create secret generic ambassador-auth-client-id --from-literal=client-id="flinckr_backend"
kubectl create secret generic ambassador-auth-client-secret --from-literal=client-secret= <MY_SECRET>
I'm able to successfully go thru the OIDC process, as was nicely explained in the OIDC-flow.png
The problem I'm facing is that the last redirect is wrong.
I'll explain....
Here what I see in the browser:
1- going to http://localhost/events
Call is being directed to keycloak as was expected
2- Keycloak shows login screen
3- Code is being sent to the browser and from there back to oidc-auth service for generating JWT (if I get it correctly)
4- And now that user is authenticated, I expect the browser to be redirected back to my original request which was http://localhost/events.
But here is the problem, the browser is being redirected to http://localhost (which of course returns 404 Error), instead of going to http://localhost/events.
You could see from the attached images, that the authentication process is correct. The only thing is missing how to make sure the last redirect URL is set correctly
This is the Ambassador image:
image:
repository: quay.io/datawire/ambassador
tag: 0.86.0
pullPolicy: IfNotPresent
This is the Keycloak image:
image:
repository: jboss/keycloak
tag: 7.0.0
pullPolicy: IfNotPresent
Appreciate your help
Hi there!
This is likely an extremely stupid question. But I wanted to ask why you add the following extra paths to the SELF_URL variable? I assumed that this variable was the URL we would want to end up redirecting to, and wanted to understand why this was needed?
redirURL = redirURL + "/login/oidc"
Thanks for your help!
Jay
I have trouble understanding the auth process. Looking at the flow diagaram client authenticates against auth service and ambassador should just proxy the request to the app on successful auth. How ever after setting everything up as in the example deployment (using Auth0) i get the following:
https://redacted.domain/login/oidc?code=Hg9neaU6hCkunLDu&state=8gLxLOLb
is 404
What service should get this /login/oidc reqest?
in some OIDC provider such as Azure Active Directory it's possible to configure the client application to emit groups claim (or roles claim). For instance, this and that.
With these claims, we can implement access control so that only ones in the selected group can access the sensitive resources protected by Ambassador API gateway.
Since I have already implemented this feature in my fork, I'm happy to send a PR if you think it's useful.
Currently, when the JWT validation fails, an HTTP error code is sent along with a small message.
Clearing the cookie and redirecting to login would make for a better UX.
@ajmyyra what do you think? I will open a PR for this if you agree.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.