Cognito logout + revamp (#1063)

* application yml example

* Update SSO guide

* Add cognito logout handler

* Fix annotations

* impl a separate config file for cognito

* cleanup

* Rollback auth.type change

* Add compose example

* Auth revamp

* Review suggestions

* Use configurationProperties, rename urls to uris
This commit is contained in:
Roman Zabaluev 2022-09-15 05:36:21 +04:00 committed by GitHub
parent 6df2d0b602
commit 5db2c17994
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 206 additions and 6 deletions

View file

@ -11,3 +11,4 @@
9. [kafka-ui-reverse-proxy.yaml](./kafka-ui-reverse-proxy.yaml) - An example for using the app behind a proxy (like nginx).
10. [kafka-ui-sasl.yaml](./kafka-ui-sasl.yaml) - SASL auth for Kafka.
11. [kafka-ui-traefik-proxy.yaml](./kafka-ui-traefik-proxy.yaml) - Traefik specific proxy configuration.
12. [oauth-cognito.yaml](./oauth-cognito.yaml) - OAuth2 with Cognito

View file

@ -0,0 +1,22 @@
---
version: '3.4'
services:
kafka-ui:
container_name: kafka-ui
image: provectuslabs/kafka-ui:local
ports:
- 8080:8080
depends_on:
- kafka0 # OMITTED, TAKE UP AN EXAMPLE FROM OTHER COMPOSE FILES
environment:
KAFKA_CLUSTERS_0_NAME: local
KAFKA_CLUSTERS_0_PROPERTIES_SECURITY_PROTOCOL: SSL
KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka0:29092
AUTH_TYPE: OAUTH2_COGNITO
AUTH_COGNITO_ISSUER_URI: "https://cognito-idp.eu-central-1.amazonaws.com/eu-central-xxxxxx"
AUTH_COGNITO_CLIENT_ID: ""
AUTH_COGNITO_CLIENT_SECRET: ""
AUTH_COGNITO_SCOPE: "openid"
AUTH_COGNITO_USER_NAME_ATTRIBUTE: "username"
AUTH_COGNITO_LOGOUT_URI: "https://<domain>.auth.eu-central-1.amazoncognito.com/logout"

View file

@ -0,0 +1,53 @@
package com.provectus.kafka.ui.config;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.web.server.WebSession;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
@RequiredArgsConstructor
public class CognitoOidcLogoutSuccessHandler implements ServerLogoutSuccessHandler {
private final String logoutUrl;
private final String clientId;
@Override
public Mono<Void> onLogoutSuccess(final WebFilterExchange exchange, final Authentication authentication) {
final ServerHttpResponse response = exchange.getExchange().getResponse();
response.setStatusCode(HttpStatus.FOUND);
final var requestUri = exchange.getExchange().getRequest().getURI();
final var fullUrl = UrlUtils.buildFullRequestUrl(requestUri.getScheme(),
requestUri.getHost(), requestUri.getPort(),
requestUri.getPath(), requestUri.getQuery());
final UriComponents baseUrl = UriComponentsBuilder
.fromHttpUrl(fullUrl)
.replacePath("/")
.replaceQuery(null)
.fragment(null)
.build();
final var uri = UriComponentsBuilder
.fromUri(URI.create(logoutUrl))
.queryParam("client_id", clientId)
.queryParam("logout_uri", baseUrl)
.encode(StandardCharsets.UTF_8)
.build()
.toUri();
response.getHeaders().setLocation(uri);
return exchange.getExchange().getSession().flatMap(WebSession::invalidate);
}
}

View file

@ -2,7 +2,7 @@ package com.provectus.kafka.ui.config.auth;
import com.provectus.kafka.ui.util.EmptyRedirectStrategy;
import java.net.URI;
import lombok.extern.log4j.Log4j2;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -17,7 +17,7 @@ import org.springframework.security.web.server.ui.LogoutPageGeneratingWebFilter;
@Configuration
@EnableWebFluxSecurity
@ConditionalOnProperty(value = "auth.type", havingValue = "LOGIN_FORM")
@Log4j2
@Slf4j
public class BasicAuthSecurityConfig extends AbstractAuthSecurityConfig {
public static final String LOGIN_URL = "/auth";

View file

@ -0,0 +1,80 @@
package com.provectus.kafka.ui.config.auth;
import com.provectus.kafka.ui.config.CognitoOidcLogoutSuccessHandler;
import com.provectus.kafka.ui.config.auth.props.CognitoProperties;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrations;
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
@Configuration
@EnableWebFluxSecurity
@ConditionalOnProperty(value = "auth.type", havingValue = "OAUTH2_COGNITO")
@RequiredArgsConstructor
@Slf4j
public class CognitoOAuthSecurityConfig extends AbstractAuthSecurityConfig {
private static final String COGNITO = "cognito";
@Bean
public SecurityWebFilterChain configure(ServerHttpSecurity http, CognitoProperties props) {
log.info("Configuring Cognito OAUTH2 authentication.");
String clientId = props.getClientId();
String logoutUrl = props.getLogoutUri();
final ServerLogoutSuccessHandler logoutHandler = new CognitoOidcLogoutSuccessHandler(logoutUrl, clientId);
return http.authorizeExchange()
.pathMatchers(AUTH_WHITELIST)
.permitAll()
.anyExchange()
.authenticated()
.and()
.oauth2Login()
.and()
.oauth2Client()
.and()
.logout()
.logoutSuccessHandler(logoutHandler)
.and()
.csrf().disable()
.build();
}
@Bean
public InMemoryReactiveClientRegistrationRepository clientRegistrationRepository(CognitoProperties props) {
ClientRegistration.Builder builder = ClientRegistrations
.fromIssuerLocation(props.getIssuerUri())
.registrationId(COGNITO);
builder.clientId(props.getClientId());
builder.clientSecret(props.getClientSecret());
Optional.ofNullable(props.getScope()).ifPresent(builder::scope);
Optional.ofNullable(props.getUserNameAttribute()).ifPresent(builder::userNameAttributeName);
return new InMemoryReactiveClientRegistrationRepository(builder.build());
}
@Bean
@ConfigurationProperties("auth.cognito")
public CognitoProperties cognitoProperties() {
return new CognitoProperties();
}
}

View file

@ -1,6 +1,6 @@
package com.provectus.kafka.ui.config.auth;
import lombok.extern.log4j.Log4j2;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
@ -14,7 +14,7 @@ import org.springframework.security.web.server.SecurityWebFilterChain;
@Configuration
@EnableWebFluxSecurity
@ConditionalOnProperty(value = "auth.type", havingValue = "DISABLED")
@Log4j2
@Slf4j
public class DisabledAuthSecurityConfig extends AbstractAuthSecurityConfig {
@Bean

View file

@ -1,7 +1,7 @@
package com.provectus.kafka.ui.config.auth;
import java.util.List;
import lombok.extern.log4j.Log4j2;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
@ -25,7 +25,7 @@ import org.springframework.security.web.server.SecurityWebFilterChain;
@Configuration
@EnableWebFluxSecurity
@ConditionalOnProperty(value = "auth.type", havingValue = "LDAP")
@Log4j2
@Slf4j
public class LdapSecurityConfig extends AbstractAuthSecurityConfig {
@Value("${spring.ldap.urls}")

View file

@ -0,0 +1,44 @@
package com.provectus.kafka.ui.config.auth.props;
import lombok.Data;
import lombok.ToString;
import org.jetbrains.annotations.Nullable;
@Data
@ToString(exclude = "clientSecret")
public class CognitoProperties {
String clientId;
String logoutUri;
String issuerUri;
String clientSecret;
@Nullable
String scope;
@Nullable
String userNameAttribute;
public String getClientId() {
return clientId;
}
public String getLogoutUri() {
return logoutUri;
}
public String getIssuerUri() {
return issuerUri;
}
public String getClientSecret() {
return clientSecret;
}
public @Nullable String getScope() {
return scope;
}
public @Nullable String getUserNameAttribute() {
return userNameAttribute;
}
}