You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@eagle.apache.org by qi...@apache.org on 2016/11/17 05:18:34 UTC

incubator-eagle git commit: [MINOR] add settings for case: ldap authentication over ssl

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 <mw...@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 <mw...@apache.org>
Authored: Thu Nov 17 13:18:27 2016 +0800
Committer: Zhao, Qingwen <qi...@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*()_+-=`[]\\{}|;':\",./<>?\u4f60";
-        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