tchiotludo / akhq

Kafka GUI for Apache Kafka to manage topics, topics data, consumers group, schema registry, connect and more...
https://akhq.io/
Apache License 2.0
3.41k stars 660 forks source link

Login with Okta - Error on autorize callback #827

Closed belboo closed 3 years ago

belboo commented 3 years ago

Probably my failure to configure something correctly, but would greatly appreciate a hint as to where I may be going wrong! 🙏🏽

Deployment in GCP k8s, trying to authenticate with Okta using roughly the following configuration:

akhq:
  security:
    oidc:
      enabled: true
      providers:
        okta:
          label: "Login with Okta"
          username-field: name
          groups-field: roles
          default-group: xxx
          groups: [yyy]

micronaut:
  security:
    token:
      jwt:
        signatures:
          secret:
            generator:
              secret: "somerandomstring"
    authentication: idtoken
    enabled: true
    oauth2:
      clients:
        okta:
          client-id: xxx
          client-secret: yyy
          openid:
            issuer: https://companydomain.okta.com/oauth2/default
    endpoints:
      logout:
        get-allowed: true

and receiving the following error on /authorize callback:

{
  "message": "Internal Server Error: null",
  "_links": {
    "self": {
      "href": "/oauth/callback/okta?code=ccc&state=sss",
      "templated": false
    }
  },
  "_embedded": {
    "stacktrace": {
      "message": "see below"
    }
  }
}

Error is:

io.micronaut.security.errors.OauthErrorResponseException
  at io.micronaut.security.oauth2.endpoint.token.response.IdTokenLoginHandler.lambda$getCookies$0(IdTokenLoginHandler.java:80)
  at java.base/java.util.Optional.orElseThrow(Unknown Source)
  at io.micronaut.security.oauth2.endpoint.token.response.IdTokenLoginHandler.getCookies(IdTokenLoginHandler.java:80)
  at io.micronaut.security.token.jwt.cookie.CookieLoginHandler.loginSuccess(CookieLoginHandler.java:117)
  at io.micronaut.security.oauth2.routes.DefaultOauthController.lambda$callback$0(DefaultOauthController.java:93)
  at io.reactivex.internal.operators.flowable.FlowableMap$MapSubscriber.onNext(FlowableMap.java:63)
  at io.micronaut.reactive.rxjava2.RxInstrumentedSubscriber.onNext(RxInstrumentedSubscriber.java:59)
  at io.reactivex.internal.operators.flowable.FlowableSwitchMap$SwitchMapSubscriber.drain(FlowableSwitchMap.java:307)
  at io.reactivex.internal.operators.flowable.FlowableSwitchMap$SwitchMapInnerSubscriber.onNext(FlowableSwitchMap.java:391)
  at io.micronaut.reactive.rxjava2.RxInstrumentedSubscriber.onNext(RxInstrumentedSubscriber.java:59)
  at io.reactivex.internal.operators.flowable.FlowableCreate$NoOverflowBaseAsyncEmitter.onNext(FlowableCreate.java:403)
  at io.micronaut.security.oauth2.endpoint.authorization.response.DefaultOpenIdAuthorizationResponseHandler.lambda$createAuthenticationResponse$1(DefaultOpenIdAuthorizationResponseHandler.java:166)
  at io.reactivex.internal.operators.flowable.FlowableCreate.subscribeActual(FlowableCreate.java:71)
  at io.reactivex.Flowable.subscribe(Flowable.java:14918)
  at io.reactivex.Flowable.subscribe(Flowable.java:14865)
  at io.micronaut.reactive.rxjava2.RxInstrumentedFlowable.subscribeActual(RxInstrumentedFlowable.java:57)
  at io.reactivex.Flowable.subscribe(Flowable.java:14918)
  at io.reactivex.Flowable.subscribe(Flowable.java:14865)
  at io.reactivex.internal.operators.flowable.FlowableSwitchMap$SwitchMapSubscriber.onNext(FlowableSwitchMap.java:129)
  at io.micronaut.reactive.rxjava2.RxInstrumentedSubscriber.onNext(RxInstrumentedSubscriber.java:59)
  at io.micronaut.core.async.publisher.Publishers$1.doOnNext(Publishers.java:216)
  at io.micronaut.core.async.subscriber.CompletionAwareSubscriber.onNext(CompletionAwareSubscriber.java:52)
  at io.reactivex.internal.util.HalfSerializer.onNext(HalfSerializer.java:45)
  at io.reactivex.internal.subscribers.StrictSubscriber.onNext(StrictSubscriber.java:97)
  at io.micronaut.reactive.rxjava2.RxInstrumentedSubscriber.onNext(RxInstrumentedSubscriber.java:59)
  at io.reactivex.internal.operators.flowable.FlowableSwitchMap$SwitchMapSubscriber.drain(FlowableSwitchMap.java:307)
  at io.reactivex.internal.operators.flowable.FlowableSwitchMap$SwitchMapInnerSubscriber.onNext(FlowableSwitchMap.java:391)
  at io.micronaut.reactive.rxjava2.RxInstrumentedSubscriber.onNext(RxInstrumentedSubscriber.java:59)
  at io.reactivex.internal.operators.flowable.FlowableOnErrorNext$OnErrorNextSubscriber.onNext(FlowableOnErrorNext.java:80)
  at io.micronaut.reactive.rxjava2.RxInstrumentedSubscriber.onNext(RxInstrumentedSubscriber.java:59)
  at io.reactivex.internal.operators.flowable.FlowableTimeoutTimed$TimeoutSubscriber.onNext(FlowableTimeoutTimed.java:101)
  at io.micronaut.reactive.rxjava2.RxInstrumentedSubscriber.onNext(RxInstrumentedSubscriber.java:59)
  at io.micronaut.configuration.metrics.binder.web.WebMetricsPublisher$1.onNext(WebMetricsPublisher.java:172)
  at io.micronaut.configuration.metrics.binder.web.WebMetricsPublisher$1.onNext(WebMetricsPublisher.java:155)
  at io.micronaut.http.client.filters.ClientServerRequestTracingPublisher$1.lambda$onNext$1(ClientServerRequestTracingPublisher.java:60)
  at io.micronaut.http.context.ServerRequestContext.with(ServerRequestContext.java:68)
  at io.micronaut.http.client.filters.ClientServerRequestTracingPublisher$1.onNext(ClientServerRequestTracingPublisher.java:60)
  at io.micronaut.http.client.filters.ClientServerRequestTracingPublisher$1.onNext(ClientServerRequestTracingPublisher.java:52)
  at io.reactivex.internal.util.HalfSerializer.onNext(HalfSerializer.java:45)
  at io.reactivex.internal.subscribers.StrictSubscriber.onNext(StrictSubscriber.java:97)
  at io.micronaut.reactive.rxjava2.RxInstrumentedSubscriber.onNext(RxInstrumentedSubscriber.java:59)
  at io.reactivex.internal.operators.flowable.FlowableCreate$NoOverflowBaseAsyncEmitter.onNext(FlowableCreate.java:403)
  at io.micronaut.http.client.netty.DefaultHttpClient$12.channelReadInstrumented(DefaultHttpClient.java:2141)
  at io.micronaut.http.client.netty.DefaultHttpClient$12.channelReadInstrumented(DefaultHttpClient.java:2076)
  at io.micronaut.http.client.netty.DefaultHttpClient$SimpleChannelInboundHandlerInstrumented.channelRead0(DefaultHttpClient.java:2780)
  at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:99)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
  at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
  at io.micronaut.http.netty.stream.HttpStreamsHandler.channelRead(HttpStreamsHandler.java:193)
  at io.micronaut.http.netty.stream.HttpStreamsClientHandler.channelRead(HttpStreamsClientHandler.java:183)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
  at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
  at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
  at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
  at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
  at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
  at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
  at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324)
  at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:296)
  at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
  at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
  at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
  at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
  at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1368)
  at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1234)
  at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1280)
  at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:507)
  at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:446)
  at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
  at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
  at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
  at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
  at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
  at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
  at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719)
  at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
  at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
  at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
  at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
  at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
  at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
  at java.base/java.lang.Thread.run(Unknown Source)

