diff --git a/conf/ldap.conf b/conf/ldap.conf index 9388ae7ee50b1e..15a62c9134f159 100644 --- a/conf/ldap.conf +++ b/conf/ldap.conf @@ -47,6 +47,9 @@ ldap_group_basedn = ou=group,dc=domain,dc=com ## ldap_use_ssl - use secured connection to LDAP server if required (disabled by default). Note: When enabling SSL, ensure ldap_port is set appropriately (typically 636 for LDAPS instead of 389 for LDAP). # ldap_use_ssl = false +## ldap_allow_empty_pass - allow to connect to ldap with empty pass (enabled by default) +# ldap_allow_empty_pass = true + # LDAP pool configuration # https://docs.spring.io/spring-ldap/docs/2.3.3.RELEASE/reference/#pool-configuration # ldap_pool_max_active = 8 diff --git a/fe/fe-common/src/main/java/org/apache/doris/common/ErrorCode.java b/fe/fe-common/src/main/java/org/apache/doris/common/ErrorCode.java index a678c2b38f9562..0707a2ccfe7631 100644 --- a/fe/fe-common/src/main/java/org/apache/doris/common/ErrorCode.java +++ b/fe/fe-common/src/main/java/org/apache/doris/common/ErrorCode.java @@ -1233,7 +1233,10 @@ public enum ErrorCode { ERR_NO_CLUSTER_ERROR(5099, new byte[]{'4', '2', '0', '0', '0'}, "No compute group (cloud cluster) selected"), ERR_NOT_CLOUD_MODE(6000, new byte[]{'4', '2', '0', '0', '0'}, - "Command only support in cloud mode."); + "Command only support in cloud mode."), + + ERR_EMPTY_PASSWORD(6001, new byte[]{'4', '2', '0', '0', '0'}, + "Access with empty password is prohibited for user %s because of current mode"); // This is error code private final int code; diff --git a/fe/fe-common/src/main/java/org/apache/doris/common/LdapConfig.java b/fe/fe-common/src/main/java/org/apache/doris/common/LdapConfig.java index 881840696dcde8..d169d93cbd5382 100644 --- a/fe/fe-common/src/main/java/org/apache/doris/common/LdapConfig.java +++ b/fe/fe-common/src/main/java/org/apache/doris/common/LdapConfig.java @@ -175,4 +175,10 @@ public class LdapConfig extends ConfigBase { public static String getConnectionURL(String hostPortInAccessibleFormat) { return ((LdapConfig.ldap_use_ssl ? "ldaps" : "ldap") + "://" + hostPortInAccessibleFormat); } + + /** + * Flag to enable login with empty pass. + */ + @ConfigBase.ConfField + public static boolean ldap_allow_empty_pass = true; } diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticator.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticator.java index d9e27e4a9fb02f..638903490bb784 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticator.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticator.java @@ -21,6 +21,7 @@ import org.apache.doris.catalog.Env; import org.apache.doris.common.ErrorCode; import org.apache.doris.common.ErrorReport; +import org.apache.doris.common.LdapConfig; import org.apache.doris.mysql.authenticate.AuthenticateRequest; import org.apache.doris.mysql.authenticate.AuthenticateResponse; import org.apache.doris.mysql.authenticate.Authenticator; @@ -83,7 +84,7 @@ public boolean canDeal(String qualifiedUser) { /** * The LDAP authentication process is as follows: - * step1: Check the LDAP password. + * step1: Check the LDAP password (if ldap_allow_empty_pass is false login with empty pass is prohibited). * step2: Get the LDAP groups privileges as a role, saved into ConnectContext. * step3: Set current userIdentity. If the user account does not exist in Doris, login as a temporary user. * Otherwise, login to the Doris account. @@ -95,6 +96,14 @@ private AuthenticateResponse internalAuthenticate(String password, String qualif LOG.debug("user:{}", userName); } + //not allow to login in case when empty password is specified but such mode is disabled by configuration + if (Strings.isNullOrEmpty(password) && !LdapConfig.ldap_allow_empty_pass) { + LOG.info("user:{} is not allowed to login to LDAP with empty password because ldap_allow_empty_pass:{}", + userName, LdapConfig.ldap_allow_empty_pass); + ErrorReport.report(ErrorCode.ERR_EMPTY_PASSWORD, qualifiedUser + "@" + remoteIp); + return AuthenticateResponse.failedResponse; + } + // check user password by ldap server. try { if (!Env.getCurrentEnv().getAuth().getLdapManager().checkUserPasswd(qualifiedUser, password)) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java index 371d4b7c2f7276..1f04c5f8f787fb 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java @@ -38,6 +38,7 @@ import org.apache.doris.common.ErrorCode; import org.apache.doris.common.ErrorReport; import org.apache.doris.common.FeConstants; +import org.apache.doris.common.LdapConfig; import org.apache.doris.common.Pair; import org.apache.doris.common.PatternMatcherException; import org.apache.doris.common.UserException; @@ -227,6 +228,11 @@ public void checkPlainPassword(String remoteUser, String remoteHost, String remo List currentUser) throws AuthenticationException { // Check the LDAP password when the user exists in the LDAP service. if (ldapManager.doesUserExist(remoteUser)) { + //not allow to login in case when empty password is specified but such mode is disabled by configuration + if (Strings.isNullOrEmpty(remotePasswd) && !LdapConfig.ldap_allow_empty_pass) { + throw new AuthenticationException(ErrorCode.ERR_EMPTY_PASSWORD, remoteUser + "@" + remoteHost); + } + if (!ldapManager.checkUserPasswd(remoteUser, remotePasswd, remoteHost, currentUser)) { throw new AuthenticationException(ErrorCode.ERR_ACCESS_DENIED_ERROR, remoteUser + "@" + remoteHost, Strings.isNullOrEmpty(remotePasswd) ? "NO" : "YES"); diff --git a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java index 99cbcdb5fad643..5a17a3caa80f31 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java @@ -18,6 +18,7 @@ package org.apache.doris.mysql.authenticate.ldap; import org.apache.doris.analysis.UserIdentity; +import org.apache.doris.common.LdapConfig; import org.apache.doris.mysql.authenticate.AuthenticateRequest; import org.apache.doris.mysql.authenticate.AuthenticateResponse; import org.apache.doris.mysql.authenticate.password.ClearPassword; @@ -27,7 +28,9 @@ import com.google.common.collect.Lists; import mockit.Expectations; import mockit.Mocked; +import org.junit.After; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import java.io.IOException; @@ -143,4 +146,32 @@ public void testCanDeal() { public void testGetPasswordResolver() { Assert.assertTrue(ldapAuthenticator.getPasswordResolver() instanceof ClearPasswordResolver); } + + @Test + public void testEmptyPassword() throws IOException { + setCheckPassword(true); + setGetUserInDoris(true); + AuthenticateRequest request = new AuthenticateRequest(USER_NAME, new ClearPassword(""), IP); + //running test with non-specified value - ldap_allow_empty_pass should be true + AuthenticateResponse response = ldapAuthenticator.authenticate(request); + Assert.assertTrue(response.isSuccess()); + //running test with specified value - true - ldap_allow_empty_pass is explicitly set to true + LdapConfig.ldap_allow_empty_pass = true; + response = ldapAuthenticator.authenticate(request); + Assert.assertTrue(response.isSuccess()); + //running test with specified value - false - ldap_allow_empty_pass is explicitly set to false + LdapConfig.ldap_allow_empty_pass = false; + response = ldapAuthenticator.authenticate(request); + Assert.assertFalse(response.isSuccess()); + } + + @After + public void tearDown() { + LdapConfig.ldap_allow_empty_pass = true; // restoring default value for other tests + } + + @Before + public void setUp() { + LdapConfig.ldap_allow_empty_pass = true; //restoring default value for other tests + } }