Skip to content

Commit 3473ca9

Browse files
authored
AllowAll for indicesAccessControl (#78498) (#79338)
This PR adds a fast path for computing indicesAccessControl if the role has all access to all indices. A role is considered to have all access to all indices if any of its IndicesPermission#Group satisfy the following criteria: 1. Any of the index patterns is a simple match-all wildcard, i.e. "*" 2. It allows access to restricted indices 3. It grants the "all" index privilege 4. It has no DLS or FLS An example of such role is the builtin superuser role. Note the fastpath does not apply to roles that have "effective" but not direct "all access of all indices". For example, if the "effective" access is achieved by combining multiple Groups belong to the role, or combining multiple index patterns within a single Group. This fast path is provided so that we have a reference baseline for authorization related performance which is useful for both production use and troubleshooting.
1 parent 61b0c3c commit 3473ca9

File tree

9 files changed

+126
-87
lines changed

9 files changed

+126
-87
lines changed

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/IndicesAccessControl.java

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
*/
3131
public class IndicesAccessControl {
3232

33-
public static final IndicesAccessControl ALLOW_ALL = new IndicesAccessControl(true, Collections.emptyMap());
3433
public static final IndicesAccessControl ALLOW_NO_INDICES = new IndicesAccessControl(true,
3534
Collections.singletonMap(IndicesAndAliasesResolverField.NO_INDEX_PLACEHOLDER,
3635
new IndicesAccessControl.IndexAccessControl(true, new FieldPermissions(), DocumentPermissions.allowAll())));
@@ -249,6 +248,12 @@ public int hashCode() {
249248
* @return {@link IndicesAccessControl}
250249
*/
251250
public IndicesAccessControl limitIndicesAccessControl(IndicesAccessControl limitedByIndicesAccessControl) {
251+
if (this instanceof AllowAllIndicesAccessControl) {
252+
return limitedByIndicesAccessControl;
253+
} else if (limitedByIndicesAccessControl instanceof AllowAllIndicesAccessControl) {
254+
return this;
255+
}
256+
252257
final boolean granted;
253258
if (this.granted == limitedByIndicesAccessControl.granted) {
254259
granted = this.granted;
@@ -275,4 +280,35 @@ public String toString() {
275280
", indexPermissions=" + indexPermissions +
276281
'}';
277282
}
283+
284+
public static IndicesAccessControl allowAll() {
285+
return AllowAllIndicesAccessControl.INSTANCE;
286+
}
287+
288+
private static class AllowAllIndicesAccessControl extends IndicesAccessControl {
289+
290+
private static final IndicesAccessControl INSTANCE = new AllowAllIndicesAccessControl();
291+
292+
private final IndexAccessControl allowAllIndexAccessControl = new IndexAccessControl(true, null, null);
293+
294+
private AllowAllIndicesAccessControl() {
295+
super(true, null);
296+
}
297+
298+
@Override
299+
public IndexAccessControl getIndexPermissions(String index) {
300+
return allowAllIndexAccessControl;
301+
}
302+
303+
@Override
304+
public boolean isGranted() {
305+
return true;
306+
}
307+
308+
@Override
309+
public Collection<?> getDeniedIndices() {
310+
return org.elasticsearch.core.Set.of();
311+
}
312+
}
313+
278314
}

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,12 +274,16 @@ public boolean canHaveBackingIndices() {
274274
/**
275275
* Authorizes the provided action against the provided indices, given the current cluster metadata
276276
*/
277-
public Map<String, IndicesAccessControl.IndexAccessControl> authorize(
277+
public IndicesAccessControl authorize(
278278
String action,
279279
Set<String> requestedIndicesOrAliases,
280280
Map<String, IndexAbstraction> lookup,
281281
FieldPermissionsCache fieldPermissionsCache
282282
) {
283+
// Short circuit if the indicesPermission allows all access to every index
284+
if (Arrays.stream(groups).anyMatch(Group::isTotal)) {
285+
return IndicesAccessControl.allowAll();
286+
}
283287

284288
final List<IndexResource> resources = new ArrayList<>(requestedIndicesOrAliases.size());
285289
int totalResourceCount = 0;
@@ -405,6 +409,7 @@ public Map<String, IndicesAccessControl.IndexAccessControl> authorize(
405409
}
406410
}
407411

412+
boolean overallGranted = true;
408413
Map<String, IndicesAccessControl.IndexAccessControl> indexPermissions = new HashMap<>(grantedBuilder.size());
409414
for (Map.Entry<String, Boolean> entry : grantedBuilder.entrySet()) {
410415
String index = entry.getKey();
@@ -424,10 +429,13 @@ public Map<String, IndicesAccessControl.IndexAccessControl> authorize(
424429
} else {
425430
fieldPermissions = FieldPermissions.DEFAULT;
426431
}
432+
if (entry.getValue() == false) {
433+
overallGranted = false;
434+
}
427435
indexPermissions.put(index, new IndicesAccessControl.IndexAccessControl(entry.getValue(), fieldPermissions,
428436
(roleQueries != null) ? DocumentPermissions.filteredBy(roleQueries) : DocumentPermissions.allowAll()));
429437
}
430-
return unmodifiableMap(indexPermissions);
438+
return new IndicesAccessControl(overallGranted, unmodifiableMap(indexPermissions));
431439
}
432440

433441
private boolean isConcreteRestrictedIndex(String indexPattern) {
@@ -449,7 +457,7 @@ public static class Group {
449457
private final IndexPrivilege privilege;
450458
private final Predicate<String> actionMatcher;
451459
private final String[] indices;
452-
private final Predicate<String> indexNameMatcher;
460+
private final StringMatcher indexNameMatcher;
453461
private final FieldPermissions fieldPermissions;
454462
private final Set<BytesReference> query;
455463
// by default certain restricted indices are exempted when granting privileges, as they should generally be hidden for ordinary
@@ -545,6 +553,14 @@ private static Predicate<IndexAbstraction> buildIndexMatcherPredicateForAction(S
545553
bwcSpecialCaseMatcher.test(indexAbstraction.getName()));
546554
};
547555
}
556+
557+
boolean isTotal() {
558+
return allowRestrictedIndices
559+
&& indexNameMatcher.isTotal()
560+
&& privilege == IndexPrivilege.ALL
561+
&& query == null
562+
&& false == fieldPermissions.hasFieldLevelSecurity();
563+
}
548564
}
549565

550566
private static class DocumentLevelPermissions {

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ public IndicesAccessControl authorize(String action, Set<String> requestedIndice
8989
super.authorize(action, requestedIndicesOrAliases, aliasAndIndexLookup, fieldPermissionsCache);
9090
IndicesAccessControl limitedByIndicesAccessControl = limitedBy.authorize(action, requestedIndicesOrAliases, aliasAndIndexLookup,
9191
fieldPermissionsCache);
92-
9392
return indicesAccessControl.limitIndicesAccessControl(limitedByIndicesAccessControl);
9493
}
9594

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

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -175,19 +175,7 @@ public ResourcePrivilegesMap checkApplicationResourcePrivileges(final String app
175175
public IndicesAccessControl authorize(String action, Set<String> requestedIndicesOrAliases,
176176
Map<String, IndexAbstraction> aliasAndIndexLookup,
177177
FieldPermissionsCache fieldPermissionsCache) {
178-
Map<String, IndicesAccessControl.IndexAccessControl> indexPermissions = indices.authorize(
179-
action, requestedIndicesOrAliases, aliasAndIndexLookup, fieldPermissionsCache
180-
);
181-
182-
// At least one role / indices permission set need to match with all the requested indices/aliases:
183-
boolean granted = true;
184-
for (Map.Entry<String, IndicesAccessControl.IndexAccessControl> entry : indexPermissions.entrySet()) {
185-
if (entry.getValue().isGranted() == false) {
186-
granted = false;
187-
break;
188-
}
189-
}
190-
return new IndicesAccessControl(granted, indexPermissions);
178+
return indices.authorize(action, requestedIndicesOrAliases, aliasAndIndexLookup, fieldPermissionsCache);
191179
}
192180

193181
@Override

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/StringMatcher.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ public boolean test(String s) {
6969
return predicate.test(s);
7070
}
7171

72+
public boolean isTotal() {
73+
return predicate == ALWAYS_TRUE_PREDICATE;
74+
}
75+
7276
// For testing
7377
Predicate<String> getPredicate() {
7478
return predicate;

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
import org.elasticsearch.xpack.core.security.action.CreateApiKeyRequest;
9797
import org.elasticsearch.xpack.core.security.action.GetApiKeyRequest;
9898
import org.elasticsearch.xpack.core.security.action.apikey.QueryApiKeyRequest;
99+
import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
99100
import org.elasticsearch.xpack.core.textstructure.action.FindStructureAction;
100101
import org.elasticsearch.xpack.core.ml.action.FlushJobAction;
101102
import org.elasticsearch.xpack.core.ml.action.ForecastJobAction;
@@ -167,7 +168,6 @@
167168
import org.elasticsearch.xpack.core.security.action.user.PutUserAction;
168169
import org.elasticsearch.xpack.core.security.authc.Authentication;
169170
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
170-
import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl.IndexAccessControl;
171171
import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsCache;
172172
import org.elasticsearch.xpack.core.security.authz.permission.Role;
173173
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
@@ -216,7 +216,6 @@
216216
import java.util.Collection;
217217
import java.util.Collections;
218218
import java.util.List;
219-
import java.util.Map;
220219
import java.util.SortedMap;
221220

222221
import static org.hamcrest.Matchers.hasEntry;
@@ -1130,12 +1129,12 @@ private void assertMonitoringOnRestrictedIndices(Role role) {
11301129
GetSettingsAction.NAME, IndicesShardStoresAction.NAME, UpgradeStatusAction.NAME, RecoveryAction.NAME);
11311130
for (final String indexMonitoringActionName : indexMonitoringActionNamesList) {
11321131
String asyncSearchIndex = RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2);
1133-
final Map<String, IndexAccessControl> authzMap = role.indices().authorize(indexMonitoringActionName,
1132+
final IndicesAccessControl iac = role.indices().authorize(indexMonitoringActionName,
11341133
Sets.newHashSet(internalSecurityIndex, RestrictedIndicesNames.SECURITY_MAIN_ALIAS, asyncSearchIndex),
11351134
metadata.getIndicesLookup(), fieldPermissionsCache);
1136-
assertThat(authzMap.get(internalSecurityIndex).isGranted(), is(true));
1137-
assertThat(authzMap.get(RestrictedIndicesNames.SECURITY_MAIN_ALIAS).isGranted(), is(true));
1138-
assertThat(authzMap.get(asyncSearchIndex).isGranted(), is(true));
1135+
assertThat(iac.getIndexPermissions(internalSecurityIndex).isGranted(), is(true));
1136+
assertThat(iac.getIndexPermissions(RestrictedIndicesNames.SECURITY_MAIN_ALIAS).isGranted(), is(true));
1137+
assertThat(iac.getIndexPermissions(asyncSearchIndex).isGranted(), is(true));
11391138
}
11401139
}
11411140

@@ -1266,25 +1265,25 @@ public void testSuperuserRole() {
12661265

12671266
FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY);
12681267
SortedMap<String, IndexAbstraction> lookup = metadata.getIndicesLookup();
1269-
Map<String, IndexAccessControl> authzMap =
1268+
IndicesAccessControl iac =
12701269
superuserRole.indices().authorize(SearchAction.NAME, Sets.newHashSet("a1", "ba"), lookup, fieldPermissionsCache);
1271-
assertThat(authzMap.get("a1").isGranted(), is(true));
1272-
assertThat(authzMap.get("b").isGranted(), is(true));
1273-
authzMap =
1270+
assertThat(iac.getIndexPermissions("a1").isGranted(), is(true));
1271+
assertThat(iac.getIndexPermissions("b").isGranted(), is(true));
1272+
iac =
12741273
superuserRole.indices().authorize(DeleteIndexAction.NAME, Sets.newHashSet("a1", "ba"), lookup, fieldPermissionsCache);
1275-
assertThat(authzMap.get("a1").isGranted(), is(true));
1276-
assertThat(authzMap.get("b").isGranted(), is(true));
1277-
authzMap = superuserRole.indices().authorize(IndexAction.NAME, Sets.newHashSet("a2", "ba"), lookup, fieldPermissionsCache);
1278-
assertThat(authzMap.get("a2").isGranted(), is(true));
1279-
assertThat(authzMap.get("b").isGranted(), is(true));
1280-
authzMap = superuserRole.indices()
1274+
assertThat(iac.getIndexPermissions("a1").isGranted(), is(true));
1275+
assertThat(iac.getIndexPermissions("b").isGranted(), is(true));
1276+
iac = superuserRole.indices().authorize(IndexAction.NAME, Sets.newHashSet("a2", "ba"), lookup, fieldPermissionsCache);
1277+
assertThat(iac.getIndexPermissions("a2").isGranted(), is(true));
1278+
assertThat(iac.getIndexPermissions("b").isGranted(), is(true));
1279+
iac = superuserRole.indices()
12811280
.authorize(UpdateSettingsAction.NAME, Sets.newHashSet("aaaaaa", "ba"), lookup, fieldPermissionsCache);
1282-
assertThat(authzMap.get("aaaaaa").isGranted(), is(true));
1283-
assertThat(authzMap.get("b").isGranted(), is(true));
1284-
authzMap = superuserRole.indices().authorize(randomFrom(IndexAction.NAME, DeleteIndexAction.NAME, SearchAction.NAME),
1281+
assertThat(iac.getIndexPermissions("aaaaaa").isGranted(), is(true));
1282+
assertThat(iac.getIndexPermissions("b").isGranted(), is(true));
1283+
iac = superuserRole.indices().authorize(randomFrom(IndexAction.NAME, DeleteIndexAction.NAME, SearchAction.NAME),
12851284
Sets.newHashSet(RestrictedIndicesNames.SECURITY_MAIN_ALIAS), lookup, fieldPermissionsCache);
1286-
assertThat(authzMap.get(RestrictedIndicesNames.SECURITY_MAIN_ALIAS).isGranted(), is(true));
1287-
assertThat(authzMap.get(internalSecurityIndex).isGranted(), is(true));
1285+
assertThat(iac.getIndexPermissions(RestrictedIndicesNames.SECURITY_MAIN_ALIAS).isGranted(), is(true));
1286+
assertThat(iac.getIndexPermissions(internalSecurityIndex).isGranted(), is(true));
12881287
assertTrue(superuserRole.indices().check(SearchAction.NAME));
12891288
assertFalse(superuserRole.indices().check("unknown"));
12901289

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ private void authorizeAction(final RequestInfo requestInfo, final String request
339339
if (ClusterPrivilegeResolver.isClusterAction(action)) {
340340
final ActionListener<AuthorizationResult> clusterAuthzListener =
341341
wrapPreservingContext(new AuthorizationResultListener<>(result -> {
342-
threadContext.putTransient(INDICES_PERMISSIONS_KEY, IndicesAccessControl.ALLOW_ALL);
342+
threadContext.putTransient(INDICES_PERMISSIONS_KEY, IndicesAccessControl.allowAll());
343343
listener.onResponse(null);
344344
}, listener::onFailure, requestInfo, requestId, authzInfo), threadContext);
345345
authzEngine.authorizeClusterAction(requestInfo, authzInfo, ActionListener.wrap(result -> {
@@ -515,7 +515,7 @@ private void authorizeSystemUser(final Authentication authentication, final Stri
515515
final TransportRequest request, final ActionListener<Void> listener) {
516516
final AuditTrail auditTrail = auditTrailService.get();
517517
if (SystemUser.isAuthorized(action)) {
518-
threadContext.putTransient(INDICES_PERMISSIONS_KEY, IndicesAccessControl.ALLOW_ALL);
518+
threadContext.putTransient(INDICES_PERMISSIONS_KEY, IndicesAccessControl.allowAll());
519519
threadContext.putTransient(AUTHORIZATION_INFO_KEY, SYSTEM_AUTHZ_INFO);
520520
auditTrail.accessGranted(requestId, authentication, action, request, SYSTEM_AUTHZ_INFO);
521521
listener.onResponse(null);

0 commit comments

Comments
 (0)