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:
parent
da40720ef0
commit
e177f79aa3
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue