Skip to content

Commit 898da16

Browse files
authored
Optimize Index Permission Automatons for Has Privileges (elastic#136625) (elastic#136678)
This PR optimizes how index permission automatons are created. In elastic#110633 a feature was added to allow for doing regular expression "has privilege" style checks against index permission groups. The reason this was added was to support regular expressions for the `manage_roles` privilege (so unrelated to the has privileges API). The implementation was known to have performance issues and the change was therefore made optional (and disabled in the has privileges API). Unfortunately, a change to simplify the logic by doing `Automatons.unionAndMinimize` on all index permissions in a group was left outside the conditional and therefore caused a performance regression. See [this](https://github.com/elastic/elasticsearch/blob/2a9c07eb6fe4e8bbb7fc9fbab6fa59ccc006e10b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/IndicesPermission.java#L840-L860) comment for details on the original change. (cherry picked from commit 3df0abb) # Conflicts: # x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/IndicesPermission.java
1 parent affcba3 commit 898da16

File tree

2 files changed

+51
-31
lines changed

2 files changed

+51
-31
lines changed

docs/changelog/136625.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 136625
2+
summary: Optimize Index Permission Automatons for Has Privileges
3+
area: Security
4+
type: bug
5+
issues: []

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/IndicesPermission.java

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.elasticsearch.common.logging.DeprecationCategory;
2424
import org.elasticsearch.common.logging.DeprecationLogger;
2525
import org.elasticsearch.common.regex.Regex;
26+
import org.elasticsearch.common.util.CachedSupplier;
2627
import org.elasticsearch.common.util.Maps;
2728
import org.elasticsearch.common.util.set.Sets;
2829
import org.elasticsearch.core.Nullable;
@@ -324,14 +325,14 @@ public boolean checkResourcePrivileges(
324325
@Nullable ResourcePrivilegesMap.Builder resourcePrivilegesMapBuilder
325326
) {
326327
boolean allMatch = true;
327-
Map<Automaton, Automaton> indexGroupAutomatonsForDataSelector = indexGroupAutomatons(
328+
Map<Automaton, List<Automaton>> indexGroupAutomatonsForDataSelector = indexGroupAutomatons(
328329
combineIndexGroups && checkForIndexPatterns.stream().anyMatch(Automatons::isLuceneRegex),
329330
IndexComponentSelector.DATA
330331
);
331332
// optimization: if there are no failures selector privileges in the set of privileges to check, we can skip building
332333
// the automaton map
333334
final boolean containsPrivilegesForFailuresSelector = containsPrivilegesForFailuresSelector(checkForPrivileges);
334-
Map<Automaton, Automaton> indexGroupAutomatonsForFailuresSelector = false == containsPrivilegesForFailuresSelector
335+
Map<Automaton, List<Automaton>> indexGroupAutomatonsForFailuresSelector = false == containsPrivilegesForFailuresSelector
335336
? Map.of()
336337
: indexGroupAutomatons(
337338
combineIndexGroups && checkForIndexPatterns.stream().anyMatch(Automatons::isLuceneRegex),
@@ -845,43 +846,54 @@ private static boolean containsPrivilegeThatGrantsMappingUpdatesForBwc(Group gro
845846
*
846847
* @param combine combine index groups to allow for checking against regular expressions
847848
*
848-
* @return a map of all index and privilege pattern automatons
849+
* @return a map of all privilege to index pattern automatons
849850
*/
850-
private Map<Automaton, Automaton> indexGroupAutomatons(boolean combine, IndexComponentSelector selector) {
851+
private Map<Automaton, List<Automaton>> indexGroupAutomatons(boolean combine, IndexComponentSelector selector) {
851852
// Map of privilege automaton object references (cached by IndexPrivilege::CACHE)
852-
Map<Automaton, Automaton> allAutomatons = new HashMap<>();
853+
var privilegeToIndexAutomatons = new HashMap<Automaton, List<Automaton>>();
853854
for (Group group : groups) {
854855
if (false == group.checkSelector(selector)) {
855856
continue;
856857
}
857858
Automaton indexAutomaton = group.getIndexMatcherAutomaton();
858-
allAutomatons.compute(
859-
group.privilege().getAutomaton(),
860-
(key, value) -> value == null ? indexAutomaton : Automatons.unionAndMinimize(List.of(value, indexAutomaton))
861-
);
859+
862860
if (combine) {
861+
privilegeToIndexAutomatons.compute(
862+
group.privilege().getAutomaton(),
863+
(key, value) -> value == null
864+
? List.of(indexAutomaton)
865+
: List.of(Automatons.unionAndMinimize(List.of(value.get(0), indexAutomaton)))
866+
);
863867
List<Tuple<Automaton, Automaton>> combinedAutomatons = new ArrayList<>();
864-
for (var indexAndPrivilegeAutomatons : allAutomatons.entrySet()) {
868+
for (var privilegeAndIndexAutomatons : privilegeToIndexAutomatons.entrySet()) {
865869
Automaton intersectingPrivileges = Operations.intersection(
866-
indexAndPrivilegeAutomatons.getKey(),
870+
privilegeAndIndexAutomatons.getKey(),
867871
group.privilege().getAutomaton()
868872
);
869873
if (Operations.isEmpty(intersectingPrivileges) == false) {
870874
Automaton indexPatternAutomaton = Automatons.unionAndMinimize(
871-
List.of(indexAndPrivilegeAutomatons.getValue(), indexAutomaton)
875+
List.of(privilegeAndIndexAutomatons.getValue().get(0), indexAutomaton)
872876
);
873877
combinedAutomatons.add(new Tuple<>(intersectingPrivileges, indexPatternAutomaton));
874878
}
875879
}
876880
combinedAutomatons.forEach(
877-
automatons -> allAutomatons.compute(
881+
automatons -> privilegeToIndexAutomatons.compute(
878882
automatons.v1(),
879-
(key, value) -> value == null ? automatons.v2() : Automatons.unionAndMinimize(List.of(value, automatons.v2()))
883+
(key, value) -> value == null
884+
? List.of(automatons.v2())
885+
: List.of(Automatons.unionAndMinimize(List.of(value.get(0), automatons.v2())))
880886
)
881887
);
888+
} else {
889+
privilegeToIndexAutomatons.compute(group.privilege().getAutomaton(), (k, v) -> {
890+
var list = v == null ? new ArrayList<Automaton>() : v;
891+
list.add(group.getIndexMatcherAutomaton());
892+
return list;
893+
});
882894
}
883895
}
884-
return allAutomatons;
896+
return privilegeToIndexAutomatons;
885897
}
886898

887899
private static boolean containsPrivilegesForFailuresSelector(Set<String> checkForPrivileges) {
@@ -898,21 +910,26 @@ private static boolean containsPrivilegesForFailuresSelector(Set<String> checkFo
898910
}
899911

900912
@Nullable
901-
private static Automaton getIndexPrivilegesAutomaton(Map<Automaton, Automaton> indexGroupAutomatons, Automaton checkIndexAutomaton) {
913+
private static Automaton getIndexPrivilegesAutomaton(
914+
Map<Automaton, List<Automaton>> indexGroupAutomatons,
915+
Automaton checkIndexAutomaton
916+
) {
902917
if (indexGroupAutomatons.isEmpty()) {
903918
return null;
904919
}
905920
Automaton allowedPrivilegesAutomaton = null;
906-
for (Map.Entry<Automaton, Automaton> indexAndPrivilegeAutomaton : indexGroupAutomatons.entrySet()) {
907-
Automaton indexNameAutomaton = indexAndPrivilegeAutomaton.getValue();
908-
if (Operations.subsetOf(checkIndexAutomaton, indexNameAutomaton)) {
909-
Automaton privilegesAutomaton = indexAndPrivilegeAutomaton.getKey();
910-
if (allowedPrivilegesAutomaton != null) {
911-
allowedPrivilegesAutomaton = Automatons.unionAndMinimize(
912-
Arrays.asList(allowedPrivilegesAutomaton, privilegesAutomaton)
913-
);
914-
} else {
915-
allowedPrivilegesAutomaton = privilegesAutomaton;
921+
for (Map.Entry<Automaton, List<Automaton>> indexAndPrivilegeAutomaton : indexGroupAutomatons.entrySet()) {
922+
List<Automaton> indexNameAutomatons = indexAndPrivilegeAutomaton.getValue();
923+
for (var indexNameAutomaton : indexNameAutomatons) {
924+
if (Operations.subsetOf(checkIndexAutomaton, indexNameAutomaton)) {
925+
Automaton privilegesAutomaton = indexAndPrivilegeAutomaton.getKey();
926+
if (allowedPrivilegesAutomaton != null) {
927+
allowedPrivilegesAutomaton = Automatons.unionAndMinimize(
928+
Arrays.asList(allowedPrivilegesAutomaton, privilegesAutomaton)
929+
);
930+
} else {
931+
allowedPrivilegesAutomaton = privilegesAutomaton;
932+
}
916933
}
917934
}
918935
}
@@ -950,15 +967,13 @@ public Group(
950967
this.selectorPredicate = privilege.getSelectorPredicate();
951968
this.indices = indices;
952969
this.allowRestrictedIndices = allowRestrictedIndices;
953-
ConcurrentHashMap<String[], Automaton> indexNameAutomatonMemo = new ConcurrentHashMap<>(1);
954970
if (allowRestrictedIndices) {
955971
this.indexNameMatcher = StringMatcher.of(indices);
956-
this.indexNameAutomaton = () -> indexNameAutomatonMemo.computeIfAbsent(indices, k -> Automatons.patterns(indices));
972+
this.indexNameAutomaton = CachedSupplier.wrap(() -> Automatons.patterns(indices));
957973
} else {
958974
this.indexNameMatcher = StringMatcher.of(indices).and(name -> restrictedIndices.isRestricted(name) == false);
959-
this.indexNameAutomaton = () -> indexNameAutomatonMemo.computeIfAbsent(
960-
indices,
961-
k -> Automatons.minusAndMinimize(Automatons.patterns(indices), restrictedIndices.getAutomaton())
975+
this.indexNameAutomaton = CachedSupplier.wrap(
976+
() -> Automatons.minusAndMinimize(Automatons.patterns(indices), restrictedIndices.getAutomaton())
962977
);
963978
}
964979
this.fieldPermissions = Objects.requireNonNull(fieldPermissions);

0 commit comments

Comments
 (0)