Skip to content

Commit bb54383

Browse files
committed
Added tests for snapshots
Signed-off-by: Nils Bandener <[email protected]>
1 parent f530aee commit bb54383

File tree

2 files changed

+401
-0
lines changed

2 files changed

+401
-0
lines changed
Lines changed: 379 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,379 @@
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.int_tests;
13+
14+
import java.util.ArrayList;
15+
import java.util.Collection;
16+
import java.util.List;
17+
18+
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
19+
import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope;
20+
import com.google.common.collect.ImmutableList;
21+
import org.apache.hc.core5.http.HttpEntity;
22+
import org.junit.After;
23+
import org.junit.AfterClass;
24+
import org.junit.Test;
25+
import org.junit.runner.RunWith;
26+
27+
import org.opensearch.action.admin.indices.refresh.RefreshRequest;
28+
import org.opensearch.test.framework.TestIndex;
29+
import org.opensearch.test.framework.TestIndexOrAliasOrDatastream;
30+
import org.opensearch.test.framework.TestSecurityConfig;
31+
import org.opensearch.test.framework.cluster.LocalCluster;
32+
import org.opensearch.test.framework.cluster.TestRestClient;
33+
34+
import static org.hamcrest.MatcherAssert.assertThat;
35+
import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL;
36+
import static org.opensearch.test.framework.cluster.TestRestClient.json;
37+
import static org.opensearch.test.framework.matcher.IndexApiResponseMatchers.OnResponseIndexMatcher.containsExactly;
38+
import static org.opensearch.test.framework.matcher.IndexApiResponseMatchers.OnUserIndexMatcher.limitedTo;
39+
import static org.opensearch.test.framework.matcher.IndexApiResponseMatchers.OnUserIndexMatcher.limitedToNone;
40+
import static org.opensearch.test.framework.matcher.IndexApiResponseMatchers.OnUserIndexMatcher.unlimitedIncludingOpenSearchSecurityIndex;
41+
import static org.opensearch.test.framework.matcher.RestMatchers.isForbidden;
42+
import static org.opensearch.test.framework.matcher.RestMatchers.isOk;
43+
44+
@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class)
45+
@ThreadLeakScope(ThreadLeakScope.Scope.NONE)
46+
public class SnapshotAuthorizationIntTests {
47+
static final TestIndex index_a1 = TestIndex.name("index_ar1").documentCount(10).seed(1).build();
48+
static final TestIndex index_a2 = TestIndex.name("index_ar2").documentCount(11).seed(2).build();
49+
static final TestIndex index_a3 = TestIndex.name("index_ar3").documentCount(12).seed(3).build();
50+
static final TestIndex index_b1 = TestIndex.name("index_br1").documentCount(4).seed(4).build();
51+
static final TestIndex index_b2 = TestIndex.name("index_br2").documentCount(5).seed(5).build();
52+
static final TestIndex index_b3 = TestIndex.name("index_br3").documentCount(6).seed(6).build();
53+
54+
static final TestIndex system_index_plugin_not_existing = TestIndex.name(".system_index_plugin_not_existing")
55+
.hidden()
56+
.documentCount(0)
57+
.build(); // not initially created
58+
59+
static final TestIndex index_awx1 = TestIndex.name("index_awx1").documentCount(10).seed(11).build(); // not initially created
60+
static final TestIndex index_awx2 = TestIndex.name("index_awx2").documentCount(10).seed(12).build(); // not initially created
61+
62+
static final TestIndex index_bwx1 = TestIndex.name("index_bwx1").documentCount(10).seed(13).build(); // not initially created
63+
static final TestIndex index_bwx2 = TestIndex.name("index_bwx2").documentCount(10).seed(14).build(); // not initially created
64+
65+
static TestSecurityConfig.User LIMITED_USER_A = new TestSecurityConfig.User("limited_user_A")//
66+
.description("index_a*")//
67+
.roles(
68+
new TestSecurityConfig.Role("r1")//
69+
.clusterPermissions("cluster_composite_ops", "cluster_monitor", "manage_snapshots")//
70+
.indexPermissions("read", "indices_monitor", "indices:admin/refresh*")
71+
.on("index_a*")//
72+
.indexPermissions("write", "manage")
73+
.on("index_aw*")
74+
)//
75+
.indexMatcher("read", limitedTo(index_a1, index_a2, index_awx1, index_awx2))//
76+
.indexMatcher("write", limitedTo(index_awx1, index_awx2));
77+
78+
static TestSecurityConfig.User LIMITED_USER_B = new TestSecurityConfig.User("limited_user_B")//
79+
.description("index_b*")//
80+
.roles(
81+
new TestSecurityConfig.Role("r1")//
82+
.clusterPermissions("cluster_composite_ops", "cluster_monitor", "manage_snapshots")//
83+
.indexPermissions("read", "indices_monitor", "indices:admin/refresh*")
84+
.on("index_b*")//
85+
.indexPermissions("write", "manage")
86+
.on("index_bw*")
87+
)//
88+
.indexMatcher("read", limitedTo(index_b1, index_b2, index_bwx1, index_bwx2))//
89+
.indexMatcher("write", limitedTo(index_bwx1, index_bwx2));
90+
91+
static TestSecurityConfig.User LIMITED_USER_B_SYSTEM_INDEX = new TestSecurityConfig.User("limited_user_B_system_index")//
92+
.description("index_b*, .system_index_plugin")//
93+
.roles(
94+
new TestSecurityConfig.Role("r1")//
95+
.clusterPermissions("cluster_composite_ops", "cluster_monitor", "manage_snapshots")//
96+
.indexPermissions("read", "indices_monitor", "indices:admin/refresh*")
97+
.on("index_b*")//
98+
.indexPermissions("write", "manage")
99+
.on("index_bw*")
100+
.indexPermissions("read", "indices_monitor", "indices:admin/refresh*", "system:admin/system_index")
101+
.on(".system_index_plugin", ".system_index_plugin_not_existing")
102+
.indexPermissions("write", "manage", "system:admin/system_index")
103+
.on(".system_index_plugin_not_existing")
104+
105+
)//
106+
.indexMatcher("read", limitedTo(index_b1, index_b2, index_bwx1, index_bwx2))//
107+
.indexMatcher("write", limitedTo(index_bwx1, index_bwx2, system_index_plugin_not_existing));
108+
109+
static TestSecurityConfig.User LIMITED_USER_AB = new TestSecurityConfig.User("limited_user_AB")//
110+
.description("index_a*, index_b*")//
111+
.roles(
112+
new TestSecurityConfig.Role("r1")//
113+
.clusterPermissions("cluster_composite_ops", "cluster_monitor", "manage_snapshots")//
114+
.indexPermissions("read", "indices_monitor", "indices:admin/refresh*")
115+
.on("index_a*", "index_b*")//
116+
.indexPermissions("write", "manage")
117+
.on("index_aw*", "index_bw*")
118+
)//
119+
.indexMatcher("read", limitedTo(index_a1, index_a2, index_awx1, index_awx2, index_b1, index_b2, index_bwx1, index_bwx2))//
120+
.indexMatcher("write", limitedTo(index_awx1, index_awx2, index_bwx1, index_bwx2));
121+
122+
static final TestSecurityConfig.User LIMITED_USER_NONE = new TestSecurityConfig.User("limited_user_none")//
123+
.description("no index privileges")//
124+
.roles(
125+
new TestSecurityConfig.Role("r1")//
126+
.clusterPermissions("cluster_composite_ops_ro", "cluster_monitor")
127+
)//
128+
.indexMatcher("read", limitedToNone())//
129+
.indexMatcher("write", limitedToNone());
130+
131+
static final TestSecurityConfig.User UNLIMITED_USER = new TestSecurityConfig.User("unlimited_user")//
132+
.description("unlimited")//
133+
.roles(
134+
new TestSecurityConfig.Role("r1")//
135+
.clusterPermissions("cluster_composite_ops_ro", "cluster_monitor", "manage_snapshots")
136+
.indexPermissions("*")
137+
.on("*")//
138+
139+
)//
140+
.indexMatcher(
141+
"read",
142+
limitedTo(index_a1, index_a2, index_a3, index_awx1, index_awx2, index_b1, index_b2, index_b3, index_bwx1, index_bwx2)
143+
)//
144+
.indexMatcher(
145+
"write",
146+
limitedTo(index_a1, index_a2, index_a3, index_awx1, index_awx2, index_b1, index_b2, index_b3, index_bwx1, index_bwx2)
147+
);
148+
149+
/**
150+
* The SUPER_UNLIMITED_USER authenticates with an admin cert, which will cause all access control code to be skipped.
151+
* This serves as a base for comparison with the default behavior.
152+
*/
153+
static final TestSecurityConfig.User SUPER_UNLIMITED_USER = new TestSecurityConfig.User("super_unlimited_user")//
154+
.description("super unlimited (admin cert)")//
155+
.adminCertUser()//
156+
.indexMatcher("read", unlimitedIncludingOpenSearchSecurityIndex())//
157+
.indexMatcher("write", unlimitedIncludingOpenSearchSecurityIndex());
158+
159+
static final List<TestSecurityConfig.User> USERS = ImmutableList.of(
160+
LIMITED_USER_A,
161+
LIMITED_USER_B,
162+
LIMITED_USER_B_SYSTEM_INDEX,
163+
LIMITED_USER_AB,
164+
LIMITED_USER_NONE,
165+
UNLIMITED_USER,
166+
SUPER_UNLIMITED_USER
167+
);
168+
169+
static LocalCluster.Builder clusterBuilder() {
170+
return new LocalCluster.Builder().singleNode()
171+
.authc(AUTHC_HTTPBASIC_INTERNAL)
172+
.users(USERS)//
173+
.indices(index_a1, index_a2, index_a3, index_b1, index_b2, index_b3)//
174+
.snapshotRepositories("test_repository")
175+
.plugin(IndexAuthorizationReadOnlyIntTests.SystemIndexTestPlugin.class);
176+
}
177+
178+
@AfterClass
179+
public static void stopClusters() {
180+
for (ClusterConfig clusterConfig : ClusterConfig.values()) {
181+
clusterConfig.shutdown();
182+
}
183+
}
184+
185+
final TestSecurityConfig.User user;
186+
final LocalCluster cluster;
187+
final ClusterConfig clusterConfig;
188+
189+
@Test
190+
public void restore_singleIndex() throws Exception {
191+
try (TestRestClient restClient = cluster.getRestClient(user)) {
192+
createInitialTestObjects(index_awx1);
193+
createInitialTestSnapshot("_snapshot/test_repository/single_index_snapshot", json("indices", "index_awx1"));
194+
195+
delete(index_awx1);
196+
197+
TestRestClient.HttpResponse httpResponse = restClient.post(
198+
"_snapshot/test_repository/single_index_snapshot/_restore?wait_for_completion=true"
199+
);
200+
201+
assertThat(
202+
httpResponse,
203+
containsExactly(index_awx1).at("snapshot.indices").butForbiddenIfIncomplete(user.indexMatcher("write"))
204+
);
205+
206+
} finally {
207+
delete("_snapshot/test_repository/single_index_snapshot");
208+
delete(index_awx1);
209+
}
210+
}
211+
212+
@Test
213+
public void restore_singleIndex_rename1() throws Exception {
214+
try (TestRestClient restClient = cluster.getRestClient(user)) {
215+
createInitialTestObjects(index_awx1);
216+
createInitialTestSnapshot("_snapshot/test_repository/single_index_snapshot", json("indices", "index_awx1"));
217+
218+
TestRestClient.HttpResponse httpResponse = restClient.post(
219+
"_snapshot/test_repository/single_index_snapshot/_restore?wait_for_completion=true",
220+
json("rename_pattern", "index_(.+)x1", "rename_replacement", "index_$1x2")
221+
);
222+
223+
assertThat(
224+
httpResponse,
225+
containsExactly(index_awx2).at("snapshot.indices").butForbiddenIfIncomplete(user.indexMatcher("write"))
226+
);
227+
228+
} finally {
229+
delete("_snapshot/test_repository/single_index_snapshot");
230+
delete(index_awx1, index_awx2);
231+
}
232+
}
233+
234+
@Test
235+
public void restore_singleIndex_rename2() throws Exception {
236+
try (TestRestClient restClient = cluster.getRestClient(user)) {
237+
createInitialTestObjects(index_awx1);
238+
createInitialTestSnapshot("_snapshot/test_repository/single_index_snapshot", json("indices", "index_awx1"));
239+
240+
TestRestClient.HttpResponse httpResponse = restClient.post(
241+
"_snapshot/test_repository/single_index_snapshot/_restore?wait_for_completion=true",
242+
json("rename_pattern", "index_a(.*)", "rename_replacement", "index_b$1")
243+
);
244+
245+
assertThat(
246+
httpResponse,
247+
containsExactly(index_bwx1).at("snapshot.indices").butForbiddenIfIncomplete(user.indexMatcher("write"))
248+
);
249+
250+
} finally {
251+
delete("_snapshot/test_repository/single_index_snapshot");
252+
delete(index_awx1, index_bwx1);
253+
}
254+
}
255+
256+
@Test
257+
public void restore_singleIndex_renameToSystemIndex() throws Exception {
258+
try (TestRestClient restClient = cluster.getRestClient(user)) {
259+
createInitialTestObjects(index_awx1);
260+
createInitialTestSnapshot("_snapshot/test_repository/single_index_snapshot", json("indices", "index_awx1"));
261+
262+
TestRestClient.HttpResponse httpResponse = restClient.post(
263+
"_snapshot/test_repository/single_index_snapshot/_restore?wait_for_completion=true",
264+
json("rename_pattern", "index_awx1", "rename_replacement", system_index_plugin_not_existing.name())
265+
);
266+
267+
if (clusterConfig.systemIndexPrivilegeEnabled || user == SUPER_UNLIMITED_USER) {
268+
assertThat(
269+
httpResponse,
270+
containsExactly(system_index_plugin_not_existing).at("snapshot.indices")
271+
.butForbiddenIfIncomplete(user.indexMatcher("write"))
272+
);
273+
} else {
274+
assertThat(httpResponse, isForbidden());
275+
}
276+
} finally {
277+
delete("_snapshot/test_repository/single_index_snapshot");
278+
delete(index_awx1, system_index_plugin_not_existing);
279+
}
280+
}
281+
282+
@Test
283+
public void restore_singleIndexFromAllIndices() throws Exception {
284+
try (TestRestClient restClient = cluster.getRestClient(user)) {
285+
createInitialTestObjects(index_awx1);
286+
createInitialTestSnapshot("_snapshot/test_repository/all_index_snapshot", json());
287+
288+
delete(index_awx1);
289+
290+
TestRestClient.HttpResponse httpResponse = restClient.post(
291+
"_snapshot/test_repository/all_index_snapshot/_restore?wait_for_completion=true",
292+
json("indices", "index_awx1")
293+
);
294+
295+
assertThat(
296+
httpResponse,
297+
containsExactly(index_awx1).at("snapshot.indices").butForbiddenIfIncomplete(user.indexMatcher("write"))
298+
);
299+
300+
} finally {
301+
delete("_snapshot/test_repository/all_index_snapshot");
302+
delete(index_awx1);
303+
}
304+
}
305+
306+
@Test
307+
public void restore_all_globalState() throws Exception {
308+
try (TestRestClient restClient = cluster.getRestClient(user)) {
309+
createInitialTestObjects(index_awx1, index_awx2, index_bwx1, index_bwx2);
310+
createInitialTestSnapshot("_snapshot/test_repository/all_index_snapshot", json("indices", "index_*w*"));
311+
312+
delete(index_awx1, index_awx2, index_bwx1, index_bwx2);
313+
314+
TestRestClient.HttpResponse httpResponse = restClient.post(
315+
"_snapshot/test_repository/all_index_snapshot/_restore?wait_for_completion=true",
316+
json("include_global_state", true)
317+
);
318+
319+
if (user == SUPER_UNLIMITED_USER) {
320+
assertThat(httpResponse, isOk());
321+
} else {
322+
assertThat(httpResponse, isForbidden());
323+
}
324+
325+
} finally {
326+
delete("_snapshot/test_repository/all_index_snapshot");
327+
delete(index_awx1, index_awx2, index_bwx1, index_bwx2);
328+
}
329+
}
330+
331+
@After
332+
public void refresh() {
333+
cluster.getInternalNodeClient().admin().indices().refresh(new RefreshRequest("*")).actionGet();
334+
}
335+
336+
@ParametersFactory(shuffle = false, argumentFormatting = "%1$s, %3$s")
337+
public static Collection<Object[]> params() {
338+
List<Object[]> result = new ArrayList<>();
339+
340+
for (ClusterConfig clusterConfig : ClusterConfig.values()) {
341+
for (TestSecurityConfig.User user : USERS) {
342+
result.add(new Object[] { clusterConfig, user, user.getDescription() });
343+
}
344+
}
345+
return result;
346+
}
347+
348+
public SnapshotAuthorizationIntTests(ClusterConfig clusterConfig, TestSecurityConfig.User user, String description) throws Exception {
349+
this.user = user;
350+
this.cluster = clusterConfig.cluster(SnapshotAuthorizationIntTests::clusterBuilder);
351+
this.clusterConfig = clusterConfig;
352+
}
353+
354+
private void createInitialTestObjects(TestIndexOrAliasOrDatastream... testIndexLikeArray) {
355+
TestIndexOrAliasOrDatastream.createInitialTestObjects(cluster, testIndexLikeArray);
356+
}
357+
358+
private void createInitialTestSnapshot(String snapshotName, HttpEntity requestBody) {
359+
try (TestRestClient client = cluster.getAdminCertRestClient()) {
360+
TestRestClient.HttpResponse httpResponse = client.put(snapshotName + "?wait_for_completion=true", requestBody);
361+
assertThat(httpResponse, isOk());
362+
}
363+
}
364+
365+
private void delete(TestIndexOrAliasOrDatastream... testIndexLikeArray) {
366+
TestIndexOrAliasOrDatastream.delete(cluster, testIndexLikeArray);
367+
}
368+
369+
private void delete(String... paths) {
370+
try (TestRestClient adminRestClient = cluster.getAdminCertRestClient()) {
371+
for (String path : paths) {
372+
TestRestClient.HttpResponse response = adminRestClient.delete(path);
373+
if (response.getStatusCode() != 200 && response.getStatusCode() != 404) {
374+
throw new RuntimeException("Error while deleting " + path + "\n" + response.getBody());
375+
}
376+
}
377+
}
378+
}
379+
}

0 commit comments

Comments
 (0)