Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,36 @@ public class LdapConfig extends ConfigBase {
public static long ldap_cache_timeout_day = 30;

/**
* LDAP pool configuration:
* LDAP read timeout in milliseconds.
* Controls the maximum time to wait for an LDAP response after a request is sent.
* Uses JNDI property "com.sun.jndi.ldap.read.timeout".
* Set to 0 for no timeout (not recommended). Default 5000ms.
*/
@ConfigBase.ConfField
public static int ldap_read_timeout_ms = 5000;

/**
* LDAP connect timeout in milliseconds.
* Controls the maximum time to wait for establishing a TCP connection to the LDAP server.
* Uses JNDI property "com.sun.jndi.ldap.connect.timeout".
* Set to 0 for no timeout (not recommended). Default 5000ms.
*/
@ConfigBase.ConfField
public static int ldap_connect_timeout_ms = 5000;

/**
* Whether to use connection pooling for LDAP search operations.
* When true (default), uses Spring PoolingContextSource with ldap_pool_* settings.
* When false, each LDAP search creates a fresh connection, avoiding dead connection
* detection cost (testOnBorrow can burn read_timeout discovering stale connections
* killed by firewalls/NAT idle timeout). Recommended to set false if experiencing
* intermittent ~5s LDAP search latency spikes.
*/
@ConfigBase.ConfField
public static boolean ldap_search_use_pool = true;

/**
* LDAP pool configuration (only effective when ldap_search_use_pool = true):
* https://docs.spring.io/spring-ldap/docs/2.3.3.RELEASE/reference/#pool-configuration
*/
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,30 @@ public boolean authenticate(ConnectContext context,
MysqlAuthPacket authPacket,
MysqlHandshakePacket handshakePacket) throws IOException {
Authenticator authenticator = chooseAuthenticator(userName);
boolean debugEnabled = LOG.isDebugEnabled();
long resolveStart = 0L;
if (debugEnabled) {
LOG.debug("AuthenticatorManager: user={}, authenticator={}",
userName, authenticator.getClass().getSimpleName());
resolveStart = System.currentTimeMillis();
}
Optional<Password> password = authenticator.getPasswordResolver()
.resolvePassword(context, channel, serializer, authPacket, handshakePacket);
if (!password.isPresent()) {
return false;
}
if (debugEnabled) {
long resolveElapsed = System.currentTimeMillis() - resolveStart;
LOG.debug("resolvePassword: user={}, elapsed={}ms", userName, resolveElapsed);
resolveStart = System.currentTimeMillis();
}
String remoteIp = context.getMysqlChannel().getRemoteIp();
AuthenticateRequest request = new AuthenticateRequest(userName, password.get(), remoteIp);
AuthenticateResponse response = authenticator.authenticate(request);
if (debugEnabled) {
long authenticateElapsed = System.currentTimeMillis() - resolveStart;
LOG.debug("authenticate: user={}, elapsed={}ms", userName, authenticateElapsed);
}
if (!response.isSuccess()) {
MysqlProto.sendResponsePacket(context);
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public LdapAuthenticator() {
*/
@Override
public AuthenticateResponse authenticate(AuthenticateRequest request) throws IOException {
long start = System.currentTimeMillis();
if (LOG.isDebugEnabled()) {
LOG.debug("user:{} start to ldap authenticate.", request.getUserName());
}
Expand All @@ -67,19 +68,29 @@ public AuthenticateResponse authenticate(AuthenticateRequest request) throws IOE
return AuthenticateResponse.failedResponse;
}
ClearPassword clearPassword = (ClearPassword) password;
return internalAuthenticate(clearPassword.getPassword(), request.getUserName(), request.getRemoteIp());
AuthenticateResponse response = internalAuthenticate(clearPassword.getPassword(),
request.getUserName(), request.getRemoteIp());
long elapsed = System.currentTimeMillis() - start;
if (LOG.isDebugEnabled()) {
LOG.debug("LdapAuthenticator.authenticate: user={}, success={}, elapsed={}ms",
request.getUserName(), response.isSuccess(), elapsed);
}
return response;
}

@Override
public boolean canDeal(String qualifiedUser) {
if (qualifiedUser.equals(Auth.ROOT_USER) || qualifiedUser.equals(Auth.ADMIN_USER)) {
return false;
}
// Fixme Note: LdapManager should be managed internally within the Ldap plugin
// and not be placed inside the Env class. This ensures that Ldap-related
// logic and dependencies are encapsulated within the plugin, promoting
// better modularity and maintainability.
return Env.getCurrentEnv().getAuth().getLdapManager().doesUserExist(qualifiedUser);
long start = System.currentTimeMillis();
boolean result = Env.getCurrentEnv().getAuth().getLdapManager().doesUserExist(qualifiedUser);
long elapsed = System.currentTimeMillis() - start;
if (LOG.isDebugEnabled()) {
LOG.debug("LdapAuthenticator.canDeal: user={}, result={}, elapsed={}ms",
qualifiedUser, result, elapsed);
}
return result;
}

/**
Expand All @@ -99,12 +110,14 @@ private AuthenticateResponse internalAuthenticate(String password, String qualif
// check user password by ldap server.
try {
if (!Env.getCurrentEnv().getAuth().getLdapManager().checkUserPasswd(qualifiedUser, password)) {
LOG.info("user:{} use check LDAP password failed.", userName);
if (LOG.isDebugEnabled()) {
LOG.debug("internalAuthenticate: user={}, success=false", userName);
}
ErrorReport.report(ErrorCode.ERR_ACCESS_DENIED_ERROR, qualifiedUser, remoteIp, usePasswd);
return AuthenticateResponse.failedResponse;
}
} catch (Exception e) {
LOG.error("Check ldap password error.", e);
LOG.warn("internalAuthenticate failed: user={}", userName, e);
return AuthenticateResponse.failedResponse;
}

Expand All @@ -115,12 +128,17 @@ private AuthenticateResponse internalAuthenticate(String password, String qualif
AuthenticateResponse response = new AuthenticateResponse(true);
if (userIdentities.isEmpty()) {
response.setUserIdentity(tempUserIdentity);
response.setTemp(true);
if (LOG.isDebugEnabled()) {
LOG.debug("User:{} does not exists in doris, login as temporary users.", userName);
LOG.debug("internalAuthenticate: user={}, tempUser=true, identity={}",
userName, tempUserIdentity);
}
response.setTemp(true);
} else {
response.setUserIdentity(userIdentities.get(0));
if (LOG.isDebugEnabled()) {
LOG.debug("internalAuthenticate: user={}, tempUser=false, identity={}",
userName, userIdentities.get(0));
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("ldap authentication success: identity:{}", response.getUserIdentity());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import lombok.Data;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.ldap.AuthenticationException;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.AbstractContextMapper;
Expand All @@ -39,7 +40,9 @@
import org.springframework.ldap.support.LdapEncoder;
import org.springframework.ldap.transaction.compensating.manager.TransactionAwareContextSourceProxy;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

// This class is used to connect to the LDAP service.
public class LdapClient {
Expand All @@ -60,7 +63,15 @@ private static class ClientInfo {
public ClientInfo(String ldapPassword) {
this.ldapPassword = ldapPassword;
setLdapTemplateNoPool(ldapPassword);
setLdapTemplatePool(ldapPassword);
if (LdapConfig.ldap_search_use_pool) {
setLdapTemplatePool(ldapPassword);
}
}

// Returns the LdapTemplate for search operations:
// pooled if ldap_search_use_pool=true, non-pooled otherwise.
public LdapTemplate getSearchTemplate() {
return ldapTemplatePool != null ? ldapTemplatePool : ldapTemplateNoPool;
}

private void setLdapTemplateNoPool(String ldapPassword) {
Expand All @@ -71,6 +82,7 @@ private void setLdapTemplateNoPool(String ldapPassword) {
contextSource.setUrl(url);
contextSource.setUserDn(LdapConfig.ldap_admin_name);
contextSource.setPassword(ldapPassword);
setLdapTimeoutProperties(contextSource);
contextSource.afterPropertiesSet();
ldapTemplateNoPool = new LdapTemplate(contextSource);
ldapTemplateNoPool.setIgnorePartialResultException(true);
Expand All @@ -84,7 +96,7 @@ private void setLdapTemplatePool(String ldapPassword) {
contextSource.setUrl(url);
contextSource.setUserDn(LdapConfig.ldap_admin_name);
contextSource.setPassword(ldapPassword);
contextSource.setPooled(true);
setLdapTimeoutProperties(contextSource);
contextSource.afterPropertiesSet();

PoolingContextSource poolingContextSource = new PoolingContextSource();
Expand All @@ -109,6 +121,21 @@ public boolean checkUpdate(String ldapPassword) {
return this.ldapPassword == null || !this.ldapPassword.equals(ldapPassword);
}

private void setLdapTimeoutProperties(LdapContextSource contextSource) {
Map<String, Object> baseEnv = new HashMap<>();
if (LdapConfig.ldap_read_timeout_ms > 0) {
baseEnv.put("com.sun.jndi.ldap.read.timeout",
String.valueOf(LdapConfig.ldap_read_timeout_ms));
}
if (LdapConfig.ldap_connect_timeout_ms > 0) {
baseEnv.put("com.sun.jndi.ldap.connect.timeout",
String.valueOf(LdapConfig.ldap_connect_timeout_ms));
}
if (!baseEnv.isEmpty()) {
contextSource.setBaseEnvironmentProperties(baseEnv);
}
}

}

private void init() {
Expand Down Expand Up @@ -143,19 +170,37 @@ boolean doesUserExist(String userName) {

boolean checkPassword(String userName, String password) {
init();
long start = System.currentTimeMillis();
try {
clientInfo.getLdapTemplateNoPool().authenticate(org.springframework.ldap.query.LdapQueryBuilder.query()
.base(LdapConfig.ldap_user_basedn)
.filter(applyLoginFilter(LdapConfig.ldap_user_filter, userName)), password);
long elapsed = System.currentTimeMillis() - start;
if (LOG.isDebugEnabled()) {
LOG.debug("LdapClient.checkPassword: user={}, success=true, elapsed={}ms",
userName, elapsed);
}
return true;
} catch (AuthenticationException e) {
long elapsed = System.currentTimeMillis() - start;
if (LOG.isDebugEnabled()) {
LOG.debug("LdapClient.checkPassword: user={}, success=false, elapsed={}ms, "
+ "errorClass={}, errorMessage={}",
userName, elapsed, e.getClass().getSimpleName(), e.getMessage());
}
return false;
} catch (Exception e) {
LOG.info("ldap client checkPassword failed, userName: {}", userName, e);
long elapsed = System.currentTimeMillis() - start;
LOG.warn("LdapClient.checkPassword failed: user={}, elapsed={}ms, "
+ "errorClass={}, errorMessage={}",
userName, elapsed, e.getClass().getSimpleName(), e.getMessage(), e);
return false;
}
}

// Search group DNs by 'member' attribution.
List<String> getGroups(String userName) {
long start = System.currentTimeMillis();
List<String> groups = Lists.newArrayList();
if (LdapConfig.ldap_group_basedn.isEmpty()) {
return groups;
Expand Down Expand Up @@ -190,6 +235,11 @@ List<String> getGroups(String userName) {
groups.add(strings[1]);
}
}
long elapsed = System.currentTimeMillis() - start;
if (LOG.isDebugEnabled()) {
LOG.debug("LdapClient.getGroups: user={}, groups={}, elapsed={}ms",
userName, groups.size(), elapsed);
}
return groups;
}

Expand All @@ -211,18 +261,27 @@ private String getUserDn(String userName) {

public List<String> getDn(LdapQuery query) {
init();
long start = System.currentTimeMillis();
try {
return clientInfo.getLdapTemplatePool().search(query,
List<String> result = clientInfo.getSearchTemplate().search(query,
new AbstractContextMapper<String>() {
protected String doMapFromContext(DirContextOperations ctx) {
return ctx.getNameInNamespace();
}
});
long elapsed = System.currentTimeMillis() - start;
if (LOG.isDebugEnabled()) {
LOG.debug("LdapClient.getDn: base={}, elapsed={}ms, results={}",
query.base(), elapsed, result == null ? 0 : result.size());
}
return result;
} catch (Exception e) {
long elapsed = System.currentTimeMillis() - start;
String msg
= "Failed to retrieve the user's Distinguished Name (DN),"
+ "This may be due to incorrect LDAP configuration or an unset/incorrect LDAP admin password.";
LOG.error(msg, e);
LOG.warn("LdapClient.getDn failed: base={}, elapsed={}ms, error={}",
query.base(), elapsed, e.getMessage(), e);
ErrorReport.report(ErrorCode.ERROR_LDAP_CONFIGURATION_ERR);
throw new RuntimeException(msg);
}
Expand Down
Loading
Loading