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.List;
import java.util.Map;
import java.util.function.Predicate;
import org.apache.lucene.util.automaton.Automaton;
import org.apache.lucene.util.automaton.Operations;
@ -81,7 +80,7 @@ public class TransportHasPrivilegesAction extends HandledTransportAction<HasPriv
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<>();
boolean allMatch = true;
@ -109,13 +108,15 @@ public class TransportHasPrivilegesAction extends HandledTransportAction<HasPriv
}
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 Automaton checkIndexAutomaton = Automatons.patterns(checkIndex);
List<Automaton> privilegeAutomatons = new ArrayList<>();
for (IndicesPermission.Group group : userRole.indices().groups()) {
final Predicate<String> predicate = predicateCache.computeIfAbsent(group, g -> Automatons.predicate(g.indices()));
if (predicate.test(checkIndex)) {
final Automaton groupIndexAutomaton = predicateCache.computeIfAbsent(group, g -> Automatons.patterns(g.indices()));
if (testIndex(checkIndexAutomaton, groupIndexAutomaton)) {
final IndexPrivilege rolePrivilege = group.privilege();
if (rolePrivilege.name().contains(checkPrivilegeName)) {
return true;
@ -126,7 +127,11 @@ public class TransportHasPrivilegesAction extends HandledTransportAction<HasPriv
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);
}
}

View File

@ -191,14 +191,16 @@ public class TransportHasPrivilegesActionTests extends ESTestCase {
}
/**
* We intentionally ignore wildcards in the request. This tests that
* <code>log*</code> in the request isn't granted by <code>logstash-*</code>
* in the role, but <code>logstash-2016-*</code> is, because it's just
* treated as the name of an index.
* Wildcards in the request are treated as
* <em>does the user have ___ privilege on every possible index that matches this pattern?</em>
* Or, expressed differently,
* <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")
.add(IndexPrivilege.ALL, "logstash-*")
.add(IndexPrivilege.ALL, "logstash-*", "foo?")
.add(IndexPrivilege.READ, "abc*")
.add(IndexPrivilege.WRITE, "*xyz")
.build();
final HasPrivilegesRequest request = new HasPrivilegesRequest();
@ -207,11 +209,31 @@ public class TransportHasPrivilegesActionTests extends ESTestCase {
request.indexPrivileges(
RoleDescriptor.IndicesPrivileges.builder()
.indices("logstash-2016-*")
.privileges("write")
.privileges("write") // Yes, because (ALL,"logstash-*")
.build(),
RoleDescriptor.IndicesPrivileges.builder()
.indices("logstash-*")
.privileges("read") // Yes, because (ALL,"logstash-*")
.build(),
RoleDescriptor.IndicesPrivileges.builder()
.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()
);
final PlainActionFuture<HasPrivilegesResponse> future = new PlainActionFuture();
@ -220,10 +242,16 @@ public class TransportHasPrivilegesActionTests extends ESTestCase {
final HasPrivilegesResponse response = future.get();
assertThat(response, notNullValue());
assertThat(response.isCompleteMatch(), is(false));
assertThat(response.getIndexPrivileges(), Matchers.iterableWithSize(2));
assertThat(response.getIndexPrivileges(), Matchers.iterableWithSize(8));
assertThat(response.getIndexPrivileges(), containsInAnyOrder(
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();
}
}