Open poom-kitti opened 1 year ago
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. 👀
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:
token-uri
by WebClientReactiveAuthorizationCodeTokenResponseClient
jwk-set-uri
by NimbusReactiveJwtDecoder
authorization-uri
by OidcReactiveOAuth2UserService
(OAuth2 equivalent is DefaultReactiveOAuth2UserService
)
Therefore, we will need to somehow set WebClient that respect proxy settings for these 3 exchanges.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: https://github.com/poom-kitti/kafka-ui/commit/3b88135168d99618bfa40da37885b996bb6d96a8
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)
The drawback of my current implementations are:
NimbusReactiveJwtDecoder
, it is using a static jwk-set-uri
to point to Okta configuration which would not be generic if the provider is not OktaSince I lack experience with Spring, I do not wish to claim this issue, but hope my investigation help in some way.
@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: https://github.com/spring-projects/spring-security/issues/13274. There's already a PR but getting one merged and released would take some time.
Related: https://github.com/spring-projects/spring-security/issues/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 https://github.com/spring-projects/spring-security/issues/13274 does get any traction to see if we can avoid copy-pasting the whole factory bean.
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 https://github.com/spring-projects/spring-security/issues/13274 is now closed. What are the next steps?
Some news about this fix ?
@1300371 this repo is not maintained: source. Please take a look at the mentioned issue right above your comment as well.
Some news about this fix ?
@seb2020 see the comment above yours
Issue submitter TODO list
master
-labeled docker image and the issue still persists thereDescribe the bug (actual behavior)
To give some context, our Kafka UI is deployed in a server inside a virtual private network. This mean:
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:
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.
Steps to reproduce
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.Logs
From logs, I see the following error:
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.Additional context
No response