Roman Zabaluev 2 år sedan
förälder
incheckning
d8f3b70cd7

+ 6 - 3
kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/auth/LdapProperties.java

@@ -9,16 +9,19 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
 public class LdapProperties {
 public class LdapProperties {
 
 
   private String urls;
   private String urls;
-  private String dnPattern;
+  private String base; // TODO was dnPattern
   private String adminUser;
   private String adminUser;
   private String adminPassword;
   private String adminPassword;
   private String userFilterSearchBase;
   private String userFilterSearchBase;
   private String userFilterSearchFilter;
   private String userFilterSearchFilter;
   private String groupSearchBase;
   private String groupSearchBase;
 
 
-  @Value("${oauth2.ldap.activeDirectory}")
+  @Value("${oauth2.ldap.activeDirectory:false}")
   private boolean isActiveDirectory;
   private boolean isActiveDirectory;
-  @Value("${oauth2.ldap.aсtiveDirectory.domain}")
+  @Value("${oauth2.ldap.aсtiveDirectory.domain:null}") // TODO null is a string here for some reason
   private String activeDirectoryDomain;
   private String activeDirectoryDomain;
 
 
+  @Value("${oauth2.ldap.groupRoleAttribute:cn}")
+  private String groupRoleAttribute;
+
 }
 }

+ 50 - 34
kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/auth/LdapSecurityConfig.java

@@ -5,10 +5,10 @@ import static com.provectus.kafka.ui.config.auth.AbstractAuthSecurityConfig.AUTH
 import com.provectus.kafka.ui.service.rbac.AccessControlService;
 import com.provectus.kafka.ui.service.rbac.AccessControlService;
 import com.provectus.kafka.ui.service.rbac.extractor.RbacLdapAuthoritiesExtractor;
 import com.provectus.kafka.ui.service.rbac.extractor.RbacLdapAuthoritiesExtractor;
 import java.util.Collection;
 import java.util.Collection;
+import java.util.List;
 import javax.annotation.Nullable;
 import javax.annotation.Nullable;
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration;
 import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -16,16 +16,24 @@ import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;
 import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.Primary;
 import org.springframework.ldap.core.DirContextOperations;
 import org.springframework.ldap.core.DirContextOperations;
+import org.springframework.ldap.core.support.BaseLdapPathContextSource;
+import org.springframework.ldap.core.support.LdapContextSource;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.authentication.AuthenticationProvider;
-import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
-import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.authentication.ProviderManager;
+import org.springframework.security.authentication.ReactiveAuthenticationManager;
+import org.springframework.security.authentication.ReactiveAuthenticationManagerAdapter;
 import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
 import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
 import org.springframework.security.config.web.server.ServerHttpSecurity;
 import org.springframework.security.config.web.server.ServerHttpSecurity;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider;
+import org.springframework.security.ldap.authentication.BindAuthenticator;
+import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
 import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
 import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
+import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
+import org.springframework.security.ldap.search.LdapUserSearch;
 import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper;
 import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper;
 import org.springframework.security.web.server.SecurityWebFilterChain;
 import org.springframework.security.web.server.SecurityWebFilterChain;
 
 
