You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@atlas.apache.org by ma...@apache.org on 2020/03/16 02:50:55 UTC
[atlas] branch branch-2.0 updated: ATLAS-3666: updated file-based
authentication to use BCrypt
This is an automated email from the ASF dual-hosted git repository.
madhan pushed a commit to branch branch-2.0
in repository https://gitbox.apache.org/repos/asf/atlas.git
The following commit(s) were added to refs/heads/branch-2.0 by this push:
new 3f9a6b9 ATLAS-3666: updated file-based authentication to use BCrypt
3f9a6b9 is described below
commit 3f9a6b9bcaa4802e63017aa7291585d0d38dd089
Author: Madhan Neethiraj <ma...@apache.org>
AuthorDate: Sat Mar 14 13:35:31 2020 -0700
ATLAS-3666: updated file-based authentication to use BCrypt
(cherry picked from commit 5783b701c16df944c7472380828db4e643e6a3b6)
---
.../atlas/util/CredentialProviderUtility.java | 87 ++++++------
.../java/org/apache/atlas/web/dao/UserDao.java | 149 +++++++++++++++++----
.../security/AtlasFileAuthenticationProvider.java | 40 ++----
.../atlas/web/security/FileAuthenticationTest.java | 13 ++
4 files changed, 194 insertions(+), 95 deletions(-)
diff --git a/webapp/src/main/java/org/apache/atlas/util/CredentialProviderUtility.java b/webapp/src/main/java/org/apache/atlas/util/CredentialProviderUtility.java
index 5005f79..871416b 100755
--- a/webapp/src/main/java/org/apache/atlas/util/CredentialProviderUtility.java
+++ b/webapp/src/main/java/org/apache/atlas/util/CredentialProviderUtility.java
@@ -17,17 +17,15 @@
package org.apache.atlas.util;
import org.apache.atlas.web.dao.UserDao;
-import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Options;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.security.alias.CredentialProvider;
import org.apache.hadoop.security.alias.CredentialProviderFactory;
-import org.apache.hadoop.security.alias.JavaKeyStoreProvider;
import java.io.Console;
-import java.io.File;
import java.io.IOException;
import java.util.Arrays;
@@ -41,8 +39,7 @@ import static org.apache.atlas.security.SecurityProperties.TRUSTSTORE_PASSWORD_K
* of the DGC server.
*/
public class CredentialProviderUtility {
- private static final String[] KEYS =
- new String[]{KEYSTORE_PASSWORD_KEY, TRUSTSTORE_PASSWORD_KEY, SERVER_CERT_PASSWORD_KEY};
+ private static final String[] KEYS = new String[] { KEYSTORE_PASSWORD_KEY, TRUSTSTORE_PASSWORD_KEY, SERVER_CERT_PASSWORD_KEY };
public static abstract class TextDevice {
public abstract void printf(String fmt, Object... params);
@@ -75,34 +72,29 @@ public class CredentialProviderUtility {
public static TextDevice textDevice = DEFAULT_TEXT_DEVICE;
public static void main(String[] args) throws IOException {
- Options options = new Options();
-
try {
- createOptions(options);
-
- CommandLine cmd = new BasicParser().parse(options, args);
-
- boolean generatePasswordOption = cmd.hasOption("g");
+ CommandLine cmd = new DefaultParser().parse(createOptions(), args);
+ boolean generatePasswordOption = cmd.hasOption("g");
if (generatePasswordOption) {
String userName = cmd.getOptionValue("u");
String password = cmd.getOptionValue("p");
if (userName != null && password != null) {
- String encryptedPassword = UserDao.encrypt(password, userName);
- boolean silentOption = cmd.hasOption("s");
+ String encryptedPassword = UserDao.encrypt(password);
+ boolean silentOption = cmd.hasOption("s");
+
if (silentOption) {
System.out.println(encryptedPassword);
} else {
System.out.println("Your encrypted password is : " + encryptedPassword);
}
} else {
- System.out.println("Please provide username and password as input. Usage:" +
- " cputil.py -g -u <username> -p <password>");
+ System.out.println("Please provide username and password as input. Usage: cputil.py -g -u <username> -p <password>");
}
+
return;
}
-
} catch (Exception e) {
System.out.println("Exception while generatePassword " + e.getMessage());
return;
@@ -112,36 +104,42 @@ public class CredentialProviderUtility {
CredentialProvider provider = getCredentialProvider(textDevice);
if(provider != null) {
- char[] cred;
for (String key : KEYS) {
- cred = getPassword(textDevice, key);
+ char[] cred = getPassword(textDevice, key);
+
// create a credential entry and store it
- boolean overwrite = true;
if (provider.getCredentialEntry(key) != null) {
- String choice = textDevice.readLine("Entry for %s already exists. Overwrite? (y/n) [y]:", key);
- overwrite = StringUtils.isEmpty(choice) || choice.equalsIgnoreCase("y");
+ String choice = textDevice.readLine("Entry for %s already exists. Overwrite? (y/n) [y]:", key);
+ boolean overwrite = StringUtils.isEmpty(choice) || choice.equalsIgnoreCase("y");
+
if (overwrite) {
provider.deleteCredentialEntry(key);
provider.flush();
provider.createCredentialEntry(key, cred);
provider.flush();
+
textDevice.printf("Entry for %s was overwritten with the new value.\n", key);
} else {
textDevice.printf("Entry for %s was not overwritten.\n", key);
}
} else {
provider.createCredentialEntry(key, cred);
+
provider.flush();
}
}
}
}
- private static void createOptions(Options options) {
+ private static Options createOptions() {
+ Options options = new Options();
+
options.addOption("g", "generatePassword", false, "Generate Password");
options.addOption("s", "silent", false, "Silent");
options.addOption("u", "username", true, "UserName");
options.addOption("p", "password", true, "Password");
+
+ return options;
}
/**
@@ -151,32 +149,39 @@ public class CredentialProviderUtility {
* @return the password.
*/
private static char[] getPassword(TextDevice textDevice, String key) {
- boolean noMatch;
- char[] cred = new char[0];
- char[] passwd1;
- char[] passwd2;
- do {
- passwd1 = textDevice.readPassword("Please enter the password value for %s:", key);
- passwd2 = textDevice.readPassword("Please enter the password value for %s again:", key);
- noMatch = !Arrays.equals(passwd1, passwd2);
- if (noMatch) {
- if (passwd1 != null) {
- Arrays.fill(passwd1, ' ');
- }
+ char[] ret;
+
+ while (true) {
+ char[] passwd1 = textDevice.readPassword("Please enter the password value for %s:", key);
+ char[] passwd2 = textDevice.readPassword("Please enter the password value for %s again:", key);
+ boolean isMatch = !Arrays.equals(passwd1, passwd2);
+
+ if (!isMatch) {
textDevice.printf("Password entries don't match. Please try again.\n");
} else {
- if (passwd1.length == 0) {
+ if (passwd1 == null || passwd1.length == 0) {
textDevice.printf("An empty password is not valid. Please try again.\n");
- noMatch = true;
} else {
- cred = passwd1;
+ ret = passwd1;
+
+ if (passwd2 != null) {
+ Arrays.fill(passwd2, ' ');
+ }
+
+ break;
}
}
+
+ if (passwd1 != null) {
+ Arrays.fill(passwd1, ' ');
+ }
+
if (passwd2 != null) {
Arrays.fill(passwd2, ' ');
}
- } while (noMatch);
- return cred;
+ }
+
+ return ret;
}
/**\
@@ -190,7 +195,9 @@ public class CredentialProviderUtility {
if (providerPath != null) {
Configuration conf = new Configuration(false);
+
conf.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, providerPath);
+
return CredentialProviderFactory.getProviders(conf).get(0);
}
diff --git a/webapp/src/main/java/org/apache/atlas/web/dao/UserDao.java b/webapp/src/main/java/org/apache/atlas/web/dao/UserDao.java
index 7fdce3a..e16796f 100644
--- a/webapp/src/main/java/org/apache/atlas/web/dao/UserDao.java
+++ b/webapp/src/main/java/org/apache/atlas/web/dao/UserDao.java
@@ -39,19 +39,20 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.security.MessageDigest;
import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.util.StringUtils;
@Repository
public class UserDao {
-
- private static final String DEFAULT_USER_CREDENTIALS_PROPERTIES = "users-credentials.properties";
-
private static final Logger LOG = LoggerFactory.getLogger(UserDao.class);
- private Properties userLogins;
+ private static final String DEFAULT_USER_CREDENTIALS_PROPERTIES = "users-credentials.properties";
+ private static final ShaPasswordEncoder sha256Encoder = new ShaPasswordEncoder(256);
+ private static boolean v1ValidationEnabled = true;
+ private static boolean v2ValidationEnabled = true;
- private static final ShaPasswordEncoder sha256Encoder = new ShaPasswordEncoder(256);
+ private Properties userLogins = new Properties();
@PostConstruct
public void init() {
@@ -59,49 +60,61 @@ public class UserDao {
}
void loadFileLoginsDetails() {
+ userLogins.clear();
+
InputStream inStr = null;
+
try {
Configuration configuration = ApplicationProperties.get();
+
+ v1ValidationEnabled = configuration.getBoolean("atlas.authentication.method.file.v1-validation.enabled", true);
+ v2ValidationEnabled = configuration.getBoolean("atlas.authentication.method.file.v2-validation.enabled", true);
+
inStr = ApplicationProperties.getFileAsInputStream(configuration, "atlas.authentication.method.file.filename", DEFAULT_USER_CREDENTIALS_PROPERTIES);
- userLogins = new Properties();
+
userLogins.load(inStr);
} catch (IOException | AtlasException e) {
LOG.error("Error while reading user.properties file", e);
+
throw new RuntimeException(e);
} finally {
- if(inStr != null) {
+ if (inStr != null) {
try {
inStr.close();
- } catch(Exception excp) {
+ } catch (Exception excp) {
// ignore
}
}
}
}
- public User loadUserByUsername(final String username)
- throws AuthenticationException {
+ public User loadUserByUsername(final String username) throws AuthenticationException {
String userdetailsStr = userLogins.getProperty(username);
+
if (userdetailsStr == null || userdetailsStr.isEmpty()) {
- throw new UsernameNotFoundException("Username not found."
- + username);
+ throw new UsernameNotFoundException("Username not found." + username);
}
- String password = "";
- String role = "";
- String dataArr[] = userdetailsStr.split("::");
+
+ String password = "";
+ String role = "";
+ String[] dataArr = userdetailsStr.split("::");
+
if (dataArr != null && dataArr.length == 2) {
- role = dataArr[0];
+ role = dataArr[0];
password = dataArr[1];
} else {
LOG.error("User role credentials is not set properly for {}", username);
+
throw new AtlasAuthenticationException("User role credentials is not set properly for " + username );
}
List<GrantedAuthority> grantedAuths = new ArrayList<>();
+
if (StringUtils.hasText(role)) {
grantedAuths.add(new SimpleGrantedAuthority(role));
} else {
LOG.error("User role credentials is not set properly for {}", username);
+
throw new AtlasAuthenticationException("User role credentials is not set properly for " + username );
}
@@ -115,25 +128,109 @@ public class UserDao {
this.userLogins = userLogins;
}
- public static String getSha256Hash(String base) throws AtlasAuthenticationException {
+ public static String encrypt(String password) {
+ String ret = null;
+
+ try {
+ ret = BCrypt.hashpw(password, BCrypt.gensalt());
+ } catch (Throwable excp) {
+ LOG.warn("UserDao.encrypt(): failed", excp);
+ }
+
+ return ret;
+ }
+
+ public static boolean checkEncrypted(String password, String encryptedPwd, String userName) {
+ boolean ret = checkPasswordBCrypt(password, encryptedPwd);
+
+ if (!ret && v2ValidationEnabled) {
+ ret = checkPasswordSHA256WithSalt(password, encryptedPwd, userName);
+ }
+
+ if (!ret && v1ValidationEnabled) {
+ ret = checkPasswordSHA256(password, encryptedPwd);
+ }
+
+ return ret;
+ }
+
+ private static boolean checkPasswordBCrypt(String password, String encryptedPwd) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("checkPasswordBCrypt()");
+ }
+
+ boolean ret = false;
+
try {
- MessageDigest digest = MessageDigest.getInstance("SHA-256");
- byte[] hash = digest.digest(base.getBytes("UTF-8"));
- StringBuffer hexString = new StringBuffer();
+ ret = BCrypt.checkpw(password, encryptedPwd);
+ } catch (Throwable excp) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("checkPasswordBCrypt(): failed", excp);
+ }
+ }
+
+ return ret;
+ }
+
+ private static boolean checkPasswordSHA256WithSalt(String password, String encryptedPwd, String salt) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("checkPasswordSHA256WithSalt()");
+ }
+
+ boolean ret = false;
+
+ try {
+ String hash = sha256Encoder.encodePassword(password, salt);
+
+ ret = hash != null && hash.equals(encryptedPwd);
+ } catch (Throwable excp) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("checkPasswordSHA256WithSalt(): failed", excp);
+ }
+ }
+
+ return ret;
+ }
+
+ private static boolean checkPasswordSHA256(String password, String encryptedPwd) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("checkPasswordSHA256()");
+ }
+
+ boolean ret = false;
+
+ try {
+ String hash = getSha256Hash(password);
+
+ ret = hash != null && hash.equals(encryptedPwd);
+ } catch (Throwable excp) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("checkPasswordSHA256(): failed", excp);
+ }
+ }
+
+ return ret;
+ }
+
+ private static String getSha256Hash(String base) throws AtlasAuthenticationException {
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ byte[] hash = digest.digest(base.getBytes("UTF-8"));
+ StringBuffer hexString = new StringBuffer();
for (byte aHash : hash) {
String hex = Integer.toHexString(0xff & aHash);
- if (hex.length() == 1) hexString.append('0');
+
+ if (hex.length() == 1) {
+ hexString.append('0');
+ }
+
hexString.append(hex);
}
- return hexString.toString();
+ return hexString.toString();
} catch (Exception ex) {
throw new AtlasAuthenticationException("Exception while encoding password.", ex);
}
}
-
- public static String encrypt(String password, String salt) {
- return sha256Encoder.encodePassword(password, salt);
- }
}
diff --git a/webapp/src/main/java/org/apache/atlas/web/security/AtlasFileAuthenticationProvider.java b/webapp/src/main/java/org/apache/atlas/web/security/AtlasFileAuthenticationProvider.java
index 7269d4c..f1603ac 100644
--- a/webapp/src/main/java/org/apache/atlas/web/security/AtlasFileAuthenticationProvider.java
+++ b/webapp/src/main/java/org/apache/atlas/web/security/AtlasFileAuthenticationProvider.java
@@ -16,9 +16,7 @@
*/
package org.apache.atlas.web.security;
-import org.apache.atlas.ApplicationProperties;
import org.apache.atlas.web.dao.UserDao;
-import org.apache.commons.configuration.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.BadCredentialsException;
@@ -30,7 +28,6 @@ import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
-import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.util.Collection;
@@ -41,57 +38,42 @@ public class AtlasFileAuthenticationProvider extends AtlasAbstractAuthentication
private static Logger logger = LoggerFactory.getLogger(AtlasFileAuthenticationProvider.class);
private final UserDetailsService userDetailsService;
- private boolean v1ValidationEnabled = true;
@Inject
public AtlasFileAuthenticationProvider(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
- @PostConstruct
- public void setup() {
- try {
- Configuration configuration = ApplicationProperties.get();
- v1ValidationEnabled = configuration.getBoolean("atlas.authentication.method.file.v1-validation.enabled", true);
- } catch (Exception e) {
- logger.error("Exception while setup", e);
- }
- }
-
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = (String) authentication.getCredentials();
+
if (username == null || username.isEmpty()) {
logger.error("Username can't be null or empty.");
- throw new BadCredentialsException(
- "Username can't be null or empty.");
+
+ throw new BadCredentialsException("Username can't be null or empty.");
}
if (password == null || password.isEmpty()) {
logger.error("Password can't be null or empty.");
- throw new BadCredentialsException(
- "Password can't be null or empty.");
- }
-
- UserDetails user = userDetailsService.loadUserByUsername(username);
- String encodedPassword = UserDao.encrypt(password, username);
- boolean isValidPassword = encodedPassword.equals(user.getPassword());
-
-
- if (!isValidPassword && v1ValidationEnabled) {
- encodedPassword = UserDao.getSha256Hash(password);
+ throw new BadCredentialsException("Password can't be null or empty.");
}
- if (!encodedPassword.equals(user.getPassword())) {
+ UserDetails user = userDetailsService.loadUserByUsername(username);
+ boolean isValidPassword = UserDao.checkEncrypted(password, user.getPassword(), username);
+
+ if (!isValidPassword) {
logger.error("Wrong password " + username);
+
throw new BadCredentialsException("Wrong password");
}
+
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
+
authentication = new UsernamePasswordAuthenticationToken(username, password, authorities);
return authentication;
}
-
}
diff --git a/webapp/src/test/java/org/apache/atlas/web/security/FileAuthenticationTest.java b/webapp/src/test/java/org/apache/atlas/web/security/FileAuthenticationTest.java
index 6cd5017..a80ec1e 100644
--- a/webapp/src/test/java/org/apache/atlas/web/security/FileAuthenticationTest.java
+++ b/webapp/src/test/java/org/apache/atlas/web/security/FileAuthenticationTest.java
@@ -92,6 +92,7 @@ public class FileAuthenticationTest {
private void setupUserCredential(String tmpDir) throws Exception {
StringBuilder credentialFileStr = new StringBuilder(1024);
+ credentialFileStr.append("adminv3=ADMIN::$2a$10$ZVnkc2if06JMLCJEAhTKbOPeWDXTCFLL8zMA6FzZoP.bu8ThT43ha\n");
credentialFileStr.append("admin=ADMIN::a4a88c0872bf652bb9ed803ece5fd6e82354838a9bf59ab4babb1dab322154e1\n");
credentialFileStr.append("adminv1=ADMIN::8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918\n");
credentialFileStr.append("michael=DATA_SCIENTIST::95bfb24de17d285d734b9eaa9109bfe922adc85f20d2e5e66a78bddb4a4ebddb\n");
@@ -112,6 +113,18 @@ public class FileAuthenticationTest {
@Test
+ public void testValidUserLoginWithV3password() {
+
+ when(authentication.getName()).thenReturn("adminv3");
+ when(authentication.getCredentials()).thenReturn("admin");
+
+ Authentication auth = authProvider.authenticate(authentication);
+ LOG.debug(" {}", auth);
+
+ assertTrue(auth.isAuthenticated());
+ }
+
+ @Test
public void testValidUserLogin() {
when(authentication.getName()).thenReturn("admin");