From 521ebe467246179281e7aa64d6fcdfcc3bb1c006 Mon Sep 17 00:00:00 2001 From: uboness Date: Thu, 23 Oct 2014 15:52:03 +0200 Subject: [PATCH 1/4] Change the way patterns are resolved in roles.yml Now, there are two types of supported patters: - wildcards (default) - simple wildcard match where `*` indicates zero or more characters and `?` indicates a single character (`\` can be used as an escape charachter) - regular expressions - can be "enabled" by wrapping the pattern in `/` (e.g. `/foo.*/`). The regex syntax is based on lucene's regex syntax (not Java's Pattern). Closes elastic/elasticsearch#253 Original commit: elastic/x-pack-elasticsearch@edd912122d8194260d996cf8fa4fac79b48d2f6f --- .../elasticsearch/shield/authz/Privilege.java | 32 ++++---- .../shield/support/Automatons.java | 68 +++++++++++++++- .../MultipleIndicesPermissionsTests.java | 4 +- .../shield/authz/PermissionTests.java | 6 +- .../IndicesResolverIntegrationTests.java | 4 +- .../authz/store/FileRolesStoreTests.java | 4 +- .../shield/support/AutomatonsTests.java | 80 +++++++++++++++++++ .../shield/test/ShieldIntegrationTest.java | 2 +- .../elasticsearch/test/ShieldRestTests.java | 2 +- .../shield/authz/store/roles.yml | 2 +- .../org/elasticsearch/shield/plugin/roles.yml | 2 +- 11 files changed, 173 insertions(+), 33 deletions(-) create mode 100644 src/test/java/org/elasticsearch/shield/support/AutomatonsTests.java diff --git a/src/main/java/org/elasticsearch/shield/authz/Privilege.java b/src/main/java/org/elasticsearch/shield/authz/Privilege.java index eb08de12769..821a93fb333 100644 --- a/src/main/java/org/elasticsearch/shield/authz/Privilege.java +++ b/src/main/java/org/elasticsearch/shield/authz/Privilege.java @@ -33,7 +33,7 @@ import static org.elasticsearch.shield.support.Automatons.patterns; */ public abstract class Privilege

> { - static final String SUB_ACTION_SUFFIX_PATTERN = ".*"; + static final String SUB_ACTION_SUFFIX_PATTERN = "*"; public static final System SYSTEM = new System(); @@ -76,7 +76,7 @@ public abstract class Privilege

> { public static class System extends Privilege { protected static final Predicate PREDICATE = new AutomatonPredicate(patterns( - "internal:.*" + "internal:*" )); private System() { @@ -97,18 +97,18 @@ public abstract class Privilege

> { public static class Index extends AutomatonPrivilege { public static final Index NONE = new Index(Name.NONE, Automata.makeEmpty()); - public static final Index ALL = new Index(Name.ALL, "indices:.*"); - public static final Index MANAGE = new Index("manage", "indices:monitor/.*", "indices:admin/.*"); + public static final Index ALL = new Index(Name.ALL, "indices:*"); + public static final Index MANAGE = new Index("manage", "indices:monitor/*", "indices:admin/*"); public static final Index CREATE_INDEX = new Index("create_index", "indices:admin/create"); - public static final Index MONITOR = new Index("monitor", "indices:monitor/.*"); - public static final Index DATA_ACCESS = new Index("data_access", "indices:data/.*"); - public static final Index CRUD = new Index("crud", "indices:data/write/.*", "indices:data/read/.*"); - public static final Index READ = new Index("read", "indices:data/read/.*"); - public static final Index SEARCH = new Index("search", SearchAction.NAME + ".*", GetAction.NAME + ".*"); - public static final Index GET = new Index("get", GetAction.NAME + ".*"); - public static final Index INDEX = new Index("index", "indices:data/write/index.*", "indices:data/write/update"); - public static final Index DELETE = new Index("delete", "indices:data/write/delete.*"); - public static final Index WRITE = new Index("write", "indices:data/write/.*"); + public static final Index MONITOR = new Index("monitor", "indices:monitor/*"); + public static final Index DATA_ACCESS = new Index("data_access", "indices:data/*"); + public static final Index CRUD = new Index("crud", "indices:data/write/*", "indices:data/read/*"); + public static final Index READ = new Index("read", "indices:data/read/*"); + public static final Index SEARCH = new Index("search", SearchAction.NAME + "*", GetAction.NAME + "*"); + public static final Index GET = new Index("get", GetAction.NAME + "*"); + public static final Index INDEX = new Index("index", "indices:data/write/index*", "indices:data/write/update"); + public static final Index DELETE = new Index("delete", "indices:data/write/delete*"); + public static final Index WRITE = new Index("write", "indices:data/write/*"); public static final Index BENCHMARK = new Index("benchmark", "indices:data/benchmark"); private static final Index[] values = new Index[] { @@ -196,8 +196,8 @@ public abstract class Privilege

> { public static class Cluster extends AutomatonPrivilege { public static final Cluster NONE = new Cluster(Name.NONE, Automata.makeEmpty()); - public static final Cluster ALL = new Cluster(Name.ALL, "cluster:.*", "indices:admin/template/.*"); - public static final Cluster MONITOR = new Cluster("monitor", "cluster:monitor/.*"); + public static final Cluster ALL = new Cluster(Name.ALL, "cluster:*", "indices:admin/template/*"); + public static final Cluster MONITOR = new Cluster("monitor", "cluster:monitor/*"); private static final Cluster[] values = new Cluster[] { NONE, ALL, MONITOR }; @@ -272,7 +272,7 @@ public abstract class Privilege

> { } static String actionToPattern(String text) { - return text.replace(":", "\\:") + SUB_ACTION_SUFFIX_PATTERN; + return text + SUB_ACTION_SUFFIX_PATTERN; } @SuppressWarnings("unchecked") diff --git a/src/main/java/org/elasticsearch/shield/support/Automatons.java b/src/main/java/org/elasticsearch/shield/support/Automatons.java index 0486672ebc9..c42b7c3ca0b 100644 --- a/src/main/java/org/elasticsearch/shield/support/Automatons.java +++ b/src/main/java/org/elasticsearch/shield/support/Automatons.java @@ -7,9 +7,12 @@ package org.elasticsearch.shield.support; import org.apache.lucene.util.automaton.Automata; import org.apache.lucene.util.automaton.Automaton; +import org.apache.lucene.util.automaton.Operations; import org.apache.lucene.util.automaton.RegExp; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import static org.apache.lucene.util.automaton.MinimizationOperations.minimize; import static org.apache.lucene.util.automaton.Operations.*; @@ -19,20 +22,30 @@ import static org.apache.lucene.util.automaton.Operations.*; */ public final class Automatons { + static final char WILDCARD_STRING = '*'; // String equality with support for wildcards + static final char WILDCARD_CHAR = '?'; // Char equality with support for wildcards + static final char WILDCARD_ESCAPE = '\\'; // Escape character + private Automatons() { } + /** + * Builds and returns an automaton that will represent the union of all the given patterns. + */ public static Automaton patterns(String... patterns) { if (patterns.length == 0) { return Automata.makeEmpty(); } - Automaton automaton = new RegExp(patterns[0]).toAutomaton(); + Automaton automaton = pattern(patterns[0]); for (String pattern : patterns) { - automaton = union(automaton, new RegExp(pattern).toAutomaton()); + automaton = union(automaton, pattern(pattern)); } return determinize(minimize(automaton)); } + /** + * Builds and returns an automaton that will represent the union of all the given patterns. + */ public static Automaton patterns(Collection patterns) { if (patterns.isEmpty()) { return Automata.makeEmpty(); @@ -40,14 +53,61 @@ public final class Automatons { Automaton automaton = null; for (String pattern : patterns) { if (automaton == null) { - automaton = new RegExp(pattern).toAutomaton(); + automaton = pattern(pattern); } else { - automaton = union(automaton, new RegExp(pattern).toAutomaton()); + automaton = union(automaton, pattern(pattern)); } } return determinize(minimize(automaton)); } + /** + * Builds and returns an automaton that represents the given pattern. + */ + static Automaton pattern(String pattern) { + if (pattern.startsWith("/")) { // it's a lucene regexp + if (pattern.length() == 1 || !pattern.endsWith("/")) { + throw new IllegalArgumentException("Invalid pattern [" + pattern + "]. Patterns starting with '/' " + + "indicate regular expression pattern and therefore must also end with '/'." + + " Other patterns (those that do not start with '/') will be treated as simple wildcard patterns"); + } + String regex = pattern.substring(1, pattern.length() - 1); + return new RegExp(regex).toAutomaton(); + } + return wildcard(pattern); + } + + /** + * Builds and returns an automaton that represents the given pattern. + */ + static Automaton wildcard(String text) { + List automata = new ArrayList<>(); + for (int i = 0; i < text.length();) { + final int c = text.codePointAt(i); + int length = Character.charCount(c); + switch(c) { + case WILDCARD_STRING: + automata.add(Automata.makeAnyString()); + break; + case WILDCARD_CHAR: + automata.add(Automata.makeAnyChar()); + break; + case WILDCARD_ESCAPE: + // add the next codepoint instead, if it exists + if (i + length < text.length()) { + final int nextChar = text.codePointAt(i + length); + length += Character.charCount(nextChar); + automata.add(Automata.makeChar(nextChar)); + break; + } // else fallthru, lenient parsing with a trailing \ + default: + automata.add(Automata.makeChar(c)); + } + i += length; + } + return Operations.concatenate(automata); + } + public static Automaton unionAndDeterminize(Automaton a1, Automaton a2) { return determinize(union(a1, a2)); } diff --git a/src/test/java/org/elasticsearch/shield/MultipleIndicesPermissionsTests.java b/src/test/java/org/elasticsearch/shield/MultipleIndicesPermissionsTests.java index 3cc2e2bae81..f27a24de418 100644 --- a/src/test/java/org/elasticsearch/shield/MultipleIndicesPermissionsTests.java +++ b/src/test/java/org/elasticsearch/shield/MultipleIndicesPermissionsTests.java @@ -27,8 +27,8 @@ public class MultipleIndicesPermissionsTests extends ShieldIntegrationTest { public static final String ROLES = "user:\n" + " cluster: all\n" + " indices:\n" + - " '.*': manage\n" + - " '.*': write\n" + + " '*': manage\n" + + " '/.*/': write\n" + " 'test': read\n" + " 'test1': read\n"; diff --git a/src/test/java/org/elasticsearch/shield/authz/PermissionTests.java b/src/test/java/org/elasticsearch/shield/authz/PermissionTests.java index acf6c481a07..a422bae8088 100644 --- a/src/test/java/org/elasticsearch/shield/authz/PermissionTests.java +++ b/src/test/java/org/elasticsearch/shield/authz/PermissionTests.java @@ -25,9 +25,9 @@ public class PermissionTests extends ElasticsearchTestCase { @Before public void init() { Permission.Global.Builder builder = Permission.Global.builder(mock(AuthorizationService.class)); - builder.add(union(SEARCH, MONITOR), "test_.*", "foo.*"); - builder.add(union(READ), "baz_.*foo", "fool.*bar"); - builder.add(union(MONITOR), "bar.*"); + builder.add(union(SEARCH, MONITOR), "test_*", "/foo.*/"); + builder.add(union(READ), "baz_*foo", "/fool.*bar/"); + builder.add(union(MONITOR), "/bar.*/"); permission = builder.build(); } diff --git a/src/test/java/org/elasticsearch/shield/authz/indicesresolver/IndicesResolverIntegrationTests.java b/src/test/java/org/elasticsearch/shield/authz/indicesresolver/IndicesResolverIntegrationTests.java index 0c34a1db0ab..55e005e9ea7 100644 --- a/src/test/java/org/elasticsearch/shield/authz/indicesresolver/IndicesResolverIntegrationTests.java +++ b/src/test/java/org/elasticsearch/shield/authz/indicesresolver/IndicesResolverIntegrationTests.java @@ -29,8 +29,8 @@ public class IndicesResolverIntegrationTests extends ShieldIntegrationTest { return DEFAULT_ROLE + ":\n" + " cluster: ALL\n" + " indices:\n" + - " '.*': manage,write\n" + - " 'test.*': read\n"; + " '*': manage,write\n" + + " '/test.*/': read\n"; } @Test diff --git a/src/test/java/org/elasticsearch/shield/authz/store/FileRolesStoreTests.java b/src/test/java/org/elasticsearch/shield/authz/store/FileRolesStoreTests.java index f6449d58329..920b4585e35 100644 --- a/src/test/java/org/elasticsearch/shield/authz/store/FileRolesStoreTests.java +++ b/src/test/java/org/elasticsearch/shield/authz/store/FileRolesStoreTests.java @@ -89,7 +89,7 @@ public class FileRolesStoreTests extends ElasticsearchTestCase { group = permission.indices().groups()[0]; assertThat(group.indices(), notNullValue()); assertThat(group.indices().length, is(1)); - assertThat(group.indices()[0], equalTo(".*_.*")); + assertThat(group.indices()[0], equalTo("/.*_.*/")); assertThat(group.privilege(), notNullValue()); assertThat(group.privilege().isAlias(Privilege.Index.union(Privilege.Index.READ, Privilege.Index.WRITE)), is(true)); } @@ -164,7 +164,7 @@ public class FileRolesStoreTests extends ElasticsearchTestCase { @Test(expected = ElasticsearchException.class) public void testThatInvalidYAMLThrowsElasticsearchException() throws Exception { File file = tempFolder.newFile(); - com.google.common.io.Files.write("user: cluster: ALL indices: '.*': ALL".getBytes(Charsets.UTF_8), file); + com.google.common.io.Files.write("user: cluster: ALL indices: '*': ALL".getBytes(Charsets.UTF_8), file); FileRolesStore.parseFile(file.toPath(), logger, mock(AuthorizationService.class)); } } diff --git a/src/test/java/org/elasticsearch/shield/support/AutomatonsTests.java b/src/test/java/org/elasticsearch/shield/support/AutomatonsTests.java new file mode 100644 index 00000000000..5857863d911 --- /dev/null +++ b/src/test/java/org/elasticsearch/shield/support/AutomatonsTests.java @@ -0,0 +1,80 @@ +/* + * 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.shield.support; + +import org.apache.lucene.util.automaton.Automaton; +import org.apache.lucene.util.automaton.CharacterRunAutomaton; +import org.elasticsearch.test.ElasticsearchTestCase; +import org.junit.Test; + +import static org.elasticsearch.shield.support.Automatons.*; +import static org.hamcrest.Matchers.*; + +/** + * + */ +public class AutomatonsTests extends ElasticsearchTestCase { + + @Test + public void testPatterns_UnionOfMultiplePatterns() throws Exception { + assertMatch(patterns("/fo.*/", "ba*"), "foo"); + assertMatch(patterns("/fo.*/", "ba*"), "bar"); + assertMismatch(patterns("/fo.*/", "ba*"), "zipfoo"); + } + + @Test + public void testPattern_Single() throws Exception { + assertMatch(pattern("/.*st/"), "test"); + assertMatch(pattern("/t.*st/"), "test"); + assertMatch(pattern("/tes*./"), "test"); + assertMatch(pattern("/test/"), "test"); + assertMismatch(pattern("/.*st/"), "tet"); + assertMatch(pattern("*st"), "test"); + assertMatch(pattern("t*t"), "test"); + assertMatch(pattern("t?st"), "test"); + assertMismatch(pattern("t?t"), "test"); + assertMatch(pattern("tes*"), "test"); + assertMatch(pattern("test"), "test"); + assertMismatch(pattern("*st"), "tet"); + assertInvalidPattern("/test"); + assertInvalidPattern("/te*"); + assertInvalidPattern("/te.*"); + assertMismatch(pattern(".*st"), "test"); + assertMatch(pattern("*st\\"), "test\\"); + assertMatch(pattern("tes.*/"), "tes.t/"); + assertMatch(pattern("\\/test"), "/test"); + } + + @Test + public void testWildcard() throws Exception { + assertMatch(wildcard("*st"), "test"); + assertMatch(wildcard("t*st"), "test"); + assertMatch(wildcard("tes*"), "test"); + assertMatch(wildcard("test"), "test"); + assertMismatch(wildcard("*st"), "tet"); + assertMismatch(wildcard("t\\*st"), "test"); + assertMatch(wildcard("t\\*st"), "t*st"); + } + + private void assertMatch(Automaton automaton, String text) { + CharacterRunAutomaton runAutomaton = new CharacterRunAutomaton(automaton); + assertThat(runAutomaton.run(text), is(true)); + } + + private void assertMismatch(Automaton automaton, String text) { + CharacterRunAutomaton runAutomaton = new CharacterRunAutomaton(automaton); + assertThat(runAutomaton.run(text), is(false)); + } + + private void assertInvalidPattern(String text) { + try { + pattern(text); + fail("expected an error on invalid pattern [" + text + "]"); + } catch (IllegalArgumentException iae) { + // expected + } + } +} diff --git a/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java b/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java index 1dfa2a01b9c..d2825fa077f 100644 --- a/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java +++ b/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java @@ -53,7 +53,7 @@ public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest public static final String CONFIG_ROLE_ALLOW_ALL = DEFAULT_ROLE + ":\n" + " cluster: ALL\n" + " indices:\n" + - " '.*': ALL\n"; + " '*': ALL\n"; @ClassRule public static TemporaryFolder tmpFolder = new TemporaryFolder(); diff --git a/src/test/java/org/elasticsearch/test/ShieldRestTests.java b/src/test/java/org/elasticsearch/test/ShieldRestTests.java index 3a83e723908..bee7579c5d4 100644 --- a/src/test/java/org/elasticsearch/test/ShieldRestTests.java +++ b/src/test/java/org/elasticsearch/test/ShieldRestTests.java @@ -56,7 +56,7 @@ public class ShieldRestTests extends ElasticsearchRestTests { public static final String CONFIG_ROLE_ALLOW_ALL = DEFAULT_ROLE + ":\n" + " cluster: ALL\n" + " indices:\n" + - " '.*': ALL\n"; + " '*': ALL\n"; static { diff --git a/src/test/resources/org/elasticsearch/shield/authz/store/roles.yml b/src/test/resources/org/elasticsearch/shield/authz/store/roles.yml index 2ab39b7a510..0c9b98823d4 100644 --- a/src/test/resources/org/elasticsearch/shield/authz/store/roles.yml +++ b/src/test/resources/org/elasticsearch/shield/authz/store/roles.yml @@ -9,5 +9,5 @@ role2: role3: indices: - '.*_.*': READ, WRITE + '/.*_.*/': READ, WRITE diff --git a/src/test/resources/org/elasticsearch/shield/plugin/roles.yml b/src/test/resources/org/elasticsearch/shield/plugin/roles.yml index 6099c708ed9..b4feb8357bd 100644 --- a/src/test/resources/org/elasticsearch/shield/plugin/roles.yml +++ b/src/test/resources/org/elasticsearch/shield/plugin/roles.yml @@ -1,4 +1,4 @@ user: cluster: ALL indices: - '.*': ALL \ No newline at end of file + '*': ALL \ No newline at end of file From b7dac66c8ac5133fda41daacb15308b10c61a704 Mon Sep 17 00:00:00 2001 From: uboness Date: Fri, 17 Oct 2014 19:03:35 -0700 Subject: [PATCH 2/4] 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@e2d1b3116be994cdfcf09b2ebe6de1d3254a2cf6 --- .../java/org/elasticsearch/shield/User.java | 23 +++++++++++++++++++ .../shield/authc/esusers/ESUsersModule.java | 2 +- .../shield/authc/esusers/ESUsersRealm.java | 7 +++--- .../support/CachingUsernamePasswordRealm.java | 13 +++++++---- .../shield/authc/support/Hasher.java | 7 +++++- .../authc/esusers/ESUsersRealmTests.java | 20 +++++++++++++--- 6 files changed, 59 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/elasticsearch/shield/User.java b/src/main/java/org/elasticsearch/shield/User.java index ea5749adcaf..49329562747 100644 --- a/src/main/java/org/elasticsearch/shield/User.java +++ b/src/main/java/org/elasticsearch/shield/User.java @@ -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; } + } } diff --git a/src/main/java/org/elasticsearch/shield/authc/esusers/ESUsersModule.java b/src/main/java/org/elasticsearch/shield/authc/esusers/ESUsersModule.java index bfa2316ee66..3a3a58bc495 100644 --- a/src/main/java/org/elasticsearch/shield/authc/esusers/ESUsersModule.java +++ b/src/main/java/org/elasticsearch/shield/authc/esusers/ESUsersModule.java @@ -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 { diff --git a/src/main/java/org/elasticsearch/shield/authc/esusers/ESUsersRealm.java b/src/main/java/org/elasticsearch/shield/authc/esusers/ESUsersRealm.java index 151db907706..a1fd481cf59 100644 --- a/src/main/java/org/elasticsearch/shield/authc/esusers/ESUsersRealm.java +++ b/src/main/java/org/elasticsearch/shield/authc/esusers/ESUsersRealm.java @@ -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 { +public class ESUsersRealm extends CachingUsernamePasswordRealm { public static final String TYPE = "esusers"; @@ -60,7 +59,7 @@ public class ESUsersRealm extends AbstractComponent implements Realm 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); } } } diff --git a/src/main/java/org/elasticsearch/shield/authc/support/Hasher.java b/src/main/java/org/elasticsearch/shield/authc/support/Hasher.java index 19b112cda18..f1dfbda4852 100644 --- a/src/main/java/org/elasticsearch/shield/authc/support/Hasher.java +++ b/src/main/java/org/elasticsearch/shield/authc/support/Hasher.java @@ -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; } diff --git a/src/test/java/org/elasticsearch/shield/authc/esusers/ESUsersRealmTests.java b/src/test/java/org/elasticsearch/shield/authc/esusers/ESUsersRealmTests.java index 30caa896bd3..263bca1f495 100644 --- a/src/test/java/org/elasticsearch/shield/authc/esusers/ESUsersRealmTests.java +++ b/src/test/java/org/elasticsearch/shield/authc/esusers/ESUsersRealmTests.java @@ -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(); From a287863ab0d765afc1391307fbdd29b135b53d1e Mon Sep 17 00:00:00 2001 From: uboness Date: Tue, 21 Oct 2014 18:55:22 +0200 Subject: [PATCH 3/4] Added cluster & indices monitoring privileges to System This is required for marvel agent to collect its data. Closes elastic/elasticsearch#137 Original commit: elastic/x-pack-elasticsearch@c1ed58aafb5bc17ae4b1c9c7018cef984890807e --- .../elasticsearch/shield/SecurityFilter.java | 1 + .../authc/InternalAuthenticationService.java | 5 ++++ .../elasticsearch/shield/authz/Privilege.java | 4 ++- .../shield/authz/PrivilegeTests.java | 11 ++++++++ .../shield/authz/SystemRoleTests.java | 27 +++++++++++++++++++ 5 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 src/test/java/org/elasticsearch/shield/authz/SystemRoleTests.java diff --git a/src/main/java/org/elasticsearch/shield/SecurityFilter.java b/src/main/java/org/elasticsearch/shield/SecurityFilter.java index a0bccfa3601..685eb10a884 100644 --- a/src/main/java/org/elasticsearch/shield/SecurityFilter.java +++ b/src/main/java/org/elasticsearch/shield/SecurityFilter.java @@ -59,6 +59,7 @@ public class SecurityFilter extends AbstractComponent { AuthenticationToken token = authcService.token(action, request, defaultToken); User user = authcService.authenticate(action, request, token); + authzService.authorize(user, action, request); return user; } diff --git a/src/main/java/org/elasticsearch/shield/authc/InternalAuthenticationService.java b/src/main/java/org/elasticsearch/shield/authc/InternalAuthenticationService.java index 01ed547fec4..45b55b735ef 100644 --- a/src/main/java/org/elasticsearch/shield/authc/InternalAuthenticationService.java +++ b/src/main/java/org/elasticsearch/shield/authc/InternalAuthenticationService.java @@ -62,6 +62,11 @@ public class InternalAuthenticationService extends AbstractComponent implements for (Realm realm : realms) { token = realm.token(message); if (token != null) { + + if (logger.isTraceEnabled()) { + logger.trace("Realm [{}] resolved auth token [{}] from transport request with action [{}]", realm.type(), token.principal(), action); + } + message.putInContext(TOKEN_CTX_KEY, token); return token; } diff --git a/src/main/java/org/elasticsearch/shield/authz/Privilege.java b/src/main/java/org/elasticsearch/shield/authz/Privilege.java index 821a93fb333..45d0d463eaf 100644 --- a/src/main/java/org/elasticsearch/shield/authz/Privilege.java +++ b/src/main/java/org/elasticsearch/shield/authz/Privilege.java @@ -76,7 +76,9 @@ public abstract class Privilege

> { public static class System extends Privilege { protected static final Predicate PREDICATE = new AutomatonPredicate(patterns( - "internal:*" + "internal:*", + "indices:monitor/*", // added for marvel + "cluster:monitor/*" // added for marvel )); private System() { diff --git a/src/test/java/org/elasticsearch/shield/authz/PrivilegeTests.java b/src/test/java/org/elasticsearch/shield/authz/PrivilegeTests.java index 2b70ef0fa91..339d9ba3b45 100644 --- a/src/test/java/org/elasticsearch/shield/authz/PrivilegeTests.java +++ b/src/test/java/org/elasticsearch/shield/authz/PrivilegeTests.java @@ -7,6 +7,7 @@ package org.elasticsearch.shield.authz; import com.carrotsearch.randomizedtesting.annotations.Repeat; import org.elasticsearch.ElasticsearchIllegalArgumentException; +import org.elasticsearch.common.base.Predicate; import org.elasticsearch.shield.support.AutomatonPredicate; import org.elasticsearch.shield.support.Automatons; import org.elasticsearch.test.ElasticsearchTestCase; @@ -147,4 +148,14 @@ public class PrivilegeTests extends ElasticsearchTestCase { } } + @Test + public void testSystem() throws Exception { + Predicate predicate = Privilege.SYSTEM.predicate(); + assertThat(predicate.apply("indices:monitor/whatever"), is(true)); + assertThat(predicate.apply("cluster:monitor/whatever"), is(true)); + assertThat(predicate.apply("internal:whatever"), is(true)); + assertThat(predicate.apply("indices:whatever"), is(false)); + assertThat(predicate.apply("cluster:whatever"), is(false)); + assertThat(predicate.apply("whatever"), is(false)); + } } diff --git a/src/test/java/org/elasticsearch/shield/authz/SystemRoleTests.java b/src/test/java/org/elasticsearch/shield/authz/SystemRoleTests.java new file mode 100644 index 00000000000..fcc6673d569 --- /dev/null +++ b/src/test/java/org/elasticsearch/shield/authz/SystemRoleTests.java @@ -0,0 +1,27 @@ +/* + * 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.shield.authz; + +import org.elasticsearch.test.ElasticsearchTestCase; +import org.junit.Test; + +import static org.hamcrest.Matchers.*; + +/** + * + */ +public class SystemRoleTests extends ElasticsearchTestCase { + + @Test + public void testCheck() throws Exception { + assertThat(SystemRole.INSTANCE.check("indices:monitor/whatever"), is(true)); + assertThat(SystemRole.INSTANCE.check("cluster:monitor/whatever"), is(true)); + assertThat(SystemRole.INSTANCE.check("internal:whatever"), is(true)); + assertThat(SystemRole.INSTANCE.check("indices:whatever"), is(false)); + assertThat(SystemRole.INSTANCE.check("cluster:whatever"), is(false)); + assertThat(SystemRole.INSTANCE.check("whatever"), is(false)); + } +} From a52993db78b2f6f96dbcd87a3a0b93618dbd3c99 Mon Sep 17 00:00:00 2001 From: Alexander Reelsen Date: Thu, 23 Oct 2014 10:30:58 -0700 Subject: [PATCH 4/4] esvm: Added user configurations to make esvm usable again Also added a logstash configuration for simple performance testing (useful for comparing different hash functions) Original commit: elastic/x-pack-elasticsearch@c9f08fbb125c7d3f8cec3725399cee77ef99c5e9 --- .esvm-shield-config/roles.yml | 13 ++++ .esvm-shield-config/system_key | 1 + .esvm-shield-config/users | 5 ++ .esvm-shield-config/users_roles | 1 + .esvmrc | 12 +++- src/test/resources/logstash-shield.conf | 82 +++++++++++++++++++++++++ 6 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 .esvm-shield-config/roles.yml create mode 100644 .esvm-shield-config/system_key create mode 100644 .esvm-shield-config/users create mode 100644 .esvm-shield-config/users_roles create mode 100644 src/test/resources/logstash-shield.conf diff --git a/.esvm-shield-config/roles.yml b/.esvm-shield-config/roles.yml new file mode 100644 index 00000000000..a73127b55f5 --- /dev/null +++ b/.esvm-shield-config/roles.yml @@ -0,0 +1,13 @@ +admin: + cluster: all + indices: + '.*': all + +power_user: + cluster: monitor + indices: + '.*': all + +user: + indices: + '.*': read diff --git a/.esvm-shield-config/system_key b/.esvm-shield-config/system_key new file mode 100644 index 00000000000..e606d9e8c8a --- /dev/null +++ b/.esvm-shield-config/system_key @@ -0,0 +1 @@ +*43Q]/>e.g^lHgu O/Gao Ⱥrrk_2*m?>д,]UpI婳򼣸sOYwuK+_0 \ No newline at end of file diff --git a/.esvm-shield-config/users b/.esvm-shield-config/users new file mode 100644 index 00000000000..785313aa101 --- /dev/null +++ b/.esvm-shield-config/users @@ -0,0 +1,5 @@ +admin-bcrypt:$2a$10$5uCJHPn3p0ZPQp6rIIgcDO0VZ3urZZmA.egHiy/WknxIkAyZXPGpy +admin-plain:{plain}changeme +admin-sha:{SHA}+pvrmeQCmtWmYVOZ57uuITVghrM= +admin-apr:$apr1$fCQ4kkwA$ETvNx37ooOcdau5a61S/s. +admin-sha2:$5$mw0LEbLr$s57Rbo0wfH8Z690Dc0..VgC1qn/a5h73bbpt8kql8B4 diff --git a/.esvm-shield-config/users_roles b/.esvm-shield-config/users_roles new file mode 100644 index 00000000000..cf9ed6a2f86 --- /dev/null +++ b/.esvm-shield-config/users_roles @@ -0,0 +1 @@ +admin:admin-bcrypt,admin-sha,admin-plain,admin-apr,admin-sha2 diff --git a/.esvmrc b/.esvmrc index 57c97253a55..272b9133808 100644 --- a/.esvmrc +++ b/.esvmrc @@ -3,12 +3,15 @@ "plugins": [ "lmenezes/elasticsearch-kopf", { "name": "shield", "path" : "file:./target/releases/elasticsearch-shield-1.0.0-SNAPSHOT.zip" } ], "config" : { "cluster": { "name": "shield" }, + "indices.store.throttle.max_bytes_per_sec": "100mb", "discovery" : { "type" : "zen", "zen.ping.multicast.enabled": false, "zen.ping.unicast.hosts" : [ "localhost:9300", "localhost:9301" ] }, "shield" : { + "enabled" : true, + "system_key.file": ".esvm-shield-config/system_key", "audit.enabled" : false, "transport.ssl": true, "http.ssl": true, @@ -17,7 +20,14 @@ "keystore_password" : "testnode", "truststore" : "src/test/resources/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks", "truststore_password" : "testnode" - } + }, + "authc": { + "esusers.files" : { + "users" : ".esvm-shield-config/users", + "users_roles" : ".esvm-shield-config/users_roles" + } + }, + "authz.store.files.roles" : ".esvm-shield-config/roles.yml" } } }, diff --git a/src/test/resources/logstash-shield.conf b/src/test/resources/logstash-shield.conf new file mode 100644 index 00000000000..ef241289adb --- /dev/null +++ b/src/test/resources/logstash-shield.conf @@ -0,0 +1,82 @@ +############## +## Logstash configuration for shield load testing +## +## This configuration makes performance testing against SHIELD a lot easier +## The generator input creates 1000000 very small documents +## The elasticsearch outputs (you should choose only one!) then can be configured to use +## one of the configured setups (shield enabled/disabled, ssl enabled/disabled, HTTP or transport) +## to find out, what kind of overhead the plugin has +## +## Note: Please make sure you are using a logstash instance, that supports shield (as of 10-2014, this resided in its own branch) +## +############# +## Running the tests +## 1. Start an elasticsearch cluster using esvm (this one only starts one node): esvm shield -n 1 +## 2. Start logstash against it: ../logstash-output-es-shield/build/tarball/logstash-1.4.2/bin/logstash -p ~/.esvm/1.4.0.Beta1/plugins/ agent -f src/test/resources/logstash-shield.conf +############# + +input { + generator { + count => 1000000 + } +} + +output { + + ########## + ## HTTP + ########## + + # Use this when shield is disabled +# elasticsearch { +# protocol => 'http' +# } + + # Shield enabled, SSL disabled + elasticsearch { + protocol => 'http' + # can be one of admin-plain, admin-bcrypt, admin-sha, admin-apr, admin-sha2 + user => 'admin-plain' + password => 'changeme' + } + + # Shield enabled, SSL enabled +# elasticsearch { +# protocol => 'http' +# user => 'admin' +# password => 'changeme' +# ssl => 'true' +# ssl_verify => false +# } + + + + ########## + ## Transport Client + ########## + + # Use this when shield is disabled +# elasticsearch { +# protocol => 'transport' +# } + + # Shield enabled, SSL disabled +# elasticsearch { +# protocol => 'transport' +# user => 'admin' +# password => 'changeme' +# } + + # Shield enabled, SSL enabled +# elasticsearch { +# protocol => 'transport' +# user => 'admin' +# password => 'changeme' +# ssl => 'true' +# keystore => 'src/test/resources/org/elasticsearch/shield/transport/ssl/certs/simple/testclient.jks' +# keystore_password => 'testclient' +# truststore => 'src/test/resources/org/elasticsearch/shield/transport/ssl/certs/simple/testclient.jks' +# truststore_password => 'testclient' +# } +} +