You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by lm...@apache.org on 2016/08/17 13:39:03 UTC

knox git commit: KNOX-537 Linux PAM Authentication Provider (jeffreyr97/hkropp via lmccay)

Repository: knox
Updated Branches:
  refs/heads/master d64d848dc -> b37e4293d


KNOX-537 Linux PAM Authentication Provider (jeffreyr97/hkropp via lmccay)

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

Branch: refs/heads/master
Commit: b37e4293d2abfb2db0b2a63a571ffe7d54866242
Parents: d64d848
Author: Larry McCay <lm...@hortonworks.com>
Authored: Wed Aug 17 09:38:45 2016 -0400
Committer: Larry McCay <lm...@hortonworks.com>
Committed: Wed Aug 17 09:38:45 2016 -0400

----------------------------------------------------------------------
 gateway-provider-security-shiro/pom.xml         |   6 +
 .../filter/ShiroSubjectIdentityAdapter.java     |  21 ++-
 .../hadoop/gateway/shirorealm/KnoxPamRealm.java | 144 +++++++++++++++++++
 .../gateway/shirorealm/UnixUserPrincipal.java   |  46 ++++++
 .../gateway/shirorealm/KnoxPamRealmTest.java    |  70 +++++++++
 pom.xml                                         |  12 ++
 6 files changed, 294 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/knox/blob/b37e4293/gateway-provider-security-shiro/pom.xml
----------------------------------------------------------------------
diff --git a/gateway-provider-security-shiro/pom.xml b/gateway-provider-security-shiro/pom.xml
index 40614b9..1ed7302 100644
--- a/gateway-provider-security-shiro/pom.xml
+++ b/gateway-provider-security-shiro/pom.xml
@@ -65,6 +65,12 @@
         </dependency>
 
         <dependency>
+            <groupId>org.kohsuke</groupId>
+            <artifactId>libpam4j</artifactId>
+            <version>1.8</version>
+        </dependency>
+
+        <dependency>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
         </dependency>

http://git-wip-us.apache.org/repos/asf/knox/blob/b37e4293/gateway-provider-security-shiro/src/main/java/org/apache/hadoop/gateway/filter/ShiroSubjectIdentityAdapter.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-shiro/src/main/java/org/apache/hadoop/gateway/filter/ShiroSubjectIdentityAdapter.java b/gateway-provider-security-shiro/src/main/java/org/apache/hadoop/gateway/filter/ShiroSubjectIdentityAdapter.java
index 11a0780..692cf8d 100644
--- a/gateway-provider-security-shiro/src/main/java/org/apache/hadoop/gateway/filter/ShiroSubjectIdentityAdapter.java
+++ b/gateway-provider-security-shiro/src/main/java/org/apache/hadoop/gateway/filter/ShiroSubjectIdentityAdapter.java
@@ -68,7 +68,7 @@ public class ShiroSubjectIdentityAdapter implements Filter {
     // we use shiro authorization realm to look up groups
     subject.hasRole("authenticatedUser");
     
-    final String principalName = (String) subject.getPrincipal();
+    final String principalName = (String) subject.getPrincipal().toString();
 
     CallableChain callableChain = new CallableChain(request, response, chain);
     SecurityUtils.getSubject().execute(callableChain);
@@ -95,7 +95,7 @@ public class ShiroSubjectIdentityAdapter implements Filter {
         }
       };
       Subject shiroSubject = SecurityUtils.getSubject();
-      final String principal = (String) shiroSubject.getPrincipal();
+      final String principal = (String) shiroSubject.getPrincipal().toString();
       HashSet emptySet = new HashSet();
       Set<Principal> principals = new HashSet<Principal>();
       Principal p = new PrimaryPrincipal(principal);
