Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BE: RBAC: Impl Active Directory populator #717

Merged
merged 8 commits into from
Dec 31, 2024
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package io.kafbat.ui.config.auth;

import static io.kafbat.ui.config.auth.AbstractAuthSecurityConfig.AUTH_WHITELIST;

import io.kafbat.ui.service.rbac.AccessControlService;
import io.kafbat.ui.service.rbac.extractor.RbacActiveDirectoryAuthoritiesExtractor;
import io.kafbat.ui.service.rbac.extractor.RbacLdapAuthoritiesExtractor;
import java.util.Collection;
import java.util.List;
Expand All @@ -17,7 +16,6 @@
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.ProviderManager;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManagerAdapter;
Expand All @@ -29,10 +27,11 @@
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.NullLdapAuthoritiesPopulator;
import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
import org.springframework.security.ldap.authentication.ad.DefaultActiveDirectoryAuthoritiesPopulator;
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
import org.springframework.security.ldap.search.LdapUserSearch;
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper;
import org.springframework.security.web.server.SecurityWebFilterChain;
Expand All @@ -43,44 +42,55 @@
@EnableConfigurationProperties(LdapProperties.class)
@RequiredArgsConstructor
@Slf4j
public class LdapSecurityConfig {
public class LdapSecurityConfig extends AbstractAuthSecurityConfig {

private final LdapProperties props;

@Bean
public ReactiveAuthenticationManager authenticationManager(LdapContextSource ldapContextSource,
LdapAuthoritiesPopulator authoritiesExtractor,
AccessControlService acs) {
public ReactiveAuthenticationManager authenticationManager(AbstractLdapAuthenticationProvider authProvider) {
return new ReactiveAuthenticationManagerAdapter(new ProviderManager(List.of(authProvider)));
}

@Bean
public AbstractLdapAuthenticationProvider authenticationProvider(LdapAuthoritiesPopulator authoritiesExtractor,
BindAuthenticator bindAuthenticator,
Haarolean marked this conversation as resolved.
Show resolved Hide resolved
AccessControlService acs) {
var rbacEnabled = acs.isRbacEnabled();

AbstractLdapAuthenticationProvider authProvider;

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

if (rbacEnabled) {
authProvider.setUserDetailsContextMapper(new RbacUserDetailsMapper());
}

return authProvider;
}

@Bean
public BindAuthenticator ldapBindAuthentication(LdapContextSource ldapContextSource) {
BindAuthenticator ba = new BindAuthenticator(ldapContextSource);
Haarolean marked this conversation as resolved.
Show resolved Hide resolved

if (props.getBase() != null) {
ba.setUserDnPatterns(new String[] {props.getBase()});
}

if (props.getUserFilterSearchFilter() != null) {
LdapUserSearch userSearch =
new FilterBasedLdapUserSearch(props.getUserFilterSearchBase(), props.getUserFilterSearchFilter(),
ldapContextSource);
ba.setUserSearch(userSearch);
}

AbstractLdapAuthenticationProvider authenticationProvider;
if (!props.isActiveDirectory()) {
authenticationProvider = rbacEnabled
? new LdapAuthenticationProvider(ba, authoritiesExtractor)
: new LdapAuthenticationProvider(ba);
} else {
authenticationProvider = new ActiveDirectoryLdapAuthenticationProvider(props.getActiveDirectoryDomain(),
props.getUrls()); // TODO Issue #3741
authenticationProvider.setUseAuthenticationRequestCredentials(true);
}

if (rbacEnabled) {
authenticationProvider.setUserDetailsContextMapper(new UserDetailsMapper());
}

AuthenticationManager am = new ProviderManager(List.of(authenticationProvider));

return new ReactiveAuthenticationManagerAdapter(am);
return ba;
}

@Bean
Expand All @@ -94,24 +104,27 @@ public LdapContextSource ldapContextSource() {
}

@Bean
public DefaultLdapAuthoritiesPopulator ldapAuthoritiesExtractor(ApplicationContext context,
BaseLdapPathContextSource contextSource,
AccessControlService acs) {
var rbacEnabled = acs != null && acs.isRbacEnabled();
public LdapAuthoritiesPopulator authoritiesExtractor(ApplicationContext ctx,
BaseLdapPathContextSource ldapCtx,
AccessControlService acs) {
if (!props.isActiveDirectory()) {
if (!acs.isRbacEnabled()) {
return new NullLdapAuthoritiesPopulator();
}

DefaultLdapAuthoritiesPopulator extractor;
var extractor = new RbacLdapAuthoritiesExtractor(ctx, ldapCtx, props.getGroupFilterSearchBase());

if (rbacEnabled) {
extractor = new RbacLdapAuthoritiesExtractor(context, contextSource, props.getGroupFilterSearchBase());
Optional.ofNullable(props.getGroupFilterSearchFilter()).ifPresent(extractor::setGroupSearchFilter);
extractor.setRolePrefix("");
extractor.setConvertToUpperCase(false);
extractor.setSearchSubtree(true);

return extractor;
} else {
extractor = new DefaultLdapAuthoritiesPopulator(contextSource, props.getGroupFilterSearchBase());
return acs.isRbacEnabled()
? new RbacActiveDirectoryAuthoritiesExtractor(ctx)
: new DefaultActiveDirectoryAuthoritiesPopulator();
}

Optional.ofNullable(props.getGroupFilterSearchFilter()).ifPresent(extractor::setGroupSearchFilter);
extractor.setRolePrefix("");
extractor.setConvertToUpperCase(false);
extractor.setSearchSubtree(true);
return extractor;
}

@Bean
Expand All @@ -133,7 +146,7 @@ public SecurityWebFilterChain configureLdap(ServerHttpSecurity http) {
.build();
}

private static class UserDetailsMapper extends LdapUserDetailsMapper {
private static class RbacUserDetailsMapper extends LdapUserDetailsMapper {
@Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
Collection<? extends GrantedAuthority> authorities) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ private Mono<ReactiveAdminClient> createAdminClient(KafkaCluster cluster) {
return AdminClient.create(properties);
}).flatMap(ac -> ReactiveAdminClient.create(ac).doOnError(th -> ac.close()))
.onErrorMap(th -> new IllegalStateException(
"Error while creating AdminClient for Cluster " + cluster.getName(), th));
"Error while creating AdminClient for the cluster " + cluster.getName(), th));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package io.kafbat.ui.service.rbac.extractor;

import io.kafbat.ui.model.rbac.Role;
import io.kafbat.ui.model.rbac.provider.Provider;
import io.kafbat.ui.service.rbac.AccessControlService;
import java.util.Collection;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.ldap.authentication.ad.DefaultActiveDirectoryAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;

@Slf4j
public class RbacActiveDirectoryAuthoritiesExtractor implements LdapAuthoritiesPopulator {

private final DefaultActiveDirectoryAuthoritiesPopulator populator = new DefaultActiveDirectoryAuthoritiesPopulator();
private final AccessControlService acs;

public RbacActiveDirectoryAuthoritiesExtractor(ApplicationContext context) {
this.acs = context.getBean(AccessControlService.class);
}

@Override
public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
var adGroups = populator.getGrantedAuthorities(userData, username)
.stream()
.map(GrantedAuthority::getAuthority)
.peek(group -> log.trace("Found AD group [{}] for user [{}]", group, username))
.collect(Collectors.toSet());

return acs.getRoles()
.stream()
.filter(r -> r.getSubjects()
.stream()
.filter(subject -> subject.getProvider().equals(Provider.LDAP_AD))
.filter(subject -> subject.getType().equals("group"))
Haarolean marked this conversation as resolved.
Show resolved Hide resolved
.anyMatch(subject -> adGroups.contains(subject.getValue()))
)
.map(Role::getName)
.peek(role -> log.trace("Mapped role [{}] for user [{}]", role, username))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public class RbacLdapAuthoritiesExtractor extends NestedLdapAuthoritiesPopulator
private final AccessControlService acs;

public RbacLdapAuthoritiesExtractor(ApplicationContext context,
BaseLdapPathContextSource contextSource, String groupFilterSearchBase) {
BaseLdapPathContextSource contextSource,
String groupFilterSearchBase) {
super(contextSource, groupFilterSearchBase);
this.acs = context.getBean(AccessControlService.class);
}
Expand Down
Loading