Support wildcards in has_privileges API (elastic/x-pack-elasticsearch#1454)

The has_privileges API now supports wildcards.
The semantics are that the user must have a superset of the wildcard being checked.

---------------------
Role | Check | Result
---------------------
*    | foo*  | true
f*   | foo*  | true
foo* | foo*  | true
foo* | foo?  | true
foo? | foo?  | true
foo? | foo*  | false
foo  | foo*  | false

Original commit: elastic/x-pack-elasticsearch@817550db17
This commit is contained in:
Tim Vernum 2017-05-30 13:40:29 +10:00 committed by GitHub
parent da40720ef0
commit e177f79aa3
2 changed files with 54 additions and 16 deletions

View File

@ -12,7 +12,6 @@ import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Predicate;
import org.apache.lucene.util.automaton.Automaton; import org.apache.lucene.util.automaton.Automaton;
import org.apache.lucene.util.automaton.Operations; import org.apache.lucene.util.automaton.Operations;
@ -81,7 +80,7 @@ public class TransportHasPrivilegesAction extends HandledTransportAction<HasPriv
cluster.put(checkAction, testPrivilege(checkPrivilege, rolePrivilege.getAutomaton())); cluster.put(checkAction, testPrivilege(checkPrivilege, rolePrivilege.getAutomaton()));
} }
final Map<IndicesPermission.Group, Predicate<String>> predicateCache = new HashMap<>(); final Map<IndicesPermission.Group, Automaton> predicateCache = new HashMap<>();
final Map<String, HasPrivilegesResponse.IndexPrivileges> indices = new LinkedHashMap<>(); final Map<String, HasPrivilegesResponse.IndexPrivileges> indices = new LinkedHashMap<>();
boolean allMatch = true; boolean allMatch = true;
@ -109,13 +108,15 @@ public class TransportHasPrivilegesAction extends HandledTransportAction<HasPriv
} }
private boolean testIndexMatch(String checkIndex, String checkPrivilegeName, Role userRole, private boolean testIndexMatch(String checkIndex, String checkPrivilegeName, Role userRole,
Map<IndicesPermission.Group, Predicate<String>> predicateCache) { Map<IndicesPermission.Group, Automaton> predicateCache) {
final IndexPrivilege checkPrivilege = IndexPrivilege.get(Collections.singleton(checkPrivilegeName)); final IndexPrivilege checkPrivilege = IndexPrivilege.get(Collections.singleton(checkPrivilegeName));
final Automaton checkIndexAutomaton = Automatons.patterns(checkIndex);
List<Automaton> privilegeAutomatons = new ArrayList<>(); List<Automaton> privilegeAutomatons = new ArrayList<>();
for (IndicesPermission.Group group : userRole.indices().groups()) { for (IndicesPermission.Group group : userRole.indices().groups()) {
final Predicate<String> predicate = predicateCache.computeIfAbsent(group, g -> Automatons.predicate(g.indices())); final Automaton groupIndexAutomaton = predicateCache.computeIfAbsent(group, g -> Automatons.patterns(g.indices()));
if (predicate.test(checkIndex)) { if (testIndex(checkIndexAutomaton, groupIndexAutomaton)) {
final IndexPrivilege rolePrivilege = group.privilege(); final IndexPrivilege rolePrivilege = group.privilege();
if (rolePrivilege.name().contains(checkPrivilegeName)) { if (rolePrivilege.name().contains(checkPrivilegeName)) {
return true; return true;
@ -126,7 +127,11 @@ public class TransportHasPrivilegesAction extends HandledTransportAction<HasPriv
return testPrivilege(checkPrivilege, Automatons.unionAndMinimize(privilegeAutomatons)); return testPrivilege(checkPrivilege, Automatons.unionAndMinimize(privilegeAutomatons));
} }
private boolean testPrivilege(Privilege checkPrivilege, Automaton roleAutomaton) { private static boolean testIndex(Automaton checkIndex, Automaton roleIndex) {
return Operations.subsetOf(checkIndex, roleIndex);
}
private static boolean testPrivilege(Privilege checkPrivilege, Automaton roleAutomaton) {
return Operations.subsetOf(checkPrivilege.getAutomaton(), roleAutomaton); return Operations.subsetOf(checkPrivilege.getAutomaton(), roleAutomaton);
} }
} }

View File

@ -191,14 +191,16 @@ public class TransportHasPrivilegesActionTests extends ESTestCase {
} }
/** /**
* We intentionally ignore wildcards in the request. This tests that * Wildcards in the request are treated as
* <code>log*</code> in the request isn't granted by <code>logstash-*</code> * <em>does the user have ___ privilege on every possible index that matches this pattern?</em>
* in the role, but <code>logstash-2016-*</code> is, because it's just * Or, expressed differently,
* treated as the name of an index. * <em>does the user have ___ privilege on a wildcard that covers (is a superset of) this pattern?</em>
*/ */
public void testWildcardsInRequestAreIgnored() throws Exception { public void testWildcardHandling() throws Exception {
role = Role.builder("test3") role = Role.builder("test3")
.add(IndexPrivilege.ALL, "logstash-*") .add(IndexPrivilege.ALL, "logstash-*", "foo?")
.add(IndexPrivilege.READ, "abc*")
.add(IndexPrivilege.WRITE, "*xyz")
.build(); .build();
final HasPrivilegesRequest request = new HasPrivilegesRequest(); final HasPrivilegesRequest request = new HasPrivilegesRequest();
@ -207,11 +209,31 @@ public class TransportHasPrivilegesActionTests extends ESTestCase {
request.indexPrivileges( request.indexPrivileges(
RoleDescriptor.IndicesPrivileges.builder() RoleDescriptor.IndicesPrivileges.builder()
.indices("logstash-2016-*") .indices("logstash-2016-*")
.privileges("write") .privileges("write") // Yes, because (ALL,"logstash-*")
.build(),
RoleDescriptor.IndicesPrivileges.builder()
.indices("logstash-*")
.privileges("read") // Yes, because (ALL,"logstash-*")
.build(), .build(),
RoleDescriptor.IndicesPrivileges.builder() RoleDescriptor.IndicesPrivileges.builder()
.indices("log*") .indices("log*")
.privileges("read") .privileges("manage") // No, because "log*" includes indices that "logstash-*" does not
.build(),
RoleDescriptor.IndicesPrivileges.builder()
.indices("foo*", "foo?")
.privileges("read") // Yes, "foo?", but not "foo*", because "foo*" > "foo?"
.build(),
RoleDescriptor.IndicesPrivileges.builder()
.indices("abcd*")
.privileges("read", "write") // read = Yes, because (READ, "abc*"), write = No
.build(),
RoleDescriptor.IndicesPrivileges.builder()
.indices("abc*xyz")
.privileges("read", "write", "manage") // read = Yes ( READ "abc*"), write = Yes (WRITE, "*xyz"), manage = No
.build(),
RoleDescriptor.IndicesPrivileges.builder()
.indices("a*xyz")
.privileges("read", "write", "manage") // read = No, write = Yes (WRITE, "*xyz"), manage = No
.build() .build()
); );
final PlainActionFuture<HasPrivilegesResponse> future = new PlainActionFuture(); final PlainActionFuture<HasPrivilegesResponse> future = new PlainActionFuture();
@ -220,10 +242,16 @@ public class TransportHasPrivilegesActionTests extends ESTestCase {
final HasPrivilegesResponse response = future.get(); final HasPrivilegesResponse response = future.get();
assertThat(response, notNullValue()); assertThat(response, notNullValue());
assertThat(response.isCompleteMatch(), is(false)); assertThat(response.isCompleteMatch(), is(false));
assertThat(response.getIndexPrivileges(), Matchers.iterableWithSize(2)); assertThat(response.getIndexPrivileges(), Matchers.iterableWithSize(8));
assertThat(response.getIndexPrivileges(), containsInAnyOrder( assertThat(response.getIndexPrivileges(), containsInAnyOrder(
new IndexPrivileges("logstash-2016-*", Collections.singletonMap("write", true)), new IndexPrivileges("logstash-2016-*", Collections.singletonMap("write", true)),
new IndexPrivileges("log*", Collections.singletonMap("read", false)) new IndexPrivileges("logstash-*", Collections.singletonMap("read", true)),
new IndexPrivileges("log*", Collections.singletonMap("manage", false)),
new IndexPrivileges("foo?", Collections.singletonMap("read", true)),
new IndexPrivileges("foo*", Collections.singletonMap("read", false)),
new IndexPrivileges("abcd*", mapBuilder().put("read", true).put("write", false).map()),
new IndexPrivileges("abc*xyz", mapBuilder().put("read", true).put("write", true).put("manage", false).map()),
new IndexPrivileges("a*xyz", mapBuilder().put("read", false).put("write", true).put("manage", false).map())
)); ));
} }
@ -259,4 +287,9 @@ public class TransportHasPrivilegesActionTests extends ESTestCase {
) )
)); ));
} }
private static MapBuilder<String, Boolean> mapBuilder() {
return MapBuilder.newMapBuilder();
}
} }