Skip to content

Commit 49894b8

Browse files
authored
BE: Auth: Support LDAPS for AD (#840)
1 parent a05709f commit 49894b8

File tree

7 files changed

+320
-59
lines changed

7 files changed

+320
-59
lines changed

api/pom.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,19 @@
212212
<version>${okhttp3.mockwebserver.version}</version>
213213
<scope>test</scope>
214214
</dependency>
215+
<dependency>
216+
<groupId>org.apache.kafka</groupId>
217+
<artifactId>kafka-clients</artifactId>
218+
<version>${confluent.version}-ccs</version>
219+
<classifier>test</classifier>
220+
<scope>test</scope>
221+
</dependency>
222+
<dependency>
223+
<groupId>org.bouncycastle</groupId>
224+
<artifactId>bcpkix-jdk18on</artifactId>
225+
<version>1.80</version>
226+
<scope>test</scope>
227+
</dependency>
215228

216229
<dependency>
217230
<groupId>org.springframework.boot</groupId>

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

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
import io.kafbat.ui.service.rbac.AccessControlService;
44
import io.kafbat.ui.service.rbac.extractor.RbacActiveDirectoryAuthoritiesExtractor;
55
import io.kafbat.ui.service.rbac.extractor.RbacLdapAuthoritiesExtractor;
6+
import io.kafbat.ui.util.CustomSslSocketFactory;
67
import io.kafbat.ui.util.StaticFileWebFilter;
78
import java.util.Collection;
89
import java.util.List;
10+
import java.util.Map;
911
import java.util.Optional;
12+
import java.util.stream.Stream;
1013
import lombok.RequiredArgsConstructor;
1114
import lombok.extern.slf4j.Slf4j;
1215
import org.springframework.beans.factory.annotation.Autowired;
@@ -47,6 +50,9 @@
4750
@RequiredArgsConstructor
4851
@Slf4j
4952
public class LdapSecurityConfig extends AbstractAuthSecurityConfig {
53+
private static final Map<String, Object> BASE_ENV_PROPS = Map.of(
54+
"java.naming.ldap.factory.socket", CustomSslSocketFactory.class.getName()
55+
);
5056

5157
private final LdapProperties props;
5258

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

6470
AbstractLdapAuthenticationProvider authProvider;
6571

66-
if (!props.isActiveDirectory()) {
67-
authProvider = new LdapAuthenticationProvider(ba, authoritiesExtractor);
72+
if (props.isActiveDirectory()) {
73+
authProvider = activeDirectoryProvider(authoritiesExtractor);
6874
} else {
69-
authProvider = new ActiveDirectoryLdapAuthenticationProvider(props.getActiveDirectoryDomain(),
70-
props.getUrls());
71-
authProvider.setUseAuthenticationRequestCredentials(true);
72-
((ActiveDirectoryLdapAuthenticationProvider) authProvider).setAuthoritiesPopulator(authoritiesExtractor);
75+
authProvider = new LdapAuthenticationProvider(ba, authoritiesExtractor);
7376
}
7477

7578
if (rbacEnabled) {
@@ -159,6 +162,22 @@ public SecurityWebFilterChain configureLdap(ServerHttpSecurity http) {
159162
return builder.build();
160163
}
161164

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+
162181
private static class RbacUserDetailsMapper extends LdapUserDetailsMapper {
163182
@Override
164183
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package io.kafbat.ui.util;
2+
3+
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
4+
import java.io.IOException;
5+
import java.net.InetAddress;
6+
import java.net.Socket;
7+
import java.security.SecureRandom;
8+
import javax.net.SocketFactory;
9+
import javax.net.ssl.SSLContext;
10+
import javax.net.ssl.SSLSocketFactory;
11+
12+
public class CustomSslSocketFactory extends SSLSocketFactory {
13+
private final SSLSocketFactory socketFactory;
14+
15+
public CustomSslSocketFactory() {
16+
try {
17+
SSLContext sslContext = SSLContext.getInstance("TLS");
18+
sslContext.init(null, InsecureTrustManagerFactory.INSTANCE.getTrustManagers(), new SecureRandom());
19+
20+
socketFactory = sslContext.getSocketFactory();
21+
} catch (Exception e) {
22+
throw new RuntimeException(e);
23+
}
24+
}
25+
26+
public static SocketFactory getDefault() {
27+
return new CustomSslSocketFactory();
28+
}
29+
30+
@Override
31+
public String[] getDefaultCipherSuites() {
32+
return socketFactory.getDefaultCipherSuites();
33+
}
34+
35+
@Override
36+
public String[] getSupportedCipherSuites() {
37+
return socketFactory.getSupportedCipherSuites();
38+
}
39+
40+
@Override
41+
public Socket createSocket(Socket socket, String string, int i, boolean bln) throws IOException {
42+
return socketFactory.createSocket(socket, string, i, bln);
43+
}
44+
45+
@Override
46+
public Socket createSocket(String string, int i) throws IOException {
47+
return socketFactory.createSocket(string, i);
48+
}
49+
50+
@Override
51+
public Socket createSocket(String string, int i, InetAddress ia, int i1) throws IOException {
52+
return socketFactory.createSocket(string, i, ia, i1);
53+
}
54+
55+
@Override
56+
public Socket createSocket(InetAddress ia, int i) throws IOException {
57+
return socketFactory.createSocket(ia, i);
58+
}
59+
60+
@Override
61+
public Socket createSocket(InetAddress ia, int i, InetAddress ia1, int i1) throws IOException {
62+
return socketFactory.createSocket(ia, i, ia1, i1);
63+
}
64+
65+
@Override
66+
public Socket createSocket() throws IOException {
67+
return socketFactory.createSocket();
68+
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package io.kafbat.ui;
22

33
import static io.kafbat.ui.AbstractIntegrationTest.LOCAL;
4-
import static io.kafbat.ui.container.ActiveDirectoryContainer.DOMAIN;
54
import static io.kafbat.ui.container.ActiveDirectoryContainer.EMPTY_PERMISSIONS_USER;
65
import static io.kafbat.ui.container.ActiveDirectoryContainer.FIRST_USER_WITH_GROUP;
76
import static io.kafbat.ui.container.ActiveDirectoryContainer.PASSWORD;
@@ -12,52 +11,26 @@
1211
import static org.junit.jupiter.api.Assertions.assertNotNull;
1312
import static org.junit.jupiter.api.Assertions.assertTrue;
1413

15-
import io.kafbat.ui.container.ActiveDirectoryContainer;
1614
import io.kafbat.ui.model.AuthenticationInfoDTO;
1715
import io.kafbat.ui.model.ResourceTypeDTO;
1816
import io.kafbat.ui.model.UserPermissionDTO;
1917
import java.util.List;
2018
import java.util.Objects;
21-
import org.jetbrains.annotations.NotNull;
22-
import org.junit.jupiter.api.AfterAll;
23-
import org.junit.jupiter.api.BeforeAll;
24-
import org.junit.jupiter.api.Test;
25-
import org.springframework.beans.factory.annotation.Autowired;
2619
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
2720
import org.springframework.boot.test.context.SpringBootTest;
28-
import org.springframework.context.ApplicationContextInitializer;
29-
import org.springframework.context.ConfigurableApplicationContext;
3021
import org.springframework.http.MediaType;
3122
import org.springframework.test.context.ActiveProfiles;
32-
import org.springframework.test.context.ContextConfiguration;
3323
import org.springframework.test.web.reactive.server.WebTestClient;
3424
import org.springframework.web.reactive.function.BodyInserters;
3525

3626
@SpringBootTest
3727
@ActiveProfiles("rbac-ad")
3828
@AutoConfigureWebTestClient(timeout = "60000")
39-
@ContextConfiguration(initializers = {ActiveDirectoryIntegrationTest.Initializer.class})
40-
public class ActiveDirectoryIntegrationTest {
29+
public abstract class AbstractActiveDirectoryIntegrationTest {
4130
private static final String SESSION = "SESSION";
4231

43-
private static final ActiveDirectoryContainer ACTIVE_DIRECTORY = new ActiveDirectoryContainer();
44-
45-
@Autowired
46-
private WebTestClient webTestClient;
47-
48-
@BeforeAll
49-
public static void setup() {
50-
ACTIVE_DIRECTORY.start();
51-
}
52-
53-
@AfterAll
54-
public static void shutdown() {
55-
ACTIVE_DIRECTORY.stop();
56-
}
57-
58-
@Test
59-
public void testUserPermissions() {
60-
AuthenticationInfoDTO info = authenticationInfo(FIRST_USER_WITH_GROUP);
32+
protected static void checkUserPermissions(WebTestClient client) {
33+
AuthenticationInfoDTO info = authenticationInfo(client, FIRST_USER_WITH_GROUP);
6134

6235
assertNotNull(info);
6336
assertTrue(info.getRbacEnabled());
@@ -67,22 +40,21 @@ public void testUserPermissions() {
6740
assertFalse(permissions.isEmpty());
6841
assertTrue(permissions.stream().anyMatch(permission ->
6942
permission.getClusters().contains(LOCAL) && permission.getResource() == ResourceTypeDTO.TOPIC));
70-
assertEquals(permissions, authenticationInfo(SECOND_USER_WITH_GROUP).getUserInfo().getPermissions());
71-
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());
7245
}
7346

74-
@Test
75-
public void testEmptyPermissions() {
76-
assertTrue(Objects.requireNonNull(authenticationInfo(EMPTY_PERMISSIONS_USER))
47+
protected static void checkEmptyPermissions(WebTestClient client) {
48+
assertTrue(Objects.requireNonNull(authenticationInfo(client, EMPTY_PERMISSIONS_USER))
7749
.getUserInfo()
7850
.getPermissions()
7951
.isEmpty()
8052
);
8153
}
8254

83-
private String session(String name) {
55+
protected static String session(WebTestClient client, String name) {
8456
return Objects.requireNonNull(
85-
webTestClient
57+
client
8658
.post()
8759
.uri("/login")
8860
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
@@ -96,25 +68,16 @@ private String session(String name) {
9668
.getValue();
9769
}
9870

99-
private AuthenticationInfoDTO authenticationInfo(String name) {
100-
return webTestClient
71+
protected static AuthenticationInfoDTO authenticationInfo(WebTestClient client, String name) {
72+
return client
10173
.get()
10274
.uri("/api/authorization")
103-
.cookie(SESSION, session(name))
75+
.cookie(SESSION, session(client, name))
10476
.exchange()
10577
.expectStatus()
10678
.isOk()
10779
.returnResult(AuthenticationInfoDTO.class)
10880
.getResponseBody()
10981
.blockFirst();
11082
}
111-
112-
public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
113-
@Override
114-
public void initialize(@NotNull ConfigurableApplicationContext context) {
115-
System.setProperty("spring.ldap.urls", ACTIVE_DIRECTORY.getLdapUrl());
116-
System.setProperty("oauth2.ldap.activeDirectory", "true");
117-
System.setProperty("oauth2.ldap.activeDirectory.domain", DOMAIN);
118-
}
119-
}
12083
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package io.kafbat.ui;
2+
3+
import static io.kafbat.ui.container.ActiveDirectoryContainer.DOMAIN;
4+
5+
import io.kafbat.ui.container.ActiveDirectoryContainer;
6+
import org.jetbrains.annotations.NotNull;
7+
import org.junit.jupiter.api.AfterAll;
8+
import org.junit.jupiter.api.BeforeAll;
9+
import org.junit.jupiter.api.Test;
10+
import org.springframework.beans.factory.annotation.Autowired;
11+
import org.springframework.context.ApplicationContextInitializer;
12+
import org.springframework.context.ConfigurableApplicationContext;
13+
import org.springframework.test.context.ContextConfiguration;
14+
import org.springframework.test.web.reactive.server.WebTestClient;
15+
16+
@ContextConfiguration(initializers = {ActiveDirectoryLdapTest.Initializer.class})
17+
public class ActiveDirectoryLdapTest extends AbstractActiveDirectoryIntegrationTest {
18+
private static final ActiveDirectoryContainer ACTIVE_DIRECTORY = new ActiveDirectoryContainer(false);
19+
20+
@Autowired
21+
private WebTestClient webTestClient;
22+
23+
@BeforeAll
24+
public static void setup() {
25+
ACTIVE_DIRECTORY.start();
26+
}
27+
28+
@AfterAll
29+
public static void shutdown() {
30+
ACTIVE_DIRECTORY.stop();
31+
}
32+
33+
@Test
34+
public void testUserPermissions() {
35+
checkUserPermissions(webTestClient);
36+
}
37+
38+
@Test
39+
public void testEmptyPermissions() {
40+
checkEmptyPermissions(webTestClient);
41+
}
42+
43+
public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
44+
@Override
45+
public void initialize(@NotNull ConfigurableApplicationContext context) {
46+
System.setProperty("spring.ldap.urls", ACTIVE_DIRECTORY.getLdapUrl());
47+
System.setProperty("oauth2.ldap.activeDirectory", "true");
48+
System.setProperty("oauth2.ldap.activeDirectory.domain", DOMAIN);
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)