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

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,9 +3,11 @@
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 lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -47,6 +49,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 @@ -70,6 +75,11 @@ public AbstractLdapAuthenticationProvider authenticationProvider(LdapAuthorities
props.getUrls());
authProvider.setUseAuthenticationRequestCredentials(true);
((ActiveDirectoryLdapAuthenticationProvider) authProvider).setAuthoritiesPopulator(authoritiesExtractor);

List<String> urls = List.of(props.getUrls().split(","));
if (urls.stream().anyMatch(url -> url.startsWith("ldaps://"))) {
((ActiveDirectoryLdapAuthenticationProvider) authProvider).setContextEnvironmentProperties(BASE_ENV_PROPS);
}
}

if (rbacEnabled) {
Expand Down
90 changes: 90 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,90 @@
package io.kafbat.ui.util;

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

public class CustomSslSocketFactory extends SSLSocketFactory {
private final SSLSocketFactory socketFactory;

public CustomSslSocketFactory() {
try {
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, new TrustManager[] { new DisabledX509TrustManager() }, new SecureRandom());
socketFactory = ctx.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();
}

private static class DisabledX509TrustManager implements X509TrustManager {
/** Empty certificate array. */
private static final X509Certificate[] CERTS = new X509Certificate[0];

@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
// No-op, all clients are trusted.
}

@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
// No-op, all servers are trusted.
}

@Override
public X509Certificate[] getAcceptedIssuers() {
return CERTS;
}
}
}
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,49 +11,29 @@
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);
Expand Down Expand Up @@ -108,13 +87,4 @@ private AuthenticationInfoDTO authenticationInfo(String name) {
.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);
}
}
}
35 changes: 35 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,35 @@
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.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextConfiguration;

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

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

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

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);
}
}
}
100 changes: 100 additions & 0 deletions api/src/test/java/io/kafbat/ui/ActiveDirectoryLdapsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package io.kafbat.ui;

import static io.kafbat.ui.container.ActiveDirectoryContainer.CONTAINER_CERT_PATH;
import static io.kafbat.ui.container.ActiveDirectoryContainer.CONTAINER_KEY_PATH;
import static io.kafbat.ui.container.ActiveDirectoryContainer.DOMAIN;
import static io.kafbat.ui.container.ActiveDirectoryContainer.PASSWORD;
import static org.testcontainers.utility.MountableFile.forHostPath;

import io.kafbat.ui.container.ActiveDirectoryContainer;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Map;
import org.apache.kafka.common.config.types.Password;
import org.apache.kafka.test.TestSslUtils;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.testcontainers.shaded.org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator;
import org.testcontainers.shaded.org.bouncycastle.openssl.jcajce.JcaPKCS8Generator;
import org.testcontainers.shaded.org.bouncycastle.util.io.pem.PemWriter;

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

private static File certPem = null;
private static File privateKeyPem = null;

@BeforeAll
public static void setup() throws Exception {
generateCerts();

ACTIVE_DIRECTORY.withCopyFileToContainer(forHostPath(certPem.getAbsolutePath()), CONTAINER_CERT_PATH);
ACTIVE_DIRECTORY.withCopyFileToContainer(forHostPath(privateKeyPem.getAbsolutePath()), CONTAINER_KEY_PATH);

ACTIVE_DIRECTORY.start();
}

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

private static void generateCerts() throws Exception {
File truststore = File.createTempFile("truststore", ".jks");

truststore.deleteOnExit();

String host = "localhost";
KeyPair clientKeyPair = TestSslUtils.generateKeyPair("RSA");

X509Certificate clientCert = new TestSslUtils.CertificateBuilder(365, "SHA256withRSA")
.sanDnsNames(host)
.sanIpAddress(InetAddress.getByName(host))
.generate("O=Samba Administration, OU=Samba, CN=" + host, clientKeyPair);

TestSslUtils.createTrustStore(truststore.getPath(), new Password(PASSWORD), Map.of("client", clientCert));

certPem = File.createTempFile("cert", ".pem");
try (FileWriter fw = new FileWriter(certPem)) {
fw.write(certOrKeyToString(clientCert));
}

privateKeyPem = File.createTempFile("key", ".pem");
try (FileWriter fw = new FileWriter(privateKeyPem)) {
fw.write(certOrKeyToString(clientKeyPair.getPrivate()));
}
}

private static String certOrKeyToString(Object certOrKey) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try (PemWriter pemWriter = new PemWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))) {
if (certOrKey instanceof X509Certificate) {
pemWriter.writeObject(new JcaMiscPEMGenerator(certOrKey));
} else {
pemWriter.writeObject(new JcaPKCS8Generator((PrivateKey) certOrKey, null));
}
}
return out.toString(StandardCharsets.UTF_8);
}

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