You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2016/01/19 09:58:55 UTC

[3/4] mina-sshd git commit: [SSHD-608] Provide PublicKeyAuthenticator that uses LDAP

[SSHD-608] Provide PublicKeyAuthenticator that uses LDAP


Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/e6991a73
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/e6991a73
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/e6991a73

Branch: refs/heads/master
Commit: e6991a73bd7829c327f1d678b3f2e403d673a418
Parents: f374e72
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Tue Jan 19 10:56:55 2016 +0200
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Tue Jan 19 10:56:55 2016 +0200

----------------------------------------------------------------------
 sshd-ldap/pom.xml                               |  4 +-
 .../auth/pubkey/LdapPublickeyAuthenticator.java | 95 ++++++++++++++++++++
 .../sshd/server/auth/BaseAuthenticatorTest.java | 29 ++++--
 .../password/LdapPasswordAuthenticatorTest.java |  2 +
 .../pubkey/LdapPublickeyAuthenticatorTest.java  | 95 ++++++++++++++++++++
 sshd-ldap/src/test/resources/auth-users.ldif    |  8 +-
 6 files changed, 222 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e6991a73/sshd-ldap/pom.xml
----------------------------------------------------------------------
diff --git a/sshd-ldap/pom.xml b/sshd-ldap/pom.xml
index b692860..5921ccb 100644
--- a/sshd-ldap/pom.xml
+++ b/sshd-ldap/pom.xml
@@ -28,9 +28,9 @@
     </parent>
 
     <artifactId>sshd-ldap</artifactId>
-    <name>Apache Mina SSHD :: Git</name>
+    <name>Apache Mina SSHD :: LDAP</name>
     <packaging>jar</packaging>
