Skip to content

Commit c2d7b55

Browse files
committed
Introduced the next gen action privilege evaluation
Signed-off-by: Nils Bandener <[email protected]>
1 parent fed1804 commit c2d7b55

File tree

64 files changed

+5200
-1499
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+5200
-1499
lines changed

src/integrationTest/java/org/opensearch/security/privileges/ClusterStateMetadataDependentPrivilegesTest.java

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,10 @@
1717

1818
import org.opensearch.cluster.ClusterState;
1919
import org.opensearch.cluster.metadata.Metadata;
20-
import org.opensearch.cluster.service.ClusterService;
2120
import org.opensearch.common.settings.Settings;
2221
import org.opensearch.node.Node;
2322
import org.opensearch.threadpool.ThreadPool;
2423

25-
import org.mockito.Mockito;
26-
import org.mockito.stubbing.Answer;
27-
2824
public class ClusterStateMetadataDependentPrivilegesTest {
2925

3026
@Test
@@ -33,10 +29,8 @@ public void simpleUpdate() {
3329
try {
3430
ConcreteTestSubject subject = new ConcreteTestSubject();
3531
ClusterState clusterState = clusterState(metadata(1));
36-
ClusterService clusterService = Mockito.mock(ClusterService.class);
37-
Mockito.when(clusterService.state()).thenReturn(clusterState);
3832

39-
subject.updateClusterStateMetadataAsync(clusterService, threadPool);
33+
subject.updateClusterStateMetadataAsync(() -> clusterState, threadPool);
4034
Awaitility.await().until(() -> subject.getCurrentlyUsedMetadataVersion() == 1);
4135
subject.shutdown();
4236
} finally {
@@ -50,14 +44,12 @@ public void frequentUpdates() throws Exception {
5044
try {
5145
ConcreteTestSubject subject = new ConcreteTestSubject();
5246
AtomicReference<ClusterState> clusterStateReference = new AtomicReference<>(clusterState(metadata(1)));
53-
ClusterService clusterService = Mockito.mock(ClusterService.class);
54-
Mockito.when(clusterService.state()).thenAnswer((Answer<ClusterState>) invocationOnMock -> clusterStateReference.get());
55-
subject.updateClusterStateMetadataAsync(clusterService, threadPool);
56-
subject.updateClusterStateMetadataAsync(clusterService, threadPool);
47+
subject.updateClusterStateMetadataAsync(clusterStateReference::get, threadPool);
48+
subject.updateClusterStateMetadataAsync(clusterStateReference::get, threadPool);
5749

5850
for (int i = 2; i <= 100; i++) {
5951
clusterStateReference.set(clusterState(metadata(i)));
60-
subject.updateClusterStateMetadataAsync(clusterService, threadPool);
52+
subject.updateClusterStateMetadataAsync(clusterStateReference::get, threadPool);
6153
Thread.sleep(10);
6254
}
6355

@@ -74,9 +66,7 @@ public void shutdown() {
7466
try {
7567
ConcreteTestSubject subject = new ConcreteTestSubject();
7668
ClusterState clusterState = clusterState(metadata(1));
77-
ClusterService clusterService = Mockito.mock(ClusterService.class);
78-
Mockito.when(clusterService.state()).thenReturn(clusterState);
79-
subject.updateClusterStateMetadataAsync(clusterService, threadPool);
69+
subject.updateClusterStateMetadataAsync(() -> clusterState, threadPool);
8070
subject.shutdown();
8171
} finally {
8272
threadPool.shutdown();

src/integrationTest/java/org/opensearch/security/privileges/IndexPatternTest.java

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,11 @@
1313
import java.time.ZonedDateTime;
1414
import java.time.temporal.ChronoField;
1515

16-
import com.google.common.collect.ImmutableMap;
17-
import com.google.common.collect.ImmutableSet;
1816
import org.junit.Test;
1917

2018
import org.opensearch.cluster.ClusterState;
21-
import org.opensearch.cluster.metadata.IndexNameExpressionResolver;
2219
import org.opensearch.cluster.metadata.Metadata;
23-
import org.opensearch.common.settings.Settings;
24-
import org.opensearch.common.util.concurrent.ThreadContext;
25-
import org.opensearch.security.resolver.IndexResolverReplacer;
2620
import org.opensearch.security.support.WildcardMatcher;
27-
import org.opensearch.security.user.User;
2821
import org.opensearch.security.util.MockPrivilegeEvaluationContextBuilder;
2922

3023
import static org.opensearch.security.util.MockIndexMetadataBuilder.indices;
@@ -234,9 +227,9 @@ public void equals() {
234227

235228
private static PrivilegesEvaluationContext ctx() {
236229
return MockPrivilegeEvaluationContextBuilder.ctx()
237-
.action("indices:action/test")
238-
.attr("attrs.a11", "a11")
239-
.attr("attrs.year", "year")
240-
.get();
230+
.action("indices:action/test")
231+
.attr("attrs.a11", "a11")
232+
.attr("attrs.year", "year")
233+
.get();
241234
}
242235
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*
8+
* Modifications Copyright OpenSearch Contributors. See
9+
* GitHub history for details.
10+
*/
11+
12+
package org.opensearch.security.privileges;
13+
14+
import java.util.Arrays;
15+
import java.util.Collection;
16+
import java.util.Collections;
17+
import java.util.Map;
18+
19+
import org.junit.Test;
20+
import org.junit.runner.RunWith;
21+
import org.junit.runners.Parameterized;
22+
import org.junit.runners.Suite;
23+
24+
import org.opensearch.action.ActionRequest;
25+
import org.opensearch.action.IndicesRequest;
26+
import org.opensearch.action.OriginalIndices;
27+
import org.opensearch.action.index.IndexRequest;
28+
import org.opensearch.action.search.SearchRequest;
29+
import org.opensearch.action.support.IndicesOptions;
30+
import org.opensearch.cluster.ClusterState;
31+
import org.opensearch.cluster.metadata.IndexNameExpressionResolver;
32+
import org.opensearch.cluster.metadata.Metadata;
33+
import org.opensearch.cluster.metadata.ResolvedIndices;
34+
import org.opensearch.common.settings.Settings;
35+
import org.opensearch.common.util.concurrent.ThreadContext;
36+
import org.opensearch.security.util.MockIndexMetadataBuilder;
37+
38+
import static org.junit.Assert.assertArrayEquals;
39+
import static org.junit.Assert.assertEquals;
40+
import static org.junit.Assert.assertFalse;
41+
import static org.junit.Assert.assertTrue;
42+
43+
@RunWith(Suite.class)
44+
@Suite.SuiteClasses({ IndexRequestModifierTest.SetLocalIndices.class, IndexRequestModifierTest.SetLocalIndicesToEmpty.class })
45+
public class IndexRequestModifierTest {
46+
47+
static final IndexNameExpressionResolver indexNameExpressionResolver = new IndexNameExpressionResolver(
48+
new ThreadContext(Settings.EMPTY)
49+
);
50+
static final Metadata metadata = MockIndexMetadataBuilder.indices("index", "index1", "index2", "index3").build();
51+
final static ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE).metadata(metadata).build();
52+
static final IndicesRequestModifier subject = new IndicesRequestModifier();
53+
54+
public static class SetLocalIndices {
55+
@Test
56+
public void basic() {
57+
ResolvedIndices resolvedIndices = ResolvedIndices.of("index1");
58+
SearchRequest request = new SearchRequest("index1", "index2", "index3");
59+
60+
boolean success = subject.setLocalIndices(request, resolvedIndices, Collections.singletonList("index1"));
61+
assertTrue(success);
62+
assertArrayEquals(new String[] { "index1" }, request.indices());
63+
}
64+
65+
@Test
66+
public void withRemote() {
67+
ResolvedIndices resolvedIndices = ResolvedIndices.of("index1")
68+
.withRemoteIndices(
69+
Map.of("remote", new OriginalIndices(new String[] { "index_remote" }, IndicesOptions.LENIENT_EXPAND_OPEN))
70+
);
71+
SearchRequest request = new SearchRequest("index1", "index2", "index3", "remote:index_remote");
72+
73+
boolean success = subject.setLocalIndices(request, resolvedIndices, Collections.singletonList("index1"));
74+
assertTrue(success);
75+
assertArrayEquals(new String[] { "index1", "remote:index_remote" }, request.indices());
76+
}
77+
78+
@Test
79+
public void empty() {
80+
ResolvedIndices resolvedIndices = ResolvedIndices.of("index1");
81+
SearchRequest request = new SearchRequest("index1", "index2", "index3");
82+
83+
boolean success = subject.setLocalIndices(request, resolvedIndices, Collections.emptyList());
84+
assertTrue(success);
85+
String[] finalResolvedIndices = indexNameExpressionResolver.concreteIndexNames(clusterState, request);
86+
assertArrayEquals(new String[0], finalResolvedIndices);
87+
}
88+
89+
@Test
90+
public void unsupportedType() {
91+
ResolvedIndices resolvedIndices = ResolvedIndices.of("index1");
92+
IndexRequest request = new IndexRequest("index1");
93+
94+
boolean success = subject.setLocalIndices(request, resolvedIndices, Collections.singletonList("index1"));
95+
assertFalse(success);
96+
}
97+
}
98+
99+
@RunWith(Parameterized.class)
100+
public static class SetLocalIndicesToEmpty {
101+
102+
String description;
103+
IndicesRequest request;
104+
105+
@Test
106+
public void setLocalIndicesToEmpty() {
107+
108+
ResolvedIndices resolvedIndices = ResolvedIndices.of("index");
109+
110+
if (Arrays.asList(request.indices()).contains("remote:index")) {
111+
resolvedIndices = resolvedIndices.withRemoteIndices(
112+
Map.of("remote", new OriginalIndices(new String[] { "index" }, request.indicesOptions()))
113+
);
114+
}
115+
116+
boolean success = subject.setLocalIndicesToEmpty((ActionRequest) request, resolvedIndices);
117+
118+
if (!(request instanceof IndicesRequest.Replaceable)) {
119+
assertFalse(success);
120+
} else if (!request.indicesOptions().allowNoIndices()) {
121+
assertFalse(success);
122+
} else {
123+
assertTrue(success);
124+
125+
String[] finalResolvedIndices = indexNameExpressionResolver.concreteIndexNames(clusterState, request);
126+
127+
assertEquals("Resolved to empty indices: " + Arrays.asList(finalResolvedIndices), 0, finalResolvedIndices.length);
128+
}
129+
}
130+
131+
@Parameterized.Parameters(name = "{0}")
132+
public static Collection<Object[]> params() {
133+
return Arrays.asList(
134+
new Object[] { "lenient expand open", new SearchRequest("index").indicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN) },
135+
new Object[] {
136+
"lenient expand open/closed",
137+
new SearchRequest("index").indicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN_CLOSED) },
138+
new Object[] {
139+
"lenient expand open/closed/hidden",
140+
new SearchRequest("index").indicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN_CLOSED_HIDDEN) },
141+
new Object[] {
142+
"allow no indices",
143+
new SearchRequest("index").indicesOptions(IndicesOptions.fromOptions(false, true, false, false)) },
144+
new Object[] {
145+
"ignore unavailable",
146+
new SearchRequest("index").indicesOptions(IndicesOptions.fromOptions(true, false, false, false)) },
147+
new Object[] {
148+
"strict single index",
149+
new SearchRequest("index").indicesOptions(IndicesOptions.STRICT_SINGLE_INDEX_NO_EXPAND_FORBID_CLOSED) },
150+
new Object[] {
151+
"with remote index",
152+
new SearchRequest("index", "remote:index").indicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN) },
153+
new Object[] { "not implementing IndicesRequest.Replaceable", new IndexRequest("index") }
154+
);
155+
156+
}
157+
158+
public SetLocalIndicesToEmpty(String description, IndicesRequest request) {
159+
this.description = description;
160+
this.request = request;
161+
}
162+
}
163+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*
8+
* Modifications Copyright OpenSearch Contributors. See
9+
* GitHub history for details.
10+
*/
11+
12+
package org.opensearch.security.privileges;
13+
14+
import java.util.Set;
15+
16+
import org.junit.Test;
17+
18+
import org.opensearch.action.admin.cluster.stats.ClusterStatsRequest;
19+
import org.opensearch.action.search.SearchRequest;
20+
import org.opensearch.action.support.ActionRequestMetadata;
21+
import org.opensearch.cluster.ClusterState;
22+
import org.opensearch.cluster.metadata.IndexNameExpressionResolver;
23+
import org.opensearch.cluster.metadata.Metadata;
24+
import org.opensearch.cluster.metadata.OptionallyResolvedIndices;
25+
import org.opensearch.cluster.metadata.ResolvedIndices;
26+
import org.opensearch.common.settings.Settings;
27+
import org.opensearch.common.util.concurrent.ThreadContext;
28+
import org.opensearch.security.util.MockIndexMetadataBuilder;
29+
import org.opensearch.security.util.MockPrivilegeEvaluationContextBuilder;
30+
31+
import static org.junit.Assert.assertEquals;
32+
import static org.junit.Assert.assertFalse;
33+
import static org.junit.Assert.fail;
34+
import static org.mockito.Mockito.mock;
35+
import static org.mockito.Mockito.when;
36+
37+
public class IndicesRequestResolverTest {
38+
39+
static final Metadata metadata = MockIndexMetadataBuilder.indices("index_a1", "index_a2", "index_b1", "index_b2").build();
40+
final static ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE).metadata(metadata).build();
41+
static final IndicesRequestResolver subject = new IndicesRequestResolver(
42+
new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY))
43+
);
44+
45+
@Test
46+
public void resolve_normal() {
47+
SearchRequest request = new SearchRequest("index1");
48+
ActionRequestMetadata<SearchRequest, ?> actionRequestMetadata = mock();
49+
ResolvedIndices resolvedIndices = ResolvedIndices.of("index1");
50+
when(actionRequestMetadata.resolvedIndices()).thenReturn(resolvedIndices);
51+
52+
OptionallyResolvedIndices returnedResolvedIndices = subject.resolve(request, actionRequestMetadata, () -> clusterState);
53+
assertEquals(resolvedIndices, returnedResolvedIndices);
54+
}
55+
56+
@Test
57+
public void resolve_fallback() {
58+
SearchRequest request = new SearchRequest("index1");
59+
ActionRequestMetadata<SearchRequest, ?> actionRequestMetadata = mock();
60+
when(actionRequestMetadata.resolvedIndices()).thenReturn(OptionallyResolvedIndices.unknown());
61+
62+
OptionallyResolvedIndices returnedResolvedIndices = subject.resolve(request, actionRequestMetadata, () -> clusterState);
63+
if (returnedResolvedIndices instanceof ResolvedIndices castReturnedResovledIndices) {
64+
assertEquals(Set.of("index1"), castReturnedResovledIndices.local().names());
65+
} else {
66+
fail("Expected ResolvedIndices, got: " + returnedResolvedIndices);
67+
}
68+
}
69+
70+
@Test
71+
public void resolve_fallbackUnsupported() {
72+
ClusterStatsRequest request = new ClusterStatsRequest();
73+
ActionRequestMetadata<SearchRequest, ?> actionRequestMetadata = mock();
74+
when(actionRequestMetadata.resolvedIndices()).thenReturn(OptionallyResolvedIndices.unknown());
75+
76+
OptionallyResolvedIndices returnedResolvedIndices = subject.resolve(request, actionRequestMetadata, () -> clusterState);
77+
assertFalse(returnedResolvedIndices instanceof ResolvedIndices);
78+
}
79+
80+
@Test
81+
public void resolve_withPrivilegesEvaluationContext() {
82+
SearchRequest request = new SearchRequest("index_a*");
83+
ActionRequestMetadata<SearchRequest, ?> actionRequestMetadata = mock();
84+
when(actionRequestMetadata.resolvedIndices()).thenReturn(OptionallyResolvedIndices.unknown());
85+
PrivilegesEvaluationContext context = MockPrivilegeEvaluationContextBuilder.ctx().clusterState(clusterState).get();
86+
87+
OptionallyResolvedIndices returnedResolvedIndices = subject.resolve(request, actionRequestMetadata, context);
88+
assertEquals(Set.of("index_a1", "index_a2"), returnedResolvedIndices.local().names(clusterState));
89+
}
90+
}

src/integrationTest/java/org/opensearch/security/privileges/RestEndpointPermissionTests.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.opensearch.security.dlic.rest.api.Endpoint;
4848
import org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.PermissionBuilder;
4949
import org.opensearch.security.privileges.actionlevel.RoleBasedActionPrivileges;
50+
import org.opensearch.security.privileges.actionlevel.RuntimeOptimizedActionPrivileges;
5051
import org.opensearch.security.securityconf.FlattenedActionGroups;
5152
import org.opensearch.security.securityconf.impl.CType;
5253
import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration;
@@ -117,7 +118,13 @@ static String[] allRestApiPermissions() {
117118
final RoleBasedActionPrivileges actionPrivileges;
118119

119120
public RestEndpointPermissionTests() throws IOException {
120-
this.actionPrivileges = new RoleBasedActionPrivileges(createRolesConfig(), FlattenedActionGroups.EMPTY, Settings.EMPTY);
121+
this.actionPrivileges = new RoleBasedActionPrivileges(
122+
createRolesConfig(),
123+
FlattenedActionGroups.EMPTY,
124+
RuntimeOptimizedActionPrivileges.SpecialIndexProtection.NONE,
125+
Settings.EMPTY,
126+
false
127+
);
121128
}
122129

123130
@Test

0 commit comments

Comments
 (0)