Commit 5783b701 by Madhan Neethiraj

ATLAS-3666: updated file-based authentication to use BCrypt

parent f1df13dc
......@@ -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);
}
......
......@@ -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);
}
}
......@@ -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;
}
}
......@@ -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");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment