ATLAS-1804 : Allow PAM for authentication.
Signed-off-by: nixonrodrigues <nixon@apache.org>
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 @@
<groupId>com.webcohesion.enunciate</groupId>
<artifactId>enunciate-core-annotations</artifactId>
</dependency>
+ <!-- PAM -->
+ <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>
</dependencies>
<build>
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 @@
.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 @@
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 @@
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 @@
} 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()) {
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<String, String> options = new HashMap<String, String>();
+ 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<GrantedAuthority> 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<String, AppConfigurationEntry[]> appConfigurationEntriesOptions =
+ new HashMap<String, AppConfigurationEntry[]>();
+ 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);
+ }
+ }
+}
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<String, ?> 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<String, ?> sharedState, Map<String, ?> 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<Principal> 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();
+ }
+}
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<String> 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<String> getGroups()
+ {
+ return groups;
+ }
+}
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<String> grant(Principal principal) {
+ Collections.singleton("DATA_SCIENTIST");
+ return null;
+ }
+}