Changed the cached hashing algorithm for cached realms

Now the passwords are hashed in-memory using SHA2 by default (instead of original bcrypt). Also, it's now possible to configure the in-memory hashing algorithm.

Original commit: elastic/x-pack-elasticsearch@e2d1b3116b
This commit is contained in:
uboness 2014-10-17 19:03:35 -07:00
parent 521ebe4672
commit b7dac66c8a
6 changed files with 59 additions and 13 deletions

View File

@ -7,6 +7,8 @@ package org.elasticsearch.shield;
import org.elasticsearch.shield.authz.SystemRole;
import java.util.Arrays;
/**
* An authenticated user
*/
@ -48,6 +50,26 @@ public abstract class User {
public String[] roles() {
return roles;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Simple simple = (Simple) o;
if (!Arrays.equals(roles, simple.roles)) return false;
if (!username.equals(simple.username)) return false;
return true;
}
@Override
public int hashCode() {
int result = username.hashCode();
result = 31 * result + Arrays.hashCode(roles);
return result;
}
}
private static class System extends User {
@ -67,6 +89,7 @@ public abstract class User {
public String[] roles() {
return ROLES;
}
}
}

View File

@ -29,7 +29,7 @@ public class ESUsersModule extends AbstractShieldModule.Node {
@Override
protected void configureNode() {
if (enabled) {
bind(Realm.class).annotatedWith(named(ESUsersRealm.TYPE)).to(ESUsersRealm.class).asEagerSingleton();
bind(ESUsersRealm.class).asEagerSingleton();
bind(UserPasswdStore.class).annotatedWith(named("file")).to(FileUserPasswdStore.class).asEagerSingleton();
bind(UserRolesStore.class).annotatedWith(named("file")).to(FileUserRolesStore.class).asEagerSingleton();
} else {

View File

@ -6,7 +6,6 @@
package org.elasticsearch.shield.authc.esusers;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.name.Named;
import org.elasticsearch.common.settings.Settings;
@ -14,7 +13,7 @@ import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.authc.AuthenticationToken;
import org.elasticsearch.shield.authc.Realm;
import org.elasticsearch.shield.authc.support.CachingUsernamePasswordRealm;
import org.elasticsearch.shield.authc.support.UserPasswdStore;
import org.elasticsearch.shield.authc.support.UserRolesStore;
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
@ -23,7 +22,7 @@ import org.elasticsearch.transport.TransportMessage;
/**
*
*/
public class ESUsersRealm extends AbstractComponent implements Realm<UsernamePasswordToken> {
public class ESUsersRealm extends CachingUsernamePasswordRealm {
public static final String TYPE = "esusers";
@ -60,7 +59,7 @@ public class ESUsersRealm extends AbstractComponent implements Realm<UsernamePas
}
@Override
public User authenticate(UsernamePasswordToken token) {
protected User doAuthenticate(UsernamePasswordToken token) {
if (userPasswdStore == null) {
return null;
}

View File

@ -30,8 +30,11 @@ public abstract class CachingUsernamePasswordRealm extends AbstractComponent imp
private final Cache<String, UserWithHash> cache;
private final Hasher hasher;
protected CachingUsernamePasswordRealm(Settings settings) {
super(settings);
hasher = Hasher.resolve(componentSettings.get("cache.hash_algo", null), Hasher.SHA2);
TimeValue ttl = componentSettings.getAsTime(CACHE_TTL, DEFAULT_TTL);
if (ttl.millis() > 0) {
cache = CacheBuilder.newBuilder()
@ -91,7 +94,7 @@ public abstract class CachingUsernamePasswordRealm extends AbstractComponent imp
if (user == null) {
throw new AuthenticationException("Could not authenticate ['" + token.principal() + "]");
}
return new UserWithHash(user, token.credentials());
return new UserWithHash(user, token.credentials(), hasher);
}
};
@ -116,13 +119,15 @@ public abstract class CachingUsernamePasswordRealm extends AbstractComponent imp
public static class UserWithHash {
User user;
char[] hash;
public UserWithHash(User user, SecuredString password){
Hasher hasher;
public UserWithHash(User user, SecuredString password, Hasher hasher){
this.user = user;
this.hash = Hasher.HTPASSWD.hash(password);
this.hash = hasher.hash(password);
this.hasher = hasher;
}
public boolean verify(SecuredString password){
return Hasher.HTPASSWD.verify(password, hash);
return hasher.verify(password, hash);
}
}
}

View File

@ -12,6 +12,7 @@ import org.apache.commons.codec.digest.Md5Crypt;
import org.apache.commons.codec.digest.Sha2Crypt;
import org.elasticsearch.ElasticsearchIllegalArgumentException;
import org.elasticsearch.common.os.OsUtils;
import org.elasticsearch.shield.ShieldSettingsException;
import java.util.Locale;
@ -147,6 +148,10 @@ public enum Hasher {
}
switch (name.toLowerCase(Locale.ROOT)) {
case "htpasswd" : return HTPASSWD;
case "bcrypt" : return BCRYPT;
case "sha1" : return SHA1;
case "sha2" : return SHA2;
case "md5" : return MD5;
default:
return defaultHasher;
}
@ -155,7 +160,7 @@ public enum Hasher {
public static Hasher resolve(String name) {
Hasher hasher = resolve(name, null);
if (hasher == null) {
throw new ElasticsearchIllegalArgumentException("Unknown hash function [" + name + "]");
throw new ShieldSettingsException("Unknown hash function [" + name + "]");
}
return hasher;
}

View File

@ -5,6 +5,7 @@
*/
package org.elasticsearch.shield.authc.esusers;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.action.Action;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionRequest;
@ -28,9 +29,9 @@ import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.Matchers.arrayContaining;
import java.util.Locale;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
/**
@ -70,6 +71,19 @@ public class ESUsersRealmTests extends ElasticsearchTestCase {
assertThat(user.roles(), arrayContaining("role1", "role2"));
}
@Test @Repeat(iterations = 20)
public void testAuthenticate_Caching() throws Exception {
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.cache.hash_algo", Hasher.values()[randomIntBetween(0, Hasher.values().length - 1)].name().toLowerCase(Locale.ROOT))
.build();
MockUserPasswdStore userPasswdStore = new MockUserPasswdStore("user1", "test123");
MockUserRolesStore userRolesStore = new MockUserRolesStore("user1", "role1", "role2");
ESUsersRealm realm = new ESUsersRealm(settings, userPasswdStore, userRolesStore, restController);
User user1 = realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123")));
User user2 = realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123")));
assertThat(user1, sameInstance(user2));
}
@Test
public void testToken() throws Exception {
Settings settings = ImmutableSettings.builder().build();