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 org.elasticsearch.shield.authz.SystemRole;
import java.util.Arrays;
/** /**
* An authenticated user * An authenticated user
*/ */
@ -48,6 +50,26 @@ public abstract class User {
public String[] roles() { public String[] roles() {
return 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 { private static class System extends User {
@ -67,6 +89,7 @@ public abstract class User {
public String[] roles() { public String[] roles() {
return ROLES; return ROLES;
} }
} }
} }

View File

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

View File

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

View File

@ -30,8 +30,11 @@ public abstract class CachingUsernamePasswordRealm extends AbstractComponent imp
private final Cache<String, UserWithHash> cache; private final Cache<String, UserWithHash> cache;
private final Hasher hasher;
protected CachingUsernamePasswordRealm(Settings settings) { protected CachingUsernamePasswordRealm(Settings settings) {
super(settings); super(settings);
hasher = Hasher.resolve(componentSettings.get("cache.hash_algo", null), Hasher.SHA2);
TimeValue ttl = componentSettings.getAsTime(CACHE_TTL, DEFAULT_TTL); TimeValue ttl = componentSettings.getAsTime(CACHE_TTL, DEFAULT_TTL);
if (ttl.millis() > 0) { if (ttl.millis() > 0) {
cache = CacheBuilder.newBuilder() cache = CacheBuilder.newBuilder()
@ -91,7 +94,7 @@ public abstract class CachingUsernamePasswordRealm extends AbstractComponent imp
if (user == null) { if (user == null) {
throw new AuthenticationException("Could not authenticate ['" + token.principal() + "]"); 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 { public static class UserWithHash {
User user; User user;
char[] hash; char[] hash;
public UserWithHash(User user, SecuredString password){ Hasher hasher;
public UserWithHash(User user, SecuredString password, Hasher hasher){
this.user = user; this.user = user;
this.hash = Hasher.HTPASSWD.hash(password); this.hash = hasher.hash(password);
this.hasher = hasher;
} }
public boolean verify(SecuredString password){ 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.apache.commons.codec.digest.Sha2Crypt;
import org.elasticsearch.ElasticsearchIllegalArgumentException; import org.elasticsearch.ElasticsearchIllegalArgumentException;
import org.elasticsearch.common.os.OsUtils; import org.elasticsearch.common.os.OsUtils;
import org.elasticsearch.shield.ShieldSettingsException;
import java.util.Locale; import java.util.Locale;
@ -147,6 +148,10 @@ public enum Hasher {
} }
switch (name.toLowerCase(Locale.ROOT)) { switch (name.toLowerCase(Locale.ROOT)) {
case "htpasswd" : return HTPASSWD; case "htpasswd" : return HTPASSWD;
case "bcrypt" : return BCRYPT;
case "sha1" : return SHA1;
case "sha2" : return SHA2;
case "md5" : return MD5;
default: default:
return defaultHasher; return defaultHasher;
} }
@ -155,7 +160,7 @@ public enum Hasher {
public static Hasher resolve(String name) { public static Hasher resolve(String name) {
Hasher hasher = resolve(name, null); Hasher hasher = resolve(name, null);
if (hasher == null) { if (hasher == null) {
throw new ElasticsearchIllegalArgumentException("Unknown hash function [" + name + "]"); throw new ShieldSettingsException("Unknown hash function [" + name + "]");
} }
return hasher; return hasher;
} }

View File

@ -5,6 +5,7 @@
*/ */
package org.elasticsearch.shield.authc.esusers; package org.elasticsearch.shield.authc.esusers;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.action.Action; import org.elasticsearch.action.Action;
import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionRequest;
@ -28,9 +29,9 @@ import org.hamcrest.Matchers;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import static org.hamcrest.CoreMatchers.equalTo; import java.util.Locale;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
/** /**
@ -70,6 +71,19 @@ public class ESUsersRealmTests extends ElasticsearchTestCase {
assertThat(user.roles(), arrayContaining("role1", "role2")); 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 @Test
public void testToken() throws Exception { public void testToken() throws Exception {
Settings settings = ImmutableSettings.builder().build(); Settings settings = ImmutableSettings.builder().build();