Skip to content

Commit 20061fc

Browse files
committed
[Failure Store] manage privileges grant data and failures access
1 parent fd2cc97 commit 20061fc

File tree

4 files changed

+79
-23
lines changed

4 files changed

+79
-23
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ public record IndexComponentSelectorPredicate(Set<String> names, Predicate<Index
3434
"failures",
3535
IndexComponentSelector.FAILURES::equals
3636
);
37+
public static final IndexComponentSelectorPredicate DATA_AND_FAILURES = new IndexComponentSelectorPredicate(
38+
Set.of("data", "failures"),
39+
DATA.predicate.or(FAILURES.predicate)
40+
);
3741

3842
@Override
3943
public boolean test(IndexComponentSelector selector) {

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,11 @@ public final class IndexPrivilege extends Privilege {
195195
public static final IndexPrivilege WRITE = new IndexPrivilege("write", WRITE_AUTOMATON);
196196
public static final IndexPrivilege CREATE_DOC = new IndexPrivilege("create_doc", CREATE_DOC_AUTOMATON);
197197
public static final IndexPrivilege MONITOR = new IndexPrivilege("monitor", MONITOR_AUTOMATON);
198-
public static final IndexPrivilege MANAGE = new IndexPrivilege("manage", MANAGE_AUTOMATON);
198+
public static final IndexPrivilege MANAGE = new IndexPrivilege(
199+
"manage",
200+
MANAGE_AUTOMATON,
201+
IndexComponentSelectorPredicate.DATA_AND_FAILURES
202+
);
199203
public static final IndexPrivilege DELETE_INDEX = new IndexPrivilege("delete_index", DELETE_INDEX_AUTOMATON);
200204
public static final IndexPrivilege CREATE_INDEX = new IndexPrivilege("create_index", CREATE_INDEX_AUTOMATON);
201205
public static final IndexPrivilege VIEW_METADATA = new IndexPrivilege("view_index_metadata", VIEW_METADATA_AUTOMATON);
@@ -204,7 +208,8 @@ public final class IndexPrivilege extends Privilege {
204208
public static final IndexPrivilege MANAGE_ILM = new IndexPrivilege("manage_ilm", MANAGE_ILM_AUTOMATON);
205209
public static final IndexPrivilege MANAGE_DATA_STREAM_LIFECYCLE = new IndexPrivilege(
206210
"manage_data_stream_lifecycle",
207-
MANAGE_DATA_STREAM_LIFECYCLE_AUTOMATON
211+
MANAGE_DATA_STREAM_LIFECYCLE_AUTOMATON,
212+
IndexComponentSelectorPredicate.DATA_AND_FAILURES
208213
);
209214
public static final IndexPrivilege MAINTENANCE = new IndexPrivilege("maintenance", MAINTENANCE_AUTOMATON);
210215
public static final IndexPrivilege AUTO_CONFIGURE = new IndexPrivilege("auto_configure", AUTO_CONFIGURE_AUTOMATON);
@@ -364,6 +369,7 @@ private static Set<IndexPrivilege> resolve(Set<String> name) {
364369
final Set<IndexPrivilege> allSelectorAccessPrivileges = new HashSet<>();
365370
final Set<IndexPrivilege> dataSelectorAccessPrivileges = new HashSet<>();
366371
final Set<IndexPrivilege> failuresSelectorAccessPrivileges = new HashSet<>();
372+
final Set<IndexPrivilege> dataAndFailuresSelectorAccessPrivileges = new HashSet<>();
367373

368374
boolean containsAllAccessPrivilege = name.stream().anyMatch(n -> getNamedOrNull(n) == ALL);
369375
for (String part : name) {
@@ -383,6 +389,8 @@ private static Set<IndexPrivilege> resolve(Set<String> name) {
383389
dataSelectorAccessPrivileges.add(indexPrivilege);
384390
} else if (indexPrivilege.selectorPredicate == IndexComponentSelectorPredicate.FAILURES) {
385391
failuresSelectorAccessPrivileges.add(indexPrivilege);
392+
} else if (indexPrivilege.selectorPredicate == IndexComponentSelectorPredicate.DATA_AND_FAILURES) {
393+
dataAndFailuresSelectorAccessPrivileges.add(indexPrivilege);
386394
} else {
387395
String errorMessage = "unexpected selector [" + indexPrivilege.selectorPredicate + "]";
388396
assert false : errorMessage;
@@ -406,6 +414,7 @@ private static Set<IndexPrivilege> resolve(Set<String> name) {
406414
allSelectorAccessPrivileges,
407415
dataSelectorAccessPrivileges,
408416
failuresSelectorAccessPrivileges,
417+
dataAndFailuresSelectorAccessPrivileges,
409418
actions
410419
);
411420
assertNamesMatch(name, combined);
@@ -416,11 +425,13 @@ private static Set<IndexPrivilege> combineIndexPrivileges(
416425
Set<IndexPrivilege> allSelectorAccessPrivileges,
417426
Set<IndexPrivilege> dataSelectorAccessPrivileges,
418427
Set<IndexPrivilege> failuresSelectorAccessPrivileges,
428+
Set<IndexPrivilege> dataAndFailuresSelectorAccessPrivileges,
419429
Set<String> actions
420430
) {
421431
assert false == allSelectorAccessPrivileges.isEmpty()
422432
|| false == dataSelectorAccessPrivileges.isEmpty()
423433
|| false == failuresSelectorAccessPrivileges.isEmpty()
434+
|| false == dataAndFailuresSelectorAccessPrivileges.isEmpty()
424435
|| false == actions.isEmpty() : "at least one of the privilege sets or actions must be non-empty";
425436

426437
if (false == allSelectorAccessPrivileges.isEmpty()) {
@@ -434,6 +445,9 @@ private static Set<IndexPrivilege> combineIndexPrivileges(
434445
if (false == dataSelectorAccessPrivileges.isEmpty() || false == actions.isEmpty()) {
435446
combined.add(union(dataSelectorAccessPrivileges, actions, IndexComponentSelectorPredicate.DATA));
436447
}
448+
if (false == dataAndFailuresSelectorAccessPrivileges.isEmpty()) {
449+
combined.add(union(dataAndFailuresSelectorAccessPrivileges, Set.of(), IndexComponentSelectorPredicate.DATA_AND_FAILURES));
450+
}
437451
if (false == failuresSelectorAccessPrivileges.isEmpty()) {
438452
combined.add(union(failuresSelectorAccessPrivileges, Set.of(), IndexComponentSelectorPredicate.FAILURES));
439453
}

x-pack/plugin/security/qa/security-trial/src/javaRestTest/java/org/elasticsearch/xpack/security/failurestore/FailureStoreSecurityRestIT.java

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -124,14 +124,10 @@ public void testGetUserPrivileges() throws IOException {
124124
"global": [],
125125
"indices": [{
126126
"names": ["*"],
127-
"privileges": ["read"],
127+
"privileges": ["read", "read_failure_store"],
128128
"allow_restricted_indices": false
129-
},
130-
{
131-
"names": ["*"],
132-
"privileges": ["read_failure_store"],
133-
"allow_restricted_indices": false
134-
}],
129+
}
130+
],
135131
"applications": [],
136132
"run_as": []
137133
}""");
@@ -208,14 +204,10 @@ public void testGetUserPrivileges() throws IOException {
208204
"indices": [
209205
{
210206
"names": ["*"],
211-
"privileges": ["read", "write"],
212-
"allow_restricted_indices": false
213-
},
214-
{
215-
"names": ["*"],
216-
"privileges": ["manage_failure_store", "read_failure_store"],
207+
"privileges": ["manage_failure_store", "read", "read_failure_store", "write"],
217208
"allow_restricted_indices": false
218-
}],
209+
}
210+
],
219211
"applications": [],
220212
"run_as": []
221213
}""");
@@ -1761,7 +1753,7 @@ public void testFailureStoreAccess() throws Exception {
17611753
}
17621754
}
17631755

1764-
public void testWriteOperations() throws IOException {
1756+
public void testWriteAndManageOperations() throws IOException {
17651757
setupDataStream();
17661758
Tuple<String, String> backingIndices = getSingleDataAndFailureIndices("test1");
17671759
String dataIndexName = backingIndices.v1();
@@ -1797,15 +1789,28 @@ public void testWriteOperations() throws IOException {
17971789
}
17981790
""");
17991791

1800-
// user with manage access to data stream does NOT get direct access to failure index
1801-
expectThrows(() -> deleteIndex(MANAGE_ACCESS, failureIndexName), 403);
1792+
// explain lifecycle API with and without failures selector is granted by manage
1793+
assertOK(performRequest(MANAGE_ACCESS, new Request("GET", "test1/_lifecycle/explain")));
1794+
assertOK(performRequest(MANAGE_ACCESS, new Request("GET", "test1::failures/_lifecycle/explain")));
1795+
assertOK(performRequest(MANAGE_ACCESS, new Request("GET", failureIndexName + "/_lifecycle/explain")));
1796+
assertOK(performRequest(MANAGE_ACCESS, new Request("GET", dataIndexName + "/_lifecycle/explain")));
1797+
1798+
// explain lifecycle API is granted by manage_failure_store only for failures selector
1799+
expectThrows(() -> performRequest(MANAGE_FAILURE_STORE_ACCESS, new Request("GET", "test1/_lifecycle/explain")), 403);
1800+
assertOK(performRequest(MANAGE_FAILURE_STORE_ACCESS, new Request("GET", "test1::failures/_lifecycle/explain")));
1801+
assertOK(performRequest(MANAGE_FAILURE_STORE_ACCESS, new Request("GET", failureIndexName + "/_lifecycle/explain")));
1802+
expectThrows(() -> performRequest(MANAGE_FAILURE_STORE_ACCESS, new Request("GET", dataIndexName + "/_lifecycle/explain")), 403);
1803+
1804+
// user with manage access to data stream can delete failure index because manage grants access to both data and failures
1805+
expectThrows(() -> deleteIndex(MANAGE_ACCESS, failureIndexName), 400);
18021806
expectThrows(() -> deleteIndex(MANAGE_ACCESS, dataIndexName), 400);
1803-
// manage_failure_store user COULD delete failure index (not valid because it's a write index, but allow security-wise)
1804-
expectThrows(() -> deleteIndex(MANAGE_FAILURE_STORE_ACCESS, dataIndexName), 403);
1807+
// manage_failure_store user COULD delete failure index (not valid because it's a write index, but allowed security-wise)
18051808
expectThrows(() -> deleteIndex(MANAGE_FAILURE_STORE_ACCESS, failureIndexName), 400);
1809+
expectThrows(() -> deleteIndex(MANAGE_FAILURE_STORE_ACCESS, dataIndexName), 403);
18061810
expectThrows(() -> deleteDataStream(MANAGE_FAILURE_STORE_ACCESS, dataIndexName), 403);
18071811

18081812
expectThrows(() -> deleteDataStream(MANAGE_FAILURE_STORE_ACCESS, "test1"), 403);
1813+
// selectors aren't supported for deletes so we get a 403
18091814
expectThrows(() -> deleteDataStream(MANAGE_FAILURE_STORE_ACCESS, "test1::failures"), 403);
18101815

18111816
// manage user can delete data stream

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

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.elasticsearch.common.settings.Settings;
4444
import org.elasticsearch.common.util.CachedSupplier;
4545
import org.elasticsearch.common.util.set.Sets;
46+
import org.elasticsearch.core.Tuple;
4647
import org.elasticsearch.index.Index;
4748
import org.elasticsearch.index.shard.ShardId;
4849
import org.elasticsearch.transport.TransportActionProxy;
@@ -101,6 +102,7 @@
101102
import java.util.Collections;
102103
import java.util.HashMap;
103104
import java.util.HashSet;
105+
import java.util.LinkedHashMap;
104106
import java.util.LinkedHashSet;
105107
import java.util.List;
106108
import java.util.Map;
@@ -792,7 +794,7 @@ static GetUserPrivilegesResponse buildUserPrivilegesResponseObject(Role userRole
792794
}
793795
}
794796

795-
final Set<GetUserPrivilegesResponse.Indices> indices = new LinkedHashSet<>();
797+
final LinkedHashSet<GetUserPrivilegesResponse.Indices> indices = new LinkedHashSet<>();
796798
for (IndicesPermission.Group group : userRole.indices().groups()) {
797799
indices.add(toIndices(group));
798800
}
@@ -833,14 +835,45 @@ static GetUserPrivilegesResponse buildUserPrivilegesResponseObject(Role userRole
833835
return new GetUserPrivilegesResponse(
834836
cluster,
835837
conditionalCluster,
836-
indices,
838+
combineIndices(indices),
837839
application,
838840
runAs,
839841
remoteIndices,
840842
userRole.remoteCluster()
841843
);
842844
}
843845

846+
private static LinkedHashSet<GetUserPrivilegesResponse.Indices> combineIndices(
847+
LinkedHashSet<GetUserPrivilegesResponse.Indices> indices
848+
) {
849+
final LinkedHashMap<Tuple<Boolean, Set<String>>, GetUserPrivilegesResponse.Indices> combinedIndices = new LinkedHashMap<>();
850+
for (GetUserPrivilegesResponse.Indices index : indices) {
851+
final GetUserPrivilegesResponse.Indices existing = combinedIndices.get(
852+
new Tuple<>(index.allowRestrictedIndices(), index.getIndices())
853+
);
854+
if (existing == null) {
855+
combinedIndices.put(new Tuple<>(index.allowRestrictedIndices(), index.getIndices()), index);
856+
} else {
857+
Set<String> combined = new LinkedHashSet<>(existing.getPrivileges());
858+
combined.addAll(index.getPrivileges());
859+
assert existing.allowRestrictedIndices() == index.allowRestrictedIndices();
860+
assert Objects.equals(existing.getFieldSecurity(), index.getFieldSecurity());
861+
assert Objects.equals(existing.getQueries(), index.getQueries());
862+
combinedIndices.put(
863+
new Tuple<>(index.allowRestrictedIndices(), index.getIndices()),
864+
new GetUserPrivilegesResponse.Indices(
865+
index.getIndices(),
866+
combined,
867+
index.getFieldSecurity(),
868+
index.getQueries(),
869+
index.allowRestrictedIndices()
870+
)
871+
);
872+
}
873+
}
874+
return new LinkedHashSet<>(combinedIndices.values());
875+
}
876+
844877
private static GetUserPrivilegesResponse.Indices toIndices(final IndicesPermission.Group group) {
845878
final Set<BytesReference> queries = group.getQuery() == null ? Collections.emptySet() : group.getQuery();
846879
final Set<FieldPermissionsDefinition.FieldGrantExcludeGroup> fieldSecurity = getFieldGrantExcludeGroups(group);

0 commit comments

Comments
 (0)