diff --git a/server/src/main/java/org/elasticsearch/common/settings/Setting.java b/server/src/main/java/org/elasticsearch/common/settings/Setting.java index 7f3906ff5a251..94edb5a297afa 100644 --- a/server/src/main/java/org/elasticsearch/common/settings/Setting.java +++ b/server/src/main/java/org/elasticsearch/common/settings/Setting.java @@ -1017,6 +1017,18 @@ public static Setting simpleString(String key, Validator validat return new Setting<>(new SimpleKey(key), null, s -> "", Function.identity(), validator, properties); } + /** + * Creates a new Setting instance with a String value + * + * @param key the settings key for this setting. + * @param defaultValue the default String value. + * @param properties properties for this setting like scope, filtering... + * @return the Setting Object + */ + public static Setting simpleString(String key, String defaultValue, Property... properties) { + return new Setting<>(key, s -> defaultValue, Function.identity(), properties); + } + public static int parseInt(String s, int minValue, String key) { return parseInt(s, minValue, Integer.MAX_VALUE, key); } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java index 6ccc67a952753..11404d51700ec 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting.Property; import org.elasticsearch.xpack.core.security.SecurityField; +import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.ssl.SSLClientAuth; import org.elasticsearch.xpack.core.ssl.SSLConfigurationSettings; import org.elasticsearch.xpack.core.ssl.VerificationMode; @@ -20,6 +21,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Locale; +import java.util.function.Function; import static org.elasticsearch.xpack.core.security.SecurityField.USER_SETTING; @@ -27,6 +30,11 @@ * A container for xpack setting constants. */ public class XPackSettings { + + private XPackSettings() { + throw new IllegalStateException("Utility class should not be instantiated"); + } + /** Setting for enabling or disabling security. Defaults to true. */ public static final Setting SECURITY_ENABLED = Setting.boolSetting("xpack.security.enabled", true, Setting.Property.NodeScope); @@ -113,6 +121,17 @@ public class XPackSettings { DEFAULT_CIPHERS = ciphers; } + /* + * Do not allow insecure hashing algorithms to be used for password hashing + */ + public static final Setting PASSWORD_HASHING_ALGORITHM = new Setting<>( + "xpack.security.authc.password_hashing.algorithm", "bcrypt", Function.identity(), (v, s) -> { + if (Hasher.getAvailableAlgoStoredHash().contains(v.toLowerCase(Locale.ROOT)) == false) { + throw new IllegalArgumentException("Invalid algorithm: " + v + ". Only pbkdf2 or bcrypt family algorithms can be used for " + + "password hashing."); + } + }, Setting.Property.NodeScope); + public static final List DEFAULT_SUPPORTED_PROTOCOLS = Arrays.asList("TLSv1.2", "TLSv1.1", "TLSv1"); public static final SSLClientAuth CLIENT_AUTH_DEFAULT = SSLClientAuth.REQUIRED; public static final SSLClientAuth HTTP_CLIENT_AUTH_DEFAULT = SSLClientAuth.NONE; @@ -151,6 +170,7 @@ public static List> getAllSettings() { settings.add(SQL_ENABLED); settings.add(USER_SETTING); settings.add(ROLLUP_ENABLED); + settings.add(PASSWORD_HASHING_ALGORITHM); return Collections.unmodifiableList(settings); } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/user/ChangePasswordRequestBuilder.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/user/ChangePasswordRequestBuilder.java index b7ff68118e3cb..1d319aecb13b9 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/user/ChangePasswordRequestBuilder.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/user/ChangePasswordRequestBuilder.java @@ -41,22 +41,22 @@ public ChangePasswordRequestBuilder username(String username) { return this; } - public static char[] validateAndHashPassword(SecureString password) { + public static char[] validateAndHashPassword(SecureString password, Hasher hasher) { Validation.Error error = Validation.Users.validatePassword(password.getChars()); if (error != null) { ValidationException validationException = new ValidationException(); validationException.addValidationError(error.toString()); throw validationException; } - return Hasher.BCRYPT.hash(password); + return hasher.hash(password); } /** * Sets the password. Note: the char[] passed to this method will be cleared. */ - public ChangePasswordRequestBuilder password(char[] password) { + public ChangePasswordRequestBuilder password(char[] password, Hasher hasher) { try (SecureString secureString = new SecureString(password)) { - char[] hash = validateAndHashPassword(secureString); + char[] hash = validateAndHashPassword(secureString, hasher); request.passwordHash(hash); } return this; @@ -65,7 +65,8 @@ public ChangePasswordRequestBuilder password(char[] password) { /** * Populate the change password request from the source in the provided content type */ - public ChangePasswordRequestBuilder source(BytesReference source, XContentType xContentType) throws IOException { + public ChangePasswordRequestBuilder source(BytesReference source, XContentType xContentType, Hasher hasher) throws + IOException { // EMPTY is ok here because we never call namedObject try (InputStream stream = source.streamInput(); XContentParser parser = xContentType.xContent() @@ -80,7 +81,7 @@ public ChangePasswordRequestBuilder source(BytesReference source, XContentType x if (token == XContentParser.Token.VALUE_STRING) { String password = parser.text(); final char[] passwordChars = password.toCharArray(); - password(passwordChars); + password(passwordChars, hasher); assert CharBuffer.wrap(passwordChars).chars().noneMatch((i) -> (char) i != (char) 0) : "expected password to " + "clear the char[] but it did not!"; } else { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/user/PutUserRequestBuilder.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/user/PutUserRequestBuilder.java index 9974716055db6..a619bca2258c7 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/user/PutUserRequestBuilder.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/user/PutUserRequestBuilder.java @@ -9,7 +9,6 @@ import org.elasticsearch.action.ActionRequestBuilder; import org.elasticsearch.action.support.WriteRequestBuilder; import org.elasticsearch.client.ElasticsearchClient; -import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Strings; import org.elasticsearch.common.ValidationException; import org.elasticsearch.common.bytes.BytesReference; @@ -33,8 +32,6 @@ public class PutUserRequestBuilder extends ActionRequestBuilder implements WriteRequestBuilder { - private final Hasher hasher = Hasher.BCRYPT; - public PutUserRequestBuilder(ElasticsearchClient client) { this(client, PutUserAction.INSTANCE); } @@ -53,7 +50,7 @@ public PutUserRequestBuilder roles(String... roles) { return this; } - public PutUserRequestBuilder password(@Nullable char[] password) { + public PutUserRequestBuilder password(char[] password, Hasher hasher) { if (password != null) { Validation.Error error = Validation.Users.validatePassword(password); if (error != null) { @@ -96,7 +93,8 @@ public PutUserRequestBuilder enabled(boolean enabled) { /** * Populate the put user request using the given source and username */ - public PutUserRequestBuilder source(String username, BytesReference source, XContentType xContentType) throws IOException { + public PutUserRequestBuilder source(String username, BytesReference source, XContentType xContentType, Hasher hasher) throws + IOException { Objects.requireNonNull(xContentType); username(username); // EMPTY is ok here because we never call namedObject @@ -113,7 +111,7 @@ public PutUserRequestBuilder source(String username, BytesReference source, XCon if (token == XContentParser.Token.VALUE_STRING) { String password = parser.text(); char[] passwordChars = password.toCharArray(); - password(passwordChars); + password(passwordChars, hasher); Arrays.fill(passwordChars, (char) 0); } else { throw new ElasticsearchParseException( diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/CachingUsernamePasswordRealmSettings.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/CachingUsernamePasswordRealmSettings.java index a1d031a5b00c0..6d060b0febbd4 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/CachingUsernamePasswordRealmSettings.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/CachingUsernamePasswordRealmSettings.java @@ -13,7 +13,8 @@ import java.util.Set; public final class CachingUsernamePasswordRealmSettings { - public static final Setting CACHE_HASH_ALGO_SETTING = Setting.simpleString("cache.hash_algo", Setting.Property.NodeScope); + public static final Setting CACHE_HASH_ALGO_SETTING = Setting.simpleString("cache.hash_algo", "ssha256", + Setting.Property.NodeScope); private static final TimeValue DEFAULT_TTL = TimeValue.timeValueMinutes(20); public static final Setting CACHE_TTL_SETTING = Setting.timeSetting("cache.ttl", DEFAULT_TTL, Setting.Property.NodeScope); private static final int DEFAULT_MAX_USERS = 100_000; //100k users diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/Hasher.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/Hasher.java index 0d4a1d23e7910..d12547bd90645 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/Hasher.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/Hasher.java @@ -5,15 +5,22 @@ */ package org.elasticsearch.xpack.core.security.authc.support; -import org.elasticsearch.common.Randomness; +import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.hash.MessageDigests; import org.elasticsearch.common.settings.SecureString; -import java.nio.charset.StandardCharsets; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import java.nio.CharBuffer; import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; import java.util.Base64; +import java.util.List; import java.util.Locale; -import java.util.Random; +import java.util.stream.Collectors; public enum Hasher { @@ -26,11 +33,7 @@ public char[] hash(SecureString text) { @Override public boolean verify(SecureString text, char[] hash) { - String hashStr = new String(hash); - if (!hashStr.startsWith(BCRYPT_PREFIX)) { - return false; - } - return BCrypt.checkpw(text, hashStr); + return verifyBcryptHash(text, hash); } }, @@ -43,11 +46,7 @@ public char[] hash(SecureString text) { @Override public boolean verify(SecureString text, char[] hash) { - String hashStr = new String(hash); - if (!hashStr.startsWith(BCRYPT_PREFIX)) { - return false; - } - return BCrypt.checkpw(text, hashStr); + return verifyBcryptHash(text, hash); } }, @@ -60,11 +59,7 @@ public char[] hash(SecureString text) { @Override public boolean verify(SecureString text, char[] hash) { - String hashStr = new String(hash); - if (!hashStr.startsWith(BCRYPT_PREFIX)) { - return false; - } - return BCrypt.checkpw(text, hashStr); + return verifyBcryptHash(text, hash); } }, @@ -77,11 +72,7 @@ public char[] hash(SecureString text) { @Override public boolean verify(SecureString text, char[] hash) { - String hashStr = new String(hash); - if (!hashStr.startsWith(BCRYPT_PREFIX)) { - return false; - } - return BCrypt.checkpw(text, hashStr); + return verifyBcryptHash(text, hash); } }, @@ -94,11 +85,7 @@ public char[] hash(SecureString text) { @Override public boolean verify(SecureString text, char[] hash) { - String hashStr = new String(hash); - if (!hashStr.startsWith(BCRYPT_PREFIX)) { - return false; - } - return BCrypt.checkpw(text, hashStr); + return verifyBcryptHash(text, hash); } }, @@ -111,11 +98,7 @@ public char[] hash(SecureString text) { @Override public boolean verify(SecureString text, char[] hash) { - String hashStr = new String(hash); - if (!hashStr.startsWith(BCRYPT_PREFIX)) { - return false; - } - return BCrypt.checkpw(text, hashStr); + return verifyBcryptHash(text, hash); } }, @@ -128,14 +111,166 @@ public char[] hash(SecureString text) { @Override public boolean verify(SecureString text, char[] hash) { - String hashStr = new String(hash); - if (!hashStr.startsWith(BCRYPT_PREFIX)) { - return false; - } - return BCrypt.checkpw(text, hashStr); + return verifyBcryptHash(text, hash); + } + }, + + BCRYPT10() { + @Override + public char[] hash(SecureString text) { + String salt = BCrypt.gensalt(10); + return BCrypt.hashpw(text, salt).toCharArray(); + } + + @Override + public boolean verify(SecureString text, char[] hash) { + return verifyBcryptHash(text, hash); + } + }, + + BCRYPT11() { + @Override + public char[] hash(SecureString text) { + String salt = BCrypt.gensalt(11); + return BCrypt.hashpw(text, salt).toCharArray(); + } + + @Override + public boolean verify(SecureString text, char[] hash) { + return verifyBcryptHash(text, hash); + } + }, + + BCRYPT12() { + @Override + public char[] hash(SecureString text) { + String salt = BCrypt.gensalt(12); + return BCrypt.hashpw(text, salt).toCharArray(); + } + + @Override + public boolean verify(SecureString text, char[] hash) { + return verifyBcryptHash(text, hash); } }, + BCRYPT13() { + @Override + public char[] hash(SecureString text) { + String salt = BCrypt.gensalt(13); + return BCrypt.hashpw(text, salt).toCharArray(); + } + + @Override + public boolean verify(SecureString text, char[] hash) { + return verifyBcryptHash(text, hash); + } + }, + + BCRYPT14() { + @Override + public char[] hash(SecureString text) { + String salt = BCrypt.gensalt(14); + return BCrypt.hashpw(text, salt).toCharArray(); + } + + @Override + public boolean verify(SecureString text, char[] hash) { + return verifyBcryptHash(text, hash); + } + }, + + PBKDF2() { + @Override + public char[] hash(SecureString data) { + return getPbkdf2Hash(data, PBKDF2_DEFAULT_COST); + } + + @Override + public boolean verify(SecureString data, char[] hash) { + return verifyPbkdf2Hash(data, hash); + } + + }, + + PBKDF2_1000() { + @Override + public char[] hash(SecureString data) { + return getPbkdf2Hash(data, 1000); + } + + @Override + public boolean verify(SecureString data, char[] hash) { + return verifyPbkdf2Hash(data, hash); + } + + }, + + PBKDF2_10000() { + @Override + public char[] hash(SecureString data) { + return getPbkdf2Hash(data, 10000); + } + + @Override + public boolean verify(SecureString data, char[] hash) { + return verifyPbkdf2Hash(data, hash); + } + + }, + + PBKDF2_50000() { + @Override + public char[] hash(SecureString data) { + return getPbkdf2Hash(data, 50000); + } + + @Override + public boolean verify(SecureString data, char[] hash) { + return verifyPbkdf2Hash(data, hash); + } + + }, + + PBKDF2_100000() { + @Override + public char[] hash(SecureString data) { + return getPbkdf2Hash(data, 100000); + } + + @Override + public boolean verify(SecureString data, char[] hash) { + return verifyPbkdf2Hash(data, hash); + } + + }, + + PBKDF2_500000() { + @Override + public char[] hash(SecureString data) { + return getPbkdf2Hash(data, 500000); + } + + @Override + public boolean verify(SecureString data, char[] hash) { + return verifyPbkdf2Hash(data, hash); + } + + }, + + PBKDF2_1000000() { + @Override + public char[] hash(SecureString data) { + return getPbkdf2Hash(data, 1000000); + } + + @Override + public boolean verify(SecureString data, char[] hash) { + return verifyPbkdf2Hash(data, hash); + } + + }, + SHA1() { @Override public char[] hash(SecureString text) { @@ -149,7 +284,7 @@ public char[] hash(SecureString text) { @Override public boolean verify(SecureString text, char[] hash) { String hashStr = new String(hash); - if (!hashStr.startsWith(SHA1_PREFIX)) { + if (hashStr.startsWith(SHA1_PREFIX) == false) { return false; } byte[] textBytes = CharArrays.toUtf8Bytes(text.getChars()); @@ -173,7 +308,7 @@ public char[] hash(SecureString text) { @Override public boolean verify(SecureString text, char[] hash) { String hashStr = new String(hash); - if (!hashStr.startsWith(MD5_PREFIX)) { + if (hashStr.startsWith(MD5_PREFIX) == false) { return false; } hashStr = hashStr.substring(MD5_PREFIX.length()); @@ -189,29 +324,30 @@ public boolean verify(SecureString text, char[] hash) { public char[] hash(SecureString text) { MessageDigest md = MessageDigests.sha256(); md.update(CharArrays.toUtf8Bytes(text.getChars())); - char[] salt = SaltProvider.salt(8); - md.update(CharArrays.toUtf8Bytes(salt)); + byte[] salt = generateSalt(8); + md.update(salt); String hash = Base64.getEncoder().encodeToString(md.digest()); - char[] result = new char[SSHA256_PREFIX.length() + salt.length + hash.length()]; + char[] result = new char[SSHA256_PREFIX.length() + 12 + hash.length()]; System.arraycopy(SSHA256_PREFIX.toCharArray(), 0, result, 0, SSHA256_PREFIX.length()); - System.arraycopy(salt, 0, result, SSHA256_PREFIX.length(), salt.length); - System.arraycopy(hash.toCharArray(), 0, result, SSHA256_PREFIX.length() + salt.length, hash.length()); + System.arraycopy(Base64.getEncoder().encodeToString(salt).toCharArray(), 0, result, SSHA256_PREFIX.length(), 12); + System.arraycopy(hash.toCharArray(), 0, result, SSHA256_PREFIX.length() + 12, hash.length()); return result; } @Override public boolean verify(SecureString text, char[] hash) { String hashStr = new String(hash); - if (!hashStr.startsWith(SSHA256_PREFIX)) { + if (hashStr.startsWith(SSHA256_PREFIX) == false) { return false; } hashStr = hashStr.substring(SSHA256_PREFIX.length()); char[] saltAndHash = hashStr.toCharArray(); MessageDigest md = MessageDigests.sha256(); md.update(CharArrays.toUtf8Bytes(text.getChars())); - md.update(new String(saltAndHash, 0, 8).getBytes(StandardCharsets.UTF_8)); + // Base64 string length : (4*(n/3)) rounded up to the next multiple of 4 because of padding, 12 for 8 bytes + md.update(Base64.getDecoder().decode(new String(saltAndHash, 0, 12))); String computedHash = Base64.getEncoder().encodeToString(md.digest()); - return CharArrays.constantTimeEquals(computedHash, new String(saltAndHash, 8, saltAndHash.length - 8)); + return CharArrays.constantTimeEquals(computedHash, new String(saltAndHash, 12, saltAndHash.length - 12)); } }, @@ -231,11 +367,22 @@ public boolean verify(SecureString text, char[] hash) { private static final String SHA1_PREFIX = "{SHA}"; private static final String MD5_PREFIX = "{MD5}"; private static final String SSHA256_PREFIX = "{SSHA256}"; - - public static Hasher resolve(String name, Hasher defaultHasher) { - if (name == null) { - return defaultHasher; - } + private static final String PBKDF2_PREFIX = "{PBKDF2}"; + private static final int PBKDF2_DEFAULT_COST = 10000; + private static final int PBKDF2_KEY_LENGTH = 256; + private static final int BCRYPT_DEFAULT_COST = 10; + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + + /** + * Returns a {@link Hasher} instance of the appropriate algorithm and associated cost as + * indicated by the {@code name}. Name identifiers for the default costs for + * BCRYPT and PBKDF2 return the he default BCRYPT and PBKDF2 Hasher instead of the specific + * instances for the associated cost. + * + * @param name The name of the algorithm and cost combination identifier + * @return the hasher associated with the identifier + */ + public static Hasher resolve(String name) { switch (name.toLowerCase(Locale.ROOT)) { case "bcrypt": return BCRYPT; @@ -251,6 +398,30 @@ public static Hasher resolve(String name, Hasher defaultHasher) { return BCRYPT8; case "bcrypt9": return BCRYPT9; + case "bcrypt10": + return BCRYPT; + case "bcrypt11": + return BCRYPT11; + case "bcrypt12": + return BCRYPT12; + case "bcrypt13": + return BCRYPT13; + case "bcrypt14": + return BCRYPT14; + case "pbkdf2": + return PBKDF2; + case "pbkdf2_1000": + return PBKDF2_1000; + case "pbkdf2_10000": + return PBKDF2; + case "pbkdf2_50000": + return PBKDF2_50000; + case "pbkdf2_100000": + return PBKDF2_100000; + case "pbkdf2_500000": + return PBKDF2_500000; + case "pbkdf2_1000000": + return PBKDF2_1000000; case "sha1": return SHA1; case "md5": @@ -261,38 +432,136 @@ public static Hasher resolve(String name, Hasher defaultHasher) { case "clear_text": return NOOP; default: - return defaultHasher; + throw new IllegalArgumentException("unknown hash function [" + name + "]"); } } - public static Hasher resolve(String name) { - Hasher hasher = resolve(name, null); - if (hasher == null) { - throw new IllegalArgumentException("unknown hash function [" + name + "]"); + /** + * Returns a {@link Hasher} instance that can be used to verify the {@code hash} by inspecting the + * hash prefix and determining the algorithm used for its generation. If no specific algorithm + * prefix, can be determined {@code Hasher.NOOP} is returned. + * + * @param hash the char array from which the hashing algorithm is to be deduced + * @return the hasher that can be used for validation + */ + public static Hasher resolveFromHash(char[] hash) { + if (CharArrays.charsBeginsWith(BCRYPT_PREFIX, hash)) { + int cost = Integer.parseInt(new String(Arrays.copyOfRange(hash, BCRYPT_PREFIX.length(), hash.length - 54))); + return cost == BCRYPT_DEFAULT_COST ? Hasher.BCRYPT : resolve("bcrypt" + cost); + } else if (CharArrays.charsBeginsWith(PBKDF2_PREFIX, hash)) { + int cost = Integer.parseInt(new String(Arrays.copyOfRange(hash, PBKDF2_PREFIX.length(), hash.length - 90))); + return cost == PBKDF2_DEFAULT_COST ? Hasher.PBKDF2 : resolve("pbkdf2_" + cost); + } else if (CharArrays.charsBeginsWith(SHA1_PREFIX, hash)) { + return Hasher.SHA1; + } else if (CharArrays.charsBeginsWith(MD5_PREFIX, hash)) { + return Hasher.MD5; + } else if (CharArrays.charsBeginsWith(SSHA256_PREFIX, hash)) { + return Hasher.SSHA256; + } else { + // This is either a non hashed password from cache or a corrupted hash string. + return Hasher.NOOP; } - return hasher; } - public abstract char[] hash(SecureString data); - - public abstract boolean verify(SecureString data, char[] hash); - - static final class SaltProvider { + /** + * Verifies that the cryptographic hash of {@code data} is the same as {@code hash}. The + * hashing algorithm and its parameters(cost factor-iterations, salt) are deduced from the + * hash itself. The {@code hash} char array is not cleared after verification. + * + * @param data the SecureString to be hashed and verified + * @param hash the char array with the hash against which the string is verified + * @return true if the hash corresponds to the data, false otherwise + */ + public static boolean verifyHash(SecureString data, char[] hash) { + final Hasher hasher = resolveFromHash(hash); + return hasher.verify(data, hash); + } - static final char[] ALPHABET = new char[]{ - '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', - 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', - 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', - 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' - }; + private static char[] getPbkdf2Hash(SecureString data, int cost) { + try { + // Base64 string length : (4*(n/3)) rounded up to the next multiple of 4 because of padding. + // n is 32 (PBKDF2_KEY_LENGTH in bytes) and 2 is because of the dollar sign delimiters. + CharBuffer result = CharBuffer.allocate(PBKDF2_PREFIX.length() + String.valueOf(cost).length() + 2 + 44 + 44); + result.put(PBKDF2_PREFIX); + result.put(String.valueOf(cost)); + result.put("$"); + byte[] salt = generateSalt(32); + result.put(Base64.getEncoder().encodeToString(salt)); + result.put("$"); + SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHMACSHA512"); + PBEKeySpec keySpec = new PBEKeySpec(data.getChars(), salt, cost, PBKDF2_KEY_LENGTH); + result.put(Base64.getEncoder().encodeToString(secretKeyFactory.generateSecret(keySpec).getEncoded())); + return result.array(); + } catch (InvalidKeySpecException | NoSuchAlgorithmException e) { + throw new ElasticsearchException("Can't use PBKDF2 for password hashing", e); + } + } - public static char[] salt(int length) { - Random random = Randomness.get(); - char[] salt = new char[length]; - for (int i = 0; i < length; i++) { - salt[i] = ALPHABET[(random.nextInt(ALPHABET.length))]; + private static boolean verifyPbkdf2Hash(SecureString data, char[] hash) { + // Base64 string length : (4*(n/3)) rounded up to the next multiple of 4 because of padding. + // n is 32 (PBKDF2_KEY_LENGTH in bytes), so tokenLength is 44 + final int tokenLength = 44; + char[] hashChars = null; + char[] saltChars = null; + char[] computedPwdHash = null; + try { + if (CharArrays.charsBeginsWith(PBKDF2_PREFIX, hash) == false) { + return false; + } + hashChars = Arrays.copyOfRange(hash, hash.length - tokenLength, hash.length); + saltChars = Arrays.copyOfRange(hash, hash.length - (2 * tokenLength + 1), hash.length - (tokenLength + 1)); + int cost = Integer.parseInt(new String(Arrays.copyOfRange(hash, PBKDF2_PREFIX.length(), hash.length - (2 * tokenLength + 2)))); + SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHMACSHA512"); + PBEKeySpec keySpec = new PBEKeySpec(data.getChars(), Base64.getDecoder().decode(CharArrays.toUtf8Bytes(saltChars)), + cost, PBKDF2_KEY_LENGTH); + computedPwdHash = CharArrays.utf8BytesToChars(Base64.getEncoder() + .encode(secretKeyFactory.generateSecret(keySpec).getEncoded())); + final boolean result = CharArrays.constantTimeEquals(computedPwdHash, hashChars); + return result; + } catch (InvalidKeySpecException | NoSuchAlgorithmException e) { + throw new ElasticsearchException("Can't use PBKDF2 for password hashing", e); + } finally { + if (null != hashChars) { + Arrays.fill(hashChars, '\u0000'); + } + if (null != saltChars) { + Arrays.fill(saltChars, '\u0000'); + } + if (null != computedPwdHash) { + Arrays.fill(computedPwdHash, '\u0000'); } - return salt; } } + + private static boolean verifyBcryptHash(SecureString text, char[] hash) { + String hashStr = new String(hash); + if (hashStr.startsWith(BCRYPT_PREFIX) == false) { + return false; + } + return BCrypt.checkpw(text, hashStr); + } + + /** + * Returns a list of lower case String identifiers for the Hashing algorithm and parameter + * combinations that can be used for password hashing. The identifiers can be used to get + * an instance of the appropriate {@link Hasher} by using {@link #resolve(String) resolve()} + */ + public static List getAvailableAlgoStoredHash() { + return Arrays.stream(Hasher.values()).map(Hasher::name).map(name -> name.toLowerCase(Locale.ROOT)) + .filter(name -> (name.startsWith("pbkdf2") || name.startsWith("bcrypt"))) + .collect(Collectors.toList()); + } + + public abstract char[] hash(SecureString data); + + public abstract boolean verify(SecureString data, char[] hash); + + /** + * Generates an array of {@code length} random bytes using {@link java.security.SecureRandom} + */ + private static byte[] generateSalt(int length) { + byte[] salt = new byte[length]; + SECURE_RANDOM.nextBytes(salt); + return salt; + } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/client/SecurityClient.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/client/SecurityClient.java index af1cfe0579e03..26c35db1fc92b 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/client/SecurityClient.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/client/SecurityClient.java @@ -76,6 +76,7 @@ import org.elasticsearch.xpack.core.security.action.user.SetEnabledRequest; import org.elasticsearch.xpack.core.security.action.user.SetEnabledRequestBuilder; import org.elasticsearch.xpack.core.security.action.user.SetEnabledResponse; +import org.elasticsearch.xpack.core.security.authc.support.Hasher; import java.io.IOException; import java.util.List; @@ -187,12 +188,13 @@ public void deleteUser(DeleteUserRequest request, ActionListener listener) { @@ -203,13 +205,13 @@ public void putUser(PutUserRequest request, ActionListener list * Populates the {@link ChangePasswordRequest} with the username and password. Note: the passed in char[] will be cleared by this * method. */ - public ChangePasswordRequestBuilder prepareChangePassword(String username, char[] password) { - return new ChangePasswordRequestBuilder(client).username(username).password(password); + public ChangePasswordRequestBuilder prepareChangePassword(String username, char[] password, Hasher hasher) { + return new ChangePasswordRequestBuilder(client).username(username).password(password, hasher); } - public ChangePasswordRequestBuilder prepareChangePassword(String username, BytesReference source, XContentType xContentType) - throws IOException { - return new ChangePasswordRequestBuilder(client).username(username).source(source, xContentType); + public ChangePasswordRequestBuilder prepareChangePassword(String username, BytesReference source, XContentType xContentType, + Hasher hasher) throws IOException { + return new ChangePasswordRequestBuilder(client).username(username).source(source, xContentType, hasher); } public void changePassword(ChangePasswordRequest request, ActionListener listener) { diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/XPackSettingsTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/XPackSettingsTests.java index ed76a9a27809c..17934efe0a503 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/XPackSettingsTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/XPackSettingsTests.java @@ -6,8 +6,6 @@ package org.elasticsearch.xpack.core; import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.xpack.core.XPackSettings; - import javax.crypto.Cipher; import static org.hamcrest.Matchers.hasItem; diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/PasswordHashingAlgorithmBootstrapCheck.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/PasswordHashingAlgorithmBootstrapCheck.java new file mode 100644 index 0000000000000..c60c2ea18d061 --- /dev/null +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/PasswordHashingAlgorithmBootstrapCheck.java @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.security; + +import org.elasticsearch.bootstrap.BootstrapCheck; +import org.elasticsearch.bootstrap.BootstrapContext; +import org.elasticsearch.xpack.core.XPackSettings; + +import javax.crypto.SecretKeyFactory; +import java.security.NoSuchAlgorithmException; +import java.util.Locale; + +/** + * Bootstrap check to ensure that one of the allowed password hashing algorithms is + * selected and that it is available. + */ +public class PasswordHashingAlgorithmBootstrapCheck implements BootstrapCheck { + @Override + public BootstrapCheckResult check(BootstrapContext context) { + final String selectedAlgorithm = XPackSettings.PASSWORD_HASHING_ALGORITHM.get(context.settings); + if (selectedAlgorithm.toLowerCase(Locale.ROOT).startsWith("pbkdf2")) { + try { + SecretKeyFactory.getInstance("PBKDF2withHMACSHA512"); + } catch (NoSuchAlgorithmException e) { + final String errorMessage = String.format(Locale.ROOT, + "Support for PBKDF2WithHMACSHA512 must be available in order to use any of the " + + "PBKDF2 algorithms for the [%s] setting.", XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey()); + return BootstrapCheckResult.failure(errorMessage); + } + } + return BootstrapCheckResult.success(); + } + + @Override + public boolean alwaysEnforce() { + return true; + } +} diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index d9bbbec7834a0..f86931956e471 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -297,7 +297,8 @@ public Security(Settings settings, final Path configPath) { new TokenPassphraseBootstrapCheck(settings), new TokenSSLBootstrapCheck(), new PkiRealmBootstrapCheck(settings, getSslService()), - new TLSLicenseBootstrapCheck())); + new TLSLicenseBootstrapCheck(), + new PasswordHashingAlgorithmBootstrapCheck())); checks.addAll(InternalRealms.getBootstrapChecks(settings, env)); this.bootstrapChecks = Collections.unmodifiableList(checks); } else { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/user/TransportChangePasswordAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/user/TransportChangePasswordAction.java index 047b47dfa256b..d1afab16c8fec 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/user/TransportChangePasswordAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/user/TransportChangePasswordAction.java @@ -13,9 +13,11 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.security.action.user.ChangePasswordAction; import org.elasticsearch.xpack.core.security.action.user.ChangePasswordRequest; import org.elasticsearch.xpack.core.security.action.user.ChangePasswordResponse; +import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.user.AnonymousUser; import org.elasticsearch.xpack.core.security.user.SystemUser; import org.elasticsearch.xpack.core.security.user.XPackUser; @@ -44,6 +46,13 @@ protected void doExecute(ChangePasswordRequest request, ActionListener() { @Override public void onResponse(Void v) { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java index 3c1e266ddcb20..df48aeffd1d93 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java @@ -37,6 +37,7 @@ import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHit; import org.elasticsearch.xpack.core.XPackClientActionPlugin; +import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.security.ScrollHelper; import org.elasticsearch.xpack.core.security.action.realm.ClearRealmCacheRequest; import org.elasticsearch.xpack.core.security.action.realm.ClearRealmCacheResponse; @@ -81,8 +82,11 @@ public class NativeUsersStore extends AbstractComponent { private final Hasher hasher = Hasher.BCRYPT; + public static final String RESERVED_USER_TYPE = "reserved-user"; private final Client client; private final boolean isTribeNode; + private final ReservedUserInfo disabledDefaultUserInfo; + private final ReservedUserInfo enabledDefaultUserInfo; private final SecurityIndexManager securityIndex; @@ -91,6 +95,10 @@ public NativeUsersStore(Settings settings, Client client, SecurityIndexManager s this.client = client; this.isTribeNode = XPackClientActionPlugin.isTribeNode(settings); this.securityIndex = securityIndex; + final char[] emptyPasswordHash = Hasher.resolve(XPackSettings.PASSWORD_HASHING_ALGORITHM.get(settings)). + hash(new SecureString("".toCharArray())); + this.disabledDefaultUserInfo = new ReservedUserInfo(emptyPasswordHash, false, true); + this.enabledDefaultUserInfo = new ReservedUserInfo(emptyPasswordHash, true, true); } /** @@ -499,7 +507,7 @@ void verifyPassword(String username, final SecureString password, ActionListener getUserAndPassword(username, ActionListener.wrap((userAndPassword) -> { if (userAndPassword == null || userAndPassword.passwordHash() == null) { listener.onResponse(AuthenticationResult.notHandled()); - } else if (hasher.verify(password, userAndPassword.passwordHash())) { + } else if (userAndPassword.verifyPassword(password)) { listener.onResponse(AuthenticationResult.success(userAndPassword.user())); } else { listener.onResponse(AuthenticationResult.unsuccessful("Password authentication failed for " + username, null)); @@ -528,8 +536,7 @@ public void onResponse(GetResponse getResponse) { } else if (enabled == null) { listener.onFailure(new IllegalStateException("enabled must not be null!")); } else if (password.isEmpty()) { - listener.onResponse((enabled ? ReservedRealm.ENABLED_DEFAULT_USER_INFO : ReservedRealm - .DISABLED_DEFAULT_USER_INFO).deepClone()); + listener.onResponse((enabled ? enabledDefaultUserInfo : disabledDefaultUserInfo).deepClone()); } else { listener.onResponse(new ReservedUserInfo(password.toCharArray(), enabled, false)); } @@ -665,16 +672,21 @@ static final class ReservedUserInfo { public final char[] passwordHash; public final boolean enabled; public final boolean hasEmptyPassword; + private final Hasher hasher; ReservedUserInfo(char[] passwordHash, boolean enabled, boolean hasEmptyPassword) { this.passwordHash = passwordHash; this.enabled = enabled; this.hasEmptyPassword = hasEmptyPassword; + this.hasher = Hasher.resolveFromHash(this.passwordHash); } ReservedUserInfo deepClone() { return new ReservedUserInfo(Arrays.copyOf(passwordHash, passwordHash.length), enabled, hasEmptyPassword); } + boolean verifyPassword(SecureString data) { + return hasher.verify(data, this.passwordHash); + } } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealm.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealm.java index 3946a01784b16..31de03ff4330c 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealm.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealm.java @@ -49,10 +49,6 @@ public class ReservedRealm extends CachingUsernamePasswordRealm { public static final String TYPE = "reserved"; private final ReservedUserInfo bootstrapUserInfo; - static final char[] EMPTY_PASSWORD_HASH = Hasher.BCRYPT.hash(new SecureString("".toCharArray())); - static final ReservedUserInfo DISABLED_DEFAULT_USER_INFO = new ReservedUserInfo(EMPTY_PASSWORD_HASH, false, true); - static final ReservedUserInfo ENABLED_DEFAULT_USER_INFO = new ReservedUserInfo(EMPTY_PASSWORD_HASH, true, true); - public static final Setting ACCEPT_DEFAULT_PASSWORD_SETTING = Setting.boolSetting( SecurityField.setting("authc.accept_default_password"), true, Setting.Property.NodeScope, Setting.Property.Filtered, Setting.Property.Deprecated); @@ -64,6 +60,9 @@ public class ReservedRealm extends CachingUsernamePasswordRealm { private final boolean realmEnabled; private final boolean anonymousEnabled; private final SecurityIndexManager securityIndex; + private final Hasher reservedRealmHasher; + private final ReservedUserInfo disabledDefaultUserInfo; + private final ReservedUserInfo enabledDefaultUserInfo; public ReservedRealm(Environment env, Settings settings, NativeUsersStore nativeUsersStore, AnonymousUser anonymousUser, SecurityIndexManager securityIndex, ThreadPool threadPool) { @@ -73,9 +72,13 @@ public ReservedRealm(Environment env, Settings settings, NativeUsersStore native this.anonymousUser = anonymousUser; this.anonymousEnabled = AnonymousUser.isAnonymousEnabled(settings); this.securityIndex = securityIndex; - final char[] hash = BOOTSTRAP_ELASTIC_PASSWORD.get(settings).length() == 0 ? EMPTY_PASSWORD_HASH : - Hasher.BCRYPT.hash(BOOTSTRAP_ELASTIC_PASSWORD.get(settings)); - bootstrapUserInfo = new ReservedUserInfo(hash, true, hash == EMPTY_PASSWORD_HASH); + this.reservedRealmHasher = Hasher.resolve(XPackSettings.PASSWORD_HASHING_ALGORITHM.get(settings)); + final char[] emptyPasswordHash = reservedRealmHasher.hash(new SecureString("".toCharArray())); + disabledDefaultUserInfo = new ReservedUserInfo(emptyPasswordHash, false, true); + enabledDefaultUserInfo = new ReservedUserInfo(emptyPasswordHash, true, true); + final char[] hash = BOOTSTRAP_ELASTIC_PASSWORD.get(settings).length() == 0 ? emptyPasswordHash : + reservedRealmHasher.hash(BOOTSTRAP_ELASTIC_PASSWORD.get(settings)); + bootstrapUserInfo = new ReservedUserInfo(hash, true, hash == emptyPasswordHash); } @Override @@ -91,15 +94,15 @@ protected void doAuthenticate(UsernamePasswordToken token, ActionListener> listener) { private void getUserInfo(final String username, ActionListener listener) { if (userIsDefinedForCurrentSecurityMapping(username) == false) { logger.debug("Marking user [{}] as disabled because the security mapping is not at the required version", username); - listener.onResponse(DISABLED_DEFAULT_USER_INFO.deepClone()); + listener.onResponse(disabledDefaultUserInfo.deepClone()); } else if (securityIndex.indexExists() == false) { listener.onResponse(getDefaultUserInfo(username)); } else { @@ -212,7 +215,7 @@ private ReservedUserInfo getDefaultUserInfo(String username) { if (ElasticUser.NAME.equals(username)) { return bootstrapUserInfo.deepClone(); } else { - return ENABLED_DEFAULT_USER_INFO.deepClone(); + return enabledDefaultUserInfo.deepClone(); } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/UserAndPassword.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/UserAndPassword.java index 58351ac2879b5..3f636312f0f06 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/UserAndPassword.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/UserAndPassword.java @@ -5,6 +5,8 @@ */ package org.elasticsearch.xpack.security.authc.esnative; +import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.user.User; /** @@ -20,10 +22,12 @@ class UserAndPassword { private final User user; private final char[] passwordHash; + private final Hasher hasher; UserAndPassword(User user, char[] passwordHash) { this.user = user; this.passwordHash = passwordHash; + this.hasher = Hasher.resolveFromHash(this.passwordHash); } public User user() { @@ -34,6 +38,10 @@ public char[] passwordHash() { return this.passwordHash; } + boolean verifyPassword(SecureString data) { + return hasher.verify(data, this.passwordHash); + } + @Override public boolean equals(Object o) { return false; // Don't use this for user comparison diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/file/FileUserPasswdStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/file/FileUserPasswdStore.java index 5773bf5a44861..15a6c2c41daed 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/file/FileUserPasswdStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/file/FileUserPasswdStore.java @@ -46,10 +46,8 @@ public class FileUserPasswdStore { private final Logger logger; private final Path file; - private final Hasher hasher = Hasher.BCRYPT; private final Settings settings; private final CopyOnWriteArrayList listeners; - private volatile Map users; public FileUserPasswdStore(RealmConfig config, ResourceWatcherService watcherService) { @@ -84,7 +82,7 @@ public AuthenticationResult verifyPassword(String username, SecureString passwor if (hash == null) { return AuthenticationResult.notHandled(); } - if (hasher.verify(password, hash) == false) { + if (Hasher.verifyHash(password, hash) == false) { return AuthenticationResult.unsuccessful("Password authentication failed for " + username, null); } return AuthenticationResult.success(user.get()); @@ -149,7 +147,7 @@ public static Map parseFile(Path path, @Nullable Logger logger, // only trim the line because we have a format, our tool generates the formatted text and we shouldn't be lenient // and allow spaces in the format line = line.trim(); - int i = line.indexOf(":"); + int i = line.indexOf(':'); if (i <= 0 || i == line.length() - 1) { logger.error("invalid entry in users file [{}], line [{}]. skipping...", path.toAbsolutePath(), lineNr); continue; diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/file/tool/UsersTool.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/file/tool/UsersTool.java index 5d78a7d08f258..9d4dfba327e5d 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/file/tool/UsersTool.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/file/tool/UsersTool.java @@ -124,7 +124,7 @@ protected void execute(Terminal terminal, OptionSet options, Environment env) th if (users.containsKey(username)) { throw new UserException(ExitCodes.CODE_ERROR, "User [" + username + "] already exists"); } - Hasher hasher = Hasher.BCRYPT; + final Hasher hasher = Hasher.resolve(XPackSettings.PASSWORD_HASHING_ALGORITHM.get(env.settings())); users = new HashMap<>(users); // make modifiable users.put(username, hasher.hash(new SecureString(password))); FileUserPasswdStore.writeFile(users, passwordFile); @@ -228,7 +228,8 @@ protected void execute(Terminal terminal, OptionSet options, Environment env) th if (users.containsKey(username) == false) { throw new UserException(ExitCodes.NO_USER, "User [" + username + "] doesn't exist"); } - users.put(username, Hasher.BCRYPT.hash(new SecureString(password))); + final Hasher hasher = Hasher.resolve(XPackSettings.PASSWORD_HASHING_ALGORITHM.get(env.settings())); + users.put(username, hasher.hash(new SecureString(password))); FileUserPasswdStore.writeFile(users, file); attributesChecker.check(terminal); @@ -294,7 +295,7 @@ protected void execute(Terminal terminal, OptionSet options, Environment env) th Map userRolesToWrite = new HashMap<>(userRoles.size()); userRolesToWrite.putAll(userRoles); - if (roles.size() == 0) { + if (roles.isEmpty()) { userRolesToWrite.remove(username); } else { userRolesToWrite.put(username, new LinkedHashSet<>(roles).toArray(new String[]{})); @@ -367,7 +368,7 @@ static void listUsersAndRoles(Terminal terminal, Environment env, String usernam Path rolesFile = FileRolesStore.resolveFile(env).toAbsolutePath(); terminal.println(""); terminal.println(" [*] Role is not in the [" + rolesFile.toAbsolutePath() + "] file. If the role has been created " - + "using the API, please disregard this message."); + + "using the API, please disregard this message."); } } else { terminal.println(String.format(Locale.ROOT, "%-15s: -", username)); @@ -401,7 +402,7 @@ static void listUsersAndRoles(Terminal terminal, Environment env, String usernam Path rolesFile = FileRolesStore.resolveFile(env).toAbsolutePath(); terminal.println(""); terminal.println(" [*] Role is not in the [" + rolesFile.toAbsolutePath() + "] file. If the role has been created " - + "using the API, please disregard this message."); + + "using the API, please disregard this message."); } } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealm.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealm.java index 046d5d73203d0..3e76ad515fdde 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealm.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealm.java @@ -33,11 +33,11 @@ public abstract class CachingUsernamePasswordRealm extends UsernamePasswordRealm private final Cache>> cache; private final ThreadPool threadPool; - final Hasher hasher; + final Hasher cacheHasher; protected CachingUsernamePasswordRealm(String type, RealmConfig config, ThreadPool threadPool) { super(type, config); - hasher = Hasher.resolve(CachingUsernamePasswordRealmSettings.CACHE_HASH_ALGO_SETTING.get(config.settings()), Hasher.SSHA256); + cacheHasher = Hasher.resolve(CachingUsernamePasswordRealmSettings.CACHE_HASH_ALGO_SETTING.get(config.settings())); this.threadPool = threadPool; TimeValue ttl = CachingUsernamePasswordRealmSettings.CACHE_TTL_SETTING.get(config.settings()); if (ttl.getNanos() > 0) { @@ -103,7 +103,7 @@ private void authenticateWithCache(UsernamePasswordToken token, ActionListener(result, userWithHash)); } else { future.onResponse(new Tuple<>(result, null)); @@ -234,16 +234,14 @@ public final void lookupUser(String username, ActionListener listener) { private static class UserWithHash { final User user; final char[] hash; - final Hasher hasher; UserWithHash(User user, SecureString password, Hasher hasher) { this.user = Objects.requireNonNull(user); this.hash = password == null ? null : hasher.hash(password); - this.hasher = hasher; } boolean verify(SecureString password) { - return hash != null && hasher.verify(password, hash); + return hash != null && Hasher.verifyHash(password, hash); } } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/user/RestChangePasswordAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/user/RestChangePasswordAction.java index b47881de2db34..1b64b3ce2baee 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/user/RestChangePasswordAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/user/RestChangePasswordAction.java @@ -15,8 +15,10 @@ import org.elasticsearch.rest.RestResponse; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.action.RestBuilderListener; +import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.action.user.ChangePasswordResponse; +import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.client.SecurityClient; import org.elasticsearch.xpack.core.security.rest.RestRequestFilter; import org.elasticsearch.xpack.core.security.user.User; @@ -32,6 +34,7 @@ public class RestChangePasswordAction extends SecurityBaseRestHandler implements RestRequestFilter { private final SecurityContext securityContext; + private final Hasher passwordHasher = Hasher.resolve(XPackSettings.PASSWORD_HASHING_ALGORITHM.get(settings)); public RestChangePasswordAction(Settings settings, RestController controller, SecurityContext securityContext, XPackLicenseState licenseState) { @@ -61,7 +64,7 @@ public RestChannelConsumer innerPrepareRequest(RestRequest request, NodeClient c final String refresh = request.param("refresh"); return channel -> new SecurityClient(client) - .prepareChangePassword(username, request.requiredContent(), request.getXContentType()) + .prepareChangePassword(username, request.requiredContent(), request.getXContentType(), passwordHasher) .setRefreshPolicy(refresh) .execute(new RestBuilderListener(channel) { @Override diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/user/RestPutUserAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/user/RestPutUserAction.java index eaca9d8322ed0..33ea672525b24 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/user/RestPutUserAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/user/RestPutUserAction.java @@ -16,8 +16,10 @@ import org.elasticsearch.rest.RestResponse; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.action.RestBuilderListener; +import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.security.action.user.PutUserRequestBuilder; import org.elasticsearch.xpack.core.security.action.user.PutUserResponse; +import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.client.SecurityClient; import org.elasticsearch.xpack.core.security.rest.RestRequestFilter; import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler; @@ -34,6 +36,8 @@ */ public class RestPutUserAction extends SecurityBaseRestHandler implements RestRequestFilter { + private final Hasher passwordHasher = Hasher.resolve(XPackSettings.PASSWORD_HASHING_ALGORITHM.get(settings)); + public RestPutUserAction(Settings settings, RestController controller, XPackLicenseState licenseState) { super(settings, licenseState); controller.registerHandler(POST, "/_xpack/security/user/{username}", this); @@ -58,7 +62,7 @@ public String getName() { @Override public RestChannelConsumer innerPrepareRequest(RestRequest request, NodeClient client) throws IOException { PutUserRequestBuilder requestBuilder = new SecurityClient(client) - .preparePutUser(request.param("username"), request.requiredContent(), request.getXContentType()) + .preparePutUser(request.param("username"), request.requiredContent(), request.getXContentType(), passwordHasher) .setRefreshPolicy(request.param("refresh")); return channel -> requestBuilder.execute(new RestBuilderListener(channel) { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/AbstractPrivilegeTestCase.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/AbstractPrivilegeTestCase.java index 31a1b1eaabfb0..246b584a83b01 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/AbstractPrivilegeTestCase.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/AbstractPrivilegeTestCase.java @@ -15,7 +15,6 @@ import org.elasticsearch.client.ResponseException; import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.test.SecuritySingleNodeTestCase; -import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; import java.io.IOException; @@ -33,8 +32,6 @@ */ public abstract class AbstractPrivilegeTestCase extends SecuritySingleNodeTestCase { - protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(new SecureString("passwd".toCharArray()))); - protected void assertAccessIsAllowed(String user, String method, String uri, String body, Map params) throws IOException { Response response = getRestClient().performRequest(method, uri, params, entityOrNull(body), diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/ClearRealmsCacheTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/ClearRealmsCacheTests.java index fcab6f0d73240..3b552e449f469 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/ClearRealmsCacheTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/ClearRealmsCacheTests.java @@ -21,7 +21,6 @@ import org.elasticsearch.xpack.core.security.action.realm.ClearRealmCacheResponse; import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; import org.elasticsearch.xpack.core.security.authc.Realm; -import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; import org.elasticsearch.xpack.core.security.client.SecurityClient; import org.elasticsearch.xpack.core.security.user.User; @@ -44,7 +43,6 @@ import static org.hamcrest.Matchers.sameInstance; public class ClearRealmsCacheTests extends SecurityIntegTestCase { - private static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(new SecureString("passwd".toCharArray()))); private static String[] usernames; @@ -191,8 +189,10 @@ protected String configRoles() { @Override protected String configUsers() { StringBuilder builder = new StringBuilder(SecuritySettingsSource.CONFIG_STANDARD_USER); + final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(new SecureString + ("passwd".toCharArray()))); for (String username : usernames) { - builder.append(username).append(":").append(USERS_PASSWD_HASHED).append("\n"); + builder.append(username).append(":").append(usersPasswdHashed).append("\n"); } return builder.toString(); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/ClusterPrivilegeTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/ClusterPrivilegeTests.java index 19d61ed77c5f4..a50b8fcdde4b4 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/ClusterPrivilegeTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/ClusterPrivilegeTests.java @@ -9,7 +9,9 @@ import org.elasticsearch.cluster.SnapshotsInProgress; import org.elasticsearch.common.Strings; import org.elasticsearch.common.network.NetworkModule; +import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -34,11 +36,6 @@ public class ClusterPrivilegeTests extends AbstractPrivilegeTestCase { " - names: 'someindex'\n" + " privileges: [ all ]\n"; - private static final String USERS = - "user_a:" + USERS_PASSWD_HASHED + "\n" + - "user_b:" + USERS_PASSWD_HASHED + "\n" + - "user_c:" + USERS_PASSWD_HASHED + "\n"; - private static final String USERS_ROLES = "role_a:user_a\n" + "role_b:user_b\n" + @@ -71,7 +68,13 @@ protected String configRoles() { @Override protected String configUsers() { - return super.configUsers() + USERS; + final String usersPasswdHashed = new String(Hasher.resolve( + randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt", "bcrypt9")).hash(new SecureString("passwd".toCharArray()))); + return super.configUsers() + + "user_a:" + usersPasswdHashed + "\n" + + "user_b:" + usersPasswdHashed + "\n" + + "user_c:" + usersPasswdHashed + "\n"; + } @Override diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DateMathExpressionIntegTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DateMathExpressionIntegTests.java index 016ccab87eb15..5ec9f8fbe06ad 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DateMathExpressionIntegTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DateMathExpressionIntegTests.java @@ -21,7 +21,6 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.test.SecurityIntegTestCase; -import org.elasticsearch.xpack.core.security.authc.support.Hasher; import java.util.Collections; @@ -34,12 +33,13 @@ public class DateMathExpressionIntegTests extends SecurityIntegTestCase { protected static final SecureString USERS_PASSWD = new SecureString("change_me".toCharArray()); - protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(USERS_PASSWD)); @Override protected String configUsers() { + final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(USERS_PASSWD)); + return super.configUsers() + - "user1:" + USERS_PASSWD_HASHED + "\n"; + "user1:" + usersPasswdHashed + "\n"; } @Override diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DocumentAndFieldLevelSecurityTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DocumentAndFieldLevelSecurityTests.java index 1b131c8be1b5b..ad2653d581148 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DocumentAndFieldLevelSecurityTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DocumentAndFieldLevelSecurityTests.java @@ -22,7 +22,6 @@ import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.xpack.core.XPackSettings; -import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.security.Security; import org.junit.After; @@ -42,18 +41,19 @@ public class DocumentAndFieldLevelSecurityTests extends SecurityIntegTestCase { protected static final SecureString USERS_PASSWD = new SecureString("change_me".toCharArray()); - protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(USERS_PASSWD)); private boolean indicesAdminFilteredFieldsWasSet = false; @Override protected String configUsers() { + final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(USERS_PASSWD)); + return super.configUsers() + - "user1:" + USERS_PASSWD_HASHED + "\n" + - "user2:" + USERS_PASSWD_HASHED + "\n" + - "user3:" + USERS_PASSWD_HASHED + "\n" + - "user4:" + USERS_PASSWD_HASHED + "\n" + - "user5:" + USERS_PASSWD_HASHED + "\n"; + "user1:" + usersPasswdHashed + "\n" + + "user2:" + usersPasswdHashed + "\n" + + "user3:" + usersPasswdHashed + "\n" + + "user4:" + usersPasswdHashed + "\n" + + "user5:" + usersPasswdHashed + "\n"; } @Override diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DocumentLevelSecurityRandomTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DocumentLevelSecurityRandomTests.java index f4d1f429d8c6b..27a78981af069 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DocumentLevelSecurityRandomTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DocumentLevelSecurityRandomTests.java @@ -12,7 +12,6 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.xpack.core.XPackSettings; -import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.test.SecurityIntegTestCase; import java.util.ArrayList; @@ -27,7 +26,6 @@ public class DocumentLevelSecurityRandomTests extends SecurityIntegTestCase { protected static final SecureString USERS_PASSWD = new SecureString("change_me".toCharArray()); - protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(new SecureString("change_me".toCharArray()))); // can't add a second test method, because each test run creates a new instance of this class and that will will result // in a new random value: @@ -35,9 +33,11 @@ public class DocumentLevelSecurityRandomTests extends SecurityIntegTestCase { @Override protected String configUsers() { + final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(USERS_PASSWD)); + StringBuilder builder = new StringBuilder(super.configUsers()); for (int i = 1; i <= numberOfRoles; i++) { - builder.append("user").append(i).append(':').append(USERS_PASSWD_HASHED).append('\n'); + builder.append("user").append(i).append(':').append(usersPasswdHashed).append('\n'); } return builder.toString(); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DocumentLevelSecurityTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DocumentLevelSecurityTests.java index e958a39d56e74..13af71b539081 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DocumentLevelSecurityTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DocumentLevelSecurityTests.java @@ -55,7 +55,6 @@ import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.security.LocalStateSecurity; -import org.elasticsearch.xpack.core.security.authc.support.Hasher; import java.util.Arrays; import java.util.Collection; @@ -84,7 +83,6 @@ public class DocumentLevelSecurityTests extends SecurityIntegTestCase { protected static final SecureString USERS_PASSWD = new SecureString("change_me".toCharArray()); - protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(USERS_PASSWD)); @Override protected Collection> nodePlugins() { @@ -98,10 +96,11 @@ protected Collection> transportClientPlugins() { @Override protected String configUsers() { + final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(USERS_PASSWD)); return super.configUsers() + - "user1:" + USERS_PASSWD_HASHED + "\n" + - "user2:" + USERS_PASSWD_HASHED + "\n" + - "user3:" + USERS_PASSWD_HASHED + "\n" ; + "user1:" + usersPasswdHashed + "\n" + + "user2:" + usersPasswdHashed + "\n" + + "user3:" + usersPasswdHashed + "\n"; } @Override diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/FieldLevelSecurityRandomTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/FieldLevelSecurityRandomTests.java index 2a8e9e7a0f890..f1496701e6a79 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/FieldLevelSecurityRandomTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/FieldLevelSecurityRandomTests.java @@ -12,7 +12,6 @@ import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.xpack.core.XPackSettings; -import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.test.SecurityIntegTestCase; import java.util.ArrayList; @@ -34,18 +33,19 @@ public class FieldLevelSecurityRandomTests extends SecurityIntegTestCase { protected static final SecureString USERS_PASSWD = new SecureString("change_me".toCharArray()); - protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(new SecureString("change_me".toCharArray()))); private static Set allowedFields; private static Set disAllowedFields; @Override protected String configUsers() { + final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(USERS_PASSWD)); + return super.configUsers() + - "user1:" + USERS_PASSWD_HASHED + "\n" + - "user2:" + USERS_PASSWD_HASHED + "\n" + - "user3:" + USERS_PASSWD_HASHED + "\n" + - "user4:" + USERS_PASSWD_HASHED + "\n" ; + "user1:" + usersPasswdHashed + "\n" + + "user2:" + usersPasswdHashed + "\n" + + "user3:" + usersPasswdHashed + "\n" + + "user4:" + usersPasswdHashed + "\n"; } @Override diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/FieldLevelSecurityTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/FieldLevelSecurityTests.java index b13c0dca8f3fa..161babc83df8d 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/FieldLevelSecurityTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/FieldLevelSecurityTests.java @@ -40,7 +40,6 @@ import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.security.LocalStateSecurity; -import org.elasticsearch.xpack.core.security.authc.support.Hasher; import java.util.Arrays; import java.util.Collection; @@ -69,7 +68,6 @@ public class FieldLevelSecurityTests extends SecurityIntegTestCase { protected static final SecureString USERS_PASSWD = new SecureString("change_me".toCharArray()); - protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(new SecureString("change_me".toCharArray()))); @Override protected Collection> nodePlugins() { @@ -84,15 +82,16 @@ protected Collection> transportClientPlugins() { @Override protected String configUsers() { + final String usersPasswHashed = new String(getFastStoredHashAlgoForTests().hash(USERS_PASSWD)); return super.configUsers() + - "user1:" + USERS_PASSWD_HASHED + "\n" + - "user2:" + USERS_PASSWD_HASHED + "\n" + - "user3:" + USERS_PASSWD_HASHED + "\n" + - "user4:" + USERS_PASSWD_HASHED + "\n" + - "user5:" + USERS_PASSWD_HASHED + "\n" + - "user6:" + USERS_PASSWD_HASHED + "\n" + - "user7:" + USERS_PASSWD_HASHED + "\n" + - "user8:" + USERS_PASSWD_HASHED + "\n"; + "user1:" + usersPasswHashed + "\n" + + "user2:" + usersPasswHashed + "\n" + + "user3:" + usersPasswHashed + "\n" + + "user4:" + usersPasswHashed + "\n" + + "user5:" + usersPasswHashed + "\n" + + "user6:" + usersPasswHashed + "\n" + + "user7:" + usersPasswHashed + "\n" + + "user8:" + usersPasswHashed + "\n"; } @Override diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/IndexPrivilegeTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/IndexPrivilegeTests.java index b1428040080d4..c41ca2b470fad 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/IndexPrivilegeTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/IndexPrivilegeTests.java @@ -10,6 +10,7 @@ import org.elasticsearch.common.network.NetworkModule; import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; import org.junit.Before; @@ -86,22 +87,6 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase { " privileges: [ index ]\n" + "\n"; - private static final String USERS = - "admin:" + USERS_PASSWD_HASHED + "\n" + - "u1:" + USERS_PASSWD_HASHED + "\n" + - "u2:" + USERS_PASSWD_HASHED + "\n" + - "u3:" + USERS_PASSWD_HASHED + "\n" + - "u4:" + USERS_PASSWD_HASHED + "\n" + - "u5:" + USERS_PASSWD_HASHED + "\n" + - "u6:" + USERS_PASSWD_HASHED + "\n" + - "u7:" + USERS_PASSWD_HASHED + "\n"+ - "u8:" + USERS_PASSWD_HASHED + "\n"+ - "u9:" + USERS_PASSWD_HASHED + "\n" + - "u11:" + USERS_PASSWD_HASHED + "\n" + - "u12:" + USERS_PASSWD_HASHED + "\n" + - "u13:" + USERS_PASSWD_HASHED + "\n" + - "u14:" + USERS_PASSWD_HASHED + "\n"; - private static final String USERS_ROLES = "all_indices_role:admin,u8\n" + "all_cluster_role:admin\n" + @@ -133,7 +118,24 @@ protected String configRoles() { @Override protected String configUsers() { - return super.configUsers() + USERS; + final String usersPasswdHashed = new String(Hasher.resolve( + randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt", "bcrypt9")).hash(new SecureString("passwd".toCharArray()))); + + return super.configUsers() + + "admin:" + usersPasswdHashed + "\n" + + "u1:" + usersPasswdHashed + "\n" + + "u2:" + usersPasswdHashed + "\n" + + "u3:" + usersPasswdHashed + "\n" + + "u4:" + usersPasswdHashed + "\n" + + "u5:" + usersPasswdHashed + "\n" + + "u6:" + usersPasswdHashed + "\n" + + "u7:" + usersPasswdHashed + "\n" + + "u8:" + usersPasswdHashed + "\n" + + "u9:" + usersPasswdHashed + "\n" + + "u11:" + usersPasswdHashed + "\n" + + "u12:" + usersPasswdHashed + "\n" + + "u13:" + usersPasswdHashed + "\n" + + "u14:" + usersPasswdHashed + "\n"; } @Override diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/IndicesPermissionsWithAliasesWildcardsAndRegexsTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/IndicesPermissionsWithAliasesWildcardsAndRegexsTests.java index 9982b42b859f1..b20c1adb9b997 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/IndicesPermissionsWithAliasesWildcardsAndRegexsTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/IndicesPermissionsWithAliasesWildcardsAndRegexsTests.java @@ -10,7 +10,6 @@ import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.xpack.core.XPackSettings; -import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.test.SecurityIntegTestCase; import java.util.Collections; @@ -24,12 +23,12 @@ public class IndicesPermissionsWithAliasesWildcardsAndRegexsTests extends SecurityIntegTestCase { protected static final SecureString USERS_PASSWD = new SecureString("change_me".toCharArray()); - protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(new SecureString("change_me".toCharArray()))); @Override protected String configUsers() { + final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(USERS_PASSWD)); return super.configUsers() + - "user1:" + USERS_PASSWD_HASHED + "\n"; + "user1:" + usersPasswdHashed + "\n"; } @Override diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/KibanaUserRoleIntegTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/KibanaUserRoleIntegTests.java index a3174a02e99dd..cc080a846fae3 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/KibanaUserRoleIntegTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/KibanaUserRoleIntegTests.java @@ -21,7 +21,6 @@ import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.test.SecurityIntegTestCase; -import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; import java.util.Locale; @@ -40,7 +39,6 @@ public class KibanaUserRoleIntegTests extends SecurityIntegTestCase { protected static final SecureString USERS_PASSWD = new SecureString("change_me".toCharArray()); - protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(new SecureString("change_me".toCharArray()))); @Override public String configRoles() { @@ -55,8 +53,9 @@ public String configRoles() { @Override public String configUsers() { + final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(USERS_PASSWD)); return super.configUsers() + - "kibana_user:" + USERS_PASSWD_HASHED; + "kibana_user:" + usersPasswdHashed; } @Override diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/MultipleIndicesPermissionsTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/MultipleIndicesPermissionsTests.java index a8750d5b80232..675300e25760e 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/MultipleIndicesPermissionsTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/MultipleIndicesPermissionsTests.java @@ -13,7 +13,6 @@ import org.elasticsearch.client.Client; import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.rest.RestStatus; -import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.test.SecuritySettingsSource; @@ -28,8 +27,8 @@ import static org.hamcrest.Matchers.is; public class MultipleIndicesPermissionsTests extends SecurityIntegTestCase { - protected static final SecureString PASSWD = new SecureString("passwd".toCharArray()); - protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(PASSWD)); + + protected static final SecureString USERS_PASSWD = new SecureString("passwd".toCharArray()); @Override protected String configRoles() { @@ -58,9 +57,10 @@ protected String configRoles() { @Override protected String configUsers() { + final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(USERS_PASSWD)); return SecuritySettingsSource.CONFIG_STANDARD_USER + - "user_a:" + USERS_PASSWD_HASHED + "\n" + - "user_ab:" + USERS_PASSWD_HASHED + "\n"; + "user_a:" + usersPasswdHashed + "\n" + + "user_ab:" + usersPasswdHashed + "\n"; } @Override @@ -145,7 +145,7 @@ public void testMultipleRoles() throws Exception { Client client = internalCluster().transportClient(); SearchResponse response = client - .filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user_a", PASSWD))) + .filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user_a", USERS_PASSWD))) .prepareSearch("a") .get(); assertNoFailures(response); @@ -156,7 +156,7 @@ public void testMultipleRoles() throws Exception { new String[] { "*" } : new String[] {}; response = client - .filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user_a", PASSWD))) + .filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user_a", USERS_PASSWD))) .prepareSearch(indices) .get(); assertNoFailures(response); @@ -165,7 +165,7 @@ public void testMultipleRoles() throws Exception { try { indices = randomBoolean() ? new String[] { "a", "b" } : new String[] { "b", "a" }; client - .filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user_a", PASSWD))) + .filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user_a", USERS_PASSWD))) .prepareSearch(indices) .get(); fail("expected an authorization excpetion when trying to search on multiple indices where there are no search permissions on " + @@ -175,14 +175,14 @@ public void testMultipleRoles() throws Exception { assertThat(e.status(), is(RestStatus.FORBIDDEN)); } - response = client.filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user_ab", PASSWD))) + response = client.filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user_ab", USERS_PASSWD))) .prepareSearch("b") .get(); assertNoFailures(response); assertHitCount(response, 1); indices = randomBoolean() ? new String[] { "a", "b" } : new String[] { "b", "a" }; - response = client.filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user_ab", PASSWD))) + response = client.filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user_ab", USERS_PASSWD))) .prepareSearch(indices) .get(); assertNoFailures(response); @@ -192,7 +192,7 @@ public void testMultipleRoles() throws Exception { new String[] { "_all"} : randomBoolean() ? new String[] { "*" } : new String[] {}; - response = client.filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user_ab", PASSWD))) + response = client.filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user_ab", USERS_PASSWD))) .prepareSearch(indices) .get(); assertNoFailures(response); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/PermissionPrecedenceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/PermissionPrecedenceTests.java index 41b5787cb26f3..f3fea81e20127 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/PermissionPrecedenceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/PermissionPrecedenceTests.java @@ -13,7 +13,6 @@ import org.elasticsearch.cluster.metadata.IndexTemplateMetaData; import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.test.SecurityIntegTestCase; -import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; import java.util.Collections; @@ -33,7 +32,6 @@ * index template actions. */ public class PermissionPrecedenceTests extends SecurityIntegTestCase { - protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(new SecureString("test123".toCharArray()))); @Override protected String configRoles() { @@ -51,9 +49,10 @@ protected String configRoles() { @Override protected String configUsers() { - return "admin:" + USERS_PASSWD_HASHED + "\n" + - "client:" + USERS_PASSWD_HASHED + "\n" + - "user:" + USERS_PASSWD_HASHED + "\n"; + final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(new SecureString("test123".toCharArray()))); + return "admin:" + usersPasswdHashed + "\n" + + "client:" + usersPasswdHashed + "\n" + + "user:" + usersPasswdHashed + "\n"; } @Override diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/SecurityClearScrollTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/SecurityClearScrollTests.java index 2ec899dbafe0d..9f86887566ac4 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/SecurityClearScrollTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/SecurityClearScrollTests.java @@ -15,7 +15,6 @@ import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.xpack.core.security.SecurityField; -import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.test.SecurityIntegTestCase; import org.junit.After; import org.junit.Before; @@ -33,15 +32,15 @@ import static org.hamcrest.Matchers.is; public class SecurityClearScrollTests extends SecurityIntegTestCase { - protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(new SecureString("change_me".toCharArray()))); private List scrollIds; @Override protected String configUsers() { + final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(new SecureString("change_me".toCharArray()))); return super.configUsers() + - "allowed_user:" + USERS_PASSWD_HASHED + "\n" + - "denied_user:" + USERS_PASSWD_HASHED + "\n" ; + "allowed_user:" + usersPasswdHashed + "\n" + + "denied_user:" + usersPasswdHashed + "\n"; } @Override diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/license/LicensingTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/license/LicensingTests.java index 7e91c1a91c0ef..627d12b97120f 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/license/LicensingTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/license/LicensingTests.java @@ -86,11 +86,6 @@ public class LicensingTests extends SecurityIntegTestCase { " - names: 'b'\n" + " privileges: [all]\n"; - public static final String USERS = - SecuritySettingsSource.CONFIG_STANDARD_USER + - "user_a:{plain}passwd\n" + - "user_b:{plain}passwd\n"; - public static final String USERS_ROLES = SecuritySettingsSource.CONFIG_STANDARD_USER_ROLES + "role_a:user_a,user_b\n" + @@ -103,7 +98,9 @@ protected String configRoles() { @Override protected String configUsers() { - return USERS; + return SecuritySettingsSource.CONFIG_STANDARD_USER + + "user_a:{plain}passwd\n" + + "user_b:{plain}passwd\n"; } @Override diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java index aac16e7c7ab78..cda7715521ada 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java @@ -38,6 +38,7 @@ import org.elasticsearch.plugins.Plugin; import org.elasticsearch.xpack.core.XPackClient; import org.elasticsearch.xpack.core.XPackSettings; +import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; import org.elasticsearch.xpack.core.security.client.SecurityClient; import org.elasticsearch.xpack.security.LocalStateSecurity; @@ -532,4 +533,8 @@ private static Index resolveSecurityIndex(MetaData metaData) { protected boolean isTransportSSLEnabled() { return customSecuritySettingsSource.isSslEnabled(); } + + protected static Hasher getFastStoredHashAlgoForTests() { + return Hasher.resolve(randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt", "bcrypt9")); + } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/test/SecuritySettingsSource.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/test/SecuritySettingsSource.java index 8ad1c61029a97..cc8c61a5c32e4 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/test/SecuritySettingsSource.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/test/SecuritySettingsSource.java @@ -42,6 +42,7 @@ import static com.carrotsearch.randomizedtesting.RandomizedTest.randomBoolean; import static org.apache.lucene.util.LuceneTestCase.createTempFile; +import static org.elasticsearch.test.ESTestCase.randomFrom; import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; import static org.elasticsearch.xpack.security.test.SecurityTestUtils.writeFile; @@ -57,7 +58,8 @@ public class SecuritySettingsSource extends ClusterDiscoveryConfiguration.Unicas public static final String TEST_USER_NAME = "test_user"; public static final String TEST_PASSWORD_HASHED = - new String(Hasher.BCRYPT.hash(new SecureString(SecuritySettingsSourceField.TEST_PASSWORD.toCharArray()))); + new String(Hasher.resolve(randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt9", "bcrypt8", "bcrypt")). + hash(new SecureString(SecuritySettingsSourceField.TEST_PASSWORD.toCharArray()))); public static final String TEST_ROLE = "user"; public static final String TEST_SUPERUSER = "test_superuser"; @@ -133,7 +135,7 @@ public Settings nodeSettings(int nodeOrdinal) { .put("xpack.security.authc.realms.file.type", FileRealmSettings.TYPE) .put("xpack.security.authc.realms.file.order", 0) .put("xpack.security.authc.realms.index.type", NativeRealmSettings.TYPE) - .put("xpack.security.authc.realms.index.order", "1"); + .put("xpack.security.authc.realms.index.order", "1"); addNodeSSLSettings(builder); return builder.build(); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/PasswordHashingAlgorithmBootstrapCheckTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/PasswordHashingAlgorithmBootstrapCheckTests.java new file mode 100644 index 0000000000000..8ca5c6c7216c5 --- /dev/null +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/PasswordHashingAlgorithmBootstrapCheckTests.java @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.security; + +import org.elasticsearch.bootstrap.BootstrapContext; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.core.XPackSettings; + +import javax.crypto.SecretKeyFactory; +import java.security.NoSuchAlgorithmException; + +public class PasswordHashingAlgorithmBootstrapCheckTests extends ESTestCase { + + public void testPasswordHashingAlgorithmBootstrapCheck() { + Settings settings = Settings.EMPTY; + assertFalse(new PasswordHashingAlgorithmBootstrapCheck().check(new BootstrapContext(settings, null)).isFailure()); + // The following two will always pass because for now we only test in environments where PBKDF2WithHMACSHA512 is supported + assertTrue(isSecretkeyFactoryAlgoAvailable("PBKDF2WithHMACSHA512")); + settings = Settings.builder().put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), "PBKDF2_10000").build(); + assertFalse(new PasswordHashingAlgorithmBootstrapCheck().check(new BootstrapContext(settings, null)).isFailure()); + + settings = Settings.builder().put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), "PBKDF2").build(); + assertFalse(new PasswordHashingAlgorithmBootstrapCheck().check(new BootstrapContext(settings, null)).isFailure()); + + settings = Settings.builder().put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), "BCRYPT").build(); + assertFalse(new PasswordHashingAlgorithmBootstrapCheck().check(new BootstrapContext(settings, null)).isFailure()); + + settings = Settings.builder().put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), "BCRYPT11").build(); + assertFalse(new PasswordHashingAlgorithmBootstrapCheck().check(new BootstrapContext(settings, null)).isFailure()); + } + + private boolean isSecretkeyFactoryAlgoAvailable(String algorithmId) { + try { + SecretKeyFactory.getInstance(algorithmId); + return true; + } catch (NoSuchAlgorithmException e) { + return false; + } + } +} diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/PutUserRequestBuilderTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/PutUserRequestBuilderTests.java index ac3308e2cc35d..a7bad498bdc33 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/PutUserRequestBuilderTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/PutUserRequestBuilderTests.java @@ -12,6 +12,7 @@ import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.core.security.action.user.PutUserRequest; import org.elasticsearch.xpack.core.security.action.user.PutUserRequestBuilder; +import org.elasticsearch.xpack.core.security.authc.support.Hasher; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -35,7 +36,7 @@ public void testNullValuesForEmailAndFullName() throws IOException { "}"; PutUserRequestBuilder builder = new PutUserRequestBuilder(mock(Client.class)); - builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON); + builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON, Hasher.BCRYPT); PutUserRequest request = builder.request(); assertThat(request.username(), is("kibana4")); @@ -55,7 +56,7 @@ public void testMissingEmailFullName() throws Exception { "}"; PutUserRequestBuilder builder = new PutUserRequestBuilder(mock(Client.class)); - builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON); + builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON, Hasher.BCRYPT); PutUserRequest request = builder.request(); assertThat(request.username(), is("kibana4")); @@ -76,7 +77,7 @@ public void testWithFullNameAndEmail() throws IOException { "}"; PutUserRequestBuilder builder = new PutUserRequestBuilder(mock(Client.class)); - builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON); + builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON, Hasher.BCRYPT); PutUserRequest request = builder.request(); assertThat(request.username(), is("kibana4")); @@ -98,7 +99,7 @@ public void testInvalidFullname() throws IOException { PutUserRequestBuilder builder = new PutUserRequestBuilder(mock(Client.class)); ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class, - () -> builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON)); + () -> builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON, Hasher.BCRYPT)); assertThat(e.getMessage(), containsString("expected field [full_name] to be of type string")); } @@ -114,7 +115,7 @@ public void testInvalidEmail() throws IOException { PutUserRequestBuilder builder = new PutUserRequestBuilder(mock(Client.class)); ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class, - () -> builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON)); + () -> builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON, Hasher.BCRYPT)); assertThat(e.getMessage(), containsString("expected field [email] to be of type string")); } @@ -131,7 +132,7 @@ public void testWithEnabled() throws IOException { PutUserRequestBuilder builder = new PutUserRequestBuilder(mock(Client.class)); PutUserRequest request = - builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON).request(); + builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON, Hasher.BCRYPT).request(); assertFalse(request.enabled()); } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/TransportChangePasswordActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/TransportChangePasswordActionTests.java index 6e85268f5855e..1a9a21a21089d 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/TransportChangePasswordActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/TransportChangePasswordActionTests.java @@ -15,6 +15,7 @@ import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.Transport; import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.security.action.user.ChangePasswordRequest; import org.elasticsearch.xpack.core.security.action.user.ChangePasswordResponse; import org.elasticsearch.xpack.core.security.authc.support.Hasher; @@ -48,17 +49,19 @@ public class TransportChangePasswordActionTests extends ESTestCase { public void testAnonymousUser() { - Settings settings = Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "superuser").build(); + final String hashingAlgorithm = randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt", "bcrypt9"); + Settings settings = Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "superuser") + .put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), hashingAlgorithm).build(); AnonymousUser anonymousUser = new AnonymousUser(settings); NativeUsersStore usersStore = mock(NativeUsersStore.class); TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet()); TransportChangePasswordAction action = new TransportChangePasswordAction(settings, mock(ThreadPool.class), transportService, - mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore); + mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore); ChangePasswordRequest request = new ChangePasswordRequest(); request.username(anonymousUser.principal()); - request.passwordHash(Hasher.BCRYPT.hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)); + request.passwordHash(Hasher.resolve(hashingAlgorithm).hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)); final AtomicReference throwableRef = new AtomicReference<>(); final AtomicReference responseRef = new AtomicReference<>(); @@ -81,14 +84,17 @@ public void onFailure(Exception e) { } public void testInternalUsers() { + final String hashingAlgorithm = randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt", "bcrypt9"); + Settings passwordHashingSettings = Settings.builder().put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(),hashingAlgorithm) + .build(); NativeUsersStore usersStore = mock(NativeUsersStore.class); TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet()); - TransportChangePasswordAction action = new TransportChangePasswordAction(Settings.EMPTY, mock(ThreadPool.class), transportService, - mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore); + TransportChangePasswordAction action = new TransportChangePasswordAction(passwordHashingSettings, mock(ThreadPool.class), + transportService, mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore); ChangePasswordRequest request = new ChangePasswordRequest(); request.username(randomFrom(SystemUser.INSTANCE.principal(), XPackUser.INSTANCE.principal())); - request.passwordHash(Hasher.BCRYPT.hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)); + request.passwordHash(Hasher.resolve(hashingAlgorithm).hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)); final AtomicReference throwableRef = new AtomicReference<>(); final AtomicReference responseRef = new AtomicReference<>(); @@ -111,11 +117,13 @@ public void onFailure(Exception e) { } public void testValidUser() { + final String hashingAlgorithm = randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt", "bcrypt9"); final User user = randomFrom(new ElasticUser(true), new KibanaUser(true), new User("joe")); NativeUsersStore usersStore = mock(NativeUsersStore.class); + final Hasher hasher = Hasher.resolve(hashingAlgorithm); ChangePasswordRequest request = new ChangePasswordRequest(); request.username(user.principal()); - request.passwordHash(Hasher.BCRYPT.hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)); + request.passwordHash(hasher.hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)); doAnswer(invocation -> { Object[] args = invocation.getArguments(); assert args.length == 2; @@ -123,10 +131,12 @@ public void testValidUser() { listener.onResponse(null); return null; }).when(usersStore).changePassword(eq(request), any(ActionListener.class)); + Settings passwordHashingSettings = Settings.builder().put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(),hashingAlgorithm) + .build(); TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet()); - TransportChangePasswordAction action = new TransportChangePasswordAction(Settings.EMPTY, mock(ThreadPool.class), transportService, - mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore); + TransportChangePasswordAction action = new TransportChangePasswordAction(passwordHashingSettings, mock(ThreadPool.class), + transportService, mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore); final AtomicReference throwableRef = new AtomicReference<>(); final AtomicReference responseRef = new AtomicReference<>(); action.doExecute(request, new ActionListener() { @@ -147,12 +157,47 @@ public void onFailure(Exception e) { verify(usersStore, times(1)).changePassword(eq(request), any(ActionListener.class)); } + public void testIncorrectPasswordHashingAlgorithm() { + final User user = randomFrom(new ElasticUser(true), new KibanaUser(true), new User("joe")); + final Hasher hasher = Hasher.resolve(randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt9", "bcrypt5")); + NativeUsersStore usersStore = mock(NativeUsersStore.class); + ChangePasswordRequest request = new ChangePasswordRequest(); + request.username(user.principal()); + request.passwordHash(hasher.hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)); + final AtomicReference throwableRef = new AtomicReference<>(); + final AtomicReference responseRef = new AtomicReference<>(); + TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null, + TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet()); + Settings passwordHashingSettings = Settings.builder().put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), + randomFrom("pbkdf2_50000", "pbkdf2_100000", "bcrypt11", "bcrypt8", "bcrypt")).build(); + TransportChangePasswordAction action = new TransportChangePasswordAction(passwordHashingSettings, mock(ThreadPool.class), + transportService, + mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore); + action.doExecute(request, new ActionListener() { + @Override + public void onResponse(ChangePasswordResponse changePasswordResponse) { + responseRef.set(changePasswordResponse); + } + + @Override + public void onFailure(Exception e) { + throwableRef.set(e); + } + }); + + assertThat(responseRef.get(), is(nullValue())); + assertThat(throwableRef.get(), instanceOf(IllegalArgumentException.class)); + assertThat(throwableRef.get().getMessage(), containsString("incorrect password hashing algorithm")); + verifyZeroInteractions(usersStore); + } + public void testException() { + final String hashingAlgorithm = randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt", "bcrypt9"); final User user = randomFrom(new ElasticUser(true), new KibanaUser(true), new User("joe")); NativeUsersStore usersStore = mock(NativeUsersStore.class); ChangePasswordRequest request = new ChangePasswordRequest(); request.username(user.principal()); - request.passwordHash(Hasher.BCRYPT.hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)); + request.passwordHash(Hasher.resolve(hashingAlgorithm).hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)); final Exception e = randomFrom(new ElasticsearchSecurityException(""), new IllegalStateException(), new RuntimeException()); doAnswer(new Answer() { public Void answer(InvocationOnMock invocation) { @@ -165,8 +210,10 @@ public Void answer(InvocationOnMock invocation) { }).when(usersStore).changePassword(eq(request), any(ActionListener.class)); TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet()); - TransportChangePasswordAction action = new TransportChangePasswordAction(Settings.EMPTY, mock(ThreadPool.class), transportService, - mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore); + Settings passwordHashingSettings = Settings.builder(). + put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), hashingAlgorithm).build(); + TransportChangePasswordAction action = new TransportChangePasswordAction(passwordHashingSettings, mock(ThreadPool.class), + transportService, mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore); final AtomicReference throwableRef = new AtomicReference<>(); final AtomicReference responseRef = new AtomicReference<>(); action.doExecute(request, new ActionListener() { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/TransportPutUserActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/TransportPutUserActionTests.java index aef8a38e41262..01dcdb56893c7 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/TransportPutUserActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/TransportPutUserActionTests.java @@ -168,7 +168,8 @@ public void testValidUser() { final PutUserRequest request = new PutUserRequest(); request.username(user.principal()); if (isCreate) { - request.passwordHash(Hasher.BCRYPT.hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)); + request.passwordHash(Hasher.resolve( + randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt", "bcrypt9")).hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)); } final boolean created = isCreate ? randomBoolean() : false; // updates should always return false for create doAnswer(new Answer() { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmSettingsTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmSettingsTests.java index 3871574b76c51..b177d17793f89 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmSettingsTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmSettingsTests.java @@ -27,8 +27,7 @@ import static org.hamcrest.Matchers.notNullValue; public class RealmSettingsTests extends ESTestCase { - - private static final List HASH_ALGOS = Arrays.stream(Hasher.values()).map(Hasher::name).collect(Collectors.toList()); + private static final List CACHE_HASHING_ALGOS = Arrays.stream(Hasher.values()).map(Hasher::name).collect(Collectors.toList()); public void testRealmWithoutTypeDoesNotValidate() throws Exception { final Settings.Builder builder = baseSettings("x", false); @@ -260,8 +259,8 @@ private Settings.Builder baseSettings(String type, boolean withCacheSettings) { .put("enabled", true); if (withCacheSettings) { builder.put("cache.ttl", randomPositiveTimeValue()) - .put("cache.max_users", randomIntBetween(1_000, 1_000_000)) - .put("cache.hash_algo", randomFrom(HASH_ALGOS)); + .put("cache.max_users", randomIntBetween(1_000, 1_000_000)) + .put("cache.hash_algo", randomFrom(CACHE_HASHING_ALGOS)); } return builder; } @@ -330,4 +329,4 @@ private Setting group() { assertThat(list, hasSize(1)); return list.get(0); } -} \ No newline at end of file +} diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java index 0b96c55b5ff35..1078adf646a80 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java @@ -74,7 +74,7 @@ public void testRetrieveUsers() throws Exception { Set addedUsers = new HashSet(numToAdd); for (int i = 0; i < numToAdd; i++) { String uname = randomAlphaOfLength(5); - c.preparePutUser(uname, "s3kirt".toCharArray(), "role1", "user").get(); + c.preparePutUser(uname, "s3kirt".toCharArray(), getFastStoredHashAlgoForTests(), "role1", "user").get(); addedUsers.add(uname); } logger.error("--> waiting for .security index"); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java index a0550b4c1ce15..a880c22ccda8b 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java @@ -35,6 +35,7 @@ import org.elasticsearch.xpack.core.security.action.user.ChangePasswordResponse; import org.elasticsearch.xpack.core.security.action.user.DeleteUserResponse; import org.elasticsearch.xpack.core.security.action.user.GetUsersResponse; +import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; import org.elasticsearch.xpack.core.security.authz.permission.Role; import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore; @@ -72,22 +73,24 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { private static boolean anonymousEnabled; + private static Hasher hasher; private boolean roleExists; @BeforeClass public static void init() { anonymousEnabled = randomBoolean(); + hasher = getFastStoredHashAlgoForTests(); } @Override public Settings nodeSettings(int nodeOrdinal) { + Settings.Builder builder = Settings.builder().put(super.nodeSettings(nodeOrdinal)) + .put("xpack.security.authc.password_hashing.algorithm", hasher.name()); if (anonymousEnabled) { - return Settings.builder().put(super.nodeSettings(nodeOrdinal)) - .put(AnonymousUser.ROLES_SETTING.getKey(), "native_anonymous") - .build(); + builder.put(AnonymousUser.ROLES_SETTING.getKey(), "native_anonymous"); } - return super.nodeSettings(nodeOrdinal); + return builder.build(); } @Before @@ -111,7 +114,7 @@ public void setupAnonymousRoleIfNecessary() throws Exception { public void testDeletingNonexistingUserAndRole() throws Exception { SecurityClient c = securityClient(); // first create the index so it exists - c.preparePutUser("joe", "s3kirt".toCharArray(), "role1", "user").get(); + c.preparePutUser("joe", "s3kirt".toCharArray(), hasher, "role1", "user").get(); DeleteUserResponse resp = c.prepareDeleteUser("missing").get(); assertFalse("user shouldn't be found", resp.found()); DeleteRoleResponse resp2 = c.prepareDeleteRole("role").get(); @@ -131,7 +134,7 @@ public void testAddAndGetUser() throws Exception { final List existingUsers = Arrays.asList(c.prepareGetUsers().get().users()); final int existing = existingUsers.size(); logger.error("--> creating user"); - c.preparePutUser("joe", "s3kirt".toCharArray(), "role1", "user").get(); + c.preparePutUser("joe", "s3kirt".toCharArray(), hasher, "role1", "user").get(); logger.error("--> waiting for .security index"); ensureGreen(SECURITY_INDEX_NAME); logger.info("--> retrieving user"); @@ -142,8 +145,8 @@ public void testAddAndGetUser() throws Exception { assertArrayEquals(joe.roles(), new String[]{"role1", "user"}); logger.info("--> adding two more users"); - c.preparePutUser("joe2", "s3kirt2".toCharArray(), "role2", "user").get(); - c.preparePutUser("joe3", "s3kirt3".toCharArray(), "role3", "user").get(); + c.preparePutUser("joe2", "s3kirt2".toCharArray(), hasher, "role2", "user").get(); + c.preparePutUser("joe3", "s3kirt3".toCharArray(), hasher, "role3", "user").get(); GetUsersResponse allUsersResp = c.prepareGetUsers().get(); assertTrue("users should exist", allUsersResp.hasUsers()); assertEquals("should be " + (3 + existing) + " users total", 3 + existing, allUsersResp.users().length); @@ -237,7 +240,7 @@ public void testAddUserAndRoleThenAuth() throws Exception { new BytesArray("{\"match_all\": {}}")) .get(); logger.error("--> creating user"); - c.preparePutUser("joe", "s3krit".toCharArray(), "test_role").get(); + c.preparePutUser("joe", "s3krit".toCharArray(), hasher, "test_role").get(); logger.error("--> waiting for .security index"); ensureGreen(SECURITY_INDEX_NAME); logger.info("--> retrieving user"); @@ -252,13 +255,13 @@ public void testAddUserAndRoleThenAuth() throws Exception { String token = basicAuthHeaderValue("joe", new SecureString("s3krit".toCharArray())); SearchResponse searchResp = client().filterWithHeader(Collections.singletonMap("Authorization", token)).prepareSearch("idx").get(); - assertEquals(searchResp.getHits().getTotalHits(), 1L); + assertEquals(1L, searchResp.getHits().getTotalHits()); } public void testUpdatingUserAndAuthentication() throws Exception { SecurityClient c = securityClient(); logger.error("--> creating user"); - c.preparePutUser("joe", "s3krit".toCharArray(), SecuritySettingsSource.TEST_ROLE).get(); + c.preparePutUser("joe", "s3krit".toCharArray(), hasher, SecuritySettingsSource.TEST_ROLE).get(); logger.error("--> waiting for .security index"); ensureGreen(SECURITY_INDEX_NAME); logger.info("--> retrieving user"); @@ -273,9 +276,9 @@ public void testUpdatingUserAndAuthentication() throws Exception { String token = basicAuthHeaderValue("joe", new SecureString("s3krit".toCharArray())); SearchResponse searchResp = client().filterWithHeader(Collections.singletonMap("Authorization", token)).prepareSearch("idx").get(); - assertEquals(searchResp.getHits().getTotalHits(), 1L); + assertEquals(1L, searchResp.getHits().getTotalHits()); - c.preparePutUser("joe", "s3krit2".toCharArray(), SecuritySettingsSource.TEST_ROLE).get(); + c.preparePutUser("joe", "s3krit2".toCharArray(), hasher, SecuritySettingsSource.TEST_ROLE).get(); try { client().filterWithHeader(Collections.singletonMap("Authorization", token)).prepareSearch("idx").get(); @@ -287,13 +290,13 @@ public void testUpdatingUserAndAuthentication() throws Exception { token = basicAuthHeaderValue("joe", new SecureString("s3krit2".toCharArray())); searchResp = client().filterWithHeader(Collections.singletonMap("Authorization", token)).prepareSearch("idx").get(); - assertEquals(searchResp.getHits().getTotalHits(), 1L); + assertEquals(1L, searchResp.getHits().getTotalHits()); } public void testCreateDeleteAuthenticate() { SecurityClient c = securityClient(); logger.error("--> creating user"); - c.preparePutUser("joe", "s3krit".toCharArray(), SecuritySettingsSource.TEST_ROLE).get(); + c.preparePutUser("joe", "s3krit".toCharArray(), hasher, SecuritySettingsSource.TEST_ROLE).get(); logger.error("--> waiting for .security index"); ensureGreen(SECURITY_INDEX_NAME); logger.info("--> retrieving user"); @@ -308,7 +311,7 @@ public void testCreateDeleteAuthenticate() { String token = basicAuthHeaderValue("joe", new SecureString("s3krit".toCharArray())); SearchResponse searchResp = client().filterWithHeader(Collections.singletonMap("Authorization", token)).prepareSearch("idx").get(); - assertEquals(searchResp.getHits().getTotalHits(), 1L); + assertEquals(1L, searchResp.getHits().getTotalHits()); DeleteUserResponse response = c.prepareDeleteUser("joe").get(); assertThat(response.found(), is(true)); @@ -331,7 +334,7 @@ public void testCreateAndUpdateRole() { new BytesArray("{\"match_all\": {}}")) .get(); logger.error("--> creating user"); - c.preparePutUser("joe", "s3krit".toCharArray(), "test_role").get(); + c.preparePutUser("joe", "s3krit".toCharArray(), hasher, "test_role").get(); logger.error("--> waiting for .security index"); ensureGreen(SECURITY_INDEX_NAME); @@ -380,7 +383,7 @@ public void testAuthenticateWithDeletedRole() { .addIndices(new String[]{"*"}, new String[]{"read"}, new String[]{"body", "title"}, null, new BytesArray("{\"match_all\": {}}")) .get(); - c.preparePutUser("joe", "s3krit".toCharArray(), "test_role").get(); + c.preparePutUser("joe", "s3krit".toCharArray(), hasher, "test_role").get(); logger.error("--> waiting for .security index"); ensureGreen(SECURITY_INDEX_NAME); @@ -414,7 +417,7 @@ public void testPutUserWithoutPassword() { assertThat(client.prepareGetUsers("joes").get().hasUsers(), is(false)); // check that putting a user without a password fails if the user doesn't exist try { - client.preparePutUser("joe", null, "admin_role").get(); + client.preparePutUser("joe", null, hasher, "admin_role").get(); fail("cannot create a user without a password"); } catch (IllegalArgumentException e) { assertThat(e.getMessage(), containsString("password must be specified")); @@ -423,7 +426,7 @@ public void testPutUserWithoutPassword() { assertThat(client.prepareGetUsers("joes").get().hasUsers(), is(false)); // create joe with a password and verify the user works - client.preparePutUser("joe", SecuritySettingsSourceField.TEST_PASSWORD.toCharArray(), "admin_role").get(); + client.preparePutUser("joe", SecuritySettingsSourceField.TEST_PASSWORD.toCharArray(), hasher, "admin_role").get(); assertThat(client.prepareGetUsers("joe").get().hasUsers(), is(true)); final String token = basicAuthHeaderValue("joe", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING); ClusterHealthResponse response = client().filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster() @@ -431,7 +434,7 @@ public void testPutUserWithoutPassword() { assertFalse(response.isTimedOut()); // modify joe without sending the password - client.preparePutUser("joe", null, "read_role").fullName("Joe Smith").get(); + client.preparePutUser("joe", null, hasher, "read_role").fullName("Joe Smith").get(); GetUsersResponse getUsersResponse = client.prepareGetUsers("joe").get(); assertThat(getUsersResponse.hasUsers(), is(true)); assertThat(getUsersResponse.users().length, is(1)); @@ -452,7 +455,8 @@ public void testPutUserWithoutPassword() { // update the user with password and admin role again String secondPassword = SecuritySettingsSourceField.TEST_PASSWORD + "2"; - client.preparePutUser("joe", secondPassword.toCharArray(), "admin_role").fullName("Joe Smith").get(); + client.preparePutUser("joe", secondPassword.toCharArray(), hasher, "admin_role"). + fullName("Joe Smith").get(); getUsersResponse = client.prepareGetUsers("joe").get(); assertThat(getUsersResponse.hasUsers(), is(true)); assertThat(getUsersResponse.users().length, is(1)); @@ -480,7 +484,8 @@ public void testPutUserWithoutPassword() { public void testCannotCreateUserWithShortPassword() throws Exception { SecurityClient client = securityClient(); try { - client.preparePutUser("joe", randomAlphaOfLengthBetween(0, 5).toCharArray(), "admin_role").get(); + client.preparePutUser("joe", randomAlphaOfLengthBetween(0, 5).toCharArray(), hasher, + "admin_role").get(); fail("cannot create a user without a password < 6 characters"); } catch (ValidationException v) { assertThat(v.getMessage().contains("password"), is(true)); @@ -490,7 +495,8 @@ public void testCannotCreateUserWithShortPassword() throws Exception { public void testCannotCreateUserWithInvalidCharactersInName() throws Exception { SecurityClient client = securityClient(); ValidationException v = expectThrows(ValidationException.class, - () -> client.preparePutUser("fóóbár", "my-am@zing-password".toCharArray(), "admin_role").get() + () -> client.preparePutUser("fóóbár", "my-am@zing-password".toCharArray(), hasher, + "admin_role").get() ); assertThat(v.getMessage(), containsString("names must be")); } @@ -500,7 +506,8 @@ public void testUsersAndRolesDoNotInterfereWithIndicesStats() throws Exception { SecurityClient client = securityClient(); if (randomBoolean()) { - client.preparePutUser("joe", "s3krit".toCharArray(), SecuritySettingsSource.TEST_ROLE).get(); + client.preparePutUser("joe", "s3krit".toCharArray(), hasher, + SecuritySettingsSource.TEST_ROLE).get(); } else { client.preparePutRole("read_role") .cluster("none") @@ -520,7 +527,7 @@ public void testOperationsOnReservedUsers() throws Exception { final String username = randomFrom(ElasticUser.NAME, KibanaUser.NAME); IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> securityClient().preparePutUser(username, randomBoolean() ? SecuritySettingsSourceField.TEST_PASSWORD.toCharArray() - : null, "admin").get()); + : null, hasher, "admin").get()); assertThat(exception.getMessage(), containsString("Username [" + username + "] is reserved")); exception = expectThrows(IllegalArgumentException.class, @@ -532,19 +539,19 @@ public void testOperationsOnReservedUsers() throws Exception { assertThat(exception.getMessage(), containsString("user [" + AnonymousUser.DEFAULT_ANONYMOUS_USERNAME + "] is anonymous")); exception = expectThrows(IllegalArgumentException.class, - () -> securityClient().prepareChangePassword(AnonymousUser.DEFAULT_ANONYMOUS_USERNAME, "foobar".toCharArray()).get()); + () -> securityClient().prepareChangePassword(AnonymousUser.DEFAULT_ANONYMOUS_USERNAME, "foobar".toCharArray(), hasher).get()); assertThat(exception.getMessage(), containsString("user [" + AnonymousUser.DEFAULT_ANONYMOUS_USERNAME + "] is anonymous")); exception = expectThrows(IllegalArgumentException.class, - () -> securityClient().preparePutUser(AnonymousUser.DEFAULT_ANONYMOUS_USERNAME, "foobar".toCharArray()).get()); + () -> securityClient().preparePutUser(AnonymousUser.DEFAULT_ANONYMOUS_USERNAME, "foobar".toCharArray(), hasher).get()); assertThat(exception.getMessage(), containsString("Username [" + AnonymousUser.DEFAULT_ANONYMOUS_USERNAME + "] is reserved")); exception = expectThrows(IllegalArgumentException.class, - () -> securityClient().preparePutUser(SystemUser.NAME, "foobar".toCharArray()).get()); + () -> securityClient().preparePutUser(SystemUser.NAME, "foobar".toCharArray(), hasher).get()); assertThat(exception.getMessage(), containsString("user [" + SystemUser.NAME + "] is internal")); exception = expectThrows(IllegalArgumentException.class, - () -> securityClient().prepareChangePassword(SystemUser.NAME, "foobar".toCharArray()).get()); + () -> securityClient().prepareChangePassword(SystemUser.NAME, "foobar".toCharArray(), hasher).get()); assertThat(exception.getMessage(), containsString("user [" + SystemUser.NAME + "] is internal")); exception = expectThrows(IllegalArgumentException.class, @@ -582,7 +589,8 @@ public void testOperationsOnReservedRoles() throws Exception { } public void testCreateAndChangePassword() throws Exception { - securityClient().preparePutUser("joe", "s3krit".toCharArray(), SecuritySettingsSource.TEST_ROLE).get(); + securityClient().preparePutUser("joe", "s3krit".toCharArray(), hasher, + SecuritySettingsSource.TEST_ROLE).get(); final String token = basicAuthHeaderValue("joe", new SecureString("s3krit".toCharArray())); ClusterHealthResponse response = client().filterWithHeader(Collections.singletonMap("Authorization", token)) .admin().cluster().prepareHealth().get(); @@ -590,8 +598,7 @@ public void testCreateAndChangePassword() throws Exception { ChangePasswordResponse passwordResponse = securityClient( client().filterWithHeader(Collections.singletonMap("Authorization", token))) - .prepareChangePassword("joe", SecuritySettingsSourceField.TEST_PASSWORD.toCharArray()) - .get(); + .prepareChangePassword("joe", SecuritySettingsSourceField.TEST_PASSWORD.toCharArray(), hasher).get(); assertThat(passwordResponse, notNullValue()); @@ -671,7 +678,7 @@ public void testRealmUsageStats() { final int numNativeUsers = scaledRandomIntBetween(1, 32); SecurityClient securityClient = new SecurityClient(client()); for (int i = 0; i < numNativeUsers; i++) { - securityClient.preparePutUser("joe" + i, "s3krit".toCharArray(), "superuser").get(); + securityClient.preparePutUser("joe" + i, "s3krit".toCharArray(), hasher,"superuser").get(); } XPackUsageResponse response = new XPackUsageRequestBuilder(client()).get(); @@ -690,7 +697,8 @@ public void testRealmUsageStats() { } public void testSetEnabled() throws Exception { - securityClient().preparePutUser("joe", "s3krit".toCharArray(), SecuritySettingsSource.TEST_ROLE).get(); + + securityClient().preparePutUser("joe", "s3krit".toCharArray(), hasher, SecuritySettingsSource.TEST_ROLE).get(); final String token = basicAuthHeaderValue("joe", new SecureString("s3krit".toCharArray())); ClusterHealthResponse response = client().filterWithHeader(Collections.singletonMap("Authorization", token)) .admin().cluster().prepareHealth().get(); @@ -714,7 +722,7 @@ public void testSetEnabled() throws Exception { public void testNegativeLookupsThenCreateRole() throws Exception { SecurityClient securityClient = new SecurityClient(client()); - securityClient.preparePutUser("joe", "s3krit".toCharArray(), "unknown_role").get(); + securityClient.preparePutUser("joe", "s3krit".toCharArray(), hasher, "unknown_role").get(); final int negativeLookups = scaledRandomIntBetween(1, 10); for (int i = 0; i < negativeLookups; i++) { @@ -750,8 +758,9 @@ public void testNegativeLookupsThenCreateRole() throws Exception { * the loader returned a null value, while the other caller(s) would get a null value unexpectedly */ public void testConcurrentRunAs() throws Exception { - securityClient().preparePutUser("joe", "s3krit".toCharArray(), SecuritySettingsSource.TEST_ROLE).get(); - securityClient().preparePutUser("executor", "s3krit".toCharArray(), "superuser").get(); + securityClient().preparePutUser("joe", "s3krit".toCharArray(), hasher, SecuritySettingsSource + .TEST_ROLE).get(); + securityClient().preparePutUser("executor", "s3krit".toCharArray(), hasher, "superuser").get(); final String token = basicAuthHeaderValue("executor", new SecureString("s3krit".toCharArray())); final Client client = client().filterWithHeader(MapBuilder.newMapBuilder() .put("Authorization", token) diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStoreTests.java index ba5c2ce26b487..52cbb5d4535ed 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStoreTests.java @@ -129,7 +129,7 @@ public void testBlankPasswordInIndexImpliesDefaultPassword() throws Exception { final NativeUsersStore.ReservedUserInfo userInfo = future.get(); assertThat(userInfo.hasEmptyPassword, equalTo(true)); assertThat(userInfo.enabled, equalTo(true)); - assertThat(userInfo.passwordHash, equalTo(ReservedRealm.EMPTY_PASSWORD_HASH)); + assertTrue(Hasher.verifyHash(new SecureString("".toCharArray()), userInfo.passwordHash)); } public void testVerifyUserWithCorrectPassword() throws Exception { @@ -215,6 +215,7 @@ private Tuple> find } private void respondToGetUserRequest(String username, SecureString password, String[] roles) throws IOException { + // Native users store is initiated with default hashing algorithm final Map values = new HashMap<>(); values.put(User.Fields.USERNAME.getPreferredName(), username); values.put(User.Fields.PASSWORD.getPreferredName(), String.valueOf(Hasher.BCRYPT.hash(password))); @@ -249,4 +250,4 @@ private NativeUsersStore startNativeUsersStore() { return new NativeUsersStore(Settings.EMPTY, client, securityIndex); } -} \ No newline at end of file +} diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealmIntegTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealmIntegTests.java index 2ec7ed7ea2204..1824597a6adc6 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealmIntegTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealmIntegTests.java @@ -8,13 +8,16 @@ import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.common.settings.Settings; import org.elasticsearch.test.NativeRealmIntegTestCase; import org.elasticsearch.xpack.core.security.action.user.ChangePasswordResponse; +import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.client.SecurityClient; import org.elasticsearch.xpack.core.security.user.BeatsSystemUser; import org.elasticsearch.xpack.core.security.user.ElasticUser; import org.elasticsearch.xpack.core.security.user.KibanaUser; import org.elasticsearch.xpack.core.security.user.LogstashSystemUser; +import org.junit.BeforeClass; import java.util.Arrays; @@ -29,6 +32,22 @@ */ public class ReservedRealmIntegTests extends NativeRealmIntegTestCase { + private static Hasher hasher; + + @BeforeClass + public static void setHasher() { + hasher = getFastStoredHashAlgoForTests(); + } + + @Override + public Settings nodeSettings(int nodeOrdinal) { + Settings settings = Settings.builder() + .put(super.nodeSettings(nodeOrdinal)) + .put("xpack.security.authc.password_hashing.algorithm", hasher.name()) + .build(); + return settings; + } + public void testAuthenticate() { for (String username : Arrays.asList(ElasticUser.NAME, KibanaUser.NAME, LogstashSystemUser.NAME, BeatsSystemUser.NAME)) { ClusterHealthResponse response = client() @@ -76,7 +95,7 @@ public void testChangingPassword() { } ChangePasswordResponse response = securityClient() - .prepareChangePassword(username, Arrays.copyOf(newPassword, newPassword.length)) + .prepareChangePassword(username, Arrays.copyOf(newPassword, newPassword.length), hasher) .get(); assertThat(response, notNullValue()); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealmTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealmTests.java index 6f3eff02da165..ad62c4f44adea 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealmTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealmTests.java @@ -77,12 +77,21 @@ public void setupMocks() throws Exception { when(threadPool.getThreadContext()).thenReturn(new ThreadContext(Settings.EMPTY)); } + public void testInvalidHashingAlgorithmFails() { + final String invalidAlgoId = randomFrom("sha1", "md5", "noop"); + final Settings invalidSettings = Settings.builder().put("xpack.security.authc.password_hashing.algorithm", invalidAlgoId).build(); + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> new ReservedRealm(mock(Environment.class), + invalidSettings, usersStore, new AnonymousUser(Settings.EMPTY), securityIndex, threadPool)); + assertThat(exception.getMessage(), containsString(invalidAlgoId)); + assertThat(exception.getMessage(), containsString("Only pbkdf2 or bcrypt family algorithms can be used for password hashing")); + } + public void testReservedUserEmptyPasswordAuthenticationFails() throws Throwable { final String principal = randomFrom(UsernamesField.ELASTIC_NAME, UsernamesField.KIBANA_NAME, UsernamesField.LOGSTASH_NAME, - UsernamesField.BEATS_NAME); + UsernamesField.BEATS_NAME); final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore, - new AnonymousUser(Settings.EMPTY), securityIndex, threadPool); + new AnonymousUser(Settings.EMPTY), securityIndex, threadPool); PlainActionFuture listener = new PlainActionFuture<>(); @@ -97,8 +106,8 @@ public void testAuthenticationDisabled() throws Throwable { when(securityIndex.indexExists()).thenReturn(true); } final ReservedRealm reservedRealm = - new ReservedRealm(mock(Environment.class), settings, usersStore, - new AnonymousUser(settings), securityIndex, threadPool); + new ReservedRealm(mock(Environment.class), settings, usersStore, + new AnonymousUser(settings), securityIndex, threadPool); final User expected = randomReservedUser(true); final String principal = expected.principal(); @@ -120,14 +129,16 @@ public void testAuthenticationDisabledUserWithStoredPassword() throws Throwable private void verifySuccessfulAuthentication(boolean enabled) throws Exception { final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore, - new AnonymousUser(Settings.EMPTY), securityIndex, threadPool); + new AnonymousUser(Settings.EMPTY), securityIndex, threadPool); final User expectedUser = randomReservedUser(enabled); final String principal = expectedUser.principal(); final SecureString newPassword = new SecureString("foobar".toCharArray()); + // Mocked users store is initiated with default hashing algorithm + final Hasher hasher = Hasher.resolve("bcrypt"); when(securityIndex.indexExists()).thenReturn(true); doAnswer((i) -> { ActionListener callback = (ActionListener) i.getArguments()[1]; - callback.onResponse(new ReservedUserInfo(Hasher.BCRYPT.hash(newPassword), enabled, false)); + callback.onResponse(new ReservedUserInfo(hasher.hash(newPassword), enabled, false)); return null; }).when(usersStore).getReservedUserInfo(eq(principal), any(ActionListener.class)); @@ -139,7 +150,7 @@ private void verifySuccessfulAuthentication(boolean enabled) throws Exception { // the realm assumes it owns the hashed password so it fills it with 0's doAnswer((i) -> { ActionListener callback = (ActionListener) i.getArguments()[1]; - callback.onResponse(new ReservedUserInfo(Hasher.BCRYPT.hash(newPassword), true, false)); + callback.onResponse(new ReservedUserInfo(hasher.hash(newPassword), true, false)); return null; }).when(usersStore).getReservedUserInfo(eq(principal), any(ActionListener.class)); @@ -160,8 +171,8 @@ private void verifySuccessfulAuthentication(boolean enabled) throws Exception { public void testLookup() throws Exception { final ReservedRealm reservedRealm = - new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore, - new AnonymousUser(Settings.EMPTY), securityIndex, threadPool); + new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore, + new AnonymousUser(Settings.EMPTY), securityIndex, threadPool); final User expectedUser = randomReservedUser(true); final String principal = expectedUser.principal(); @@ -185,8 +196,8 @@ public void testLookup() throws Exception { public void testLookupDisabled() throws Exception { Settings settings = Settings.builder().put(XPackSettings.RESERVED_REALM_ENABLED_SETTING.getKey(), false).build(); final ReservedRealm reservedRealm = - new ReservedRealm(mock(Environment.class), settings, usersStore, new AnonymousUser(settings), - securityIndex, threadPool); + new ReservedRealm(mock(Environment.class), settings, usersStore, new AnonymousUser(settings), + securityIndex, threadPool); final User expectedUser = randomReservedUser(true); final String principal = expectedUser.principal(); @@ -199,8 +210,8 @@ public void testLookupDisabled() throws Exception { public void testLookupThrows() throws Exception { final ReservedRealm reservedRealm = - new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore, - new AnonymousUser(Settings.EMPTY), securityIndex, threadPool); + new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore, + new AnonymousUser(Settings.EMPTY), securityIndex, threadPool); final User expectedUser = randomReservedUser(true); final String principal = expectedUser.principal(); when(securityIndex.indexExists()).thenReturn(true); @@ -247,22 +258,22 @@ public void testIsReservedDisabled() { public void testGetUsers() { final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore, - new AnonymousUser(Settings.EMPTY), securityIndex, threadPool); + new AnonymousUser(Settings.EMPTY), securityIndex, threadPool); PlainActionFuture> userFuture = new PlainActionFuture<>(); reservedRealm.users(userFuture); assertThat(userFuture.actionGet(), - containsInAnyOrder(new ElasticUser(true), new KibanaUser(true), new LogstashSystemUser(true), new BeatsSystemUser(true))); + containsInAnyOrder(new ElasticUser(true), new KibanaUser(true), new LogstashSystemUser(true), new BeatsSystemUser(true))); } public void testGetUsersDisabled() { final boolean anonymousEnabled = randomBoolean(); Settings settings = Settings.builder() - .put(XPackSettings.RESERVED_REALM_ENABLED_SETTING.getKey(), false) - .put(AnonymousUser.ROLES_SETTING.getKey(), anonymousEnabled ? "user" : "") - .build(); + .put(XPackSettings.RESERVED_REALM_ENABLED_SETTING.getKey(), false) + .put(AnonymousUser.ROLES_SETTING.getKey(), anonymousEnabled ? "user" : "") + .build(); final AnonymousUser anonymousUser = new AnonymousUser(settings); final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore, anonymousUser, - securityIndex, threadPool); + securityIndex, threadPool); PlainActionFuture> userFuture = new PlainActionFuture<>(); reservedRealm.users(userFuture); if (anonymousEnabled) { @@ -275,11 +286,13 @@ public void testGetUsersDisabled() { public void testFailedAuthentication() throws Exception { when(securityIndex.indexExists()).thenReturn(true); SecureString password = new SecureString("password".toCharArray()); - char[] hash = Hasher.BCRYPT.hash(password); + // Mocked users store is initiated with default hashing algorithm + final Hasher hasher = Hasher.resolve("bcrypt"); + char[] hash = hasher.hash(password); ReservedUserInfo userInfo = new ReservedUserInfo(hash, true, false); mockGetAllReservedUserInfo(usersStore, Collections.singletonMap("elastic", userInfo)); final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore, - new AnonymousUser(Settings.EMPTY), securityIndex, threadPool); + new AnonymousUser(Settings.EMPTY), securityIndex, threadPool); if (randomBoolean()) { PlainActionFuture future = new PlainActionFuture<>(); @@ -309,7 +322,7 @@ public void testBootstrapElasticPasswordWorksOnceSecurityIndexExists() throws Ex when(securityIndex.indexExists()).thenReturn(true); final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore, - new AnonymousUser(Settings.EMPTY), securityIndex, threadPool); + new AnonymousUser(Settings.EMPTY), securityIndex, threadPool); PlainActionFuture listener = new PlainActionFuture<>(); doAnswer((i) -> { @@ -318,8 +331,8 @@ public void testBootstrapElasticPasswordWorksOnceSecurityIndexExists() throws Ex return null; }).when(usersStore).getReservedUserInfo(eq("elastic"), any(ActionListener.class)); reservedRealm.doAuthenticate(new UsernamePasswordToken(new ElasticUser(true).principal(), - mockSecureSettings.getString("bootstrap.password")), - listener); + mockSecureSettings.getString("bootstrap.password")), + listener); final AuthenticationResult result = listener.get(); assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS)); } @@ -331,18 +344,20 @@ public void testBootstrapElasticPasswordFailsOnceElasticUserExists() throws Exce when(securityIndex.indexExists()).thenReturn(true); final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore, - new AnonymousUser(Settings.EMPTY), securityIndex, threadPool); + new AnonymousUser(Settings.EMPTY), securityIndex, threadPool); PlainActionFuture listener = new PlainActionFuture<>(); SecureString password = new SecureString("password".toCharArray()); + // Mocked users store is initiated with default hashing algorithm + final Hasher hasher = Hasher.resolve("bcrypt"); doAnswer((i) -> { ActionListener callback = (ActionListener) i.getArguments()[1]; - char[] hash = Hasher.BCRYPT.hash(password); + char[] hash = hasher.hash(password); ReservedUserInfo userInfo = new ReservedUserInfo(hash, true, false); callback.onResponse(userInfo); return null; }).when(usersStore).getReservedUserInfo(eq("elastic"), any(ActionListener.class)); reservedRealm.doAuthenticate(new UsernamePasswordToken(new ElasticUser(true).principal(), - mockSecureSettings.getString("bootstrap.password")), listener); + mockSecureSettings.getString("bootstrap.password")), listener); assertFailedAuthentication(listener, "elastic"); // now try with the real password listener = new PlainActionFuture<>(); @@ -358,12 +373,12 @@ public void testBootstrapElasticPasswordWorksBeforeSecurityIndexExists() throws when(securityIndex.indexExists()).thenReturn(false); final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore, - new AnonymousUser(Settings.EMPTY), securityIndex, threadPool); + new AnonymousUser(Settings.EMPTY), securityIndex, threadPool); PlainActionFuture listener = new PlainActionFuture<>(); reservedRealm.doAuthenticate(new UsernamePasswordToken(new ElasticUser(true).principal(), - mockSecureSettings.getString("bootstrap.password")), - listener); + mockSecureSettings.getString("bootstrap.password")), + listener); final AuthenticationResult result = listener.get(); assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS)); } @@ -376,7 +391,7 @@ public void testNonElasticUsersCannotUseBootstrapPasswordWhenSecurityIndexExists when(securityIndex.indexExists()).thenReturn(true); final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore, - new AnonymousUser(Settings.EMPTY), securityIndex, threadPool); + new AnonymousUser(Settings.EMPTY), securityIndex, threadPool); PlainActionFuture listener = new PlainActionFuture<>(); final String principal = randomFrom(KibanaUser.NAME, LogstashSystemUser.NAME, BeatsSystemUser.NAME); @@ -398,7 +413,7 @@ public void testNonElasticUsersCannotUseBootstrapPasswordWhenSecurityIndexDoesNo when(securityIndex.indexExists()).thenReturn(false); final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore, - new AnonymousUser(Settings.EMPTY), securityIndex, threadPool); + new AnonymousUser(Settings.EMPTY), securityIndex, threadPool); PlainActionFuture listener = new PlainActionFuture<>(); final String principal = randomFrom(KibanaUser.NAME, LogstashSystemUser.NAME, BeatsSystemUser.NAME); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/file/FileRealmTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/file/FileRealmTests.java index 7295e48d00394..f5dad8b7c684c 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/file/FileRealmTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/file/FileRealmTests.java @@ -58,7 +58,8 @@ public class FileRealmTests extends ESTestCase { public void init() throws Exception { userPasswdStore = mock(FileUserPasswdStore.class); userRolesStore = mock(FileUserRolesStore.class); - globalSettings = Settings.builder().put("path.home", createTempDir()).build(); + globalSettings = Settings.builder().put("path.home", createTempDir()).put("xpack.security.authc.password_hashing.algorithm", + randomFrom("bcrypt9", "pbkdf2")).build(); threadPool = mock(ThreadPool.class); threadContext = new ThreadContext(globalSettings); when(threadPool.getThreadContext()).thenReturn(threadContext); @@ -85,8 +86,7 @@ public void testAuthenticate() throws Exception { public void testAuthenticateCaching() throws Exception { Settings settings = Settings.builder() - .put("cache.hash_algo", Hasher.values()[randomIntBetween(0, Hasher.values().length - 1)].name().toLowerCase(Locale.ROOT)) - .build(); + .put("cache.hash_algo", Hasher.values()[randomIntBetween(0, Hasher.values().length - 1)].name().toLowerCase(Locale.ROOT)).build(); RealmConfig config = new RealmConfig("file-test", settings, globalSettings, TestEnvironment.newEnvironment(globalSettings), threadContext); when(userPasswdStore.verifyPassword(eq("user1"), eq(new SecureString("test123")), any(Supplier.class))) diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/file/FileUserPasswdStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/file/FileUserPasswdStoreTests.java index 367313c58a6cb..739952af63ee7 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/file/FileUserPasswdStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/file/FileUserPasswdStoreTests.java @@ -53,9 +53,11 @@ public class FileUserPasswdStoreTests extends ESTestCase { @Before public void init() { settings = Settings.builder() - .put("resource.reload.interval.high", "2s") - .put("path.home", createTempDir()) - .build(); + .put("resource.reload.interval.high", "2s") + .put("path.home", createTempDir()) + .put("xpack.security.authc.password_hashing.algorithm", randomFrom("bcrypt", "bcrypt11", "pbkdf2", "pbkdf2_1000", + "pbkdf2_50000")) + .build(); env = TestEnvironment.newEnvironment(settings); threadPool = new TestThreadPool("test"); } @@ -86,17 +88,18 @@ public void testStore_AutoReload() throws Exception { Files.createDirectories(xpackConf); Path file = xpackConf.resolve("users"); Files.copy(users, file, StandardCopyOption.REPLACE_EXISTING); - + final Hasher hasher = Hasher.resolve(settings.get("xpack.security.authc.password_hashing.algorithm")); Settings fileSettings = randomBoolean() ? Settings.EMPTY : Settings.builder().put("files.users", file.toAbsolutePath()).build(); RealmConfig config = new RealmConfig("file-test", fileSettings, settings, env, threadPool.getThreadContext()); ResourceWatcherService watcherService = new ResourceWatcherService(settings, threadPool); final CountDownLatch latch = new CountDownLatch(1); FileUserPasswdStore store = new FileUserPasswdStore(config, watcherService, latch::countDown); - - User user = new User("bcrypt"); - assertThat(store.userExists("bcrypt"), is(true)); - AuthenticationResult result = store.verifyPassword("bcrypt", new SecureString("test123"), () -> user); + //Test users share the hashing algorithm name for convenience + String username = settings.get("xpack.security.authc.password_hashing.algorithm"); + User user = new User(username); + assertThat(store.userExists(username), is(true)); + AuthenticationResult result = store.verifyPassword(username, new SecureString("test123"), () -> user); assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS)); assertThat(result.getUser(), is(user)); @@ -104,7 +107,7 @@ public void testStore_AutoReload() throws Exception { try (BufferedWriter writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8, StandardOpenOption.APPEND)) { writer.newLine(); - writer.append("foobar:").append(new String(Hasher.BCRYPT.hash(new SecureString("barfoo")))); + writer.append("foobar:").append(new String(hasher.hash(new SecureString("barfoo")))); } if (!latch.await(5, TimeUnit.SECONDS)) { @@ -133,9 +136,10 @@ public void testStore_AutoReload_WithParseFailures() throws Exception { final CountDownLatch latch = new CountDownLatch(1); FileUserPasswdStore store = new FileUserPasswdStore(config, watcherService, latch::countDown); - - User user = new User("bcrypt"); - final AuthenticationResult result = store.verifyPassword("bcrypt", new SecureString("test123"), () -> user); + //Test users share the hashing algorithm name for convenience + String username = settings.get("xpack.security.authc.password_hashing.algorithm"); + User user = new User(username); + final AuthenticationResult result = store.verifyPassword(username, new SecureString("test123"), () -> user); assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS)); assertThat(result.getUser(), is(user)); @@ -155,11 +159,11 @@ public void testParseFile() throws Exception { Path path = getDataPath("users"); Map users = FileUserPasswdStore.parseFile(path, null, Settings.EMPTY); assertThat(users, notNullValue()); - assertThat(users.size(), is(6)); + assertThat(users.size(), is(11)); assertThat(users.get("bcrypt"), notNullValue()); assertThat(new String(users.get("bcrypt")), equalTo("$2a$05$zxnP0vdREMxnEpkLCDI2OuSaSk/QEKA2.A42iOpI6U2u.RLLOWm1e")); assertThat(users.get("bcrypt10"), notNullValue()); - assertThat(new String(users.get("bcrypt10")), equalTo("$2y$10$FMhmFjwU5.qxQ/BsEciS9OqcJVkFMgXMo4uH5CelOR1j4N9zIv67e")); + assertThat(new String(users.get("bcrypt10")), equalTo("$2a$10$cFxpMx6YDrH/PXwLpTlux.KVykN1TG2Pgdl5oJX5/G/KYp3G6jbFG")); assertThat(users.get("md5"), notNullValue()); assertThat(new String(users.get("md5")), equalTo("$apr1$R3DdqiAZ$aljIkaIVPSarmDMlJUBBP.")); assertThat(users.get("crypt"), notNullValue()); @@ -168,6 +172,15 @@ public void testParseFile() throws Exception { assertThat(new String(users.get("plain")), equalTo("{plain}test123")); assertThat(users.get("sha"), notNullValue()); assertThat(new String(users.get("sha")), equalTo("{SHA}cojt0Pw//L6ToM8G41aOKFIWh7w=")); + assertThat(users.get("pbkdf2"), notNullValue()); + assertThat(new String(users.get("pbkdf2")), + equalTo("{PBKDF2}10000$ekcItXk4jtK2bBjbVk0rZuWRjT0DoQqQJOIfyMeLIxg=$RA2/Nn1jRi8QskRS5IVotCV0FBO6M8DlNXC37GKa/8c=")); + assertThat(users.get("pbkdf2_1000"), notNullValue()); + assertThat(new String(users.get("pbkdf2_1000")), + equalTo("{PBKDF2}1000$32yPZSShxuKYAl47ip0g6VwbFrD8tvFJuQCoRPGhXC8=$cXAE1BkBXRmkv7pQA7fw4TZ1+rFWS2/nZGeA3kL1Eu8=")); + assertThat(users.get("pbkdf2_50000"), notNullValue()); + assertThat(new String(users.get("pbkdf2_50000")), + equalTo("{PBKDF2}50000$z1CLJt0MEFjkIK5iEfgvfnA6xq7lF25uasspsTKSo5Q=$XxCVLbaKDimOdyWgLCLJiyoiWpA/XDMe/xtVgn1r5Sg=")); } public void testParseFile_Empty() throws Exception { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealmTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealmTests.java index 38a6344f98e54..052758d83718c 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealmTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealmTests.java @@ -19,7 +19,6 @@ import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; import org.elasticsearch.xpack.core.security.authc.Realm; import org.elasticsearch.xpack.core.security.authc.RealmConfig; -import org.elasticsearch.xpack.core.security.authc.support.BCrypt; import org.elasticsearch.xpack.core.security.authc.support.CachingUsernamePasswordRealmSettings; import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; @@ -29,6 +28,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; @@ -61,12 +61,11 @@ public void stop() throws InterruptedException { } public void testSettings() throws Exception { - String hashAlgo = randomFrom("bcrypt", "bcrypt4", "bcrypt5", "bcrypt6", "bcrypt7", "bcrypt8", "bcrypt9", - "sha1", "ssha256", "md5", "clear_text", "noop"); + String cachingHashAlgo = Hasher.values()[randomIntBetween(0, Hasher.values().length - 1)].name().toLowerCase(Locale.ROOT); int maxUsers = randomIntBetween(10, 100); TimeValue ttl = TimeValue.timeValueMinutes(randomIntBetween(10, 20)); Settings settings = Settings.builder() - .put(CachingUsernamePasswordRealmSettings.CACHE_HASH_ALGO_SETTING.getKey(), hashAlgo) + .put(CachingUsernamePasswordRealmSettings.CACHE_HASH_ALGO_SETTING.getKey(), cachingHashAlgo) .put(CachingUsernamePasswordRealmSettings.CACHE_MAX_USERS_SETTING.getKey(), maxUsers) .put(CachingUsernamePasswordRealmSettings.CACHE_TTL_SETTING.getKey(), ttl) .build(); @@ -84,8 +83,7 @@ protected void doLookupUser(String username, ActionListener listener) { listener.onFailure(new UnsupportedOperationException("this method should not be called")); } }; - - assertThat(realm.hasher, sameInstance(Hasher.resolve(hashAlgo))); + assertThat(realm.cacheHasher, sameInstance(Hasher.resolve(cachingHashAlgo))); } public void testAuthCache() { @@ -347,8 +345,8 @@ public void testSingleAuthPerUserLimit() throws Exception { final String username = "username"; final SecureString password = SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING; final AtomicInteger authCounter = new AtomicInteger(0); - - final String passwordHash = new String(Hasher.BCRYPT.hash(password)); + final Hasher pwdHasher = Hasher.resolve(randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt", "bcrypt9")); + final String passwordHash = new String(pwdHasher.hash(password)); RealmConfig config = new RealmConfig("test_realm", Settings.EMPTY, globalSettings, TestEnvironment.newEnvironment(globalSettings), new ThreadContext(Settings.EMPTY)); final CachingUsernamePasswordRealm realm = new CachingUsernamePasswordRealm("test", config, threadPool) { @@ -356,7 +354,7 @@ public void testSingleAuthPerUserLimit() throws Exception { protected void doAuthenticate(UsernamePasswordToken token, ActionListener listener) { authCounter.incrementAndGet(); // do something slow - if (BCrypt.checkpw(token.credentials(), passwordHash)) { + if (pwdHasher.verify(token.credentials(), passwordHash.toCharArray())) { listener.onResponse(AuthenticationResult.success(new User(username, new String[]{"r1", "r2", "r3"}))); } else { listener.onFailure(new IllegalStateException("password auth should never fail")); @@ -413,15 +411,15 @@ public void testCacheConcurrency() throws Exception { final String username = "username"; final SecureString password = SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING; final SecureString randomPassword = new SecureString(randomAlphaOfLength(password.length()).toCharArray()); - - final String passwordHash = new String(Hasher.BCRYPT.hash(password)); + final Hasher localHasher = Hasher.resolve(randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt", "bcrypt9")); + final String passwordHash = new String(localHasher.hash(password)); RealmConfig config = new RealmConfig("test_realm", Settings.EMPTY, globalSettings, TestEnvironment.newEnvironment(globalSettings), new ThreadContext(Settings.EMPTY)); final CachingUsernamePasswordRealm realm = new CachingUsernamePasswordRealm("test", config, threadPool) { @Override protected void doAuthenticate(UsernamePasswordToken token, ActionListener listener) { // do something slow - if (BCrypt.checkpw(token.credentials(), passwordHash)) { + if (localHasher.verify(token.credentials(), passwordHash.toCharArray())) { listener.onResponse(AuthenticationResult.success(new User(username, new String[]{"r1", "r2", "r3"}))); } else { listener.onResponse(AuthenticationResult.unsuccessful("Incorrect password", null)); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/HasherTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/HasherTests.java index 0a8e8e9ac3936..6086dc642d22f 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/HasherTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/HasherTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.core.security.authc.support.Hasher; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.sameInstance; public class HasherTests extends ESTestCase { @@ -20,6 +21,21 @@ public void testBcryptFamilySelfGenerated() throws Exception { testHasherSelfGenerated(Hasher.BCRYPT7); testHasherSelfGenerated(Hasher.BCRYPT8); testHasherSelfGenerated(Hasher.BCRYPT9); + testHasherSelfGenerated(Hasher.BCRYPT10); + testHasherSelfGenerated(Hasher.BCRYPT11); + testHasherSelfGenerated(Hasher.BCRYPT12); + testHasherSelfGenerated(Hasher.BCRYPT13); + testHasherSelfGenerated(Hasher.BCRYPT14); + } + + public void testPBKDF2FamilySelfGenerated() throws Exception { + testHasherSelfGenerated(Hasher.PBKDF2); + testHasherSelfGenerated(Hasher.PBKDF2_1000); + testHasherSelfGenerated(Hasher.PBKDF2_10000); + testHasherSelfGenerated(Hasher.PBKDF2_50000); + testHasherSelfGenerated(Hasher.PBKDF2_100000); + testHasherSelfGenerated(Hasher.PBKDF2_500000); + testHasherSelfGenerated(Hasher.PBKDF2_1000000); } public void testMd5SelfGenerated() throws Exception { @@ -38,7 +54,7 @@ public void testNoopSelfGenerated() throws Exception { testHasherSelfGenerated(Hasher.NOOP); } - public void testResolve() throws Exception { + public void testResolve() { assertThat(Hasher.resolve("bcrypt"), sameInstance(Hasher.BCRYPT)); assertThat(Hasher.resolve("bcrypt4"), sameInstance(Hasher.BCRYPT4)); assertThat(Hasher.resolve("bcrypt5"), sameInstance(Hasher.BCRYPT5)); @@ -46,23 +62,77 @@ public void testResolve() throws Exception { assertThat(Hasher.resolve("bcrypt7"), sameInstance(Hasher.BCRYPT7)); assertThat(Hasher.resolve("bcrypt8"), sameInstance(Hasher.BCRYPT8)); assertThat(Hasher.resolve("bcrypt9"), sameInstance(Hasher.BCRYPT9)); + assertThat(Hasher.resolve("bcrypt10"), sameInstance(Hasher.BCRYPT)); + assertThat(Hasher.resolve("bcrypt11"), sameInstance(Hasher.BCRYPT11)); + assertThat(Hasher.resolve("bcrypt12"), sameInstance(Hasher.BCRYPT12)); + assertThat(Hasher.resolve("bcrypt13"), sameInstance(Hasher.BCRYPT13)); + assertThat(Hasher.resolve("bcrypt14"), sameInstance(Hasher.BCRYPT14)); + assertThat(Hasher.resolve("pbkdf2"), sameInstance(Hasher.PBKDF2)); + assertThat(Hasher.resolve("pbkdf2_1000"), sameInstance(Hasher.PBKDF2_1000)); + assertThat(Hasher.resolve("pbkdf2_10000"), sameInstance(Hasher.PBKDF2)); + assertThat(Hasher.resolve("pbkdf2_50000"), sameInstance(Hasher.PBKDF2_50000)); + assertThat(Hasher.resolve("pbkdf2_100000"), sameInstance(Hasher.PBKDF2_100000)); + assertThat(Hasher.resolve("pbkdf2_500000"), sameInstance(Hasher.PBKDF2_500000)); + assertThat(Hasher.resolve("pbkdf2_1000000"), sameInstance(Hasher.PBKDF2_1000000)); assertThat(Hasher.resolve("sha1"), sameInstance(Hasher.SHA1)); assertThat(Hasher.resolve("md5"), sameInstance(Hasher.MD5)); assertThat(Hasher.resolve("ssha256"), sameInstance(Hasher.SSHA256)); assertThat(Hasher.resolve("noop"), sameInstance(Hasher.NOOP)); assertThat(Hasher.resolve("clear_text"), sameInstance(Hasher.NOOP)); - try { + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> { Hasher.resolve("unknown_hasher"); - fail("expected a settings error when trying to resolve an unknown hasher"); - } catch (IllegalArgumentException e) { - // expected - } - Hasher hasher = randomFrom(Hasher.values()); - assertThat(Hasher.resolve("unknown_hasher", hasher), sameInstance(hasher)); + }); + assertThat(e.getMessage(), containsString("unknown hash function ")); + } + + public void testResolveFromHash() { + assertThat(Hasher.resolveFromHash("$2a$10$1oZj.8KmlwiCy4DWKvDH3OU0Ko4WRF4FknyvCh3j/ZtaRCNYA6Xzm".toCharArray()), + sameInstance(Hasher.BCRYPT)); + assertThat(Hasher.resolveFromHash("$2a$04$GwJtIQiGMHASEYphMiCpjeZh1cDyYC5U.DKfNKa4i/y0IbOvc2LiG".toCharArray()), + sameInstance(Hasher.BCRYPT4)); + assertThat(Hasher.resolveFromHash("$2a$05$xLmwSB7Nw7PcqP.6hXdc4eUZbT.4.iAZ3CTPzSaUibrrYjC6Vwq1m".toCharArray()), + sameInstance(Hasher.BCRYPT5)); + assertThat(Hasher.resolveFromHash("$2a$06$WQX1MALAjVOhR2YKmLcHYed2oROzBl3OZPtvq3FkVZYwm9X2LVKYm".toCharArray()), + sameInstance(Hasher.BCRYPT6)); + assertThat(Hasher.resolveFromHash("$2a$07$Satxnu2fCvwYXpHIk8A2sO2uwROrsV7WrNiRJPq1oXEl5lc9FE.7S".toCharArray()), + sameInstance(Hasher.BCRYPT7)); + assertThat(Hasher.resolveFromHash("$2a$08$LLfkTt2C9TUl5sDtgqmE3uRw9nHt748d3eMSGfbFYgQQQhjbXHFo2".toCharArray()), + sameInstance(Hasher.BCRYPT8)); + assertThat(Hasher.resolveFromHash("$2a$09$.VCWA3yFVdd6gfI526TUrufb4TvxMuhW0jIuMfhd4/fy1Ak/zrSFe".toCharArray()), + sameInstance(Hasher.BCRYPT9)); + assertThat(Hasher.resolveFromHash("$2a$10$OEiXFrUUY02Nm7YsEgzFuuJ3yO3HAYzJUU7omseluy28s7FYaictu".toCharArray()), + sameInstance(Hasher.BCRYPT)); + assertThat(Hasher.resolveFromHash("$2a$11$Ya53LCozFlKABu05xsAbj.9xmrczyuAY/fTvxKkDiHOJc5GYcaNRy".toCharArray()), + sameInstance(Hasher.BCRYPT11)); + assertThat(Hasher.resolveFromHash("$2a$12$oUW2hiWBHYwbJamWi6YDPeKS2NBCvD4GR50zh9QZCcgssNFcbpg/a".toCharArray()), + sameInstance(Hasher.BCRYPT12)); + assertThat(Hasher.resolveFromHash("$2a$13$0PDx6mxKK4bLSgpc5H6eaeylWub7UFghjxV03lFYSz4WS4slDT30q".toCharArray()), + sameInstance(Hasher.BCRYPT13)); + assertThat(Hasher.resolveFromHash("$2a$14$lFyXmX7p9/FHr7W4nxTnfuCkjAoBHv6awQlv8jlKZ/YCMI65i38e6".toCharArray()), + sameInstance(Hasher.BCRYPT14)); + assertThat(Hasher.resolveFromHash( + "{PBKDF2}1000$oNl3JWiDZhXqhrpk9Kl+T0tKpVNNV3UHNxENPePpo2M=$g9lERDX5op20eX534bHdQy7ySRwobxwtaxxsz3AYPIU=".toCharArray()), + sameInstance(Hasher.PBKDF2_1000)); + assertThat(Hasher.resolveFromHash( + "{PBKDF2}10000$UrwrHBY4GA1na9KxRpoFkUiICTeZe+mMZCZOg6bRSLc=$1Wl32wRQ9Q3Sv1IFoNwgSrUa5YifLv0MoxAO6leyip8=".toCharArray()), + sameInstance(Hasher.PBKDF2)); + assertThat(Hasher.resolveFromHash( + "{PBKDF2}50000$mxa5m9AlgtKLUXKi/pE5+4w7ZexGSOtlUHD043NHVdc=$LE5Ncph672M8PtugfRgk2k3ue9qY2cKgiguuAd+e3I0=".toCharArray()), + sameInstance(Hasher.PBKDF2_50000)); + assertThat(Hasher.resolveFromHash( + "{PBKDF2}100000$qFs8H0FjietnI7sgr/1Av4H+Z7d/9dehfZ2ptU474jk=$OFj40Ha0XcHWUXSspRx6EeXnTcuN0Nva2/i2c/hvnZE=".toCharArray()), + sameInstance(Hasher.PBKDF2_100000)); + assertThat(Hasher.resolveFromHash( + "{PBKDF2}500000$wyttuDlppd5KYD35uDZN6vudB50Cjshm5efZhOxZZQI=$ADZpOHY6llJZsjupZCn6s4Eocg0dKKdBiNjDBYqhlzA=".toCharArray()), + sameInstance(Hasher.PBKDF2_500000)); + assertThat(Hasher.resolveFromHash( + "{PBKDF2}1000000$UuyhtjDEzWmE2wyY80akZKPWWpy2r2X50so41YML82U=$WFasYLelqbjQwt3EqFlUcwHiC38EZC45Iu/Iz0xL1GQ=".toCharArray()), + sameInstance(Hasher.PBKDF2_1000000)); + assertThat(Hasher.resolveFromHash("notavalidhashformat".toCharArray()), sameInstance(Hasher.NOOP)); } - private static void testHasherSelfGenerated(Hasher hasher) throws Exception { - SecureString passwd = new SecureString(randomAlphaOfLength(10)); + private static void testHasherSelfGenerated(Hasher hasher) { + SecureString passwd = new SecureString(randomAlphaOfLength(10).toCharArray()); char[] hash = hasher.hash(passwd); assertTrue(hasher.verify(passwd, hash)); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AnalyzeTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AnalyzeTests.java index b8634f2e9f358..3c3574704ac9c 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AnalyzeTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AnalyzeTests.java @@ -8,7 +8,6 @@ import org.elasticsearch.action.admin.indices.analyze.AnalyzeAction; import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.test.SecurityIntegTestCase; -import org.elasticsearch.xpack.core.security.authc.support.Hasher; import java.util.Collections; @@ -17,13 +16,14 @@ import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; public class AnalyzeTests extends SecurityIntegTestCase { - protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(new SecureString("test123".toCharArray()))); @Override protected String configUsers() { + final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(new SecureString + ("test123".toCharArray()))); return super.configUsers() + - "analyze_indices:" + USERS_PASSWD_HASHED + "\n" + - "analyze_cluster:" + USERS_PASSWD_HASHED + "\n"; + "analyze_indices:" + usersPasswdHashed + "\n" + + "analyze_cluster:" + usersPasswdHashed + "\n"; } @Override diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndexAliasesTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndexAliasesTests.java index d273d61959e2a..c6cb8bb662c7c 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndexAliasesTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndexAliasesTests.java @@ -16,7 +16,6 @@ import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.test.SecurityIntegTestCase; -import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.junit.Before; import java.util.Collections; @@ -31,16 +30,16 @@ public class IndexAliasesTests extends SecurityIntegTestCase { - protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(new SecureString("test123".toCharArray()))); - @Override protected String configUsers() { + final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(new SecureString + ("test123".toCharArray()))); return super.configUsers() + - "create_only:" + USERS_PASSWD_HASHED + "\n" + - "create_test_aliases_test:" + USERS_PASSWD_HASHED + "\n" + - "create_test_aliases_alias:" + USERS_PASSWD_HASHED + "\n" + - "create_test_aliases_test_alias:" + USERS_PASSWD_HASHED + "\n" + - "aliases_only:" + USERS_PASSWD_HASHED + "\n"; + "create_only:" + usersPasswdHashed + "\n" + + "create_test_aliases_test:" + usersPasswdHashed + "\n" + + "create_test_aliases_alias:" + usersPasswdHashed + "\n" + + "create_test_aliases_test_alias:" + usersPasswdHashed + "\n" + + "aliases_only:" + usersPasswdHashed + "\n"; } @Override diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/SecurityScrollTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/SecurityScrollTests.java index c3d24e1adc7a4..4466190f83ded 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/SecurityScrollTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/SecurityScrollTests.java @@ -32,7 +32,9 @@ public void testScrollIsPerUser() throws Exception { securityClient().preparePutRole("scrollable") .addIndices(new String[] { randomAlphaOfLengthBetween(4, 12) }, new String[] { "read" }, null, null, null) .get(); - securityClient().preparePutUser("other", SecuritySettingsSourceField.TEST_PASSWORD.toCharArray(), "scrollable").get(); + securityClient().preparePutUser("other", SecuritySettingsSourceField.TEST_PASSWORD.toCharArray(), getFastStoredHashAlgoForTests(), + "scrollable") + .get(); final int numDocs = randomIntBetween(4, 16); IndexRequestBuilder[] docs = new IndexRequestBuilder[numDocs]; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/support/SecurityIndexManagerIntegTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/support/SecurityIndexManagerIntegTests.java index 677be9a94e7ce..55cd659509bfe 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/support/SecurityIndexManagerIntegTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/support/SecurityIndexManagerIntegTests.java @@ -48,7 +48,8 @@ protected void doRun() throws Exception { for (int i = 0; i < numRequests; i++) { requests.add(securityClient() .preparePutUser("user" + userNumber.getAndIncrement(), "password".toCharArray(), - randomAlphaOfLengthBetween(1, 16)) + getFastStoredHashAlgoForTests(), + randomAlphaOfLengthBetween(1, 16)) .request()); } diff --git a/x-pack/plugin/security/src/test/resources/org/elasticsearch/xpack/security/authc/file/users b/x-pack/plugin/security/src/test/resources/org/elasticsearch/xpack/security/authc/file/users index 997c839c2695b..435c3fc5ed9e2 100644 --- a/x-pack/plugin/security/src/test/resources/org/elasticsearch/xpack/security/authc/file/users +++ b/x-pack/plugin/security/src/test/resources/org/elasticsearch/xpack/security/authc/file/users @@ -5,4 +5,9 @@ plain:{plain}test123 sha:{SHA}cojt0Pw//L6ToM8G41aOKFIWh7w= # this is a comment line # another comment line -bcrypt10:$2y$10$FMhmFjwU5.qxQ/BsEciS9OqcJVkFMgXMo4uH5CelOR1j4N9zIv67e \ No newline at end of file +pbkdf2:{PBKDF2}10000$ekcItXk4jtK2bBjbVk0rZuWRjT0DoQqQJOIfyMeLIxg=$RA2/Nn1jRi8QskRS5IVotCV0FBO6M8DlNXC37GKa/8c= +pbkdf2_1000:{PBKDF2}1000$32yPZSShxuKYAl47ip0g6VwbFrD8tvFJuQCoRPGhXC8=$cXAE1BkBXRmkv7pQA7fw4TZ1+rFWS2/nZGeA3kL1Eu8= +pbkdf2_50000:{PBKDF2}50000$z1CLJt0MEFjkIK5iEfgvfnA6xq7lF25uasspsTKSo5Q=$XxCVLbaKDimOdyWgLCLJiyoiWpA/XDMe/xtVgn1r5Sg= +bcrypt9:$2a$09$YhstxoAjO7M5MtAIFv7dVO70/pElJAYrzyumeCpLpZV2Gcz4J2/F. +bcrypt10:$2a$10$cFxpMx6YDrH/PXwLpTlux.KVykN1TG2Pgdl5oJX5/G/KYp3G6jbFG +bcrypt11:$2a$11$uxr7b0qgCrLV9VIz9XS7M.Eoc0gJRR60oV48UK5DKfLOp.9HjfYF2 \ No newline at end of file diff --git a/x-pack/qa/security-example-spi-extension/src/test/java/org/elasticsearch/example/role/CustomRolesProviderIT.java b/x-pack/qa/security-example-spi-extension/src/test/java/org/elasticsearch/example/role/CustomRolesProviderIT.java index 4e1fb72256086..bd3c53a3b41be 100644 --- a/x-pack/qa/security-example-spi-extension/src/test/java/org/elasticsearch/example/role/CustomRolesProviderIT.java +++ b/x-pack/qa/security-example-spi-extension/src/test/java/org/elasticsearch/example/role/CustomRolesProviderIT.java @@ -16,6 +16,7 @@ import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.xpack.core.XPackClientPlugin; +import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; import org.elasticsearch.xpack.core.security.client.SecurityClient; @@ -52,7 +53,7 @@ protected Collection> transportClientPlugins() { public void setupTestUser(String role) { SecurityClient securityClient = new SecurityClient(client()); - securityClient.preparePutUser(TEST_USER, TEST_PWD.toCharArray(), role).get(); + securityClient.preparePutUser(TEST_USER, TEST_PWD.toCharArray(), Hasher.BCRYPT, role).get(); } public void testAuthorizedCustomRoleSucceeds() throws Exception { diff --git a/x-pack/qa/security-migrate-tests/src/test/java/org/elasticsearch/xpack/security/MigrateToolIT.java b/x-pack/qa/security-migrate-tests/src/test/java/org/elasticsearch/xpack/security/MigrateToolIT.java index 719971bf8a829..4ac927c6646c1 100644 --- a/x-pack/qa/security-migrate-tests/src/test/java/org/elasticsearch/xpack/security/MigrateToolIT.java +++ b/x-pack/qa/security-migrate-tests/src/test/java/org/elasticsearch/xpack/security/MigrateToolIT.java @@ -20,6 +20,7 @@ import org.elasticsearch.xpack.core.security.action.role.GetRolesResponse; import org.elasticsearch.xpack.core.security.action.user.GetUsersResponse; import org.elasticsearch.xpack.core.security.action.user.PutUserResponse; +import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissions; import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsDefinition; @@ -46,7 +47,7 @@ public void setupUpTest() throws Exception { SecurityClient c = new SecurityClient(client); // Add an existing user so the tool will skip it - PutUserResponse pur = c.preparePutUser("existing", "s3kirt".toCharArray(), "role1", "user").get(); + PutUserResponse pur = c.preparePutUser("existing", "s3kirt".toCharArray(), Hasher.BCRYPT, "role1", "user").get(); assertTrue(pur.created()); } diff --git a/x-pack/qa/security-tools-tests/src/test/java/org/elasticsearch/xpack/security/authc/file/tool/UsersToolTests.java b/x-pack/qa/security-tools-tests/src/test/java/org/elasticsearch/xpack/security/authc/file/tool/UsersToolTests.java index 72e69a873c26d..5800a3c2a3666 100644 --- a/x-pack/qa/security-tools-tests/src/test/java/org/elasticsearch/xpack/security/authc/file/tool/UsersToolTests.java +++ b/x-pack/qa/security-tools-tests/src/test/java/org/elasticsearch/xpack/security/authc/file/tool/UsersToolTests.java @@ -55,6 +55,8 @@ public class UsersToolTests extends CommandTestCase { // settings used to create an Environment for tools Settings settings; + Hasher hasher; + @BeforeClass public static void setupJimfs() throws IOException { String view = randomFrom("basic", "posix"); @@ -69,11 +71,12 @@ public void setupHome() throws IOException { IOUtils.rm(homeDir); confDir = homeDir.resolve("config"); Files.createDirectories(confDir); + hasher = Hasher.resolve(randomFrom("bcrypt", "pbkdf2")); String defaultPassword = SecuritySettingsSourceField.TEST_PASSWORD; Files.write(confDir.resolve("users"), Arrays.asList( - "existing_user:" + new String(Hasher.BCRYPT.hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)), - "existing_user2:" + new String(Hasher.BCRYPT.hash(new SecureString((defaultPassword + "2").toCharArray()))), - "existing_user3:" + new String(Hasher.BCRYPT.hash(new SecureString((defaultPassword + "3").toCharArray()))) + "existing_user:" + new String(hasher.hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)), + "existing_user2:" + new String(hasher.hash(new SecureString((defaultPassword + "2").toCharArray()))), + "existing_user3:" + new String(hasher.hash(new SecureString((defaultPassword + "3").toCharArray()))) ), StandardCharsets.UTF_8); Files.write(confDir.resolve("users_roles"), Arrays.asList( "test_admin:existing_user,existing_user2", @@ -171,9 +174,10 @@ void assertUser(String username, String password) throws IOException { continue; } String gotHash = usernameHash[1]; - SecureString expectedHash = new SecureString(password); - assertTrue("Expected hash " + expectedHash + " for password " + password + " but got " + gotHash, - Hasher.BCRYPT.verify(expectedHash, gotHash.toCharArray())); + SecureString expectedHash = new SecureString(password.toCharArray()); + // CommandTestCase#execute runs passwd with default settings, so bcrypt with cost of 10 + Hasher bcryptHasher = Hasher.resolve("bcrypt"); + assertTrue("Could not validate password for user", bcryptHasher.verify(expectedHash, gotHash.toCharArray())); return; } fail("Could not find username " + username + " in users file:\n" + lines.toString()); diff --git a/x-pack/qa/tribe-tests-with-security/src/test/java/org/elasticsearch/test/TribeWithSecurityIT.java b/x-pack/qa/tribe-tests-with-security/src/test/java/org/elasticsearch/test/TribeWithSecurityIT.java index 0f23946ae81f5..243e70b373154 100644 --- a/x-pack/qa/tribe-tests-with-security/src/test/java/org/elasticsearch/test/TribeWithSecurityIT.java +++ b/x-pack/qa/tribe-tests-with-security/src/test/java/org/elasticsearch/test/TribeWithSecurityIT.java @@ -19,10 +19,12 @@ import org.elasticsearch.xpack.core.security.SecurityField; import org.elasticsearch.xpack.core.security.action.role.GetRolesResponse; import org.elasticsearch.xpack.core.security.action.role.PutRoleResponse; +import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; import org.elasticsearch.xpack.core.security.client.SecurityClient; import org.junit.After; import org.junit.AfterClass; +import org.junit.BeforeClass; import java.io.IOException; import java.net.InetAddress; @@ -44,6 +46,12 @@ public class TribeWithSecurityIT extends SecurityIntegTestCase { private static TestCluster cluster2; private static TestCluster tribeNode; + private static Hasher hasher; + + @BeforeClass + public static void init() { + hasher = Hasher.resolve("bcrypt"); + } @Override public void setUp() throws Exception { @@ -60,6 +68,7 @@ public void setUp() throws Exception { protected Settings nodeSettings(int nodeOrdinal) { Settings.Builder builder = Settings.builder() .put(super.nodeSettings(nodeOrdinal)) + .put("xpack.security.authc.password_hashing.algorithm", hasher.name()) .put(LicenseService.SELF_GENERATED_LICENSE_TYPE.getKey(), "trial"); return builder.build(); } @@ -131,7 +140,7 @@ public void testThatTribeCanAuthenticateElasticUser() throws Exception { public void testThatTribeCanAuthenticateElasticUserWithChangedPassword() throws Exception { assertSecurityIndexActive(); - securityClient(client()).prepareChangePassword("elastic", "password".toCharArray()).get(); + securityClient(client()).prepareChangePassword("elastic", "password".toCharArray(), hasher).get(); assertTribeNodeHasAllIndices(); ClusterHealthResponse response = tribeNode.client().filterWithHeader(Collections.singletonMap("Authorization", @@ -143,8 +152,9 @@ public void testThatTribeCanAuthenticateElasticUserWithChangedPassword() throws public void testThatTribeClustersHaveDifferentPasswords() throws Exception { assertSecurityIndexActive(); assertSecurityIndexActive(cluster2); - securityClient().prepareChangePassword("elastic", "password".toCharArray()).get(); - securityClient(cluster2.client()).prepareChangePassword("elastic", "password2".toCharArray()).get(); + securityClient().prepareChangePassword("elastic", "password".toCharArray(), hasher).get(); + securityClient(cluster2.client()). + prepareChangePassword("elastic", "password2".toCharArray(), hasher).get(); assertTribeNodeHasAllIndices(); ClusterHealthResponse response = tribeNode.client().filterWithHeader(Collections.singletonMap("Authorization", @@ -156,12 +166,12 @@ public void testThatTribeClustersHaveDifferentPasswords() throws Exception { public void testUserModificationUsingTribeNodeAreDisabled() throws Exception { SecurityClient securityClient = securityClient(tribeNode.client()); NotSerializableExceptionWrapper e = expectThrows(NotSerializableExceptionWrapper.class, - () -> securityClient.preparePutUser("joe", "password".toCharArray()).get()); + () -> securityClient.preparePutUser("joe", "password".toCharArray(), hasher).get()); assertThat(e.getMessage(), containsString("users may not be created or modified using a tribe node")); e = expectThrows(NotSerializableExceptionWrapper.class, () -> securityClient.prepareSetEnabled("elastic", randomBoolean()).get()); assertThat(e.getMessage(), containsString("users may not be created or modified using a tribe node")); e = expectThrows(NotSerializableExceptionWrapper.class, - () -> securityClient.prepareChangePassword("elastic", "password".toCharArray()).get()); + () -> securityClient.prepareChangePassword("elastic", "password".toCharArray(), hasher).get()); assertThat(e.getMessage(), containsString("users may not be created or modified using a tribe node")); e = expectThrows(NotSerializableExceptionWrapper.class, () -> securityClient.prepareDeleteUser("joe").get()); assertThat(e.getMessage(), containsString("users may not be deleted using a tribe node")); diff --git a/x-pack/qa/tribe-tests-with-security/src/test/java/org/elasticsearch/xpack/security/SecurityTribeTests.java b/x-pack/qa/tribe-tests-with-security/src/test/java/org/elasticsearch/xpack/security/SecurityTribeTests.java index ce7d587d10e76..edf725e53939a 100644 --- a/x-pack/qa/tribe-tests-with-security/src/test/java/org/elasticsearch/xpack/security/SecurityTribeTests.java +++ b/x-pack/qa/tribe-tests-with-security/src/test/java/org/elasticsearch/xpack/security/SecurityTribeTests.java @@ -38,6 +38,7 @@ import org.elasticsearch.xpack.core.security.action.role.GetRolesResponse; import org.elasticsearch.xpack.core.security.action.role.PutRoleResponse; import org.elasticsearch.xpack.core.security.action.user.PutUserResponse; +import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; import org.elasticsearch.xpack.core.security.client.SecurityClient; import org.elasticsearch.xpack.security.support.SecurityIndexManager; @@ -74,6 +75,13 @@ public class SecurityTribeTests extends NativeRealmIntegTestCase { private static final String SECOND_CLUSTER_NODE_PREFIX = "node_cluster2_"; private static InternalTestCluster cluster2; private static boolean useSSL; + private static Hasher hasher; + + @BeforeClass + public static void setHasher() { + hasher = Hasher.resolve("BCRYPT"); + } + private Node tribeNode; private Client tribeClient; @@ -86,8 +94,8 @@ public static void setupSSL() { @Override protected Settings nodeSettings(int nodeOrdinal) { Settings.Builder builder = Settings.builder() - .put(super.nodeSettings(nodeOrdinal)) - .put(LicenseService.SELF_GENERATED_LICENSE_TYPE.getKey(), "trial"); + .put(super.nodeSettings(nodeOrdinal)) + .put(LicenseService.SELF_GENERATED_LICENSE_TYPE.getKey(), "trial"); return builder.build(); } @@ -235,9 +243,9 @@ private void setupTribeNode(Settings settings) throws Exception { @Override public Settings nodeSettings(int nodeOrdinal) { return Settings.builder() - .put(super.nodeSettings(nodeOrdinal)) - .put(NetworkModule.HTTP_ENABLED.getKey(), true) - .build(); + .put(super.nodeSettings(nodeOrdinal)) + .put(NetworkModule.HTTP_ENABLED.getKey(), true) + .build(); } }; final Settings settingsTemplate = cluster2SettingsSource.nodeSettings(0); @@ -285,7 +293,6 @@ public Settings nodeSettings(int nodeOrdinal) { .put("node.name", "tribe_node") // make sure we can identify threads from this node .setSecureSettings(secureSettings) .build(); - final List> classpathPlugins = new ArrayList<>(nodePlugins()); classpathPlugins.addAll(getMockPlugins()); tribeNode = new MockNode(merged, classpathPlugins, cluster2SettingsSource.nodeConfigPath(0)).start(); @@ -338,7 +345,7 @@ public void testThatTribeCanAuthenticateElasticUserWithChangedPassword() throws InternalTestCluster cluster = randomBoolean() ? internalCluster() : cluster2; ensureElasticPasswordBootstrapped(cluster); setupTribeNode(Settings.EMPTY); - securityClient(cluster.client()).prepareChangePassword("elastic", "password".toCharArray()).get(); + securityClient(cluster.client()).prepareChangePassword("elastic", "password".toCharArray(), hasher).get(); assertTribeNodeHasAllIndices(); ClusterHealthResponse response = tribeClient.filterWithHeader(Collections.singletonMap("Authorization", @@ -351,8 +358,9 @@ public void testThatTribeClustersHaveDifferentPasswords() throws Exception { ensureElasticPasswordBootstrapped(internalCluster()); ensureElasticPasswordBootstrapped(cluster2); setupTribeNode(Settings.EMPTY); - securityClient().prepareChangePassword("elastic", "password".toCharArray()).get(); - securityClient(cluster2.client()).prepareChangePassword("elastic", "password2".toCharArray()).get(); + securityClient().prepareChangePassword("elastic", "password".toCharArray(), hasher).get(); + securityClient(cluster2.client()).prepareChangePassword("elastic", "password2".toCharArray(), hasher) + .get(); assertTribeNodeHasAllIndices(); ClusterHealthResponse response = tribeClient.filterWithHeader(Collections.singletonMap("Authorization", @@ -378,8 +386,8 @@ public void testUsersInBothTribes() throws Exception { final String username = "user" + i; Client clusterClient = randomBoolean() ? cluster1Client : cluster2Client; - PutUserResponse response = - securityClient(clusterClient).preparePutUser(username, "password".toCharArray(), "superuser").get(); + PutUserResponse response = securityClient(clusterClient) + .preparePutUser(username, "password".toCharArray(), hasher, "superuser").get(); assertTrue(response.created()); // if it was the first client, we should expect authentication to succeed @@ -420,8 +428,8 @@ public void testUsersInNonPreferredClusterOnly() throws Exception { for (int i = 0; i < randomUsers; i++) { final String username = "user" + i; - PutUserResponse response = - securityClient(nonPreferredCluster.client()).preparePutUser(username, "password".toCharArray(), "superuser").get(); + PutUserResponse response = securityClient(nonPreferredCluster.client()) + .preparePutUser(username, "password".toCharArray(), hasher, "superuser").get(); assertTrue(response.created()); shouldBeSuccessfulUsers.add(username); } @@ -451,12 +459,12 @@ public void testUserModificationUsingTribeNodeAreDisabled() throws Exception { setupTribeNode(Settings.EMPTY); SecurityClient securityClient = securityClient(tribeClient); UnsupportedOperationException e = expectThrows(UnsupportedOperationException.class, - () -> securityClient.preparePutUser("joe", "password".toCharArray()).get()); + () -> securityClient.preparePutUser("joe", "password".toCharArray(), hasher).get()); assertThat(e.getMessage(), containsString("users may not be created or modified using a tribe node")); e = expectThrows(UnsupportedOperationException.class, () -> securityClient.prepareSetEnabled("elastic", randomBoolean()).get()); assertThat(e.getMessage(), containsString("users may not be created or modified using a tribe node")); e = expectThrows(UnsupportedOperationException.class, - () -> securityClient.prepareChangePassword("elastic", "password".toCharArray()).get()); + () -> securityClient.prepareChangePassword("elastic", "password".toCharArray(), hasher).get()); assertThat(e.getMessage(), containsString("users may not be created or modified using a tribe node")); e = expectThrows(UnsupportedOperationException.class, () -> securityClient.prepareDeleteUser("joe").get()); assertThat(e.getMessage(), containsString("users may not be deleted using a tribe node"));