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 <[email protected]> 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 <[email protected]> Authored: Fri May 19 15:04:18 2017 +0200 Committer: Lee moon soo <[email protected]> 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); }
