security: use lucene automatons and remove dependency on briks

This commit removes the dependency on the briks automatons library and instead uses the lucene
version. Shield was originally implemented using the lucene version, but issues arose with supporting
multiple versions of elasticsearch and API changes, so we moved to using the briks library.

x-pack and elasticsearch are always the same version so we can use the lucene version of the
automatons and remove the briks library. This also brings with it protection from huge automatons
that we did not have before.

Original commit: elastic/x-pack-elasticsearch@e3f34b6b55
This commit is contained in:
jaymode 2016-10-19 14:13:00 -04:00
parent ff3d685833
commit 388bfd761d
9 changed files with 60 additions and 70 deletions

View File

@ -33,7 +33,6 @@ dependencies {
// security deps
compile project(path: ':modules:transport-netty3', configuration: 'runtime')
compile project(path: ':modules:transport-netty4', configuration: 'runtime')
compile 'dk.brics.automaton:automaton:1.11-8'
compile 'com.unboundid:unboundid-ldapsdk:3.2.0'
compile 'org.bouncycastle:bcprov-jdk15on:1.55'
compile 'org.bouncycastle:bcpkix-jdk15on:1.55'

View File

@ -5,14 +5,13 @@
*/
package org.elasticsearch.xpack.security.authz.permission;
import dk.brics.automaton.Automaton;
import org.apache.lucene.util.automaton.Automaton;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.logging.ESLoggerFactory;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
@ -27,6 +26,15 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import static org.apache.lucene.util.automaton.MinimizationOperations.minimize;
import static org.apache.lucene.util.automaton.Operations.DEFAULT_MAX_DETERMINIZED_STATES;
import static org.apache.lucene.util.automaton.Operations.isTotal;
import static org.apache.lucene.util.automaton.Operations.run;
import static org.apache.lucene.util.automaton.Operations.sameLanguage;
import static org.apache.lucene.util.automaton.Operations.subsetOf;
import static org.apache.lucene.util.automaton.Operations.union;
import static org.elasticsearch.xpack.security.support.Automatons.minusAndDeterminize;
/**
* Stores patterns to fields which access is granted or denied to and maintains an automaton that can be used to check if permission is
* allowed for a specific field.
@ -93,13 +101,13 @@ public class FieldPermissions implements Writeable, ToXContent {
} else {
deniedFieldsAutomaton = Automatons.patterns(deniedFieldsArray);
}
if (deniedFieldsAutomaton.subsetOf(grantedFieldsAutomaton) == false) {
if (subsetOf(deniedFieldsAutomaton, grantedFieldsAutomaton) == false) {
throw new ElasticsearchSecurityException("Exceptions for field permissions must be a subset of the " +
"granted fields but " + Arrays.toString(deniedFieldsArray) + " is not a subset of " +
Arrays.toString(grantedFieldsArray));
}
grantedFieldsAutomaton = grantedFieldsAutomaton.minus(deniedFieldsAutomaton);
grantedFieldsAutomaton = minusAndDeterminize(grantedFieldsAutomaton, deniedFieldsAutomaton);
return grantedFieldsAutomaton;
}
@ -176,27 +184,23 @@ public class FieldPermissions implements Writeable, ToXContent {
* fieldName can be a wildcard.
*/
public boolean grantsAccessTo(String fieldName) {
if (permittedFieldsAutomaton.isTotal()) {
return true;
} else {
return permittedFieldsAutomaton.run(fieldName);
}
return isTotal(permittedFieldsAutomaton) || run(permittedFieldsAutomaton, fieldName);
}
// Also, if one grants no access to fields and the other grants all access, merging should result in all access...
public static FieldPermissions merge(FieldPermissions p1, FieldPermissions p2) {
Automaton mergedPermittedFieldsAutomaton;
// we only allow the union of the two automatons
mergedPermittedFieldsAutomaton = p1.permittedFieldsAutomaton.union(p2.permittedFieldsAutomaton);
mergedPermittedFieldsAutomaton = union(p1.permittedFieldsAutomaton, p2.permittedFieldsAutomaton);
// need to minimize otherwise isTotal() might return false even if one of the merged ones returned true before
mergedPermittedFieldsAutomaton.minimize();
mergedPermittedFieldsAutomaton = minimize(mergedPermittedFieldsAutomaton, DEFAULT_MAX_DETERMINIZED_STATES);
// if one of them allows access to _all we allow it for the merged too
boolean allFieldIsAllowedInMerged = p1.allFieldIsAllowed || p2.allFieldIsAllowed;
return new MergedFieldPermissions(mergedPermittedFieldsAutomaton, allFieldIsAllowedInMerged);
}
public boolean hasFieldLevelSecurity() {
return permittedFieldsAutomaton.isTotal() == false;
return isTotal(permittedFieldsAutomaton) == false;
}
public Set<String> resolveAllowedFields(Set<String> allowedMetaFields, MapperService mapperService) {
@ -229,7 +233,7 @@ public class FieldPermissions implements Writeable, ToXContent {
if (!Arrays.equals(grantedFieldsArray, that.grantedFieldsArray)) return false;
// Probably incorrect - comparing Object[] arrays with Arrays.equals
if (!Arrays.equals(deniedFieldsArray, that.deniedFieldsArray)) return false;
return permittedFieldsAutomaton.equals(that.permittedFieldsAutomaton);
return sameLanguage(permittedFieldsAutomaton, that.permittedFieldsAutomaton);
}

View File

@ -5,13 +5,13 @@
*/
package org.elasticsearch.xpack.security.authz.privilege;
import dk.brics.automaton.Automaton;
import dk.brics.automaton.BasicOperations;
import org.apache.lucene.util.automaton.Automaton;
import org.elasticsearch.xpack.security.support.AutomatonPredicate;
import org.elasticsearch.xpack.security.support.Automatons;
import java.util.function.Predicate;
import static org.apache.lucene.util.automaton.Operations.subsetOf;
import static org.elasticsearch.xpack.security.support.Automatons.patterns;
@SuppressWarnings("unchecked")
@ -49,19 +49,9 @@ abstract class AbstractAutomatonPrivilege<P extends AbstractAutomatonPrivilege<P
return create(name.add(other.name), Automatons.unionAndDeterminize(automaton, other.automaton));
}
protected P minus(P other) {
if (other.implies((P) this)) {
return none();
}
if (other == none() || !this.implies(other)) {
return (P) this;
}
return create(name.remove(other.name), Automatons.minusAndDeterminize(automaton, other.automaton));
}
@Override
public boolean implies(P other) {
return BasicOperations.subsetOf(other.automaton, automaton);
return subsetOf(other.automaton, automaton);
}
@Override

View File

@ -5,7 +5,7 @@
*/
package org.elasticsearch.xpack.security.authz.privilege;
import dk.brics.automaton.Automaton;
import org.apache.lucene.util.automaton.Automaton;
import org.elasticsearch.common.Strings;
import org.elasticsearch.xpack.security.support.Automatons;

View File

@ -5,13 +5,13 @@
*/
package org.elasticsearch.xpack.security.authz.privilege;
import dk.brics.automaton.Automaton;
import dk.brics.automaton.BasicAutomata;
import org.apache.lucene.util.automaton.Automaton;
import org.elasticsearch.xpack.security.support.Automatons;
public class GeneralPrivilege extends AbstractAutomatonPrivilege<GeneralPrivilege> {
public static final GeneralPrivilege NONE = new GeneralPrivilege(Name.NONE, BasicAutomata.makeEmpty());
public static final GeneralPrivilege ALL = new GeneralPrivilege(Name.ALL, "*");
public static final GeneralPrivilege NONE = new GeneralPrivilege(Name.NONE, Automatons.EMPTY);
public static final GeneralPrivilege ALL = new GeneralPrivilege(Name.ALL, Automatons.MATCH_ALL);
public GeneralPrivilege(String name, String... patterns) {
super(name, patterns);

View File

@ -5,7 +5,7 @@
*/
package org.elasticsearch.xpack.security.authz.privilege;
import dk.brics.automaton.Automaton;
import org.apache.lucene.util.automaton.Automaton;
import org.elasticsearch.action.admin.cluster.shards.ClusterSearchShardsAction;
import org.elasticsearch.action.admin.indices.alias.exists.AliasesExistAction;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesAction;

View File

@ -5,21 +5,19 @@
*/
package org.elasticsearch.xpack.security.support;
import dk.brics.automaton.Automaton;
import dk.brics.automaton.RunAutomaton;
import org.apache.lucene.util.automaton.Automaton;
import org.apache.lucene.util.automaton.CharacterRunAutomaton;
import java.util.function.Predicate;
import static org.apache.lucene.util.automaton.Operations.DEFAULT_MAX_DETERMINIZED_STATES;
public class AutomatonPredicate implements Predicate<String> {
private final RunAutomaton automaton;
private final CharacterRunAutomaton automaton;
public AutomatonPredicate(Automaton automaton) {
this(new RunAutomaton(automaton, false));
}
public AutomatonPredicate(RunAutomaton automaton) {
this.automaton = automaton;
this.automaton = new CharacterRunAutomaton(automaton, DEFAULT_MAX_DETERMINIZED_STATES);
}
@Override

View File

@ -5,24 +5,26 @@
*/
package org.elasticsearch.xpack.security.support;
import dk.brics.automaton.Automaton;
import dk.brics.automaton.BasicAutomata;
import dk.brics.automaton.BasicOperations;
import dk.brics.automaton.RegExp;
import org.apache.lucene.util.automaton.Automata;
import org.apache.lucene.util.automaton.Automaton;
import org.apache.lucene.util.automaton.RegExp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import static dk.brics.automaton.BasicOperations.minus;
import static dk.brics.automaton.BasicOperations.union;
import static dk.brics.automaton.MinimizationOperations.minimize;
import static org.apache.lucene.util.automaton.MinimizationOperations.minimize;
import static org.apache.lucene.util.automaton.Operations.DEFAULT_MAX_DETERMINIZED_STATES;
import static org.apache.lucene.util.automaton.Operations.concatenate;
import static org.apache.lucene.util.automaton.Operations.determinize;
import static org.apache.lucene.util.automaton.Operations.minus;
import static org.apache.lucene.util.automaton.Operations.union;
public final class Automatons {
public static final Automaton EMPTY = BasicAutomata.makeEmpty();
public static final Automaton MATCH_ALL = BasicAutomata.makeAnyString();
public static final Automaton EMPTY = Automata.makeEmpty();
public static final Automaton MATCH_ALL = Automata.makeAnyString();
static final char WILDCARD_STRING = '*'; // String equality with support for wildcards
static final char WILDCARD_CHAR = '?'; // Char equality with support for wildcards
@ -43,7 +45,7 @@ public final class Automatons {
*/
public static Automaton patterns(Collection<String> patterns) {
if (patterns.isEmpty()) {
return BasicAutomata.makeEmpty();
return EMPTY;
}
Automaton automaton = null;
for (String pattern : patterns) {
@ -53,8 +55,7 @@ public final class Automatons {
automaton = union(automaton, pattern(pattern));
}
}
minimize(automaton); // minimal is also deterministic
return automaton;
return minimize(automaton, DEFAULT_MAX_DETERMINIZED_STATES); // minimal is also deterministic
}
/**
@ -84,36 +85,34 @@ public final class Automatons {
int length = 1;
switch(c) {
case WILDCARD_STRING:
automata.add(BasicAutomata.makeAnyString());
automata.add(Automata.makeAnyString());
break;
case WILDCARD_CHAR:
automata.add(BasicAutomata.makeAnyChar());
automata.add(Automata.makeAnyChar());
break;
case WILDCARD_ESCAPE:
// add the next codepoint instead, if it exists
if (i + length < text.length()) {
final char nextChar = text.charAt(i + length);
length += 1;
automata.add(BasicAutomata.makeChar(nextChar));
automata.add(Automata.makeChar(nextChar));
break;
} // else fallthru, lenient parsing with a trailing \
default:
automata.add(BasicAutomata.makeChar(c));
automata.add(Automata.makeChar(c));
}
i += length;
}
return BasicOperations.concatenate(automata);
return concatenate(automata);
}
public static Automaton unionAndDeterminize(Automaton a1, Automaton a2) {
Automaton res = union(a1, a2);
res.determinize();
return res;
return determinize(res, DEFAULT_MAX_DETERMINIZED_STATES);
}
public static Automaton minusAndDeterminize(Automaton a1, Automaton a2) {
Automaton res = minus(a1, a2);
res.determinize();
return res;
Automaton res = minus(a1, a2, DEFAULT_MAX_DETERMINIZED_STATES);
return determinize(res, DEFAULT_MAX_DETERMINIZED_STATES);
}
}

View File

@ -5,14 +5,14 @@
*/
package org.elasticsearch.xpack.security.support;
import dk.brics.automaton.Automaton;
import dk.brics.automaton.RunAutomaton;
import org.apache.lucene.util.automaton.Automaton;
import org.apache.lucene.util.automaton.CharacterRunAutomaton;
import org.elasticsearch.test.ESTestCase;
import static org.apache.lucene.util.automaton.Operations.DEFAULT_MAX_DETERMINIZED_STATES;
import static org.elasticsearch.xpack.security.support.Automatons.pattern;
import static org.elasticsearch.xpack.security.support.Automatons.patterns;
import static org.elasticsearch.xpack.security.support.Automatons.wildcard;
import static org.hamcrest.Matchers.is;
public class AutomatonsTests extends ESTestCase {
public void testPatternsUnionOfMultiplePatterns() throws Exception {
@ -54,13 +54,13 @@ public class AutomatonsTests extends ESTestCase {
}
private void assertMatch(Automaton automaton, String text) {
RunAutomaton runAutomaton = new RunAutomaton(automaton, false);
assertThat(runAutomaton.run(text), is(true));
CharacterRunAutomaton runAutomaton = new CharacterRunAutomaton(automaton, DEFAULT_MAX_DETERMINIZED_STATES);
assertTrue(runAutomaton.run(text));
}
private void assertMismatch(Automaton automaton, String text) {
RunAutomaton runAutomaton = new RunAutomaton(automaton, false);
assertThat(runAutomaton.run(text), is(false));
CharacterRunAutomaton runAutomaton = new CharacterRunAutomaton(automaton, DEFAULT_MAX_DETERMINIZED_STATES);
assertFalse(runAutomaton.run(text));
}
private void assertInvalidPattern(String text) {