Skip to content

Commit 7f1136e

Browse files
committed
BE: Auth: Support LDAPS for AD
fix review notes
1 parent d99758b commit 7f1136e

File tree

5 files changed

+77
-70
lines changed

5 files changed

+77
-70
lines changed

api/src/main/java/io/kafbat/ui/config/auth/LdapSecurityConfig.java

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.List;
1010
import java.util.Map;
1111
import java.util.Optional;
12+
import java.util.stream.Stream;
1213
import lombok.RequiredArgsConstructor;
1314
import lombok.extern.slf4j.Slf4j;
1415
import org.springframework.beans.factory.annotation.Autowired;
@@ -68,18 +69,10 @@ public AbstractLdapAuthenticationProvider authenticationProvider(LdapAuthorities
6869

6970
AbstractLdapAuthenticationProvider authProvider;
7071

71-
if (!props.isActiveDirectory()) {
72-
authProvider = new LdapAuthenticationProvider(ba, authoritiesExtractor);
72+
if (props.isActiveDirectory()) {
73+
authProvider = activeDirectoryProvider(authoritiesExtractor);
7374
} else {
74-
authProvider = new ActiveDirectoryLdapAuthenticationProvider(props.getActiveDirectoryDomain(),
75-
props.getUrls());
76-
authProvider.setUseAuthenticationRequestCredentials(true);
77-
((ActiveDirectoryLdapAuthenticationProvider) authProvider).setAuthoritiesPopulator(authoritiesExtractor);
78-
79-
List<String> urls = List.of(props.getUrls().split(","));
80-
if (urls.stream().anyMatch(url -> url.startsWith("ldaps://"))) {
81-
((ActiveDirectoryLdapAuthenticationProvider) authProvider).setContextEnvironmentProperties(BASE_ENV_PROPS);
82-
}
75+
authProvider = new LdapAuthenticationProvider(ba, authoritiesExtractor);
8376
}
8477

8578
if (rbacEnabled) {
@@ -169,6 +162,22 @@ public SecurityWebFilterChain configureLdap(ServerHttpSecurity http) {
169162
return builder.build();
170163
}
171164

165+
private ActiveDirectoryLdapAuthenticationProvider activeDirectoryProvider(LdapAuthoritiesPopulator populator) {
166+
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(
167+
props.getActiveDirectoryDomain(),
168+
props.getUrls()
169+
);
170+
171+
provider.setUseAuthenticationRequestCredentials(true);
172+
provider.setAuthoritiesPopulator(populator);
173+
174+
if (Stream.of(props.getUrls().split(",")).anyMatch(url -> url.startsWith("ldaps://"))) {
175+
provider.setContextEnvironmentProperties(BASE_ENV_PROPS);
176+
}
177+
178+
return provider;
179+
}
180+
172181
private static class RbacUserDetailsMapper extends LdapUserDetailsMapper {
173182
@Override
174183
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,

api/src/main/java/io/kafbat/ui/util/CustomSslSocketFactory.java

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,23 @@
11
package io.kafbat.ui.util;
22

3+
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
34
import java.io.IOException;
45
import java.net.InetAddress;
56
import java.net.Socket;
67
import java.security.SecureRandom;
7-
import java.security.cert.X509Certificate;
88
import javax.net.SocketFactory;
99
import javax.net.ssl.SSLContext;
1010
import javax.net.ssl.SSLSocketFactory;
11-
import javax.net.ssl.TrustManager;
12-
import javax.net.ssl.X509TrustManager;
1311

1412
public class CustomSslSocketFactory extends SSLSocketFactory {
1513
private final SSLSocketFactory socketFactory;
1614

1715
public CustomSslSocketFactory() {
1816
try {
19-
SSLContext ctx = SSLContext.getInstance("TLS");
20-
ctx.init(null, new TrustManager[] { new DisabledX509TrustManager() }, new SecureRandom());
21-
socketFactory = ctx.getSocketFactory();
17+
SSLContext sslContext = SSLContext.getInstance("TLS");
18+
sslContext.init(null, InsecureTrustManagerFactory.INSTANCE.getTrustManagers(), new SecureRandom());
19+
20+
socketFactory = sslContext.getSocketFactory();
2221
} catch (Exception e) {
2322
throw new RuntimeException(e);
2423
}
@@ -67,24 +66,4 @@ public Socket createSocket(InetAddress ia, int i, InetAddress ia1, int i1) throw
6766
public Socket createSocket() throws IOException {
6867
return socketFactory.createSocket();
6968
}
70-
71-
private static class DisabledX509TrustManager implements X509TrustManager {
72-
/** Empty certificate array. */
73-
private static final X509Certificate[] CERTS = new X509Certificate[0];
74-
75-
@Override
76-
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
77-
// No-op, all clients are trusted.
78-
}
79-
80-
@Override
81-
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
82-
// No-op, all servers are trusted.
83-
}
84-
85-
@Override
86-
public X509Certificate[] getAcceptedIssuers() {
87-
return CERTS;
88-
}
89-
}
9069
}

api/src/test/java/io/kafbat/ui/AbstractActiveDirectoryIntegrationTest.java

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616
import io.kafbat.ui.model.UserPermissionDTO;
1717
import java.util.List;
1818
import java.util.Objects;
19-
import org.junit.jupiter.api.Test;
20-
import org.springframework.beans.factory.annotation.Autowired;
2119
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
2220
import org.springframework.boot.test.context.SpringBootTest;
2321
import org.springframework.http.MediaType;
@@ -31,12 +29,8 @@
3129
public abstract class AbstractActiveDirectoryIntegrationTest {
3230
private static final String SESSION = "SESSION";
3331

34-
@Autowired
35-
private WebTestClient webTestClient;
36-
37-
@Test
38-
public void testUserPermissions() {
39-
AuthenticationInfoDTO info = authenticationInfo(FIRST_USER_WITH_GROUP);
32+
protected static void checkUserPermissions(WebTestClient client) {
33+
AuthenticationInfoDTO info = authenticationInfo(client, FIRST_USER_WITH_GROUP);
4034

4135
assertNotNull(info);
4236
assertTrue(info.getRbacEnabled());
@@ -46,22 +40,21 @@ public void testUserPermissions() {
4640
assertFalse(permissions.isEmpty());
4741
assertTrue(permissions.stream().anyMatch(permission ->
4842
permission.getClusters().contains(LOCAL) && permission.getResource() == ResourceTypeDTO.TOPIC));
49-
assertEquals(permissions, authenticationInfo(SECOND_USER_WITH_GROUP).getUserInfo().getPermissions());
50-
assertEquals(permissions, authenticationInfo(USER_WITHOUT_GROUP).getUserInfo().getPermissions());
43+
assertEquals(permissions, authenticationInfo(client, SECOND_USER_WITH_GROUP).getUserInfo().getPermissions());
44+
assertEquals(permissions, authenticationInfo(client, USER_WITHOUT_GROUP).getUserInfo().getPermissions());
5145
}
5246

53-
@Test
54-
public void testEmptyPermissions() {
55-
assertTrue(Objects.requireNonNull(authenticationInfo(EMPTY_PERMISSIONS_USER))
47+
protected static void checkEmptyPermissions(WebTestClient client) {
48+
assertTrue(Objects.requireNonNull(authenticationInfo(client, EMPTY_PERMISSIONS_USER))
5649
.getUserInfo()
5750
.getPermissions()
5851
.isEmpty()
5952
);
6053
}
6154

62-
private String session(String name) {
55+
protected static String session(WebTestClient client, String name) {
6356
return Objects.requireNonNull(
64-
webTestClient
57+
client
6558
.post()
6659
.uri("/login")
6760
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
@@ -75,11 +68,11 @@ private String session(String name) {
7568
.getValue();
7669
}
7770

78-
private AuthenticationInfoDTO authenticationInfo(String name) {
79-
return webTestClient
71+
protected static AuthenticationInfoDTO authenticationInfo(WebTestClient client, String name) {
72+
return client
8073
.get()
8174
.uri("/api/authorization")
82-
.cookie(SESSION, session(name))
75+
.cookie(SESSION, session(client, name))
8376
.exchange()
8477
.expectStatus()
8578
.isOk()

api/src/test/java/io/kafbat/ui/ActiveDirectoryLdapTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,20 @@
66
import org.jetbrains.annotations.NotNull;
77
import org.junit.jupiter.api.AfterAll;
88
import org.junit.jupiter.api.BeforeAll;
9+
import org.junit.jupiter.api.Test;
10+
import org.springframework.beans.factory.annotation.Autowired;
911
import org.springframework.context.ApplicationContextInitializer;
1012
import org.springframework.context.ConfigurableApplicationContext;
1113
import org.springframework.test.context.ContextConfiguration;
14+
import org.springframework.test.web.reactive.server.WebTestClient;
1215

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

20+
@Autowired
21+
private WebTestClient webTestClient;
22+
1723
@BeforeAll
1824
public static void setup() {
1925
ACTIVE_DIRECTORY.start();
@@ -24,6 +30,16 @@ public static void shutdown() {
2430
ACTIVE_DIRECTORY.stop();
2531
}
2632

33+
@Test
34+
public void testUserPermissions() {
35+
checkUserPermissions(webTestClient);
36+
}
37+
38+
@Test
39+
public void testEmptyPermissions() {
40+
checkEmptyPermissions(webTestClient);
41+
}
42+
2743
public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
2844
@Override
2945
public void initialize(@NotNull ConfigurableApplicationContext context) {

api/src/test/java/io/kafbat/ui/ActiveDirectoryLdapsTest.java

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@
44
import static io.kafbat.ui.container.ActiveDirectoryContainer.CONTAINER_KEY_PATH;
55
import static io.kafbat.ui.container.ActiveDirectoryContainer.DOMAIN;
66
import static io.kafbat.ui.container.ActiveDirectoryContainer.PASSWORD;
7+
import static java.nio.file.Files.writeString;
78
import static org.testcontainers.utility.MountableFile.forHostPath;
89

910
import io.kafbat.ui.container.ActiveDirectoryContainer;
10-
import java.io.ByteArrayOutputStream;
1111
import java.io.File;
12-
import java.io.FileWriter;
13-
import java.io.OutputStreamWriter;
12+
import java.io.StringWriter;
1413
import java.net.InetAddress;
15-
import java.nio.charset.StandardCharsets;
1614
import java.security.KeyPair;
1715
import java.security.PrivateKey;
1816
import java.security.cert.X509Certificate;
@@ -22,9 +20,12 @@
2220
import org.jetbrains.annotations.NotNull;
2321
import org.junit.jupiter.api.AfterAll;
2422
import org.junit.jupiter.api.BeforeAll;
23+
import org.junit.jupiter.api.Test;
24+
import org.springframework.beans.factory.annotation.Autowired;
2525
import org.springframework.context.ApplicationContextInitializer;
2626
import org.springframework.context.ConfigurableApplicationContext;
2727
import org.springframework.test.context.ContextConfiguration;
28+
import org.springframework.test.web.reactive.server.WebTestClient;
2829
import org.testcontainers.shaded.org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator;
2930
import org.testcontainers.shaded.org.bouncycastle.openssl.jcajce.JcaPKCS8Generator;
3031
import org.testcontainers.shaded.org.bouncycastle.util.io.pem.PemWriter;
@@ -36,6 +37,9 @@ public class ActiveDirectoryLdapsTest extends AbstractActiveDirectoryIntegration
3637
private static File certPem = null;
3738
private static File privateKeyPem = null;
3839

40+
@Autowired
41+
private WebTestClient webTestClient;
42+
3943
@BeforeAll
4044
public static void setup() throws Exception {
4145
generateCerts();
@@ -51,6 +55,16 @@ public static void shutdown() {
5155
ACTIVE_DIRECTORY.stop();
5256
}
5357

58+
@Test
59+
public void testUserPermissions() {
60+
checkUserPermissions(webTestClient);
61+
}
62+
63+
@Test
64+
public void testEmptyPermissions() {
65+
checkEmptyPermissions(webTestClient);
66+
}
67+
5468
private static void generateCerts() throws Exception {
5569
File truststore = File.createTempFile("truststore", ".jks");
5670

@@ -67,26 +81,22 @@ private static void generateCerts() throws Exception {
6781
TestSslUtils.createTrustStore(truststore.getPath(), new Password(PASSWORD), Map.of("client", clientCert));
6882

6983
certPem = File.createTempFile("cert", ".pem");
70-
try (FileWriter fw = new FileWriter(certPem)) {
71-
fw.write(certOrKeyToString(clientCert));
72-
}
84+
writeString(certPem.toPath(), certOrKeyToString(clientCert));
7385

7486
privateKeyPem = File.createTempFile("key", ".pem");
75-
try (FileWriter fw = new FileWriter(privateKeyPem)) {
76-
fw.write(certOrKeyToString(clientKeyPair.getPrivate()));
77-
}
87+
writeString(privateKeyPem.toPath(), certOrKeyToString(clientKeyPair.getPrivate()));
7888
}
7989

8090
private static String certOrKeyToString(Object certOrKey) throws Exception {
81-
ByteArrayOutputStream out = new ByteArrayOutputStream();
82-
try (PemWriter pemWriter = new PemWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))) {
91+
StringWriter sw = new StringWriter();
92+
try (PemWriter pw = new PemWriter(sw)) {
8393
if (certOrKey instanceof X509Certificate) {
84-
pemWriter.writeObject(new JcaMiscPEMGenerator(certOrKey));
94+
pw.writeObject(new JcaMiscPEMGenerator(certOrKey));
8595
} else {
86-
pemWriter.writeObject(new JcaPKCS8Generator((PrivateKey) certOrKey, null));
96+
pw.writeObject(new JcaPKCS8Generator((PrivateKey) certOrKey, null));
8797
}
8898
}
89-
return out.toString(StandardCharsets.UTF_8);
99+
return sw.toString();
90100
}
91101

92102
public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

0 commit comments

Comments
 (0)