@@ -40,37 +48,51 @@ public class LdapSecurityConfig {
 
 
   private final LdapProperties props;
   private final LdapProperties props;
 
 
-  @Autowired
-  public void authenticationManager(AuthenticationManagerBuilder builder, ApplicationContext context,
-                                    @Nullable AccessControlService acs) throws Exception {
-    var configurer = builder.ldapAuthentication();
-
-    if (props.isActiveDirectory()) {
-      builder.authenticationProvider(activeDirectoryAuthenticationProvider());
+  @Bean
+  public ReactiveAuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource,
+                                                             ApplicationContext context,
+                                                             @Nullable AccessControlService acs) {
+    var rbacEnabled = acs != null && acs.isRbacEnabled();
+    BindAuthenticator ba = new BindAuthenticator(contextSource);
+    if (props.getBase() != null) {
+      ba.setUserDnPatterns(new String[] {props.getBase()});
+    }
+    if (props.getUserFilterSearchBase() != null) {
+      LdapUserSearch userSearch =
+          new FilterBasedLdapUserSearch(props.getUserFilterSearchBase(), props.getUserFilterSearchFilter(),
+              contextSource);
+      ba.setUserSearch(userSearch);
     }
     }
 
 
-    if (acs != null && acs.isRbacEnabled()) {
-      configurer.ldapAuthoritiesPopulator(new RbacLdapAuthoritiesExtractor(context));
+    AbstractLdapAuthenticationProvider authenticationProvider;
+    if (!props.isActiveDirectory()) {
+      authenticationProvider = rbacEnabled
+          ? new LdapAuthenticationProvider(ba, new RbacLdapAuthoritiesExtractor(context))
+          : new LdapAuthenticationProvider(ba);
     } else {
     } else {
-      configurer.groupSearchBase(props.getGroupSearchBase());
+      authenticationProvider = new ActiveDirectoryLdapAuthenticationProvider(props.getActiveDirectoryDomain(),
+          props.getUrls()); // TODO authority extractor
+      authenticationProvider.setUseAuthenticationRequestCredentials(true);
+    }
+
+    if (rbacEnabled) {
+      authenticationProvider.setUserDetailsContextMapper(new UserDetailsMapper());
     }
     }
 
 
-    configurer.userDetailsContextMapper(new UserDetailsMapper());
+    AuthenticationManager am = new ProviderManager(List.of(authenticationProvider));
 
 
-    configurer
-        .userDnPatterns(props.getDnPattern())
-        .userSearchBase(props.getUserFilterSearchBase())
-        .userSearchFilter(props.getUserFilterSearchFilter())
-        .contextSource()
-        .url(props.getUrls())
-        .managerDn(props.getAdminUser())
-        .managerPassword(props.getAdminPassword());
+    return new ReactiveAuthenticationManagerAdapter(am);
   }
   }
 
 
   @Bean
   @Bean
-  public AuthenticationManager authenticationManager(AuthenticationConfiguration conf) throws Exception {
-    conf.authenticationManagerBuilder()
-    return conf.getAuthenticationManager();
+  @Primary
+  public BaseLdapPathContextSource contextSource() {
+    LdapContextSource ctx = new LdapContextSource();
+    ctx.setUrl(props.getUrls());
+    ctx.setUserDn(props.getAdminUser());
+    ctx.setPassword(props.getAdminPassword());
+    ctx.afterPropertiesSet();
+    return ctx;
   }
   }
 
 
   @Bean
   @Bean
@@ -80,6 +102,8 @@ public class LdapSecurityConfig {
       log.info("Active Directory support for LDAP has been enabled.");
       log.info("Active Directory support for LDAP has been enabled.");
     }
     }
 
 
+//    http.authenticationManager(authenticationManager())
+
     return http
     return http
         .authorizeExchange()
         .authorizeExchange()
         .pathMatchers(AUTH_WHITELIST)
         .pathMatchers(AUTH_WHITELIST)
@@ -98,14 +122,6 @@ public class LdapSecurityConfig {
         .build();
         .build();
   }
   }
 
 
