Skip to content

Commit 5f67b42

Browse files
n1v0lggeorgewallace
authored andcommitted
Failure store access - selector-aware role building (elastic#122715)
This PR adds the `read_failure_store` index privilege and extends the role building logic to support selector-aware authorization. Note that this PR only concerns building roles; the actual authorization logic implementation will follow in a separate PR. The overall approach is based on the proof-of-concept PR (elastic#122007). The purpose of the `read_failure_store` index privilege is to support granting selective access to the failure store of a data stream via the `::failures` selector in search and related APIs. A role with the `read_failure_store` index privilege grants access to the failure store, without granting access to the data in a data stream. Conversely, the existing `read` privilege only grants access to data and _not_ the failure store. This requires changes to both the role building logic, and authorization. Going forward, each named index privilege is assigned a selector it grants access to, e.g., `read` grants access to the implicit `::data` selector, `read_failure_store` grants access to `::failures`. When building a role from role descriptors, we partition its underlying index groups by selector access such that any given group grants access to a single selector (with the exception of `all`, which grants access to all selectors). This PR implements this partitioning logic and sets up roles to implement selector-aware authorization in a follow up. Note that parts of the code make assumption around the existence of only two distinct selectors (`::data` and `::failures`) to simplify the implementation; however, it's possible to generalize these sections to support more selectors in the future, if necessary. Closes: ES-10872
1 parent 38f083b commit 5f67b42

File tree

21 files changed

+1292
-106
lines changed

21 files changed

+1292
-106
lines changed

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/role/RoleDescriptorRequestValidator.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
1212
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
1313
import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilegeResolver;
14+
import org.elasticsearch.xpack.core.security.authz.privilege.IndexComponentSelectorPredicate;
1415
import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
1516
import org.elasticsearch.xpack.core.security.authz.restriction.WorkflowResolver;
1617
import org.elasticsearch.xpack.core.security.support.MetadataUtils;
@@ -48,7 +49,7 @@ public static ActionRequestValidationException validate(
4849
if (roleDescriptor.getIndicesPrivileges() != null) {
4950
for (RoleDescriptor.IndicesPrivileges idp : roleDescriptor.getIndicesPrivileges()) {
5051
try {
51-
IndexPrivilege.get(Set.of(idp.getPrivileges()));
52+
IndexPrivilege.resolveBySelectorAccess(Set.of(idp.getPrivileges()));
5253
} catch (IllegalArgumentException ile) {
5354
validationException = addValidationError(ile.getMessage(), validationException);
5455
}
@@ -60,7 +61,13 @@ public static ActionRequestValidationException validate(
6061
validationException = addValidationError("remote index cluster alias cannot be an empty string", validationException);
6162
}
6263
try {
63-
IndexPrivilege.get(Set.of(ridp.indicesPrivileges().getPrivileges()));
64+
Set<IndexPrivilege> privileges = IndexPrivilege.resolveBySelectorAccess(Set.of(ridp.indicesPrivileges().getPrivileges()));
65+
if (privileges.stream().anyMatch(p -> p.getSelectorPredicate() == IndexComponentSelectorPredicate.FAILURES)) {
66+
validationException = addValidationError(
67+
"remote index privileges cannot contain privileges that grant access to the failure store",
68+
validationException
69+
);
70+
}
6471
} catch (IllegalArgumentException ile) {
6572
validationException = addValidationError(ile.getMessage(), validationException);
6673
}

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@
2727
import org.elasticsearch.index.Index;
2828
import org.elasticsearch.xpack.core.security.authz.RestrictedIndices;
2929
import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
30+
import org.elasticsearch.xpack.core.security.authz.privilege.IndexComponentSelectorPredicate;
3031
import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
3132
import org.elasticsearch.xpack.core.security.support.Automatons;
3233
import org.elasticsearch.xpack.core.security.support.StringMatcher;
3334

3435
import java.util.ArrayList;
3536
import java.util.Arrays;
3637
import java.util.Collection;
37-
import java.util.Collections;
3838
import java.util.HashMap;
3939
import java.util.HashSet;
4040
import java.util.List;
@@ -301,7 +301,7 @@ public boolean checkResourcePrivileges(
301301
}
302302
}
303303
for (String privilege : checkForPrivileges) {
304-
IndexPrivilege indexPrivilege = IndexPrivilege.get(Collections.singleton(privilege));
304+
IndexPrivilege indexPrivilege = IndexPrivilege.get(privilege);
305305
if (allowedIndexPrivilegesAutomaton != null
306306
&& Automatons.subsetOf(indexPrivilege.getAutomaton(), allowedIndexPrivilegesAutomaton)) {
307307
if (resourcePrivilegesMapBuilder != null) {
@@ -792,6 +792,7 @@ public static class Group {
792792
public static final Group[] EMPTY_ARRAY = new Group[0];
793793

794794
private final IndexPrivilege privilege;
795+
private final IndexComponentSelectorPredicate selectorPredicate;
795796
private final Predicate<String> actionMatcher;
796797
private final String[] indices;
797798
private final StringMatcher indexNameMatcher;
@@ -815,6 +816,7 @@ public Group(
815816
assert indices.length != 0;
816817
this.privilege = privilege;
817818
this.actionMatcher = privilege.predicate();
819+
this.selectorPredicate = privilege.getSelectorPredicate();
818820
this.indices = indices;
819821
this.allowRestrictedIndices = allowRestrictedIndices;
820822
ConcurrentHashMap<String[], Automaton> indexNameAutomatonMemo = new ConcurrentHashMap<>(1);
@@ -862,6 +864,10 @@ boolean hasQuery() {
862864
return query != null;
863865
}
864866

867+
public boolean checkSelector(IndexComponentSelector selector) {
868+
return selectorPredicate.test(selector);
869+
}
870+
865871
public boolean allowRestrictedIndices() {
866872
return allowRestrictedIndices;
867873
}

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

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,19 @@ public Builder runAs(Privilege privilege) {
252252
}
253253

254254
public Builder add(IndexPrivilege privilege, String... indices) {
255-
groups.add(new IndicesPermissionGroupDefinition(privilege, FieldPermissions.DEFAULT, null, false, indices));
255+
return add(FieldPermissions.DEFAULT, null, privilege, false, indices);
256+
}
257+
258+
public Builder add(
259+
FieldPermissions fieldPermissions,
260+
Set<BytesReference> query,
261+
Set<IndexPrivilege> privilegesSplitBySelector,
262+
boolean allowRestrictedIndices,
263+
String... indices
264+
) {
265+
for (var indexPrivilege : privilegesSplitBySelector) {
266+
add(fieldPermissions, query, indexPrivilege, allowRestrictedIndices, indices);
267+
}
256268
return this;
257269
}
258270

@@ -267,6 +279,20 @@ public Builder add(
267279
return this;
268280
}
269281

282+
public Builder addRemoteIndicesGroup(
283+
final Set<String> remoteClusterAliases,
284+
final FieldPermissions fieldPermissions,
285+
final Set<BytesReference> query,
286+
final Set<IndexPrivilege> privilegesSplitBySelector,
287+
final boolean allowRestrictedIndices,
288+
final String... indices
289+
) {
290+
for (var indexPrivilege : privilegesSplitBySelector) {
291+
addRemoteIndicesGroup(remoteClusterAliases, fieldPermissions, query, indexPrivilege, allowRestrictedIndices, indices);
292+
}
293+
return this;
294+
}
295+
270296
public Builder addRemoteIndicesGroup(
271297
final Set<String> remoteClusterAliases,
272298
final FieldPermissions fieldPermissions,
@@ -411,7 +437,7 @@ static SimpleRole buildFromRoleDescriptor(
411437
new FieldPermissionsDefinition(indexPrivilege.getGrantedFields(), indexPrivilege.getDeniedFields())
412438
),
413439
indexPrivilege.getQuery() == null ? null : Collections.singleton(indexPrivilege.getQuery()),
414-
IndexPrivilege.get(Sets.newHashSet(indexPrivilege.getPrivileges())),
440+
IndexPrivilege.resolveBySelectorAccess(Set.of(indexPrivilege.getPrivileges())),
415441
indexPrivilege.allowRestrictedIndices(),
416442
indexPrivilege.getIndices()
417443
);
@@ -428,7 +454,7 @@ static SimpleRole buildFromRoleDescriptor(
428454
new FieldPermissionsDefinition(indicesPrivileges.getGrantedFields(), indicesPrivileges.getDeniedFields())
429455
),
430456
indicesPrivileges.getQuery() == null ? null : Collections.singleton(indicesPrivileges.getQuery()),
431-
IndexPrivilege.get(Set.of(indicesPrivileges.getPrivileges())),
457+
IndexPrivilege.resolveBySelectorAccess(Set.of(indicesPrivileges.getPrivileges())),
432458
indicesPrivileges.allowRestrictedIndices(),
433459
indicesPrivileges.getIndices()
434460
);

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ConfigurableClusterPrivileges.java

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -414,13 +414,18 @@ public ManageRolesPrivilege(List<ManageRolesIndexPermissionGroup> manageRolesInd
414414
this.requestPredicateSupplier = (restrictedIndices) -> {
415415
IndicesPermission.Builder indicesPermissionBuilder = new IndicesPermission.Builder(restrictedIndices);
416416
for (ManageRolesIndexPermissionGroup indexPatternPrivilege : manageRolesIndexPermissionGroups) {
417-
indicesPermissionBuilder.addGroup(
418-
IndexPrivilege.get(Set.of(indexPatternPrivilege.privileges())),
419-
FieldPermissions.DEFAULT,
420-
null,
421-
false,
422-
indexPatternPrivilege.indexPatterns()
423-
);
417+
Set<IndexPrivilege> privileges = IndexPrivilege.resolveBySelectorAccess(Set.of(indexPatternPrivilege.privileges()));
418+
assert privileges.stream().allMatch(p -> p.getSelectorPredicate() != IndexComponentSelectorPredicate.FAILURES)
419+
: "not support for failures store access yet";
420+
for (IndexPrivilege indexPrivilege : privileges) {
421+
indicesPermissionBuilder.addGroup(
422+
indexPrivilege,
423+
FieldPermissions.DEFAULT,
424+
null,
425+
false,
426+
indexPatternPrivilege.indexPatterns()
427+
);
428+
}
424429
}
425430
final IndicesPermission indicesPermission = indicesPermissionBuilder.build();
426431

@@ -555,6 +560,15 @@ public static ManageRolesPrivilege parse(XContentParser parser) throws IOExcepti
555560
if (indexPrivilege.privileges == null || indexPrivilege.privileges.length == 0) {
556561
throw new IllegalArgumentException("Indices privileges must define at least one privilege");
557562
}
563+
for (String privilege : indexPrivilege.privileges) {
564+
IndexPrivilege namedPrivilege = IndexPrivilege.getNamedOrNull(privilege);
565+
if (namedPrivilege != null && namedPrivilege.getSelectorPredicate() == IndexComponentSelectorPredicate.FAILURES) {
566+
throw new IllegalArgumentException(
567+
"Failure store related privileges are not supported as targets of manage roles but found [" + privilege + "]"
568+
);
569+
}
570+
}
571+
558572
}
559573
return new ManageRolesPrivilege(indexPrivileges);
560574
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.core.security.authz.privilege;
9+
10+
import org.elasticsearch.action.support.IndexComponentSelector;
11+
import org.elasticsearch.core.Predicates;
12+
13+
import java.util.Set;
14+
import java.util.function.Predicate;
15+
16+
/**
17+
* A predicate to capture role access by {@link IndexComponentSelector}.
18+
* This is assigned to each {@link org.elasticsearch.xpack.core.security.authz.permission.IndicesPermission.Group} during role building.
19+
* See also {@link org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege#resolveBySelectorAccess(Set)}.
20+
*/
21+
public record IndexComponentSelectorPredicate(Set<String> names, Predicate<IndexComponentSelector> predicate)
22+
implements
23+
Predicate<IndexComponentSelector> {
24+
IndexComponentSelectorPredicate(String name, Predicate<IndexComponentSelector> predicate) {
25+
this(Set.of(name), predicate);
26+
}
27+
28+
public static final IndexComponentSelectorPredicate ALL = new IndexComponentSelectorPredicate("all", Predicates.always());
29+
public static final IndexComponentSelectorPredicate DATA = new IndexComponentSelectorPredicate(
30+
"data",
31+
IndexComponentSelector.DATA::equals
32+
);
33+
public static final IndexComponentSelectorPredicate FAILURES = new IndexComponentSelectorPredicate(
34+
"failures",
35+
IndexComponentSelector.FAILURES::equals
36+
);
37+
38+
@Override
39+
public boolean test(IndexComponentSelector selector) {
40+
return predicate.test(selector);
41+
}
42+
}

0 commit comments

Comments
 (0)