@@ -108,9 +108,20 @@ public class ShiroSubjectIdentityAdapter implements Filter {
       // map ldap groups saved in session to Java Subject GroupPrincipal(s)
       if (SecurityUtils.getSubject().getSession().getAttribute(SUBJECT_USER_GROUPS) != null) {
         userGroups = (Set<String>)SecurityUtils.getSubject().getSession().getAttribute(SUBJECT_USER_GROUPS);
-      } else {
-        userGroups = new HashSet<String>(shiroSubject.getPrincipals().asSet());
-        userGroups.remove(principal);
+      } else { // KnoxLdapRealm case
+        if(  shiroSubject.getPrincipal() instanceof String ) { 
+           userGroups = new HashSet<String>(shiroSubject.getPrincipals().asSet());
+           userGroups.remove(principal);
+        } else { // KnoxPamRealm case
+           Set<Principal> shiroPrincipals = new HashSet<Principal>(shiroSubject.getPrincipals().asSet());
+           userGroups = new HashSet<String>(); // Here we are creating a new UserGroup
+                                               // so we don't need to remove a Principal
+                                               // In the case of LDAP the userGroup may have already Principal
+                                               // added to the list of groups, so it is not needed.
+           for( Principal shiroPrincipal: shiroPrincipals ) {
+                userGroups.add(shiroPrincipal.toString() );
+           }
+        }
       }
       for (String userGroup : userGroups) {
         Principal gp = new GroupPrincipal(userGroup);

http://git-wip-us.apache.org/repos/asf/knox/blob/b37e4293/gateway-provider-security-shiro/src/main/java/org/apache/hadoop/gateway/shirorealm/KnoxPamRealm.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-shiro/src/main/java/org/apache/hadoop/gateway/shirorealm/KnoxPamRealm.java b/gateway-provider-security-shiro/src/main/java/org/apache/hadoop/gateway/shirorealm/KnoxPamRealm.java
new file mode 100644
index 0000000..84121a7
--- /dev/null
+++ b/gateway-provider-security-shiro/src/main/java/org/apache/hadoop/gateway/shirorealm/KnoxPamRealm.java
@@ -0,0 +1,144 @@
+/*
+ *  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.hadoop.gateway.shirorealm;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+import org.apache.hadoop.gateway.GatewayMessages;
+import org.apache.hadoop.gateway.audit.api.Action;
+import org.apache.hadoop.gateway.audit.api.ActionOutcome;
+import org.apache.hadoop.gateway.audit.api.ResourceType;
+import org.apache.hadoop.gateway.audit.api.AuditService;
+import org.apache.hadoop.gateway.audit.api.AuditServiceFactory;
+import org.apache.hadoop.gateway.audit.api.Auditor;
+import org.apache.hadoop.gateway.audit.log4j.audit.AuditConstants;
+import org.apache.hadoop.gateway.i18n.messages.MessagesFactory;
+import org.apache.hadoop.gateway.shirorealm.impl.i18n.KnoxShiroMessages;
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.AuthenticationException;
+
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
+import org.apache.shiro.crypto.hash.*;
+import org.jvnet.libpam.PAM;
+import org.jvnet.libpam.PAMException;
+import org.jvnet.libpam.UnixUser;
+
+/**
+ * A Unix-style
+ * <a href="http://www.kernel.org/pub/linux/libs/pam/index.html">PAM</a>
+ * {@link org.apache.shiro.realm.Realm Realm} that uses
+ * <a href="https://github.com/kohsuke/libpam4j">libpam4j</a> to interface with
+ * the PAM system libraries.
+ * <p>
+ * This is a single Shiro {@code Realm} that interfaces with the OS's
+ * {@code PAM} subsystem which itself can be connected to several authentication
+ * methods (unix-crypt,Samba, LDAP, etc.)
+ * <p>
+ * This {@code Realm} can also take part in Shiro's Pluggable Realms concept.
+ * <p>
+ * Using a {@code KnoxPamRealm} requires a PAM {@code service} name. This is the
+ * name of the file under {@code /etc/pam.d} that is used to initialise and
+ * configure the PAM subsytem. Normally, this file reflects the application
+ * using it. For example {@code gdm}, {@code su}, etc. There is no default value
+ * for this propery.
+ * <p>
+ * For example, defining this realm in Shiro .ini:
+ * 
+ * <pre>
+ * [main]
+ * pamRealm = org.apache.shiro.realm.libpam4j.KnoxPamRealm
+ * pamRealm.service = [ knox-pam-ldap-service | knox-pam-os-service | knox-pam-winbind-service ]
+ * [urls]
+ * **=authcBasic
+ * </pre>
+ * 
+ */
+
+public class KnoxPamRealm extends AuthorizingRealm {
+	private static final String HASHING_ALGORITHM = "SHA-1";
+	private final static String  SUBJECT_USER_ROLES = "subject.userRoles";
+	private final static String  SUBJECT_USER_GROUPS = "subject.userGroups";
+	private static GatewayMessages LOG = MessagesFactory.get(GatewayMessages.class);
+	private HashService hashService = new DefaultHashService();
+	KnoxShiroMessages ShiroLog = MessagesFactory.get(KnoxShiroMessages.class);
+	GatewayMessages GatewayLog = MessagesFactory.get(GatewayMessages.class);
+	private static AuditService auditService = AuditServiceFactory.getAuditService();
+	private static Auditor auditor = auditService.getAuditor(AuditConstants.DEFAULT_AUDITOR_NAME,
+			AuditConstants.KNOX_SERVICE_NAME, AuditConstants.KNOX_COMPONENT_NAME);
+
+	private String service;
+
+	public KnoxPamRealm() {
+		HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(HASHING_ALGORITHM);
+		setCredentialsMatcher(credentialsMatcher);
+	}
+
+	public void setService(String service) {
+		this.service = service;
+	}
+
+	public String getService() {
+		return this.service;
+	}
+
+	@Override
+	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+		Set<String> roles = new LinkedHashSet<String>();
+
+		UnixUserPrincipal user = principals.oneByType(UnixUserPrincipal.class);
+		if (user != null) {
+			roles.addAll(user.getUnixUser().getGroups());
+		}
+		SecurityUtils.getSubject().getSession().setAttribute(SUBJECT_USER_ROLES, roles);
+		SecurityUtils.getSubject().getSession().setAttribute(SUBJECT_USER_GROUPS, roles);
+		GatewayLog.lookedUpUserRoles(roles, user.getName());
+		return new SimpleAuthorizationInfo(roles);
+	}
+
+	@Override
+	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
+		UsernamePasswordToken upToken = (UsernamePasswordToken) token;
+		UnixUser user=null;
+		try {
+            user = (new PAM(this.getService())).authenticate(upToken.getUsername(), 
+            		new String(upToken.getPassword()));
+		} catch (PAMException e) {
+			auditor.audit(Action.AUTHENTICATION, token.getPrincipal().toString(), ResourceType.PRINCIPAL,
+					ActionOutcome.FAILURE, e.getMessage());
+			ShiroLog.failedLoginInfo(token);
+			ShiroLog.failedLoginAttempt(e.getCause());
+			throw new AuthenticationException(e);
+		}
+		HashRequest.Builder builder = new HashRequest.Builder();
+		Hash credentialsHash = hashService
+				.computeHash(builder.setSource(token.getCredentials()).setAlgorithmName(HASHING_ALGORITHM).build());
+		return new SimpleAuthenticationInfo(new UnixUserPrincipal(user) , credentialsHash.toHex(), credentialsHash.getSalt(),
+				getName());
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/b37e4293/gateway-provider-security-shiro/src/main/java/org/apache/hadoop/gateway/shirorealm/UnixUserPrincipal.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-shiro/src/main/java/org/apache/hadoop/gateway/shirorealm/UnixUserPrincipal.java b/gateway-provider-security-shiro/src/main/java/org/apache/hadoop/gateway/shirorealm/UnixUserPrincipal.java
new file mode 100644
index 0000000..a659888
--- /dev/null
+++ b/gateway-provider-security-shiro/src/main/java/org/apache/hadoop/gateway/shirorealm/UnixUserPrincipal.java
@@ -0,0 +1,46 @@
+/*
+ *  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.hadoop.gateway.shirorealm;
+
+import java.security.Principal;
+import org.jvnet.libpam.UnixUser;
+
+public class UnixUserPrincipal implements Principal {
+	private final UnixUser userName;
+
+	public UnixUserPrincipal(UnixUser userName) {
+		this.userName = userName;
+	}
+
+	@Override
+	public String getName() {
+		return userName.getUserName();
+	}
+
+	public UnixUser getUnixUser() {
+		return userName;
+	}
+
+	@Override
+	public String toString() {
+		return String.valueOf(userName.getUserName());
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/b37e4293/gateway-provider-security-shiro/src/test/java/org/apache/hadoop/gateway/shirorealm/KnoxPamRealmTest.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-shiro/src/test/java/org/apache/hadoop/gateway/shirorealm/KnoxPamRealmTest.java b/gateway-provider-security-shiro/src/test/java/org/apache/hadoop/gateway/shirorealm/KnoxPamRealmTest.java
new file mode 100644
index 0000000..1ada3c6
--- /dev/null
+++ b/gateway-provider-security-shiro/src/test/java/org/apache/hadoop/gateway/shirorealm/KnoxPamRealmTest.java
@@ -0,0 +1,70 @@
+/*
+ *  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.hadoop.gateway.shirorealm;
+
+import java.util.Scanner;
+
+import org.junit.Test;
+
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.UsernamePasswordToken;
+
+import static org.junit.Assert.*;
+import static org.junit.Assume.assumeTrue;
+import static org.easymock.EasyMock.*;
+
+public class KnoxPamRealmTest {
+  @Test
+  public void setService() {
+    KnoxPamRealm realm = new KnoxPamRealm();
+    realm.setService("knox-pam-os-service");
+    //assertEquals(realm.getService(), "knox-pam-os-service");
+  }
+
+  @Test
+  public void testDoGetAuthenticationInfo() {
+    KnoxPamRealm realm = new KnoxPamRealm();
+    realm.setService("sshd");  // pam settings being used: /etc/pam.d/sshd
+
+    // use environment variables and skip the test if not set.
+    String pamuser = System.getenv("PAMUSER");
+    String pampass = System.getenv("PAMPASS");
+    assumeTrue(pamuser != null);
+    assumeTrue(pampass != null);
+
+    // mock shiro auth token
+    UsernamePasswordToken authToken = createMock(UsernamePasswordToken.class);
+    expect(authToken.getUsername()).andReturn(pamuser);
+    expect(authToken.getPassword()).andReturn(pampass.toCharArray());
+    expect(authToken.getCredentials()).andReturn(pampass);
+    replay(authToken);
+
+    // login
+    AuthenticationInfo authInfo = realm.doGetAuthenticationInfo(authToken);
+
+    // verify success
+    assertTrue(authInfo.getCredentials() != null);
+  }
+
+  public static void main(String[] args) throws Exception {
+    KnoxPamRealmTest pamTest = new KnoxPamRealmTest();
+    pamTest.testDoGetAuthenticationInfo();
+  }
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/b37e4293/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 2310417..8894a2b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1053,6 +1053,18 @@
             </dependency>
 
             <dependency>
+                <groupId>org.kohsuke</groupId>
+                <artifactId>libpam4j</artifactId>
+                <version>1.8</version>
+            </dependency>
+
+            <dependency>
+                <groupId>net.java.dev.jna</groupId>
+                <artifactId>jna</artifactId>
+                <version>4.1.0</version>
+            </dependency>
+
+            <dependency>
                 <groupId>org.apache.zookeeper</groupId>
                 <artifactId>zookeeper</artifactId>
                 <version>3.4.6</version>