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");