You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by mo...@apache.org on 2017/06/07 17:59:36 UTC

zeppelin git commit: Allow group/role based authentication using LdapRealm [ZEPPELIN-2539]

Repository: zeppelin
Updated Branches:
  refs/heads/master f10b71cc1 -> 169fb742f


Allow group/role based authentication using LdapRealm [ZEPPELIN-2539]

### What is this PR for?
Currently allowing authentication for selected roles/groups of an LDAP realm is not possible. The LDAPRealm allows for mapping of roles to groups but only allows authorization on URLs with respect to groups. No group based checks are carried out during authentication. This PR allows for group based authentication using LdapRealm.

### What type of PR is it?
[Improvement]

### Todos
* [x] - Merge #932 - This PR also merges changes from 932 so that needs to be merged first.

### What is the Jira issue?
https://issues.apache.org/jira/browse/ZEPPELIN-2539

### How should this be tested?
Build and configure `shiro.ini` to use the LdapRealm and verify that the realm works as before along with the added functionality of allowing only certain user groups for authentication if the `allowedRolesForAuthentication` config is set in the init. If this configuration is absent authentication should work as before without verifying roles. A sample shiro.ini is pasted here for testing purposes.

```
[main]
ldapRealm = org.apache.zeppelin.realm.LdapRealm
ldapRealm.userDnTemplate = uid={0},ou=people,dc=my-company,dc=net
ldapRealm.searchBase = dc=my-company,dc=net
ldapRealm.userSearchBase = ou=people,dc=my-company,dc=net
ldapRealm.groupSearchBase = ou=groups,dc=my-company,dc=net
ldapRealm.contextFactory.url = ldaps://auth.my-company.net:636
ldapRealm.contextFactory.authenticationMechanism = simple
ldapRealm.userObjectClass = posixAccount
ldapRealm.groupObjectClass = posixGroup
ldapRealm.authorizationEnabled = true
ldapRealm.memberAttribute = memberUid
ldapRealm.memberAttributeValueTemplate=uid={0},ou=people,dc=my-company,dc=net
ldapRealm.rolesByGroup = GLOBAL_ADMINS:admin,HKG_USERS:user
ldapRealm.allowedRolesForAuthentication = admin,user
ldapRealm.userSearchAttributeName = uid
sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
shiro.loginUrl = /api/login
securityManager.sessionManager = $sessionManager
securityManager.sessionManager.globalSessionTimeout = 86400000
securityManager.realms = $ldapRealm

[urls]
/api/version = anon
/api/login = authc
/api/login/logout = authc
/** = authc, roles[admin,user]
```

### Screenshots (if appropriate)

### Questions:
* Does the licenses files need update? No
* Is there breaking changes for older versions? No
* Does this needs documentation? Y (documentation updated in PR)

Author: Sohaib Iftikhar <if...@gmail.com>

Closes #2354 from sohaibiftikhar/ldaprealm and squashes the following commits:

f706b22 [Sohaib Iftikhar] Added role based authentication(not to be confused with authorization) for shiro


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

Branch: refs/heads/master
Commit: 169fb742fd7f24ed987a7d85cf473e49f8d02f4b
Parents: f10b71c
Author: Sohaib Iftikhar <if...@gmail.com>
Authored: Fri May 19 15:04:18 2017 +0200
Committer: Lee moon soo <mo...@apache.org>
Committed: Wed Jun 7 10:59:33 2017 -0700

----------------------------------------------------------------------
 docs/security/shiroauthentication.md            | 44 ++++++++++++++++++
 .../org/apache/zeppelin/realm/LdapRealm.java    | 48 ++++++++++++++++++++
 2 files changed, 92 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zeppelin/blob/169fb742/docs/security/shiroauthentication.md
----------------------------------------------------------------------
diff --git a/docs/security/shiroauthentication.md b/docs/security/shiroauthentication.md
index 348c438..e608a14 100644
--- a/docs/security/shiroauthentication.md
+++ b/docs/security/shiroauthentication.md
@@ -128,6 +128,10 @@ Change the following values in the Shiro.ini file, and uncomment the line:
 
 ### LDAP
 
