Repository: incubator-eagle Updated Branches: refs/heads/master 18f74d448 -> ce8e1c508
[MINOR] add settings for case: ldap authentication over ssl To make it fit for ldap authentication over ssl protocol, add: 1. one config-attribute to indicate certificate absolute path. 2. checking code for verifying certificate's existence. 3. add unit test cases to cover the logic branches. Author: anyway1021 <m...@apache.org> Closes #661 from anyway1021/ldap-auth-improve. Project: http://git-wip-us.apache.org/repos/asf/incubator-eagle/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-eagle/commit/ce8e1c50 Tree: http://git-wip-us.apache.org/repos/asf/incubator-eagle/tree/ce8e1c50 Diff: http://git-wip-us.apache.org/repos/asf/incubator-eagle/diff/ce8e1c50 Branch: refs/heads/master Commit: ce8e1c5080c0f9a8fae9010cbd29dc3c138f13d8 Parents: 18f74d4 Author: anyway1021 <m...@apache.org> Authored: Thu Nov 17 13:18:27 2016 +0800 Committer: Zhao, Qingwen <qingwz...@apache.org> Committed: Thu Nov 17 13:18:27 2016 +0800 ---------------------------------------------------------------------- eagle-server-assembly/src/main/conf/server.yml | 57 +++++++++- .../authenticator/LdapBasicAuthenticator.java | 20 ++++ .../authentication/config/LdapSettings.java | 12 ++ .../src/main/resources/configuration.yml | 4 + .../LdapBasicAuthenticatorTest.java | 110 ++++++++++++++++--- .../src/test/resources/configuration.yml | 4 + 6 files changed, 192 insertions(+), 15 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ce8e1c50/eagle-server-assembly/src/main/conf/server.yml ---------------------------------------------------------------------- diff --git a/eagle-server-assembly/src/main/conf/server.yml b/eagle-server-assembly/src/main/conf/server.yml index 5ea8b31..501d941 100644 --- a/eagle-server-assembly/src/main/conf/server.yml +++ b/eagle-server-assembly/src/main/conf/server.yml @@ -42,4 +42,59 @@ logging: archive: true archivedLogFilenamePattern: log/eagle-server-%d.log archivedFileCount: 5 - timeZone: UTC \ No newline at end of file + timeZone: UTC + +# --------------------------------------------- +# Eagle Authentication Configuration +# --------------------------------------------- +auth: + # indicating if authentication is enabled, true for enabled, false for disabled + enabled: false + + # indicating authentication mode, "simple" or "ldap" + mode: simple + + # indicating whether to use cache: cache is usually used for authentications that may + # not handle high throughput (an RDBMS or LDAP server, for example) + caching: false + + # indicating the cache policy, containing maximumSize and expireAfterWrite, e.g. maximumSize=10000, expireAfterWrite=10m + cachePolicy: maximumSize=10000, expireAfterWrite=1m + + # indicating whether authorization is needed + authorization: false + + # indicating whether @Auth annotation on parameters is needed + annotated: true + + # for basic authentication, effective only when auth.mode=simple + simple: + # username for basic authentication, effective only when auth.mode=simple + username: admin + # password for basic authentication, effective only when auth.mode=simple + password: secret + + # for ldap authentication, effective only when auth.mode=ldap + ldap: + # url providing ldap service. By convention, the port for typical ldap service is 389, and ldap service over ssl + # uses port 636 with protocol "ldaps", which requires certificates pre-installed. + providerUrl: ldap://server.address.or.domain:port + + # template string containing ${USERNAME} placeholder. This is designed for some orgs who don't use plain usernames + # to authenticate, e.g. they may use its members' email address as the username: ${USERNAME}@some.org. When username + # is supposed to be recognized originally, just configure this parameter as ${USERNAME} + principalTemplate: ${USERNAME}@maybe.email.suffix + + # string of strategy used by ldap service. "simple" is usually supported in most circumstances, we can use it by + # default or leave it a blank string. + strategy: simple + + # the absolute path of ssl certificate file. This attribute is required conditional only when the auth -> mode is set + # as "ldap" and providerUrl starting with "ldaps://". + certificateAbsolutePath: /certificate/absolute/path + + # timeout expression for connecting to ldap service endpoint + connectingTimeout: 500ms + + # timeout expression for reading from ldap service + readingTimeout: 500ms http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ce8e1c50/eagle-server/src/main/java/org/apache/eagle/server/authentication/authenticator/LdapBasicAuthenticator.java ---------------------------------------------------------------------- diff --git a/eagle-server/src/main/java/org/apache/eagle/server/authentication/authenticator/LdapBasicAuthenticator.java b/eagle-server/src/main/java/org/apache/eagle/server/authentication/authenticator/LdapBasicAuthenticator.java index 14652c3..c67dea8 100644 --- a/eagle-server/src/main/java/org/apache/eagle/server/authentication/authenticator/LdapBasicAuthenticator.java +++ b/eagle-server/src/main/java/org/apache/eagle/server/authentication/authenticator/LdapBasicAuthenticator.java @@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory; import javax.naming.Context; import javax.naming.directory.InitialDirContext; +import java.io.File; import java.util.Hashtable; public class LdapBasicAuthenticator implements Authenticator<BasicCredentials, User> { @@ -34,6 +35,10 @@ public class LdapBasicAuthenticator implements Authenticator<BasicCredentials, U private static final String LDAP_LDAP_CTX_FACTORY_NAME = "com.sun.jndi.ldap.LdapCtxFactory"; private static final String LDAP_CONNECT_TIMEOUT_KEY = "com.sun.jndi.ldap.connect.timeout"; private static final String LDAP_READ_TIMEOUT_KEY = "com.sun.jndi.ldap.read.timeout"; + private static final String SYS_PROP_SSL_KEY_STORE = "javax.net.ssl.keyStore"; + private static final String SYS_PROP_SSL_TRUST_STORE = "javax.net.ssl.trustStore"; + private static final String LDAPS_URL_PREFIX = "ldaps://"; + private static final String SSL_PROTOCOL_VALUE = "ssl"; private LdapSettings settings = null; public LdapBasicAuthenticator(LdapSettings settings) { @@ -70,6 +75,21 @@ public class LdapBasicAuthenticator implements Authenticator<BasicCredentials, U env.put(Context.SECURITY_AUTHENTICATION, strategy); } + if (providerUrl.toLowerCase().startsWith(LDAPS_URL_PREFIX)) { // using ldap over ssl to authenticate + env.put(Context.SECURITY_PROTOCOL, SSL_PROTOCOL_VALUE); + + String certificateAbsolutePath = settings.getCertificateAbsolutePath(); + if (certificateAbsolutePath == null || "".equals(certificateAbsolutePath)) { + throw new RuntimeException("The attribute 'certificateAbsolutePath' must be set when using ldap over ssl to authenticate."); + } + if (!new File(certificateAbsolutePath).exists()) { + throw new RuntimeException(String.format("The file specified not existing: %s", certificateAbsolutePath)); + } + + System.setProperty(SYS_PROP_SSL_KEY_STORE, certificateAbsolutePath); + System.setProperty(SYS_PROP_SSL_TRUST_STORE, certificateAbsolutePath); + } + env.put(Context.SECURITY_PRINCIPAL, comprisePrincipal(sanitizedUsername)); env.put(Context.SECURITY_CREDENTIALS, password); return env; http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ce8e1c50/eagle-server/src/main/java/org/apache/eagle/server/authentication/config/LdapSettings.java ---------------------------------------------------------------------- diff --git a/eagle-server/src/main/java/org/apache/eagle/server/authentication/config/LdapSettings.java b/eagle-server/src/main/java/org/apache/eagle/server/authentication/config/LdapSettings.java index 6bb3303..9297e7e 100644 --- a/eagle-server/src/main/java/org/apache/eagle/server/authentication/config/LdapSettings.java +++ b/eagle-server/src/main/java/org/apache/eagle/server/authentication/config/LdapSettings.java @@ -24,6 +24,7 @@ public class LdapSettings { private String providerUrl = ""; private String strategy = ""; private String principalTemplate = ""; + private String certificateAbsolutePath = ""; private Duration connectingTimeout = Duration.parse("500ms"); private Duration readingTimeout = Duration.parse("500ms"); @@ -81,4 +82,15 @@ public class LdapSettings { this.readingTimeout = readingTimeout; return this; } + + @JsonProperty + public String getCertificateAbsolutePath() { + return certificateAbsolutePath; + } + + @JsonProperty + public LdapSettings setCertificateAbsolutePath(String certificateAbsolutePath) { + this.certificateAbsolutePath = certificateAbsolutePath; + return this; + } } http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ce8e1c50/eagle-server/src/main/resources/configuration.yml ---------------------------------------------------------------------- diff --git a/eagle-server/src/main/resources/configuration.yml b/eagle-server/src/main/resources/configuration.yml index 8d388b0..eabb8a1 100644 --- a/eagle-server/src/main/resources/configuration.yml +++ b/eagle-server/src/main/resources/configuration.yml @@ -66,6 +66,10 @@ auth: # default or leave it a blank string. strategy: simple + # the absolute path of ssl certificate file. This attribute is required conditional only when the auth -> mode is set + # as "ldap" and providerUrl starting with "ldaps://". + certificateAbsolutePath: /certificate/absolute/path + # timeout expression for connecting to ldap service endpoint connectingTimeout: 500ms http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ce8e1c50/eagle-server/src/test/java/org/apache/eagle/server/authentication/authenticator/LdapBasicAuthenticatorTest.java ---------------------------------------------------------------------- diff --git a/eagle-server/src/test/java/org/apache/eagle/server/authentication/authenticator/LdapBasicAuthenticatorTest.java b/eagle-server/src/test/java/org/apache/eagle/server/authentication/authenticator/LdapBasicAuthenticatorTest.java index 492521f..8700a75 100644 --- a/eagle-server/src/test/java/org/apache/eagle/server/authentication/authenticator/LdapBasicAuthenticatorTest.java +++ b/eagle-server/src/test/java/org/apache/eagle/server/authentication/authenticator/LdapBasicAuthenticatorTest.java @@ -30,29 +30,38 @@ public class LdapBasicAuthenticatorTest { private static final String USERNAME_SUFFIX = "@some.emailbox.suffix"; private static final String USERNAME_TEMPLATE = "${USERNAME}" + USERNAME_SUFFIX; private static final String LDAP_SERVICE_PROVIDER_URL = "ldap://some.address:port"; + private static final String LDAP_SERVICE_PROVIDER_SSL_URL = "ldaps://some.address:port"; private static final String STRATEGY_SIMPLE = "customized"; private static final String CONNECTING_TIMEOUT_VALUE = "500ms"; private static final String READING_TIMEOUT_VALUE = "800ms"; private static final String LDAP_CTX_FACTORY_NAME = "com.sun.jndi.ldap.LdapCtxFactory"; private static final String LDAP_CONNECT_TIMEOUT_KEY = "com.sun.jndi.ldap.connect.timeout"; private static final String LDAP_READ_TIMEOUT_KEY = "com.sun.jndi.ldap.read.timeout"; - private static final LdapBasicAuthenticator AUTHENTICATOR_FOR_UTIL_METHODS = new LdapBasicAuthenticator( - new LdapSettings() - .setProviderUrl(LDAP_SERVICE_PROVIDER_URL) - .setPrincipalTemplate(USERNAME_TEMPLATE) + private static final String SYS_PROP_SSL_KEY_STORE = "javax.net.ssl.keyStore"; + private static final String SYS_PROP_SSL_TRUST_STORE = "javax.net.ssl.trustStore"; + private static final String EXISTING_MOCK_FILE_PATH = String.format("%s/pom.xml", System.getProperty("user.dir")); + private static final LdapBasicAuthenticator AUTHENTICATOR_FOR_UTIL_METHODS_WITHOUT_SSL = new LdapBasicAuthenticator( + getNonSSLPreSettings().setPrincipalTemplate(USERNAME_TEMPLATE) .setStrategy(STRATEGY_SIMPLE) .setConnectingTimeout(Duration.parse(CONNECTING_TIMEOUT_VALUE)) .setReadingTimeout(Duration.parse(READING_TIMEOUT_VALUE)) ); + private static final LdapBasicAuthenticator AUTHENTICATOR_FOR_UTIL_METHODS_WITH_SSL = new LdapBasicAuthenticator( + getSSLPreSettings().setPrincipalTemplate(USERNAME_TEMPLATE) + .setStrategy(STRATEGY_SIMPLE) + .setCertificateAbsolutePath(EXISTING_MOCK_FILE_PATH) + .setConnectingTimeout(Duration.parse(CONNECTING_TIMEOUT_VALUE)) + .setReadingTimeout(Duration.parse(READING_TIMEOUT_VALUE)) + ); @Test public void testSanitizeUsername() { String correctUsername = "userNAME_123.45Z"; - String sanitized = AUTHENTICATOR_FOR_UTIL_METHODS.sanitizeUsername(correctUsername); + String sanitized = AUTHENTICATOR_FOR_UTIL_METHODS_WITHOUT_SSL.sanitizeUsername(correctUsername); Assert.assertEquals(correctUsername, sanitized); String incorrectUsername = "userNAME-~!@#$%^&777*()_+-=`[]\\{}|;':\",./<>?ä½ "; - sanitized = AUTHENTICATOR_FOR_UTIL_METHODS.sanitizeUsername(incorrectUsername); + sanitized = AUTHENTICATOR_FOR_UTIL_METHODS_WITHOUT_SSL.sanitizeUsername(incorrectUsername); System.out.println(sanitized); Assert.assertEquals("userNAME777_.", sanitized); } @@ -60,27 +69,100 @@ public class LdapBasicAuthenticatorTest { @Test public void testComprisePrincipal() { String username = "my.userNAME_123"; - String principal = AUTHENTICATOR_FOR_UTIL_METHODS.comprisePrincipal(username); + String principal = AUTHENTICATOR_FOR_UTIL_METHODS_WITHOUT_SSL.comprisePrincipal(username); Assert.assertEquals(username+USERNAME_SUFFIX, principal); } @Test - public void testGetContextEnvironment() { + public void testGetContextEnvironmentNormal() { String username = "username"; - String secret_phrase = "secret-phrase"; - Hashtable<String, String> env = AUTHENTICATOR_FOR_UTIL_METHODS.getContextEnvironment(username, secret_phrase); + String secretPhrase = "secret-phrase"; + Hashtable<String, String> env = AUTHENTICATOR_FOR_UTIL_METHODS_WITHOUT_SSL.getContextEnvironment(username, secretPhrase); Assert.assertEquals("unexpected ldap context factory name", LDAP_CTX_FACTORY_NAME, env.get(Context.INITIAL_CONTEXT_FACTORY)); - Assert.assertEquals("unexpected ldap serivce provider url", LDAP_SERVICE_PROVIDER_URL, env.get(Context.PROVIDER_URL)); + Assert.assertEquals("unexpected ldap service provider url", LDAP_SERVICE_PROVIDER_URL, env.get(Context.PROVIDER_URL)); Assert.assertEquals("unexpected connecting timeout value", String.valueOf(Duration.parse(CONNECTING_TIMEOUT_VALUE).toMilliseconds()), env.get(LDAP_CONNECT_TIMEOUT_KEY)); Assert.assertEquals("unexpected reading timeout value", String.valueOf(Duration.parse(READING_TIMEOUT_VALUE).toMilliseconds()), env.get(LDAP_READ_TIMEOUT_KEY)); Assert.assertEquals("unexpected username", username+USERNAME_SUFFIX, env.get(Context.SECURITY_PRINCIPAL)); - Assert.assertEquals("unexpected secret credentials", secret_phrase, env.get(Context.SECURITY_CREDENTIALS)); + Assert.assertEquals("unexpected secret credentials", secretPhrase, env.get(Context.SECURITY_CREDENTIALS)); Assert.assertEquals("unexpected strategy", STRATEGY_SIMPLE, env.get(Context.SECURITY_AUTHENTICATION)); + } + + @Test + public void testGetContextEnvironmentBlankStrategy() { + String username = "username"; + String secretPhrase = "secret-phrase"; // check strategy while it's configured as "" - LdapBasicAuthenticator blankStrategyAuthenticator = new LdapBasicAuthenticator(new LdapSettings().setStrategy("")); - String strategyMaybeBlank = blankStrategyAuthenticator.getContextEnvironment(username, secret_phrase).get(Context.SECURITY_AUTHENTICATION); + LdapBasicAuthenticator blankStrategyAuthenticator = new LdapBasicAuthenticator(getNonSSLPreSettings().setStrategy("")); + String strategyMaybeBlank = blankStrategyAuthenticator.getContextEnvironment(username, secretPhrase).get(Context.SECURITY_AUTHENTICATION); Assert.assertNull("unexpected strategy", strategyMaybeBlank); } + + @Test + public void testGetContextEnvironmentNormalWithSSL() { + String username = "username"; + String secretPhrase = "secret-phrase"; + Hashtable<String, String> env = AUTHENTICATOR_FOR_UTIL_METHODS_WITH_SSL.getContextEnvironment(username, secretPhrase); + + Assert.assertEquals("unexpected ldap context factory name", LDAP_CTX_FACTORY_NAME, env.get(Context.INITIAL_CONTEXT_FACTORY)); + Assert.assertEquals("unexpected ldap service provider url", LDAP_SERVICE_PROVIDER_SSL_URL, env.get(Context.PROVIDER_URL)); + Assert.assertEquals("unexpected connecting timeout value", String.valueOf(Duration.parse(CONNECTING_TIMEOUT_VALUE).toMilliseconds()), env.get(LDAP_CONNECT_TIMEOUT_KEY)); + Assert.assertEquals("unexpected reading timeout value", String.valueOf(Duration.parse(READING_TIMEOUT_VALUE).toMilliseconds()), env.get(LDAP_READ_TIMEOUT_KEY)); + Assert.assertEquals("unexpected username", username+USERNAME_SUFFIX, env.get(Context.SECURITY_PRINCIPAL)); + Assert.assertEquals("unexpected secret credentials", secretPhrase, env.get(Context.SECURITY_CREDENTIALS)); + Assert.assertEquals("unexpected strategy", STRATEGY_SIMPLE, env.get(Context.SECURITY_AUTHENTICATION)); + Assert.assertEquals("unexpected key-store path", EXISTING_MOCK_FILE_PATH, System.getProperty(SYS_PROP_SSL_KEY_STORE)); + Assert.assertEquals("unexpected trust-store path", EXISTING_MOCK_FILE_PATH, System.getProperty(SYS_PROP_SSL_TRUST_STORE)); + } + + @Test + public void testGetContextEnvironmentMeaninglessCAPathSSL() { + String username = "username"; + String secretPhrase = "secret-phrase"; + + // check null certificateAbsolutePath + try { + LdapBasicAuthenticator blankStrategyAuthenticator = new LdapBasicAuthenticator(getSSLPreSettings().setCertificateAbsolutePath(null)); + blankStrategyAuthenticator.getContextEnvironment(username, secretPhrase).get(Context.SECURITY_AUTHENTICATION); + } + catch (Exception e) { + Assert.assertEquals("unexpected exception thrown", RuntimeException.class, e.getClass()); + Assert.assertEquals("unexpected exception message", "The attribute 'certificateAbsolutePath' must be set when using ldap over ssl to authenticate.", e.getMessage()); + } + + // check "" certificateAbsolutePath + try { + LdapBasicAuthenticator blankStrategyAuthenticator = new LdapBasicAuthenticator(getSSLPreSettings().setCertificateAbsolutePath("")); + blankStrategyAuthenticator.getContextEnvironment(username, secretPhrase).get(Context.SECURITY_AUTHENTICATION); + } + catch (Exception e) { + Assert.assertEquals("unexpected exception thrown", RuntimeException.class, e.getClass()); + Assert.assertEquals("unexpected exception message", "The attribute 'certificateAbsolutePath' must be set when using ldap over ssl to authenticate.", e.getMessage()); + } + } + + @Test + public void testGetContextEnvironmentUnexistingCA_SSL() { + String username = "username"; + String secretPhrase = "secret-phrase"; + String wrongCAPath = String.format("%s/this/cannot/be/existing", System.getProperty("user.dir")); + try { + // check with not existing path indicated by certificateAbsolutePath + LdapBasicAuthenticator blankStrategyAuthenticator = new LdapBasicAuthenticator(getSSLPreSettings().setCertificateAbsolutePath(wrongCAPath)); + blankStrategyAuthenticator.getContextEnvironment(username, secretPhrase).get(Context.SECURITY_AUTHENTICATION); + } + catch (Exception e) { + Assert.assertEquals("unexpected exception thrown", RuntimeException.class, e.getClass()); + Assert.assertEquals("unexpected exception message", String.format("The file specified not existing: %s", wrongCAPath), e.getMessage()); + } + } + + private static LdapSettings getNonSSLPreSettings() { + return new LdapSettings().setProviderUrl(LDAP_SERVICE_PROVIDER_URL); + } + + private static LdapSettings getSSLPreSettings() { + return new LdapSettings().setProviderUrl(LDAP_SERVICE_PROVIDER_SSL_URL); + } } http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ce8e1c50/eagle-server/src/test/resources/configuration.yml ---------------------------------------------------------------------- diff --git a/eagle-server/src/test/resources/configuration.yml b/eagle-server/src/test/resources/configuration.yml index 8d388b0..eabb8a1 100644 --- a/eagle-server/src/test/resources/configuration.yml +++ b/eagle-server/src/test/resources/configuration.yml @@ -66,6 +66,10 @@ auth: # default or leave it a blank string. strategy: simple + # the absolute path of ssl certificate file. This attribute is required conditional only when the auth -> mode is set + # as "ldap" and providerUrl starting with "ldaps://". + certificateAbsolutePath: /certificate/absolute/path + # timeout expression for connecting to ldap service endpoint connectingTimeout: 500ms