-  @Bean
-  @ConditionalOnProperty(value = "oauth2.ldap.activeDirectory", havingValue = "true")
-  public AuthenticationProvider activeDirectoryAuthenticationProvider() {
-    var provider = new ActiveDirectoryLdapAuthenticationProvider(props.getActiveDirectoryDomain(), props.getUrls());
-    provider.setUseAuthenticationRequestCredentials(true);
-    return provider;
-  }
-
   private static class UserDetailsMapper extends LdapUserDetailsMapper {
   private static class UserDetailsMapper extends LdapUserDetailsMapper {
     @Override
     @Override
     public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
     public UserDetails mapUserFromContext(DirContextOperations ctx, String username,

+ 5 - 5
kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/auth/condition/ActiveDirectoryCondition.java

@@ -5,17 +5,17 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 
 
 public class ActiveDirectoryCondition extends AllNestedConditions {
 public class ActiveDirectoryCondition extends AllNestedConditions {
 
 
-  public ActiveDirectoryCondition(ConfigurationPhase configurationPhase) {
-    super(configurationPhase);
+  public ActiveDirectoryCondition() {
+    super(ConfigurationPhase.PARSE_CONFIGURATION);
   }
   }
 
 
   @ConditionalOnProperty(value = "auth.type", havingValue = "LDAP")
   @ConditionalOnProperty(value = "auth.type", havingValue = "LDAP")
-  static class onAuthType {
+  public static class OnAuthType {
 
 
   }
   }
 
 
-  @ConditionalOnProperty(value = "oauth2.ldap.activeDirectory", havingValue = "true")
-  static class onActiveDirectory {
+  @ConditionalOnProperty(value = "${oauth2.ldap.activeDirectory}:false", havingValue = "true", matchIfMissing = false)
+  public static class OnActiveDirectory {
 
 
   }
   }
 }
 }

+ 13 - 8
kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/RbacLdapAuthoritiesExtractor.java

@@ -1,5 +1,6 @@
 package com.provectus.kafka.ui.service.rbac.extractor;
 package com.provectus.kafka.ui.service.rbac.extractor;
 
 
+import com.provectus.kafka.ui.config.auth.LdapProperties;
 import com.provectus.kafka.ui.model.rbac.Role;
 import com.provectus.kafka.ui.model.rbac.Role;
 import com.provectus.kafka.ui.model.rbac.provider.Provider;
 import com.provectus.kafka.ui.model.rbac.provider.Provider;
 import com.provectus.kafka.ui.service.rbac.AccessControlService;
 import com.provectus.kafka.ui.service.rbac.AccessControlService;
@@ -10,6 +11,7 @@ import java.util.Set;
 import java.util.function.Function;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationContext;
 import org.springframework.ldap.core.DirContextOperations;
 import org.springframework.ldap.core.DirContextOperations;
 import org.springframework.ldap.core.support.BaseLdapPathContextSource;
 import org.springframework.ldap.core.support.BaseLdapPathContextSource;
@@ -22,6 +24,7 @@ import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
 public class RbacLdapAuthoritiesExtractor extends DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator {
 public class RbacLdapAuthoritiesExtractor extends DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator {
 
 
   private final AccessControlService acs;
   private final AccessControlService acs;
+  private final LdapProperties props;
 
 
   private final Function<Map<String, List<String>>, GrantedAuthority> authorityMapper = (record) -> {
   private final Function<Map<String, List<String>>, GrantedAuthority> authorityMapper = (record) -> {
     String role = record.get(getGroupRoleAttribute()).get(0);
     String role = record.get(getGroupRoleAttribute()).get(0);
@@ -31,6 +34,7 @@ public class RbacLdapAuthoritiesExtractor extends DefaultLdapAuthoritiesPopulato
   public RbacLdapAuthoritiesExtractor(ApplicationContext context) {
   public RbacLdapAuthoritiesExtractor(ApplicationContext context) {
     super(context.getBean(BaseLdapPathContextSource.class), null);
     super(context.getBean(BaseLdapPathContextSource.class), null);
     this.acs = context.getBean(AccessControlService.class);
     this.acs = context.getBean(AccessControlService.class);
+    this.props = context.getBean(LdapProperties.class);
   }
   }
 
 
   @Override
   @Override
@@ -45,22 +49,23 @@ public class RbacLdapAuthoritiesExtractor extends DefaultLdapAuthoritiesPopulato
         .collect(Collectors.toSet());
         .collect(Collectors.toSet());
   }
   }
 
 
-  private Set<GrantedAuthority> getRoles(String search, String userDn, String username) {
-    if (search == null) {
+  private Set<GrantedAuthority> getRoles(String groupSearchBase, String userDn, String username) {
+    if (StringUtils.isEmpty(groupSearchBase)) {
+      log.debug("groupSearchBase empty, skipping roles lookup");
       return new HashSet<>();
       return new HashSet<>();
     }
     }
 
 
-    log.trace("Searching for roles for user [{}] with DN [{}] and filter [{}] in search base [{}]",
-        username, userDn, getGroupSearchFilter(), search);
+    log.trace(
+        "Searching for roles for user [{}] with DN [{}], groupRoleAttribute [{}] and filter [{}] in search base [{}]",
+        username, userDn, props.getGroupRoleAttribute(), getGroupSearchFilter(), groupSearchBase);
 
 
     Set<Map<String, List<String>>> userRoles = getLdapTemplate().searchForMultipleAttributeValues(
     Set<Map<String, List<String>>> userRoles = getLdapTemplate().searchForMultipleAttributeValues(
-        search, getGroupSearchFilter(), new String[] {userDn, username},
-        new String[] {getGroupRoleAttribute()});
-
-    log.debug("Found roles from search [{}]", userRoles);
+        groupSearchBase, getGroupSearchFilter(), new String[] {userDn, username},
+        new String[] {props.getGroupRoleAttribute()});
 
 
     return userRoles.stream()
     return userRoles.stream()
         .map(authorityMapper)
         .map(authorityMapper)
+        .peek(a -> log.debug("Mapped role [{}] for user [{}]", a, username))
         .collect(Collectors.toSet());
         .collect(Collectors.toSet());
   }
   }