Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,19 @@
<version>${okhttp3.mockwebserver.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>${confluent.version}-ccs</version>
<classifier>test</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>1.80</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
import io.kafbat.ui.service.rbac.AccessControlService;
import io.kafbat.ui.service.rbac.extractor.RbacActiveDirectoryAuthoritiesExtractor;
import io.kafbat.ui.service.rbac.extractor.RbacLdapAuthoritiesExtractor;
import io.kafbat.ui.util.CustomSslSocketFactory;
import io.kafbat.ui.util.StaticFileWebFilter;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -47,6 +50,9 @@
@RequiredArgsConstructor
@Slf4j
public class LdapSecurityConfig extends AbstractAuthSecurityConfig {
private static final Map<String, Object> BASE_ENV_PROPS = Map.of(
"java.naming.ldap.factory.socket", CustomSslSocketFactory.class.getName()
);

private final LdapProperties props;

Expand All @@ -63,13 +69,10 @@ public AbstractLdapAuthenticationProvider authenticationProvider(LdapAuthorities

AbstractLdapAuthenticationProvider authProvider;

if (!props.isActiveDirectory()) {
authProvider = new LdapAuthenticationProvider(ba, authoritiesExtractor);
if (props.isActiveDirectory()) {
authProvider = activeDirectoryProvider(authoritiesExtractor);
} else {
authProvider = new ActiveDirectoryLdapAuthenticationProvider(props.getActiveDirectoryDomain(),
props.getUrls());
authProvider.setUseAuthenticationRequestCredentials(true);
((ActiveDirectoryLdapAuthenticationProvider) authProvider).setAuthoritiesPopulator(authoritiesExtractor);
authProvider = new LdapAuthenticationProvider(ba, authoritiesExtractor);
}

if (rbacEnabled) {
Expand Down Expand Up @@ -159,6 +162,22 @@ public SecurityWebFilterChain configureLdap(ServerHttpSecurity http) {
return builder.build();
}

private ActiveDirectoryLdapAuthenticationProvider activeDirectoryProvider(LdapAuthoritiesPopulator populator) {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(
props.getActiveDirectoryDomain(),
props.getUrls()
);

provider.setUseAuthenticationRequestCredentials(true);
provider.setAuthoritiesPopulator(populator);

if (Stream.of(props.getUrls().split(",")).anyMatch(url -> url.startsWith("ldaps://"))) {
provider.setContextEnvironmentProperties(BASE_ENV_PROPS);
}

return provider;
}

private static class RbacUserDetailsMapper extends LdapUserDetailsMapper {
@Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
Expand Down
69 changes: 69 additions & 0 deletions api/src/main/java/io/kafbat/ui/util/CustomSslSocketFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package io.kafbat.ui.util;

import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.security.SecureRandom;
import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;

public class CustomSslSocketFactory extends SSLSocketFactory {
private final SSLSocketFactory socketFactory;

public CustomSslSocketFactory() {
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, InsecureTrustManagerFactory.INSTANCE.getTrustManagers(), new SecureRandom());

socketFactory = sslContext.getSocketFactory();
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public static SocketFactory getDefault() {
return new CustomSslSocketFactory();
}

@Override
public String[] getDefaultCipherSuites() {
return socketFactory.getDefaultCipherSuites();
}

@Override
public String[] getSupportedCipherSuites() {
return socketFactory.getSupportedCipherSuites();
}

@Override
public Socket createSocket(Socket socket, String string, int i, boolean bln) throws IOException {
return socketFactory.createSocket(socket, string, i, bln);
}

@Override
public Socket createSocket(String string, int i) throws IOException {
return socketFactory.createSocket(string, i);
}

@Override
public Socket createSocket(String string, int i, InetAddress ia, int i1) throws IOException {
return socketFactory.createSocket(string, i, ia, i1);
}

@Override
public Socket createSocket(InetAddress ia, int i) throws IOException {
return socketFactory.createSocket(ia, i);
}

@Override
public Socket createSocket(InetAddress ia, int i, InetAddress ia1, int i1) throws IOException {
return socketFactory.createSocket(ia, i, ia1, i1);
}

@Override
public Socket createSocket() throws IOException {
return socketFactory.createSocket();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.kafbat.ui;

import static io.kafbat.ui.AbstractIntegrationTest.LOCAL;
import static io.kafbat.ui.container.ActiveDirectoryContainer.DOMAIN;
import static io.kafbat.ui.container.ActiveDirectoryContainer.EMPTY_PERMISSIONS_USER;
import static io.kafbat.ui.container.ActiveDirectoryContainer.FIRST_USER_WITH_GROUP;
import static io.kafbat.ui.container.ActiveDirectoryContainer.PASSWORD;
Expand All @@ -12,52 +11,26 @@
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import io.kafbat.ui.container.ActiveDirectoryContainer;
import io.kafbat.ui.model.AuthenticationInfoDTO;
import io.kafbat.ui.model.ResourceTypeDTO;
import io.kafbat.ui.model.UserPermissionDTO;
import java.util.List;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.BodyInserters;

@SpringBootTest
@ActiveProfiles("rbac-ad")
@AutoConfigureWebTestClient(timeout = "60000")
@ContextConfiguration(initializers = {ActiveDirectoryIntegrationTest.Initializer.class})
public class ActiveDirectoryIntegrationTest {
public abstract class AbstractActiveDirectoryIntegrationTest {
private static final String SESSION = "SESSION";

private static final ActiveDirectoryContainer ACTIVE_DIRECTORY = new ActiveDirectoryContainer();

@Autowired
private WebTestClient webTestClient;

@BeforeAll
public static void setup() {
ACTIVE_DIRECTORY.start();
}

@AfterAll
public static void shutdown() {
ACTIVE_DIRECTORY.stop();
}

@Test
public void testUserPermissions() {
AuthenticationInfoDTO info = authenticationInfo(FIRST_USER_WITH_GROUP);
protected static void checkUserPermissions(WebTestClient client) {
AuthenticationInfoDTO info = authenticationInfo(client, FIRST_USER_WITH_GROUP);

assertNotNull(info);
assertTrue(info.getRbacEnabled());
Expand All @@ -67,22 +40,21 @@ public void testUserPermissions() {
assertFalse(permissions.isEmpty());
assertTrue(permissions.stream().anyMatch(permission ->
permission.getClusters().contains(LOCAL) && permission.getResource() == ResourceTypeDTO.TOPIC));
assertEquals(permissions, authenticationInfo(SECOND_USER_WITH_GROUP).getUserInfo().getPermissions());
assertEquals(permissions, authenticationInfo(USER_WITHOUT_GROUP).getUserInfo().getPermissions());
assertEquals(permissions, authenticationInfo(client, SECOND_USER_WITH_GROUP).getUserInfo().getPermissions());
assertEquals(permissions, authenticationInfo(client, USER_WITHOUT_GROUP).getUserInfo().getPermissions());
}

@Test
public void testEmptyPermissions() {
assertTrue(Objects.requireNonNull(authenticationInfo(EMPTY_PERMISSIONS_USER))
protected static void checkEmptyPermissions(WebTestClient client) {
assertTrue(Objects.requireNonNull(authenticationInfo(client, EMPTY_PERMISSIONS_USER))
.getUserInfo()
.getPermissions()
.isEmpty()
);
}

private String session(String name) {
protected static String session(WebTestClient client, String name) {
return Objects.requireNonNull(
webTestClient
client
.post()
.uri("/login")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
Expand All @@ -96,25 +68,16 @@ private String session(String name) {
.getValue();
}

private AuthenticationInfoDTO authenticationInfo(String name) {
return webTestClient
protected static AuthenticationInfoDTO authenticationInfo(WebTestClient client, String name) {
return client
.get()
.uri("/api/authorization")
.cookie(SESSION, session(name))
.cookie(SESSION, session(client, name))
.exchange()
.expectStatus()
.isOk()
.returnResult(AuthenticationInfoDTO.class)
.getResponseBody()
.blockFirst();
}

public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(@NotNull ConfigurableApplicationContext context) {
System.setProperty("spring.ldap.urls", ACTIVE_DIRECTORY.getLdapUrl());
System.setProperty("oauth2.ldap.activeDirectory", "true");
System.setProperty("oauth2.ldap.activeDirectory.domain", DOMAIN);
}
}
}
51 changes: 51 additions & 0 deletions api/src/test/java/io/kafbat/ui/ActiveDirectoryLdapTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.kafbat.ui;

import static io.kafbat.ui.container.ActiveDirectoryContainer.DOMAIN;

import io.kafbat.ui.container.ActiveDirectoryContainer;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.reactive.server.WebTestClient;

@ContextConfiguration(initializers = {ActiveDirectoryLdapTest.Initializer.class})
public class ActiveDirectoryLdapTest extends AbstractActiveDirectoryIntegrationTest {
private static final ActiveDirectoryContainer ACTIVE_DIRECTORY = new ActiveDirectoryContainer(false);

@Autowired
private WebTestClient webTestClient;

@BeforeAll
public static void setup() {
ACTIVE_DIRECTORY.start();
}

@AfterAll
public static void shutdown() {
ACTIVE_DIRECTORY.stop();
}

@Test
public void testUserPermissions() {
checkUserPermissions(webTestClient);
}

@Test
public void testEmptyPermissions() {
checkEmptyPermissions(webTestClient);
}

public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(@NotNull ConfigurableApplicationContext context) {
System.setProperty("spring.ldap.urls", ACTIVE_DIRECTORY.getLdapUrl());
System.setProperty("oauth2.ldap.activeDirectory", "true");
System.setProperty("oauth2.ldap.activeDirectory.domain", DOMAIN);
}
}
}
Loading
Loading