Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BE: Respect proxy settings for OAuth requests #4114

Open
4 tasks done
poom-kitti opened this issue Aug 12, 2023 · 8 comments
Open
4 tasks done

BE: Respect proxy settings for OAuth requests #4114

poom-kitti opened this issue Aug 12, 2023 · 8 comments
Labels
area/auth scope/backend status/accepted An issue which has passed triage and has been accepted status/on-hold Waiting for something, but not the user input. type/enhancement En enhancement to an already existing feature

Comments

@poom-kitti
Copy link

poom-kitti commented Aug 12, 2023

Issue submitter TODO list

  • I've looked up my issue in FAQ
  • I've searched for an already existing issues here
  • I've tried running master-labeled docker image and the issue still persists there
  • I'm running a supported version of the application which is listed here

Describe the bug (actual behavior)

To give some context, our Kafka UI is deployed in a server inside a virtual private network. This mean:

  • The client (user connecting to Kafka UI) is also in the private network but is capable of accessing the internet
  • The Kafka UI is unable to access the internet unless using a proxy server

When Kafka UI is deployed in the server, it does not respect using proxy declared in any of the following Java system properties when performing connection to Okta to perform authentication:

  • http.proxyHost
  • http.proxyPort
  • http.nonProxyHosts
  • https.proxyHost
  • https.proxyPort
  • https.nonProxyHosts

Once a client tried to connect to Kafka UI, they are directed to authenticate to Okta correctly. However, after the client passed the code which they received from Okta back to Kafka UI triggering Kafka UI to perform POST request to Okta's token URI, it fails due to java.net.NoRouteToHostException indicating that the proxy is not in-use.

From some exploration, I believe that Spring security is using the DefaultWebClient to perform authentication and this WebClient does not respect the system properties on using proxy.

Expected behavior

When specified the system property like -Dhttps.proxyHost=my.proxy.host -Dhttps.proxyPort=3218 -Dhttps.nonProxyHosts=XXXX.local|10.*, the authentication should be using proxy to make connection to Okta.

Your installation details

App version: 0.7.1 (as of commit b32ab0143679bd3224f097a9de0eefad4e60f8d6)

Application YAML:
I deliberately did not add any configuration regarding connection to Kafka as it is unnecessary to show behavior of Okta authentication. In addition, this way, it make showing debug log clearer as we will only get debug log regarding authentication.

auth:
  type: OAUTH2
  oauth2:
    client:
      okta:
        clientId: <client-id>
        clientSecret: <client-secret>
        scope: [ 'openid', 'profile', 'email', 'groups' ]
        client-name: Okta
        provider: okta
        redirect-uri: "{baseUrl}/login/oauth2/code/okta"
        authorization-grant-type: authorization_code
        issuer-uri: https://<okta-endpoint>
        authorization-uri: https://<okta-endpoint>/oauth2/v1/authorize
        token-uri: https://<okta-endpoint>/oauth2/v1/token
        user-info-uri: https://<okta-endpoint>/oauth2/v1/userinfo
        jwk-set-uri: https://<okta-endpoint>/oauth2/v1/keys
        user-name-attribute: email
        custom-params:
          type: oauth
          roles-field: groups
server:
  port: 8080

Steps to reproduce

  1. Add authentication with Okta configurations.
  2. Build the Docker image.
  3. Create a Docker container inside a server that cannot access the internet except through proxy server.
  4. Start Kafka UI with Java system properties related to proxy:
  • http.proxyHost
  • http.proxyPort
  • http.nonProxyHosts
  • https.proxyHost
  • https.proxyPort
  • https.nonProxyHosts
  1. Try to access Kafka UI and perform Okta authentication.

Screenshots

From the network tab in developer tool, we can see that Okta returns some code to user and this is passed to Kafka UI; however the get request to <kafka ui endpoint>/login/oauth2/code/okta?code=<some code> failed.

error_pixelate

Logs

From logs, I see the following error:

