Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id BE12C200C8E for ; Thu, 8 Jun 2017 15:05:03 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id BC97D160BD5; Thu, 8 Jun 2017 13:05:03 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 68191160BCA for ; Thu, 8 Jun 2017 15:05:02 +0200 (CEST) Received: (qmail 76273 invoked by uid 500); 8 Jun 2017 13:05:01 -0000 Mailing-List: contact commits-help@atlas.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@atlas.incubator.apache.org Delivered-To: mailing list commits@atlas.incubator.apache.org Received: (qmail 76264 invoked by uid 99); 8 Jun 2017 13:05:01 -0000 Received: from pnap-us-west-generic-nat.apache.org (HELO spamd3-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 08 Jun 2017 13:05:01 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd3-us-west.apache.org (ASF Mail Server at spamd3-us-west.apache.org) with ESMTP id 38BED185F86 for ; Thu, 8 Jun 2017 13:05:01 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd3-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: -4.222 X-Spam-Level: X-Spam-Status: No, score=-4.222 tagged_above=-999 required=6.31 tests=[KAM_ASCII_DIVIDERS=0.8, RCVD_IN_DNSWL_HI=-5, RCVD_IN_MSPIKE_H3=-0.01, RCVD_IN_MSPIKE_WL=-0.01, RP_MATCHES_RCVD=-0.001, SPF_PASS=-0.001] autolearn=disabled Received: from mx1-lw-eu.apache.org ([10.40.0.8]) by localhost (spamd3-us-west.apache.org [10.40.0.10]) (amavisd-new, port 10024) with ESMTP id OPRz6sOwojlS for ; Thu, 8 Jun 2017 13:04:57 +0000 (UTC) Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx1-lw-eu.apache.org (ASF Mail Server at mx1-lw-eu.apache.org) with SMTP id EB7EB5F659 for ; Thu, 8 Jun 2017 13:04:55 +0000 (UTC) Received: (qmail 76230 invoked by uid 99); 8 Jun 2017 13:04:55 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 08 Jun 2017 13:04:55 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id EC607DFE61; Thu, 8 Jun 2017 13:04:54 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: nixon@apache.org To: commits@atlas.incubator.apache.org Message-Id: <7b22f991315d4d7bad774114e49c010a@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: incubator-atlas git commit: ATLAS-1804 : Allow PAM for authentication. Date: Thu, 8 Jun 2017 13:04:54 +0000 (UTC) archived-at: Thu, 08 Jun 2017 13:05:03 -0000 Repository: incubator-atlas Updated Branches: refs/heads/master edd4aa9ad -> aa6d87e1c ATLAS-1804 : Allow PAM for authentication. Signed-off-by: nixonrodrigues Project: http://git-wip-us.apache.org/repos/asf/incubator-atlas/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-atlas/commit/aa6d87e1 Tree: http://git-wip-us.apache.org/repos/asf/incubator-atlas/tree/aa6d87e1 Diff: http://git-wip-us.apache.org/repos/asf/incubator-atlas/diff/aa6d87e1 Branch: refs/heads/master Commit: aa6d87e1c8f373f6c2ef0ca5586d03f64339c236 Parents: edd4aa9 Author: shiwang Authored: Wed Jun 7 13:56:31 2017 -0700 Committer: nixonrodrigues Committed: Thu Jun 8 18:26:36 2017 +0530 ---------------------------------------------------------------------- webapp/pom.xml | 12 + .../security/AtlasAuthenticationProvider.java | 18 +- .../AtlasPamAuthenticationProvider.java | 155 +++++++++++++ .../atlas/web/security/PamLoginModule.java | 221 +++++++++++++++++++ .../apache/atlas/web/security/PamPrincipal.java | 84 +++++++ .../web/security/UserAuthorityGranter.java | 35 +++ 6 files changed, 523 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aa6d87e1/webapp/pom.xml ---------------------------------------------------------------------- diff --git a/webapp/pom.xml b/webapp/pom.xml index 4cc0112..465d2a5 100755 --- a/webapp/pom.xml +++ b/webapp/pom.xml @@ -476,6 +476,18 @@ com.webcohesion.enunciate enunciate-core-annotations + + + org.kohsuke + libpam4j + 1.8 + + + + net.java.dev.jna + jna + 4.1.0 + http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aa6d87e1/webapp/src/main/java/org/apache/atlas/web/security/AtlasAuthenticationProvider.java ---------------------------------------------------------------------- diff --git a/webapp/src/main/java/org/apache/atlas/web/security/AtlasAuthenticationProvider.java b/webapp/src/main/java/org/apache/atlas/web/security/AtlasAuthenticationProvider.java index 80d6604..6827aec 100644 --- a/webapp/src/main/java/org/apache/atlas/web/security/AtlasAuthenticationProvider.java +++ b/webapp/src/main/java/org/apache/atlas/web/security/AtlasAuthenticationProvider.java @@ -34,10 +34,12 @@ public class AtlasAuthenticationProvider extends AtlasAbstractAuthenticationProv .getLogger(AtlasAuthenticationProvider.class); private boolean fileAuthenticationMethodEnabled = true; + private boolean pamAuthenticationEnabled = false; private String ldapType = "NONE"; public static final String FILE_AUTH_METHOD = "atlas.authentication.method.file"; public static final String LDAP_AUTH_METHOD = "atlas.authentication.method.ldap"; public static final String LDAP_TYPE = "atlas.authentication.method.ldap.type"; + public static final String PAM_AUTH_METHOD = "atlas.authentication.method.pam"; @@ -49,13 +51,17 @@ public class AtlasAuthenticationProvider extends AtlasAbstractAuthenticationProv final AtlasADAuthenticationProvider adAuthenticationProvider; + final AtlasPamAuthenticationProvider pamAuthenticationProvider; + @Inject public AtlasAuthenticationProvider(AtlasLdapAuthenticationProvider ldapAuthenticationProvider, AtlasFileAuthenticationProvider fileAuthenticationProvider, - AtlasADAuthenticationProvider adAuthenticationProvider) { + AtlasADAuthenticationProvider adAuthenticationProvider, + AtlasPamAuthenticationProvider pamAuthenticationProvider) { this.ldapAuthenticationProvider = ldapAuthenticationProvider; this.fileAuthenticationProvider = fileAuthenticationProvider; this.adAuthenticationProvider = adAuthenticationProvider; + this.pamAuthenticationProvider = pamAuthenticationProvider; } @PostConstruct @@ -65,6 +71,8 @@ public class AtlasAuthenticationProvider extends AtlasAbstractAuthenticationProv this.fileAuthenticationMethodEnabled = configuration.getBoolean(FILE_AUTH_METHOD, true); + this.pamAuthenticationEnabled = configuration.getBoolean(PAM_AUTH_METHOD, false); + boolean ldapAuthenticationEnabled = configuration.getBoolean(LDAP_AUTH_METHOD, false); if (ldapAuthenticationEnabled) { @@ -102,13 +110,19 @@ public class AtlasAuthenticationProvider extends AtlasAbstractAuthenticationProv } catch (Exception ex) { LOG.error("Error while AD authentication", ex); } + } else if (pamAuthenticationEnabled) { + try { + authentication = pamAuthenticationProvider.authenticate(authentication); + } catch (Exception ex) { + LOG.error("Error while PAM authentication", ex); + } } } if (authentication != null) { if (authentication.isAuthenticated()) { return authentication; - } else if (fileAuthenticationMethodEnabled) { // If the LDAP/AD authentication fails try the local filebased login method + } else if (fileAuthenticationMethodEnabled) { // If the LDAP/AD/PAM authentication fails try the local filebased login method authentication = fileAuthenticationProvider.authenticate(authentication); if (authentication != null && authentication.isAuthenticated()) { http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aa6d87e1/webapp/src/main/java/org/apache/atlas/web/security/AtlasPamAuthenticationProvider.java ---------------------------------------------------------------------- diff --git a/webapp/src/main/java/org/apache/atlas/web/security/AtlasPamAuthenticationProvider.java b/webapp/src/main/java/org/apache/atlas/web/security/AtlasPamAuthenticationProvider.java new file mode 100644 index 0000000..9a5a183 --- /dev/null +++ b/webapp/src/main/java/org/apache/atlas/web/security/AtlasPamAuthenticationProvider.java @@ -0,0 +1,155 @@ +/* + * 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.atlas.web.security; + +import org.apache.atlas.ApplicationProperties; +import org.apache.atlas.web.model.User; +import org.apache.commons.configuration.ConfigurationConverter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.authentication.jaas.DefaultJaasAuthenticationProvider; +import org.springframework.security.authentication.jaas.memory.InMemoryConfiguration; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.Configuration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +@Component +public class AtlasPamAuthenticationProvider extends AtlasAbstractAuthenticationProvider { + + private static Logger LOG = LoggerFactory.getLogger(AtlasPamAuthenticationProvider.class); + private boolean isDebugEnabled = LOG.isDebugEnabled(); + private static String loginModuleName = "org.apache.atlas.web.security.PamLoginModule"; + private static AppConfigurationEntry.LoginModuleControlFlag controlFlag = + AppConfigurationEntry.LoginModuleControlFlag.REQUIRED; + private Map options = new HashMap(); + private boolean groupsFromUGI; + private DefaultJaasAuthenticationProvider jaasAuthenticationProvider = + new DefaultJaasAuthenticationProvider(); + + @PostConstruct + public void setup() { + setPamProperties(); + init(); + } + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + Authentication auth = getPamAuthentication(authentication); + if (auth != null && auth.isAuthenticated()) { + return auth; + } else { + throw new AtlasAuthenticationException("PAM Authentication Failed"); + } + } + + private Authentication getPamAuthentication(Authentication authentication) { + if (isDebugEnabled) { + LOG.debug("==> AtlasPamAuthenticationProvider getPamAuthentication"); + } + try { + String userName = authentication.getName(); + String userPassword = ""; + if (authentication.getCredentials() != null) { + userPassword = authentication.getCredentials().toString(); + } + + // getting user authenticated + if (userName != null && userPassword != null + && !userName.trim().isEmpty() + && !userPassword.trim().isEmpty()) { + final List grantedAuths = getAuthorities(userName); + + final UserDetails principal = new User(userName, userPassword, + grantedAuths); + + final Authentication finalAuthentication = new UsernamePasswordAuthenticationToken( + principal, userPassword, grantedAuths); + + authentication = jaasAuthenticationProvider + .authenticate(finalAuthentication); + + if(groupsFromUGI) { + authentication = getAuthenticationWithGrantedAuthorityFromUGI(authentication); + } else { + authentication = getAuthenticationWithGrantedAuthority(authentication); + } + return authentication; + } else { + return authentication; + } + + } catch (Exception e) { + LOG.debug("Pam Authentication Failed:", e); + } + if (isDebugEnabled) { + LOG.debug("<== AtlasPamAuthenticationProvider getPamAuthentication"); + } + return authentication; + } + + private void setPamProperties() { + try { + this.groupsFromUGI = ApplicationProperties.get().getBoolean("atlas.authentication.method.pam.ugi-groups", true); + Properties properties = ConfigurationConverter.getProperties(ApplicationProperties.get() + .subset("atlas.authentication.method.pam")); + for (String key : properties.stringPropertyNames()) { + String value = properties.getProperty(key); + options.put(key, value); + } + if (!options.containsKey("service")) { + options.put("service", "atlas-login"); + } + } catch (Exception e) { + LOG.error("Exception while setLdapProperties", e); + } + } + + private void init() { + try { + AppConfigurationEntry appConfigurationEntry = new AppConfigurationEntry( + loginModuleName, controlFlag, options); + AppConfigurationEntry[] appConfigurationEntries = new AppConfigurationEntry[]{appConfigurationEntry}; + Map appConfigurationEntriesOptions = + new HashMap(); + appConfigurationEntriesOptions.put("SPRINGSECURITY", + appConfigurationEntries); + Configuration configuration = new InMemoryConfiguration( + appConfigurationEntriesOptions); + jaasAuthenticationProvider.setConfiguration(configuration); + UserAuthorityGranter authorityGranter = new UserAuthorityGranter(); + UserAuthorityGranter[] authorityGranters = new UserAuthorityGranter[]{authorityGranter}; + jaasAuthenticationProvider.setAuthorityGranters(authorityGranters); + jaasAuthenticationProvider.afterPropertiesSet(); + } catch (Exception e) { + LOG.error("Failed to init PAM Authentication", e); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aa6d87e1/webapp/src/main/java/org/apache/atlas/web/security/PamLoginModule.java ---------------------------------------------------------------------- diff --git a/webapp/src/main/java/org/apache/atlas/web/security/PamLoginModule.java b/webapp/src/main/java/org/apache/atlas/web/security/PamLoginModule.java new file mode 100644 index 0000000..802f6f1 --- /dev/null +++ b/webapp/src/main/java/org/apache/atlas/web/security/PamLoginModule.java @@ -0,0 +1,221 @@ +/* + * 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.atlas.web.security; + +import org.jvnet.libpam.PAM; +import org.jvnet.libpam.PAMException; +import org.jvnet.libpam.UnixUser; + +import javax.security.auth.Subject; +import javax.security.auth.callback.*; +import javax.security.auth.login.FailedLoginException; +import javax.security.auth.login.LoginException; +import javax.security.auth.spi.LoginModule; +import java.io.IOException; +import java.security.Principal; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class PamLoginModule extends Object implements LoginModule { + public static final String SERVICE_KEY = "service"; + + private PAM pam; + private Subject subject; + private CallbackHandler callbackHandler; + private Map options; + + private String username; + private String password; + + private boolean authSucceeded; + private PamPrincipal principal; + + public PamLoginModule() + { + super(); + authSucceeded = false; + } + + @Override + public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) + { + this.subject = subject; + this.callbackHandler = callbackHandler; + this.options = new HashMap<>(options); + } + + @Override + public boolean login() throws LoginException + { + initializePam(); + obtainUserAndPassword(); + return performLogin(); + } + + private void initializePam() throws LoginException + { + String service = (String) options.get(SERVICE_KEY); + if (service == null) + { + throw new LoginException("Error: PAM service was not defined"); + } + createPam(service); + } + + private void createPam(String service) throws LoginException + { + try + { + pam = new PAM(service); + } + catch (PAMException ex) + { + LoginException le = new LoginException("Error initializing PAM"); + le.initCause(ex); + throw le; + } + } + + private void obtainUserAndPassword() throws LoginException + { + if (callbackHandler == null) + { + throw new LoginException("Error: no CallbackHandler available to gather authentication information from the user"); + } + + try + { + NameCallback nameCallback = new NameCallback("username"); + PasswordCallback passwordCallback = new PasswordCallback("password", false); + + invokeCallbackHandler(nameCallback, passwordCallback); + + initUserName(nameCallback); + initPassword(passwordCallback); + } + catch (IOException | UnsupportedCallbackException ex) + { + LoginException le = new LoginException("Error in callbacks"); + le.initCause(ex); + throw le; + } + } + + private void invokeCallbackHandler(NameCallback nameCallback, PasswordCallback passwordCallback) throws IOException, UnsupportedCallbackException + { + Callback[] callbacks = new Callback[2]; + callbacks[0] = nameCallback; + callbacks[1] = passwordCallback; + + callbackHandler.handle(callbacks); + } + + private void initUserName(NameCallback nameCallback) + { + username = nameCallback.getName(); + } + + private void initPassword(PasswordCallback passwordCallback) + { + char[] password = passwordCallback.getPassword(); + if (password != null) { + this.password = new String(password); + } + passwordCallback.clearPassword(); + } + + private boolean performLogin() throws LoginException + { + try + { + UnixUser user = pam.authenticate(username, password); + principal = new PamPrincipal(user); + authSucceeded = true; + + return true; + } + catch (PAMException ex) + { + LoginException le = new FailedLoginException("Invalid username or password"); + le.initCause(ex); + throw le; + } + } + + @Override + public boolean commit() throws LoginException + { + if (authSucceeded == false) + { + return false; + } + + if (subject.isReadOnly()) + { + cleanup(); + throw new LoginException("Subject is read-only"); + } + + Set principals = subject.getPrincipals(); + if (principals.contains(principal) == false) + { + principals.add(principal); + } + + return true; + } + + @Override + public boolean abort() throws LoginException + { + if (authSucceeded == false) + { + return false; + } + + cleanup(); + return true; + } + + @Override + public boolean logout() throws LoginException + { + if (subject.isReadOnly()) + { + cleanup(); + throw new LoginException("Subject is read-only"); + } + + subject.getPrincipals().remove(principal); + + cleanup(); + return true; + } + + private void cleanup() + { + authSucceeded = false; + username = null; + password = null; + principal = null; + pam.dispose(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aa6d87e1/webapp/src/main/java/org/apache/atlas/web/security/PamPrincipal.java ---------------------------------------------------------------------- diff --git a/webapp/src/main/java/org/apache/atlas/web/security/PamPrincipal.java b/webapp/src/main/java/org/apache/atlas/web/security/PamPrincipal.java new file mode 100644 index 0000000..e7c80af --- /dev/null +++ b/webapp/src/main/java/org/apache/atlas/web/security/PamPrincipal.java @@ -0,0 +1,84 @@ +/* + * 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.atlas.web.security; + +import org.jvnet.libpam.UnixUser; + +import java.security.Principal; +import java.util.Collections; +import java.util.Set; + +public class PamPrincipal extends Object implements Principal { + private String userName; + private String gecos; + private String homeDir; + private String shell; + private int uid; + private int gid; + private Set groups; + + public PamPrincipal(UnixUser user) + { + super(); + userName = user.getUserName(); + gecos = user.getGecos(); + homeDir = user.getDir(); + shell = user.getShell(); + uid = user.getUID(); + gid = user.getGID(); + groups = Collections.unmodifiableSet(user.getGroups()); + } + + @Override + public String getName() + { + return userName; + } + + public String getGecos() + { + return gecos; + } + + public String getHomeDir() + { + return homeDir; + } + + public String getShell() + { + return shell; + } + + public int getUid() + { + return uid; + } + + public int getGid() + { + return gid; + } + + public Set getGroups() + { + return groups; + } +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aa6d87e1/webapp/src/main/java/org/apache/atlas/web/security/UserAuthorityGranter.java ---------------------------------------------------------------------- diff --git a/webapp/src/main/java/org/apache/atlas/web/security/UserAuthorityGranter.java b/webapp/src/main/java/org/apache/atlas/web/security/UserAuthorityGranter.java new file mode 100644 index 0000000..430dbd9 --- /dev/null +++ b/webapp/src/main/java/org/apache/atlas/web/security/UserAuthorityGranter.java @@ -0,0 +1,35 @@ +/* + * 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.atlas.web.security; + +import org.springframework.security.authentication.jaas.AuthorityGranter; + +import java.security.Principal; +import java.util.Collections; +import java.util.Set; + +public class UserAuthorityGranter implements AuthorityGranter { + + @Override + public Set grant(Principal principal) { + Collections.singleton("DATA_SCIENTIST"); + return null; + } +}