Commit 5783b701 by Madhan Neethiraj

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

parent f1df13dc
...@@ -17,17 +17,15 @@ ...@@ -17,17 +17,15 @@
package org.apache.atlas.util; package org.apache.atlas.util;
import org.apache.atlas.web.dao.UserDao; import org.apache.atlas.web.dao.UserDao;
import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Options; import org.apache.commons.cli.Options;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.security.alias.CredentialProvider; import org.apache.hadoop.security.alias.CredentialProvider;
import org.apache.hadoop.security.alias.CredentialProviderFactory; import org.apache.hadoop.security.alias.CredentialProviderFactory;
import org.apache.hadoop.security.alias.JavaKeyStoreProvider;
import java.io.Console; import java.io.Console;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
...@@ -41,8 +39,7 @@ import static org.apache.atlas.security.SecurityProperties.TRUSTSTORE_PASSWORD_K ...@@ -41,8 +39,7 @@ import static org.apache.atlas.security.SecurityProperties.TRUSTSTORE_PASSWORD_K
* of the DGC server. * of the DGC server.
*/ */
public class CredentialProviderUtility { public class CredentialProviderUtility {
private static final String[] KEYS = private static final String[] KEYS = new String[] { KEYSTORE_PASSWORD_KEY, TRUSTSTORE_PASSWORD_KEY, SERVER_CERT_PASSWORD_KEY };
new String[]{KEYSTORE_PASSWORD_KEY, TRUSTSTORE_PASSWORD_KEY, SERVER_CERT_PASSWORD_KEY};
public static abstract class TextDevice { public static abstract class TextDevice {
public abstract void printf(String fmt, Object... params); public abstract void printf(String fmt, Object... params);
...@@ -75,34 +72,29 @@ public class CredentialProviderUtility { ...@@ -75,34 +72,29 @@ public class CredentialProviderUtility {
public static TextDevice textDevice = DEFAULT_TEXT_DEVICE; public static TextDevice textDevice = DEFAULT_TEXT_DEVICE;
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
Options options = new Options();
try { try {
createOptions(options); CommandLine cmd = new DefaultParser().parse(createOptions(), args);
boolean generatePasswordOption = cmd.hasOption("g");
CommandLine cmd = new BasicParser().parse(options, args);
boolean generatePasswordOption = cmd.hasOption("g");
if (generatePasswordOption) { if (generatePasswordOption) {
String userName = cmd.getOptionValue("u"); String userName = cmd.getOptionValue("u");
String password = cmd.getOptionValue("p"); String password = cmd.getOptionValue("p");
if (userName != null && password != null) { if (userName != null && password != null) {
String encryptedPassword = UserDao.encrypt(password, userName); String encryptedPassword = UserDao.encrypt(password);
boolean silentOption = cmd.hasOption("s"); boolean silentOption = cmd.hasOption("s");
if (silentOption) { if (silentOption) {
System.out.println(encryptedPassword); System.out.println(encryptedPassword);
} else { } else {
System.out.println("Your encrypted password is : " + encryptedPassword); System.out.println("Your encrypted password is : " + encryptedPassword);
} }
} else { } else {
System.out.println("Please provide username and password as input. Usage:" + System.out.println("Please provide username and password as input. Usage: cputil.py -g -u <username> -p <password>");
" cputil.py -g -u <username> -p <password>");
} }
return; return;
} }
} catch (Exception e) { } catch (Exception e) {
System.out.println("Exception while generatePassword " + e.getMessage()); System.out.println("Exception while generatePassword " + e.getMessage());
return; return;
...@@ -112,36 +104,42 @@ public class CredentialProviderUtility { ...@@ -112,36 +104,42 @@ public class CredentialProviderUtility {
CredentialProvider provider = getCredentialProvider(textDevice); CredentialProvider provider = getCredentialProvider(textDevice);
if(provider != null) { if(provider != null) {
char[] cred;
for (String key : KEYS) { for (String key : KEYS) {
cred = getPassword(textDevice, key); char[] cred = getPassword(textDevice, key);
// create a credential entry and store it // create a credential entry and store it
boolean overwrite = true;
if (provider.getCredentialEntry(key) != null) { if (provider.getCredentialEntry(key) != null) {
String choice = textDevice.readLine("Entry for %s already exists. Overwrite? (y/n) [y]:", key); String choice = textDevice.readLine("Entry for %s already exists. Overwrite? (y/n) [y]:", key);
overwrite = StringUtils.isEmpty(choice) || choice.equalsIgnoreCase("y"); boolean overwrite = StringUtils.isEmpty(choice) || choice.equalsIgnoreCase("y");
if (overwrite) { if (overwrite) {
provider.deleteCredentialEntry(key); provider.deleteCredentialEntry(key);
provider.flush(); provider.flush();
provider.createCredentialEntry(key, cred); provider.createCredentialEntry(key, cred);
provider.flush(); provider.flush();
textDevice.printf("Entry for %s was overwritten with the new value.\n", key); textDevice.printf("Entry for %s was overwritten with the new value.\n", key);
} else { } else {
textDevice.printf("Entry for %s was not overwritten.\n", key); textDevice.printf("Entry for %s was not overwritten.\n", key);
} }
} else { } else {
provider.createCredentialEntry(key, cred); provider.createCredentialEntry(key, cred);
provider.flush(); 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("g", "generatePassword", false, "Generate Password");
options.addOption("s", "silent", false, "Silent"); options.addOption("s", "silent", false, "Silent");
options.addOption("u", "username", true, "UserName"); options.addOption("u", "username", true, "UserName");
options.addOption("p", "password", true, "Password"); options.addOption("p", "password", true, "Password");
return options;
} }
/** /**
...@@ -151,32 +149,39 @@ public class CredentialProviderUtility { ...@@ -151,32 +149,39 @@ public class CredentialProviderUtility {
* @return the password. * @return the password.
*/ */
private static char[] getPassword(TextDevice textDevice, String key) { private static char[] getPassword(TextDevice textDevice, String key) {
boolean noMatch; char[] ret;
char[] cred = new char[0];
char[] passwd1; while (true) {
char[] passwd2; char[] passwd1 = textDevice.readPassword("Please enter the password value for %s:", key);
do { char[] passwd2 = textDevice.readPassword("Please enter the password value for %s again:", key);
passwd1 = textDevice.readPassword("Please enter the password value for %s:", key); boolean isMatch = !Arrays.equals(passwd1, passwd2);
passwd2 = textDevice.readPassword("Please enter the password value for %s again:", key);
noMatch = !Arrays.equals(passwd1, passwd2); if (!isMatch) {
if (noMatch) {
if (passwd1 != null) {
Arrays.fill(passwd1, ' ');
}
textDevice.printf("Password entries don't match. Please try again.\n"); textDevice.printf("Password entries don't match. Please try again.\n");
} else { } else {
if (passwd1.length == 0) { if (passwd1 == null || passwd1.length == 0) {
textDevice.printf("An empty password is not valid. Please try again.\n"); textDevice.printf("An empty password is not valid. Please try again.\n");
noMatch = true;
} else { } else {
cred = passwd1; ret = passwd1;
if (passwd2 != null) {
Arrays.fill(passwd2, ' ');
}
break;
} }
} }
if (passwd1 != null) {
Arrays.fill(passwd1, ' ');
}
if (passwd2 != null) { if (passwd2 != null) {
Arrays.fill(passwd2, ' '); Arrays.fill(passwd2, ' ');
} }
} while (noMatch); }
return cred;
return ret;
} }
/**\ /**\
...@@ -190,7 +195,9 @@ public class CredentialProviderUtility { ...@@ -190,7 +195,9 @@ public class CredentialProviderUtility {
if (providerPath != null) { if (providerPath != null) {
Configuration conf = new Configuration(false); Configuration conf = new Configuration(false);
conf.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, providerPath); conf.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, providerPath);
return CredentialProviderFactory.getProviders(conf).get(0); return CredentialProviderFactory.getProviders(conf).get(0);
} }
......
...@@ -39,19 +39,20 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; ...@@ -39,19 +39,20 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.security.MessageDigest; import java.security.MessageDigest;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@Repository @Repository
public class UserDao { public class UserDao {
private static final String DEFAULT_USER_CREDENTIALS_PROPERTIES = "users-credentials.properties";
private static final Logger LOG = LoggerFactory.getLogger(UserDao.class); 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 @PostConstruct
public void init() { public void init() {
...@@ -59,49 +60,61 @@ public class UserDao { ...@@ -59,49 +60,61 @@ public class UserDao {
} }
void loadFileLoginsDetails() { void loadFileLoginsDetails() {
userLogins.clear();
InputStream inStr = null; InputStream inStr = null;
try { try {
Configuration configuration = ApplicationProperties.get(); 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); inStr = ApplicationProperties.getFileAsInputStream(configuration, "atlas.authentication.method.file.filename", DEFAULT_USER_CREDENTIALS_PROPERTIES);
userLogins = new Properties();
userLogins.load(inStr); userLogins.load(inStr);
} catch (IOException | AtlasException e) { } catch (IOException | AtlasException e) {
LOG.error("Error while reading user.properties file", e); LOG.error("Error while reading user.properties file", e);
throw new RuntimeException(e); throw new RuntimeException(e);
} finally { } finally {
if(inStr != null) { if (inStr != null) {
try { try {
inStr.close(); inStr.close();
} catch(Exception excp) { } catch (Exception excp) {
// ignore // ignore
} }
} }
} }
} }
public User loadUserByUsername(final String username) public User loadUserByUsername(final String username) throws AuthenticationException {
throws AuthenticationException {
String userdetailsStr = userLogins.getProperty(username); String userdetailsStr = userLogins.getProperty(username);
if (userdetailsStr == null || userdetailsStr.isEmpty()) { if (userdetailsStr == null || userdetailsStr.isEmpty()) {
throw new UsernameNotFoundException("Username not found." throw new UsernameNotFoundException("Username not found." + username);
+ username);
} }
String password = "";
String role = ""; String password = "";
String dataArr[] = userdetailsStr.split("::"); String role = "";
String[] dataArr = userdetailsStr.split("::");
if (dataArr != null && dataArr.length == 2) { if (dataArr != null && dataArr.length == 2) {
role = dataArr[0]; role = dataArr[0];
password = dataArr[1]; password = dataArr[1];
} else { } else {
LOG.error("User role credentials is not set properly for {}", username); LOG.error("User role credentials is not set properly for {}", username);
throw new AtlasAuthenticationException("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<>(); List<GrantedAuthority> grantedAuths = new ArrayList<>();
if (StringUtils.hasText(role)) { if (StringUtils.hasText(role)) {
grantedAuths.add(new SimpleGrantedAuthority(role)); grantedAuths.add(new SimpleGrantedAuthority(role));
} else { } else {
LOG.error("User role credentials is not set properly for {}", username); LOG.error("User role credentials is not set properly for {}", username);
throw new AtlasAuthenticationException("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 { ...@@ -115,25 +128,109 @@ public class UserDao {
this.userLogins = userLogins; 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 { try {
MessageDigest digest = MessageDigest.getInstance("SHA-256"); ret = BCrypt.checkpw(password, encryptedPwd);
byte[] hash = digest.digest(base.getBytes("UTF-8")); } catch (Throwable excp) {
StringBuffer hexString = new StringBuffer(); 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) { for (byte aHash : hash) {
String hex = Integer.toHexString(0xff & aHash); String hex = Integer.toHexString(0xff & aHash);
if (hex.length() == 1) hexString.append('0');
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex); hexString.append(hex);
} }
return hexString.toString();
return hexString.toString();
} catch (Exception ex) { } catch (Exception ex) {
throw new AtlasAuthenticationException("Exception while encoding password.", 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 @@ ...@@ -16,9 +16,7 @@
*/ */
package org.apache.atlas.web.security; package org.apache.atlas.web.security;
import org.apache.atlas.ApplicationProperties;
import org.apache.atlas.web.dao.UserDao; import org.apache.atlas.web.dao.UserDao;
import org.apache.commons.configuration.Configuration;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
...@@ -30,7 +28,6 @@ import org.springframework.security.core.userdetails.UserDetails; ...@@ -30,7 +28,6 @@ import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.Collection; import java.util.Collection;
...@@ -41,57 +38,42 @@ public class AtlasFileAuthenticationProvider extends AtlasAbstractAuthentication ...@@ -41,57 +38,42 @@ public class AtlasFileAuthenticationProvider extends AtlasAbstractAuthentication
private static Logger logger = LoggerFactory.getLogger(AtlasFileAuthenticationProvider.class); private static Logger logger = LoggerFactory.getLogger(AtlasFileAuthenticationProvider.class);
private final UserDetailsService userDetailsService; private final UserDetailsService userDetailsService;
private boolean v1ValidationEnabled = true;
@Inject @Inject
public AtlasFileAuthenticationProvider(UserDetailsService userDetailsService) { public AtlasFileAuthenticationProvider(UserDetailsService userDetailsService) {
this.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 @Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException { public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName(); String username = authentication.getName();
String password = (String) authentication.getCredentials(); String password = (String) authentication.getCredentials();
if (username == null || username.isEmpty()) { if (username == null || username.isEmpty()) {
logger.error("Username can't be null or empty."); 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()) { if (password == null || password.isEmpty()) {
logger.error("Password can't be null or empty."); 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()); throw new BadCredentialsException("Password can't be null or empty.");
if (!isValidPassword && v1ValidationEnabled) {
encodedPassword = UserDao.getSha256Hash(password);
} }
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); logger.error("Wrong password " + username);
throw new BadCredentialsException("Wrong password"); throw new BadCredentialsException("Wrong password");
} }
Collection<? extends GrantedAuthority> authorities = user.getAuthorities(); Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
authentication = new UsernamePasswordAuthenticationToken(username, password, authorities); authentication = new UsernamePasswordAuthenticationToken(username, password, authorities);
return authentication; return authentication;
} }
} }
...@@ -92,6 +92,7 @@ public class FileAuthenticationTest { ...@@ -92,6 +92,7 @@ public class FileAuthenticationTest {
private void setupUserCredential(String tmpDir) throws Exception { private void setupUserCredential(String tmpDir) throws Exception {
StringBuilder credentialFileStr = new StringBuilder(1024); StringBuilder credentialFileStr = new StringBuilder(1024);
credentialFileStr.append("adminv3=ADMIN::$2a$10$ZVnkc2if06JMLCJEAhTKbOPeWDXTCFLL8zMA6FzZoP.bu8ThT43ha\n");
credentialFileStr.append("admin=ADMIN::a4a88c0872bf652bb9ed803ece5fd6e82354838a9bf59ab4babb1dab322154e1\n"); credentialFileStr.append("admin=ADMIN::a4a88c0872bf652bb9ed803ece5fd6e82354838a9bf59ab4babb1dab322154e1\n");
credentialFileStr.append("adminv1=ADMIN::8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918\n"); credentialFileStr.append("adminv1=ADMIN::8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918\n");
credentialFileStr.append("michael=DATA_SCIENTIST::95bfb24de17d285d734b9eaa9109bfe922adc85f20d2e5e66a78bddb4a4ebddb\n"); credentialFileStr.append("michael=DATA_SCIENTIST::95bfb24de17d285d734b9eaa9109bfe922adc85f20d2e5e66a78bddb4a4ebddb\n");
...@@ -112,6 +113,18 @@ public class FileAuthenticationTest { ...@@ -112,6 +113,18 @@ public class FileAuthenticationTest {
@Test @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() { public void testValidUserLogin() {
when(authentication.getName()).thenReturn("admin"); 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