-    <inceptionYear>2008</inceptionYear>
+    <inceptionYear>2016</inceptionYear>
 
     <properties>
         <projectRoot>${basedir}/..</projectRoot>

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e6991a73/sshd-ldap/src/main/java/org/apache/sshd/server/auth/pubkey/LdapPublickeyAuthenticator.java
----------------------------------------------------------------------
diff --git a/sshd-ldap/src/main/java/org/apache/sshd/server/auth/pubkey/LdapPublickeyAuthenticator.java b/sshd-ldap/src/main/java/org/apache/sshd/server/auth/pubkey/LdapPublickeyAuthenticator.java
new file mode 100644
index 0000000..fa3e929
--- /dev/null
+++ b/sshd-ldap/src/main/java/org/apache/sshd/server/auth/pubkey/LdapPublickeyAuthenticator.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.server.auth.pubkey;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
+import java.util.Map;
+import java.util.Objects;
+
+import javax.naming.NamingException;
+
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.net.LdapNetworkConnector;
+import org.apache.sshd.server.config.keys.AuthorizedKeyEntry;
+import org.apache.sshd.server.session.ServerSession;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class LdapPublickeyAuthenticator extends LdapNetworkConnector implements PublickeyAuthenticator {
+    public static final String DEFAULT_USERNAME_ATTR_NAME = "uid";
+    public static final String DEFAULT_AUTHENTICATION_MODE = "none";
+    public static final String DEFAULT_SEARCH_FILTER_PATTERN = DEFAULT_USERNAME_ATTR_NAME + "={0}";
+    public static final String DEFAULT_PUBKEY_ATTR_NAME = "sshPublicKey";
+
+    private String keyAttributeName = DEFAULT_PUBKEY_ATTR_NAME;
+
+    public LdapPublickeyAuthenticator() {
+        setAuthenticationMode(DEFAULT_AUTHENTICATION_MODE);
+        setSearchFilterPattern(DEFAULT_SEARCH_FILTER_PATTERN);
+        setRetrievedAttributes(DEFAULT_PUBKEY_ATTR_NAME);
+    }
+
+    /**
+     * @return The LDAP attribute name containing the public key in {@code OpenSSH} format
+     */
+    public String getKeyAttributeName() {
+        return keyAttributeName;
+    }
+
+    public void setKeyAttributeName(String keyAttributeName) {
+        this.keyAttributeName = ValidateUtils.checkNotNullAndNotEmpty(keyAttributeName, "No attribute name");
+    }
+
+    @Override
+    public boolean authenticate(String username, PublicKey key, ServerSession session) {
+        try {
+            Map<String, ?> attrs = resolveAttributes(username, null, session);
+            return authenticate(username, key, session, attrs);
+        } catch (NamingException | GeneralSecurityException | IOException | RuntimeException e) {
+            log.warn("authenticate({}@{}) failed ({}) to query: {}",
+                      username, session, e.getClass().getSimpleName(), e.getMessage());
+
+            if (log.isDebugEnabled()) {
+                log.debug("authenticate(" + username + "@" + session + ") query failure details", e);
+            }
+
+            return false;
+        }
+    }
+
+    protected boolean authenticate(String username, PublicKey expected, ServerSession session, Map<String, ?> attrs)
+            throws GeneralSecurityException, IOException {
+        String attrName = getKeyAttributeName();
+        Object keyData = ValidateUtils.checkNotNull(attrs.get(attrName), "No data for attribute=%s", attrName);
+        PublicKey actual = recoverPublicKey(username, expected, session, keyData);
+        return KeyUtils.compareKeys(expected, actual);
+    }
+
+    protected PublicKey recoverPublicKey(String username, PublicKey expected, ServerSession session, Object keyData)
+            throws GeneralSecurityException, IOException {
+        AuthorizedKeyEntry entry = AuthorizedKeyEntry.parseAuthorizedKeyEntry(Objects.toString(keyData, null));
+        return ValidateUtils.checkNotNull(entry, "No key extracted").resolvePublicKey(PublicKeyEntryResolver.FAILING);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e6991a73/sshd-ldap/src/test/java/org/apache/sshd/server/auth/BaseAuthenticatorTest.java
----------------------------------------------------------------------
diff --git a/sshd-ldap/src/test/java/org/apache/sshd/server/auth/BaseAuthenticatorTest.java b/sshd-ldap/src/test/java/org/apache/sshd/server/auth/BaseAuthenticatorTest.java
index 8c0fa47..2b60fb6 100644
--- a/sshd-ldap/src/test/java/org/apache/sshd/server/auth/BaseAuthenticatorTest.java
+++ b/sshd-ldap/src/test/java/org/apache/sshd/server/auth/BaseAuthenticatorTest.java
@@ -127,6 +127,15 @@ public abstract class BaseAuthenticatorTest extends BaseTestSupport {
             directoryService.setSystemPartition(systemPartition);
         }
 
+        // Create a new partition for the special extra attributes
+        {
+            JdbmPartition partition = new JdbmPartition();
+            partition.setId("openssh-lpk");
+            partition.setSuffix("cn=openssh-lpk,cn=schema,cn=config");
+            partition.setPartitionDir(assertHierarchyTargetFolderExists(Utils.deleteRecursive(new File(workingDirectory, partition.getId()))));
+            directoryService.addPartition(partition);
+        }
+
         // Create a new partition for the users
         {
             JdbmPartition partition = new JdbmPartition();
@@ -170,12 +179,9 @@ public abstract class BaseAuthenticatorTest extends BaseTestSupport {
             int id = 1;
             for (LdifEntry entry : reader) {
                 if (log.isDebugEnabled()) {
-                    log.debug("Add LDIF entry={}", entry);
+                    log.debug("Process LDIF entry={}", entry);
                 }
 
-                ChangeType changeType = entry.getChangeType();
-                assertEquals("Mismatched change type in users ldif entry=" + entry, ChangeType.Add, changeType);
-
                 Entry data = entry.getEntry();
                 EntryAttribute userAttr = data.get("uid");
                 EntryAttribute passAttr = data.get(credentialName);
@@ -184,10 +190,19 @@ public abstract class BaseAuthenticatorTest extends BaseTestSupport {
                     ValidateUtils.checkTrue(usersMap.put(username, passAttr.getString()) == null, "Multiple entries for user=%s", username);
                 }
 
-                InternalAddRequest addRequest = new AddRequestImpl(id++);
-                addRequest.setEntry(data);
+                ChangeType changeType = entry.getChangeType();
                 try {
-                    session.add(addRequest);
+                    switch (changeType) {
+                        case Add: {
+                            InternalAddRequest addRequest = new AddRequestImpl(id++);
+                            addRequest.setEntry(data);
+                            session.add(addRequest);
+                            break;
+                        }
+
+                        default:
+                            throw new UnsupportedOperationException("Unsupported change type (" + changeType + ") for entry=" + entry);
+                    }
                 } catch (Exception e) {
                     log.error("Failed (" + e.getClass().getSimpleName() + ") to add entry=" + entry + ": " + e.getMessage(), e);
                     throw e;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e6991a73/sshd-ldap/src/test/java/org/apache/sshd/server/auth/password/LdapPasswordAuthenticatorTest.java
----------------------------------------------------------------------
diff --git a/sshd-ldap/src/test/java/org/apache/sshd/server/auth/password/LdapPasswordAuthenticatorTest.java b/sshd-ldap/src/test/java/org/apache/sshd/server/auth/password/LdapPasswordAuthenticatorTest.java
index 2a88342..aa862ca 100644
--- a/sshd-ldap/src/test/java/org/apache/sshd/server/auth/password/LdapPasswordAuthenticatorTest.java
+++ b/sshd-ldap/src/test/java/org/apache/sshd/server/auth/password/LdapPasswordAuthenticatorTest.java
@@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.directory.server.core.DirectoryService;
 import org.apache.directory.server.ldap.LdapServer;
+import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.Pair;
 import org.apache.sshd.server.auth.BaseAuthenticatorTest;
 import org.apache.sshd.server.session.ServerSession;
@@ -50,6 +51,7 @@ public class LdapPasswordAuthenticatorTest extends BaseAuthenticatorTest {
     public static void startApacheDs() throws Exception {
         ldapContextHolder.set(startApacheDs(LdapPasswordAuthenticatorTest.class));
         usersMap = populateUsers(ldapContextHolder.get().getSecond(), LdapPasswordAuthenticatorTest.class, LdapPasswordAuthenticator.DEFAULT_PASSWORD_ATTR_NAME);
+        assertFalse("No users retrieved", GenericUtils.isEmpty(usersMap));
     }
 
     @AfterClass

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e6991a73/sshd-ldap/src/test/java/org/apache/sshd/server/auth/pubkey/LdapPublickeyAuthenticatorTest.java
----------------------------------------------------------------------
diff --git a/sshd-ldap/src/test/java/org/apache/sshd/server/auth/pubkey/LdapPublickeyAuthenticatorTest.java b/sshd-ldap/src/test/java/org/apache/sshd/server/auth/pubkey/LdapPublickeyAuthenticatorTest.java
new file mode 100644
index 0000000..c697a56
--- /dev/null
+++ b/sshd-ldap/src/test/java/org/apache/sshd/server/auth/pubkey/LdapPublickeyAuthenticatorTest.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.server.auth.pubkey;
+
+import java.security.PublicKey;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.directory.server.core.DirectoryService;
+import org.apache.directory.server.ldap.LdapServer;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.Pair;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.server.auth.BaseAuthenticatorTest;
+import org.apache.sshd.server.config.keys.AuthorizedKeyEntry;
+import org.apache.sshd.server.session.ServerSession;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.mockito.Mockito;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LdapPublickeyAuthenticatorTest extends BaseAuthenticatorTest {
+    private static final AtomicReference<Pair<LdapServer, DirectoryService>> ldapContextHolder = new AtomicReference<>();
+    private static final Map<String, PublicKey> keysMap = new HashMap<>();
+    // we use this instead of the default since the default requires some extra LDIF manipulation which we don't need
+    private static final String TEST_ATTR_NAME = "description";
+
+    public LdapPublickeyAuthenticatorTest() {
+        super();
+    }
+
+    @BeforeClass
+    public static void startApacheDs() throws Exception {
+        ldapContextHolder.set(startApacheDs(LdapPublickeyAuthenticatorTest.class));
+        Map<String, String> credentials =
+                populateUsers(ldapContextHolder.get().getSecond(), LdapPublickeyAuthenticatorTest.class, TEST_ATTR_NAME);
+        assertFalse("No keys retrieved", GenericUtils.isEmpty(credentials));
+
+        for (Map.Entry<String, String> ce : credentials.entrySet()) {
+            String username = ce.getKey();
+            AuthorizedKeyEntry entry = AuthorizedKeyEntry.parseAuthorizedKeyEntry(ce.getValue());
+            PublicKey key = ValidateUtils.checkNotNull(entry, "No key extracted").resolvePublicKey(PublicKeyEntryResolver.FAILING);
+            keysMap.put(username, key);
+        }
+    }
+
+    @AfterClass
+    public static void stopApacheDs() throws Exception {
+        stopApacheDs(ldapContextHolder.getAndSet(null));
+    }
+
+    @Test
+    public void testPublicKeyComparison() throws Exception {
+        LdapPublickeyAuthenticator auth = new LdapPublickeyAuthenticator();
+        auth.setBaseDN(BASE_DN_TEST);
+        auth.setPort(getPort(ldapContextHolder.get()));
+        auth.setKeyAttributeName(TEST_ATTR_NAME);
+        auth.setRetrievedAttributes(TEST_ATTR_NAME);
+
+        ServerSession session = Mockito.mock(ServerSession.class);
+        for (Map.Entry<String, PublicKey> ke : keysMap.entrySet()) {
+            String username = ke.getKey();
+            PublicKey key = ke.getValue();
+            outputDebugMessage("Authenticate: user=%s, key-type=%s, fingerprint=%s",
+                               username, KeyUtils.getKeyType(key), KeyUtils.getFingerPrint(key));
+            assertTrue("Failed to authenticate user=" + username, auth.authenticate(username, key, session));
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e6991a73/sshd-ldap/src/test/resources/auth-users.ldif
----------------------------------------------------------------------
diff --git a/sshd-ldap/src/test/resources/auth-users.ldif b/sshd-ldap/src/test/resources/auth-users.ldif
index 4792099..0fc3b18 100644
--- a/sshd-ldap/src/test/resources/auth-users.ldif
+++ b/sshd-ldap/src/test/resources/auth-users.ldif
@@ -1,31 +1,35 @@
 version: 1
 
+## Add People group definition
 dn: ou=People,dc=sshd,dc=apache,dc=org
 ou: People
 objectClass: top
 objectClass: organizationalUnit
 description: Parent object of all users accounts
 
+## Add users
 dn: cn=Guillaume Nodet,ou=People,dc=sshd,dc=apache,dc=org
 objectClass: top
 objectClass: person
 objectClass: organizationalPerson
-objectclass: inetOrgPerson
+objectClass: inetOrgPerson
 cn: Guillaume Nodet
 givenName: Guillaume
 sn: Nodet
 uid: gnodet
 userpassword: gnodet
 mail: gnodet@apache.org
+description: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDEAGEwosEIS7koPfYFf2qXAqV+gGiuf3wizjYCz8UdYtmSBINRQhWvVtiWAiLOcHiI/nYSTXctIFyzbWJ74BqvgoZS8eujaPUyBO+asF3enfyrnug9uvqrhsGqnsqFSaAYSQG3p4paKThRhxGX1jqMy6wJaTED1d9SxHs9gnGCZLk1yAr0w/mDkF59YmPoAibmyz1aaXGkjPBWa4ac+RBTEuOxt0hz+ykA7rNua5QPMdNx3sXrv80ECDEs78poOiZgkRvBEnA4qCvSK5FBB5DkVn+3cUhd36tEhKz79u8mLAsrUrgi/JKcrdAZSCyS4oPRt9NEm9YZxcYzqDH98RDp sshd@apache.org
 
 dn: cn=Lyor Goldstein,ou=People,dc=sshd,dc=apache,dc=org
 objectClass: top
 objectClass: person
 objectClass: organizationalPerson
-objectclass: inetOrgPerson
+objectClass: inetOrgPerson
 cn: Lyor Goldstein
 givenName: Lyor
 sn: Goldstein
 uid: lgoldstein
 userpassword: lgoldstein
 mail: lgoldstein@apache.org
+description: ssh-dss AAAAB3NzaC1kc3MAAACBAJ6tabphvozXBCnOe2j48OF6pSGmV7+7mRrPxiyPY5mHcHGJXwsE4Rvu81epbTghX3A/s1sme1QtmVVOU3Y3NIxmDBK5UQmSIYS+chXe45GBwC6uJawOn4lPIYw15PJg2ZfjxC1QU5fa6HSQ3vX2MZfYFhEovlBd4mGo2+XY0DqHAAAAFQDeDO2UFNZRBFNN1jEBojpeALh7oQAAAIAI/wYgC1kTjsIFaVvgK3RcGb+lYW0EiuiuG0q37+BzcqtEgjVckvxoCTeghcyf6vhB+ZbrM9we2jH3JvmF4gmtmzjW8UtgYCB2ovRYmd21cSPQW2F9U+sIl2+2sI9rsyrAdyhW1qZ4HVIrKH0RdWkAkjQ+8EGKHKXKo6aEV/HdpAAAAIAv80m0MZoocokStZV7EclLwt3ihvN7/Tjf5aHS9fY8b1ev2Z7eIVm3UHirggEeJMQgIw3MBE/T1ttYyqwnVdOatBJVcVLXYUPuXrq6f9/TYEpYxLqowjOwPaAkZAnecVYo0eU52VSkR38onu7juk4BNzHtLILTvtZppQ6l3loOpA== sshd@apache.org