Been blindly stumbling around randomly turning on knobs around the subjects of GCP LB header forwards and cookie storage config for micronaut JWT section but to no avail.

tchiotludo commented 3 years ago

Hard to see only with stacktrace, the error throw is here : https://github.com/micronaut-projects/micronaut-security/blob/623adf5a3e8a2e895373cf7a8176f1ce082f7e63/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/response/IdTokenLoginHandler.java#L83

String accessToken = parseIdToken(userDetails).orElseThrow(() -> new OauthErrorResponseException(ObtainingAuthorizationErrorCode.SERVER_ERROR, "Cannot obtain an access token", null));

go back up on stack trace let me think the response of on the openId flow failed to get the user from the access token.

I don't know more for now, please enable more log :

curl -i -X POST -H "Content-Type: application/json" \
       -d '{ "configuredLevel": "TRACE" }' \
       http://localhost:28081/loggers/io.micronaut.security
belboo commented 3 years ago

Many thanks for looking into this and for the fast response! Very much appreciated!

Your link to the code already helped me along - opening it I realised that I was looking at the master branch where line 80 was another overload of getCookie, the one for the refresh token flow. This was confusing me like hell.

An error in parseIdToken(userDetails).orElseThrow(...) is also confusing because in my case is raised in the callback from the authorize call where micronaut requests the code, which Okta faithfully returns. Not sure what's happening there (haven't run the trace level logging yet) but it seems that instead of forwarding the code to the token endpoint it tries to parse the response as if it were the token already and comes out empty.

Shall investigate further! Perhaps ditching the authentication: idtoken line in the config would help... 🤔

Thanks again and have a wonderful evening!

tchiotludo commented 3 years ago

For information, Don't use authentication: idtoken, as I know it's not compatible with oauth and openid.

belboo commented 3 years ago

Ow, that's good to know! I've been copy-pasting the app config from the micronaut dev guide for Okta but perhaps its related to a different version of the framework... In any case, removing the idtoken setting did the trick in test env. Let's see if it works with our central Okta. 🤞

Thank you for your help!