�[30m2023-08-12 22:48:08,259�[0;39m �[1;31mERROR�[0;39m [�[34mreactor-http-epoll-3�[0;39m] �[33mo.s.b.a.w.r.e.AbstractErrorWebExceptionHandler�[0;39m: [8200782c-3]  500 Server Error for HTTP GET "/login/oauth2/code/okta?code=pV9h3TwnnHan7NjkEYWZR6oPKMcBU8WTrH3m4tLMX40&state=VHDFtviuySJvVcmLMwurglU1u81s1mw1Bw32_BlUD4Y%3D"
org.springframework.web.reactive.function.client.WebClientRequestException: null: dev-61615254.okta.com/99.83.233.105:443
	at org.springframework.web.reactive.function.client.ExchangeFunctions$DefaultExchangeFunction.lambda$wrapException$9(ExchangeFunctions.java:136)
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
	*__checkpoint ⇢ Request to POST https://dev-61615254.okta.com/oauth2/v1/token [DefaultWebClient]
	*__checkpoint ⇢ OAuth2LoginAuthenticationWebFilter [DefaultWebFilterChain]
	*__checkpoint ⇢ OAuth2AuthorizationRequestRedirectWebFilter [DefaultWebFilterChain]
	*__checkpoint ⇢ ReactorContextWebFilter [DefaultWebFilterChain]
	*__checkpoint ⇢ HttpHeaderWriterWebFilter [DefaultWebFilterChain]
	*__checkpoint ⇢ ServerWebExchangeReactorContextWebFilter [DefaultWebFilterChain]
	*__checkpoint ⇢ org.springframework.security.web.server.WebFilterChainProxy [DefaultWebFilterChain]
	*__checkpoint ⇢ org.springframework.web.filter.reactive.ServerHttpObservationFilter [DefaultWebFilterChain]
	*__checkpoint ⇢ HTTP GET "/login/oauth2/code/okta?code=pV9h3TwnnHan7NjkEYWZR6oPKMcBU8WTrH3m4tLMX40&state=VHDFtviuySJvVcmLMwurglU1u81s1mw1Bw32_BlUD4Y%3D" [ExceptionHandlingWebHandler]
Original Stack Trace:
		at org.springframework.web.reactive.function.client.ExchangeFunctions$DefaultExchangeFunction.lambda$wrapException$9(ExchangeFunctions.java:136)
		xxx
Caused by: io.netty.channel.AbstractChannel$AnnotatedNoRouteToHostException: null: dev-61615254.okta.com/99.83.233.105:443
Caused by: java.net.NoRouteToHostException: null
xxx

When setting the environment variable LOGGING_LEVEL_ROOT=debug to show debug log, I see the following logs that signify that Kafka UI is trying to connect to the Okta endpoint directly. This should not be the case because it should use proxy instead.

�[30m2023-08-12 22:51:21,497�[0;39m �[39mDEBUG�[0;39m [�[34mreactor-http-epoll-3�[0;39m] �[33mo.s.w.r.f.c.ExchangeFunctions�[0;39m: [62de54f3] HTTP POST https://dev-61615254.okta.com/oauth2/v1/token
�[30m2023-08-12 22:51:21,684�[0;39m �[39mDEBUG�[0;39m [�[34mreactor-http-epoll-3�[0;39m] �[33mi.n.r.d.DnsNameResolver�[0;39m: [id: 0xfd2c1ad8] RECEIVED: UDP [43221: /127.0.0.11:53], DatagramDnsResponse(from: /127.0.0.11:53, id: 43221, QUERY(0), NoError(0), RD RA)
	DefaultDnsQuestion(dev-61615254.okta.com. IN A)
	DefaultDnsRawRecord(dev-61615254.okta.com. 270 IN CNAME 25B)
	DefaultDnsRawRecord(ok12-crtrs.tng.okta.com. 30 IN CNAME 30B)
	DefaultDnsRawRecord(ok12-crtrs.oktaedge.okta.com. 270 IN CNAME 44B)
	DefaultDnsRawRecord(a1c0075a909445e0e.awsglobalaccelerator.com. 75 IN A 4B)
	DefaultDnsRawRecord(a1c0075a909445e0e.awsglobalaccelerator.com. 75 IN A 4B)
	DefaultDnsRawRecord(OPT flags:0 udp:4000 0B)
�[30m2023-08-12 22:51:21,688�[0;39m �[39mDEBUG�[0;39m [�[34mreactor-http-epoll-3�[0;39m] �[33mr.n.t.TransportConnector�[0;39m: [5de47a0b] Connecting to [dev-61615254.okta.com/75.2.37.199:443].
�[30m2023-08-12 22:51:21,690�[0;39m �[39mDEBUG�[0;39m [�[34mreactor-http-epoll-3�[0;39m] �[33mr.n.t.TransportConnector�[0;39m: [5de47a0b] Connect attempt to [dev-61615254.okta.com/75.2.37.199:443] failed.       

Additional context

No response

@poom-kitti poom-kitti added status/triage Issues pending maintainers triage type/bug Something isn't working labels Aug 12, 2023
@github-actions
Copy link

Hello there poom-kitti! 👋

Thank you and congratulations 🎉 for opening your very first issue in this project! 💖

In case you want to claim this issue, please comment down below! We will try to get back to you as soon as we can. 👀

@poom-kitti
Copy link
Author

I did get the proxy to work by implementing OidcAuthorizationCodeReactiveAuthenticationManager (OAuth2 equivalent is OAuth2LoginReactiveAuthenticationManager) and set to use this manager for login.

The manager seems to interact with Okta for 3 things and the following classes are used for those interactions:

  • Exchange of token via token-uri by WebClientReactiveAuthorizationCodeTokenResponseClient
  • Exchange of JWT via jwk-set-uri by NimbusReactiveJwtDecoder
  • Exchange of user information via authorization-uri by OidcReactiveOAuth2UserService (OAuth2 equivalent is DefaultReactiveOAuth2UserService)
    Therefore, we will need to somehow set WebClient that respect proxy settings for these 3 exchanges.

My Solution

I get the proxy system properties to work by making following changes in OAuthSecurityConfig:

@Configuration
@ConditionalOnProperty(value = "auth.type", havingValue = "OAUTH2")
@EnableConfigurationProperties(OAuthProperties.class)
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
@RequiredArgsConstructor
@Log4j2
public class OAuthSecurityConfig extends AbstractAuthSecurityConfig {

  private final OAuthProperties properties;

  // 1. Create WebClient that respects proxy settings
  private WebClient webClient = new WebClientConfigurator().build();

  @Bean
  public SecurityWebFilterChain configure(
      ServerHttpSecurity http,
      OAuthLogoutSuccessHandler logoutHandler,
      CustomOidcAuthenticationManagerBuilder oidcAuthenticationManagerBuilder
  ) {
    log.info("Configuring OAUTH2 authentication.");

    return http.authorizeExchange(spec -> spec
            .pathMatchers(AUTH_WHITELIST)
            .permitAll()
            .anyExchange()
            .authenticated()
        )
        .oauth2Login(spec -> spec.authenticationManager(oidcAuthenticationManagerBuilder.build())) // 2. Use custom authentication manager
        .logout(spec -> spec.logoutSuccessHandler(logoutHandler))
        .csrf(ServerHttpSecurity.CsrfSpec::disable)
        .build();
  }

  @Bean
  public ReactiveOAuth2UserService<OidcUserRequest, OidcUser> customOidcUserService(AccessControlService acs) {
    // 3. Use WebClient that respects proxy settings to get user-info
    final OidcReactiveOAuth2UserService delegate = new OidcReactiveOAuth2UserService();
    delegate.setOauth2UserService(customOauth2UserService(acs));
    ...
  }

  @Bean
  public ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> customOauth2UserService(AccessControlService acs) {
    // 3. Use WebClient that respects proxy settings to get user-info
    final DefaultReactiveOAuth2UserService delegate = new DefaultReactiveOAuth2UserService();
    delegate.setWebClient(webClient);
    ...
  }

This is the code for CustomOidcAuthenticationManagerBuilder that I created (in a new file):

@Component
@ConditionalOnProperty(value = "auth.type", havingValue = "OAUTH2")
public class CustomOidcAuthenticationManagerBuilder {
  private WebClient webClient = new WebClientConfigurator().build();
  // Reuse customOidcUserService bean from `OAuthSecurityConfig`
  private ReactiveOAuth2UserService<OidcUserRequest, OidcUser> oidcUserService;
  @Value("${auth.oauth2.client.okta.jwk-set-uri}")
  private String jwkUri;

  public CustomOidcAuthenticationManagerBuilder(ReactiveOAuth2UserService<OidcUserRequest, OidcUser> oidcUserService) {
    this.oidcUserService = oidcUserService;
  }

  public OidcAuthorizationCodeReactiveAuthenticationManager build() {
    // Use WebClient that respects proxy settings to get token
    WebClientReactiveAuthorizationCodeTokenResponseClient client =
        new WebClientReactiveAuthorizationCodeTokenResponseClient();
    client.setWebClient(webClient);

    // Use WebClient that respects proxy settings to get JWT
    ReactiveJwtDecoderFactory<ClientRegistration> idTokenDecoderFactory =
        (clientRegistration) -> NimbusReactiveJwtDecoder.withJwkSetUri(jwkUri)
            .webClient(webClient)
            .build();

    OidcAuthorizationCodeReactiveAuthenticationManager manager =
        new OidcAuthorizationCodeReactiveAuthenticationManager(client, oidcUserService);
    manager.setJwtDecoderFactory(idTokenDecoderFactory);

    return manager;
  }
}

Please check my commit which should make this clearer: poom-kitti@3b88135

Result

After applying the changes and rebuild Kafka UI along with deploying (using same Java system properties related to proxy), the authentication now works. I can confirm that proxy is used because from debug logs, instead of making connection to Okta directly, it is connecting to proxy instead.

[30m2023-08-12 23:15:49,863�[0;39m �[39mDEBUG�[0;39m [�[34mreactor-http-epoll-4�[0;39m] �[33mo.s.w.r.f.c.ExchangeFunctions�[0;39m: [52dec0ff] HTTP POST https://dev-61615254.okta.com/oauth2/v1/token
�[30m2023-08-12 23:15:49,865�[0;39m �[39mDEBUG�[0;39m [�[34mreactor-http-epoll-3�[0;39m] �[33mr.n.r.PooledConnectionProvider�[0;39m: [d894b024, L:/172.19.0.4:36860 - R:my.proxy.host/10.118.87.223:3128] Channel acquired, now: 1 active connections, 1 inactive connections and 0 pending acquire requests.
�[30m2023-08-12 23:15:49,865�[0;39m �[39mDEBUG�[0;39m [�[34mreactor-http-epoll-3�[0;39m] �[33mr.n.h.c.HttpClientConnect�[0;39m: [d894b024-2, L:/172.19.0.4:36860 - R:my.proxy.host/10.118.87.223:3128] Handler is being applied: {uri=https://dev-61615254.okta.com/oauth2/v1/token, method=POST}
�[30m2023-08-12 23:15:49,865�[0;39m �[39mDEBUG�[0;39m [�[34mreactor-http-epoll-3�[0;39m] �[33mr.n.r.DefaultPooledConnectionProvider�[0;39m: [d894b024-2, L:/172.19.0.4:36860 - R:my.proxy.host/10.118.87.223:3128] onStateChange(POST{uri=/oauth2/v1/token, connection=PooledConnection{channel=[id: 0xd894b024, L:/172.19.0.4:36860 - R:my.proxy.host/10.118.87.223:3128]}}, [request_prepared])
�[30m2023-08-12 23:15:49,866�[0;39m �[39mDEBUG�[0;39m [�[34mreactor-http-epoll-3�[0;39m] �[33mo.s.h.c.FormHttpMessageWriter�[0;39m: [52dec0ff] Writing form fields [grant_type, code, redirect_uri] (content masked)
�[30m2023-08-12 23:15:49,867�[0;39m �[39mDEBUG�[0;39m [�[34mreactor-http-epoll-3�[0;39m] �[33mr.n.r.DefaultPooledConnectionProvider�[0;39m: [d894b024-2, L:/172.19.0.4:36860 - R:my.proxy.host/10.118.87.223:3128] onStateChange(POST{uri=/oauth2/v1/token, connection=PooledConnection{channel=[id: 0xd894b024, L:/172.19.0.4:36860 - R:my.proxy.host/10.118.87.223:3128]}}, [request_sent])
�[30m2023-08-12 23:15:50,128�[0;39m �[39mDEBUG�[0;39m [�[34mreactor-http-epoll-3�[0;39m] �[33mr.n.h.c.HttpClientOperations�[0;39m: [d894b024-2, L:/172.19.0.4:36860 - R:my.proxy.host/10.118.87.223:3128] Received response (auto-read:false) : RESPONSE(decodeResult: success, version: HTTP/1.1)

Caveats

The drawback of my current implementations are:

  1. It should only work with Oauth2 authentication that uses Open ID Connect protocol (not certain is this working for all Oauth2 authentication)
  2. For NimbusReactiveJwtDecoder, it is using a static jwk-set-uri to point to Okta configuration which would not be generic if the provider is not Okta

Since I lack experience with Spring, I do not wish to claim this issue, but hope my investigation help in some way.

@Haarolean Haarolean changed the title Okta authentication does not respect proxy settings BE: Respect proxy settings for OAuth requests Aug 14, 2023
@Haarolean Haarolean added type/enhancement En enhancement to an already existing feature scope/backend status/accepted An issue which has passed triage and has been accepted area/auth and removed type/bug Something isn't working status/triage Issues pending maintainers triage labels Aug 14, 2023
@Haarolean Haarolean self-assigned this Aug 14, 2023
@Haarolean
Copy link
Contributor

@poom-kitti Hi and thank you very much for the analysis, saved me some time in research.

Even if we implement the changes in other places, we won't be able to decode JWT token without these changes.
In the case of Okta / OIDC, we'd use the underlying OidcAuthorizationCodeReactiveAuthenticationManager, which, in turn, does use ReactiveOidcIdTokenDecoderFactory, which doesn't allow customizing the webclient used for NimbusReactiveJwtDecoder. This behavior should be fixed within this spring-security issue: spring-projects/spring-security#13274. There's already a PR but getting one merged and released would take some time.

Related: spring-projects/spring-security#8882
Until that one is done, we'd have to customize the webclient manually for the following beans:

  • WebClientReactiveAuthorizationCodeTokenResponseClient
  • WebClientReactiveRefreshTokenTokenResponseClient
  • WebClientReactiveClientCredentialsTokenResponseClient
  • WebClientReactivePasswordTokenResponseClient
  • DefaultReactiveOAuth2UserService

and probably some others which we need to determine on a per-use-case basis.

I suggest we put this on hold for some time to see if spring-projects/spring-security#13274 does get any traction to see if we can avoid copy-pasting the whole factory bean.

@Haarolean Haarolean added the status/on-hold Waiting for something, but not the user input. label Aug 14, 2023
@Haarolean Haarolean removed their assignment Sep 24, 2023
@joachimBurket
Copy link

Hi,

Is there a workaround not involving editing the java code for this? Or do we have to wait for it to be solved?

It seems the issue spring-projects/spring-security#13274 is now closed. What are the next steps?

@1300371
Copy link

1300371 commented Apr 5, 2024

Some news about this fix ?

@Haarolean
Copy link
Contributor

@1300371 this repo is not maintained: source.
Please take a look at the mentioned issue right above your comment as well.

@seb2020
Copy link

seb2020 commented Jul 30, 2024

Some news about this fix ?

@Haarolean
Copy link
Contributor

@seb2020 see the comment above yours

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/auth scope/backend status/accepted An issue which has passed triage and has been accepted status/on-hold Waiting for something, but not the user input. type/enhancement En enhancement to an already existing feature
Projects
None yet
Development

No branches or pull requests

5 participants