+Two options exist for configuring an LDAP Realm. The simpler to use is the LdapGroupRealm. How ever it has limited 
+flexibility with mapping of ldap groups to users and for authorization for user groups. A sample configuration file for
+this realm is given below.
+
 ```
 ldapRealm = org.apache.zeppelin.realm.LdapGroupRealm
 # search base for ldap groups (only relevant for LdapGroupRealm):
@@ -137,6 +141,46 @@ ldapRealm.userDnTemplate = uid={0},ou=Users,dc=COMPANY,dc=COM
 ldapRealm.contextFactory.authenticationMechanism = simple
 ```
 
+The other more flexible option is to use the LdapRealm. It allows for mapping of ldapgroups to roles and also allows for
+ role/group based authentication into the zeppelin server. Sample configuration for this realm is given below.
+ ```
+[main] 
+ldapRealm=org.apache.zeppelin.realm.LdapRealm
+
+ldapRealm.contextFactory.authenticationMechanism=simple
+ldapRealm.contextFactory.url=ldap://localhost:33389
+ldapRealm.userDnTemplate=uid={0},ou=people,dc=hadoop,dc=apache,dc=org
+# Ability to set ldap paging Size if needed default is 100
+ldapRealm.pagingSize = 200
+ldapRealm.authorizationEnabled=true
+ldapRealm.contextFactory.systemAuthenticationMechanism=simple
+ldapRealm.searchBase=dc=hadoop,dc=apache,dc=org
+ldapRealm.userSearchBase = dc=hadoop,dc=apache,dc=org
+ldapRealm.groupSearchBase = ou=groups,dc=hadoop,dc=apache,dc=org
+ldapRealm.groupObjectClass=groupofnames
+# Allow userSearchAttribute to be customized
+ldapRealm.userSearchAttributeName = sAMAccountName
+ldapRealm.memberAttribute=member
+# force usernames returned from ldap to lowercase useful for AD
+ldapRealm.userLowerCase = true 
+# ability set searchScopes subtree (default), one, base
+ldapRealm.userSearchScope = subtree;
+ldapRealm.groupSearchScope = subtree;
+ldapRealm.memberAttributeValueTemplate=cn={0},ou=people,dc=hadoop,dc=apache,dc=org
+ldapRealm.contextFactory.systemUsername=uid=guest,ou=people,dc=hadoop,dc=apache,dc=org 
+ldapRealm.contextFactory.systemPassword=S{ALIAS=ldcSystemPassword}
+# enable support for nested groups using the LDAP_MATCHING_RULE_IN_CHAIN operator
+ldapRealm.groupSearchEnableMatchingRuleInChain = true
+# optional mapping from physical groups to logical application roles
+ldapRealm.rolesByGroup = LDN_USERS: user_role, NYK_USERS: user_role, HKG_USERS: user_role, GLOBAL_ADMIN: admin_role
+# optional list of roles that are allowed to authenticate. Incase not present all groups are allowed to authenticate (login). 
+# This changes nothing for url specific permissions that will continue to work as specified in [urls].
+ldapRealm.allowedRolesForAuthentication = admin_role,user_role
+ldapRealm.permissionsByRole= user_role = *:ToDoItemsJdo:*:*, *:ToDoItem:*:*; admin_role = *
+securityManager.sessionManager = $sessionManager
+securityManager.realms = $ldapRealm
+ ```
+
 ### PAM
 [PAM](https://en.wikipedia.org/wiki/Pluggable_authentication_module) authentication support allows the reuse of existing authentication 
 moduls on the host where Zeppelin is running. On a typical system modules are configured per service for example sshd, passwd, etc. under `/etc/pam.d/`. You can

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/169fb742/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapRealm.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapRealm.java b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapRealm.java
index 3381a51..97c223c 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapRealm.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapRealm.java
@@ -109,6 +109,9 @@ import javax.naming.ldap.PagedResultsControl;
  * <p># optional mapping from physical groups to logical application roles
  * ldapRealm.rolesByGroup = \ LDN_USERS: user_role,\ NYK_USERS: user_role,\
  * HKG_USERS: user_role,\ GLOBAL_ADMIN: admin_role,\ DEMOS: self-install_role
+ *
+ * <p># optional list of roles that are allowed to authenticate
+ * ldapRealm.allowedRolesForAuthentication = admin_role,user_role
  * 
  * <p>ldapRealm.permissionsByRole=\ user_role = *:ToDoItemsJdo:*:*,\
  * *:ToDoItem:*:*; \ self-install_role = *:ToDoItemsFixturesService:install:* ;
@@ -176,6 +179,7 @@ public class LdapRealm extends JndiLdapRealm {
   private String memberAttributeValueSuffix = "";
 
   private final Map<String, String> rolesByGroup = new LinkedHashMap<String, String>();
+  private final List<String> allowedRolesForAuthentication = new ArrayList<String>();
   private final Map<String, List<String>> permissionsByRole = 
       new LinkedHashMap<String, List<String>>();
 
@@ -203,6 +207,29 @@ public class LdapRealm extends JndiLdapRealm {
   }
 
   /**
+   * This overrides the implementation of queryForAuthenticationInfo inside JndiLdapRealm.
+   * In addition to calling the super method for authentication it also tries to validate
+   * if this user has atleast one of the allowed roles for authentication. In case the property
+   * allowedRolesForAuthentication is empty this check always returns true.
+   *
+   * @param token the submitted authentication token that triggered the authentication attempt.
+   * @param ldapContextFactory factory used to retrieve LDAP connections.
+   * @return AuthenticationInfo instance representing the authenticated user's information.
+   * @throws NamingException if any LDAP errors occur.
+   */
+  @Override
+  protected AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken token,
+                                                          LdapContextFactory ldapContextFactory)
+      throws NamingException {
+    AuthenticationInfo info = super.queryForAuthenticationInfo(token, ldapContextFactory);
+    // Credentials were verified. Verify that the principal has all allowedRulesForAuthentication
+    if (!hasAllowedAuthenticationRules(info.getPrincipals(), ldapContextFactory)) {
+      throw new NamingException("Principal does not have any of the allowedRolesForAuthentication");
+    }
+    return info;
+  }
+
+  /**
   * Get groups from LDAP.
   * 
   * @param principals
@@ -231,6 +258,23 @@ public class LdapRealm extends JndiLdapRealm {
     return simpleAuthorizationInfo;
   }
 
+  private boolean hasAllowedAuthenticationRules(PrincipalCollection principals,
+                                                final LdapContextFactory ldapContextFactory)
+      throws NamingException {
+    boolean allowed = allowedRolesForAuthentication.isEmpty();
+    if (!allowed) {
+      Set<String> roles = getRoles(principals, ldapContextFactory);
+      for (String allowedRole: allowedRolesForAuthentication) {
+        if (roles.contains(allowedRole)) {
+          log.debug("Allowed role for user [" + allowedRole + "] found.");
+          allowed = true;
+          break;
+        }
+      }
+    }
+    return allowed;
+  }
+
   private Set<String> getRoles(PrincipalCollection principals, 
         final LdapContextFactory ldapContextFactory)
       throws NamingException {
@@ -524,6 +568,10 @@ public class LdapRealm extends JndiLdapRealm {
     this.memberAttributeValueSuffix = suffix;
   }
 
+  public void setAllowedRolesForAuthentication(List<String> allowedRolesForAuthencation) {
+    this.allowedRolesForAuthentication.addAll(allowedRolesForAuthencation);
+  }
+
   public void setRolesByGroup(Map<String, String> rolesByGroup) {
     this.rolesByGroup.putAll(rolesByGroup);
   }