Skip to content

Commit 989c1e6

Browse files
author
Divyanshi Chouksey
committed
Added View and Rollback API for security config versions
Signed-off-by: Divyanshi Chouksey <divvv@amazon.com>
1 parent b5e7531 commit 989c1e6

12 files changed

Lines changed: 1395 additions & 39 deletions

File tree

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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.api;
13+
14+
import java.util.Map;
15+
16+
import org.apache.http.HttpStatus;
17+
import org.junit.Before;
18+
import org.junit.Test;
19+
20+
import org.opensearch.test.framework.cluster.TestRestClient;
21+
22+
import static org.hamcrest.MatcherAssert.assertThat;
23+
import static org.hamcrest.Matchers.containsString;
24+
import static org.hamcrest.Matchers.equalTo;
25+
import static org.hamcrest.Matchers.is;
26+
import static org.hamcrest.Matchers.isOneOf;
27+
import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX;
28+
import static org.opensearch.security.support.ConfigConstants.SECURITY_CONFIG_VERSION_INDEX_ENABLED;
29+
30+
public class RollbackVersionApiIntegrationTest extends AbstractApiIntegrationTest {
31+
32+
private String endpointPrefix() {
33+
return PLUGINS_PREFIX + "/api";
34+
}
35+
36+
private String RollbackBase() {
37+
return endpointPrefix() + "/rollback";
38+
}
39+
40+
private String RollbackVersion(String versionId) {
41+
return RollbackBase() + "/version/" + versionId;
42+
}
43+
44+
@Override
45+
protected Map<String, Object> getClusterSettings() {
46+
Map<String, Object> settings = super.getClusterSettings();
47+
settings.put(SECURITY_CONFIG_VERSION_INDEX_ENABLED, true);
48+
return settings;
49+
}
50+
51+
@Before
52+
public void setupConfigVersionsIndex() throws Exception {
53+
try (TestRestClient client = localCluster.getRestClient(ADMIN_USER_NAME, DEFAULT_PASSWORD)) {
54+
client.get("/_cluster/health?wait_for_status=yellow&timeout=30s");
55+
client.delete("/.opendistro_security_config_versions");
56+
client.put("/.opendistro_security_config_versions");
57+
client.post("/_refresh");
58+
client.get("/_cluster/health/.opendistro_security_config_versions?wait_for_status=yellow&timeout=5s");
59+
60+
String bulkPayload =
61+
"{ \"index\": { \"_index\": \".opendistro_security_config_versions\", \"_id\": \"opendistro_security_config_versions\" } }\n"
62+
+ "{ \"versions\": ["
63+
+ " {"
64+
+ " \"version_id\": \"v1\","
65+
+ " \"timestamp\": \"2025-04-03T00:00:00Z\","
66+
+ " \"modified_by\": \"admin\","
67+
+ " \"security_configs\": {"
68+
+ " \"internalusers\": {"
69+
+ " \"lastUpdated\": \"2025-04-03T00:00:00Z\","
70+
+ " \"configData\": { \"admin\": { \"hash\": \"$2y$12$aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\" } }"
71+
+ " }"
72+
+ " }"
73+
+ " },"
74+
+ " {"
75+
+ " \"version_id\": \"v2\","
76+
+ " \"timestamp\": \"2025-04-04T00:00:00Z\","
77+
+ " \"modified_by\": \"admin\","
78+
+ " \"security_configs\": {"
79+
+ " \"internalusers\": {"
80+
+ " \"lastUpdated\": \"2025-04-04T00:00:00Z\","
81+
+ " \"configData\": { \"admin\": { \"hash\": \"$2y$12$bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\" } }"
82+
+ " }"
83+
+ " }"
84+
+ " }"
85+
+ "] }\n";
86+
87+
var response = client.postJson("/_bulk?refresh=true", bulkPayload);
88+
assertThat("Bulk insert failed", response.getStatusCode(), is(200));
89+
}
90+
}
91+
92+
@Test
93+
public void testRollbackToPreviousVersion_success() throws Exception {
94+
withUser(ADMIN_USER_NAME, DEFAULT_PASSWORD, client -> {
95+
var response = client.post(RollbackBase());
96+
assertThat(response.getStatusCode(), is(HttpStatus.SC_OK));
97+
assertThat(response.getTextFromJsonBody("/status"), equalTo("OK"));
98+
assertThat(response.getTextFromJsonBody("/message"), containsString("config rolled back to version"));
99+
});
100+
}
101+
102+
@Test
103+
public void testRollbackToSpecificVersion_success() throws Exception {
104+
String versionId = "v1";
105+
withUser(ADMIN_USER_NAME, DEFAULT_PASSWORD, client -> {
106+
var response = client.post(RollbackVersion(versionId));
107+
assertThat(response.getStatusCode(), is(HttpStatus.SC_OK));
108+
assertThat(response.getTextFromJsonBody("/status"), equalTo("OK"));
109+
assertThat(response.getTextFromJsonBody("/message"), containsString("config rolled back to version " + versionId));
110+
});
111+
}
112+
113+
@Test
114+
public void testRollbackWithNonAdmin_shouldBeUnauthorized() throws Exception {
115+
withUser(NEW_USER, DEFAULT_PASSWORD, client -> {
116+
var response = client.post(RollbackBase());
117+
assertThat(response.getStatusCode(), isOneOf(HttpStatus.SC_FORBIDDEN, HttpStatus.SC_UNAUTHORIZED));
118+
});
119+
}
120+
121+
@Test
122+
public void testRollbackToInvalidVersion_shouldReturnNotFound() throws Exception {
123+
withUser(ADMIN_USER_NAME, DEFAULT_PASSWORD, client -> {
124+
var response = client.post(RollbackVersion("does-not-exist"));
125+
assertThat(response.getStatusCode(), is(HttpStatus.SC_NOT_FOUND));
126+
assertThat(response.getTextFromJsonBody("/message"), containsString("not found"));
127+
});
128+
}
129+
130+
@Test
131+
public void testRollbackWhenOnlyOneVersion_shouldFail() throws Exception {
132+
withUser(ADMIN_USER_NAME, DEFAULT_PASSWORD, client -> {
133+
client.delete("/.opendistro_security_config_versions");
134+
client.put("/.opendistro_security_config_versions");
135+
client.post("/_refresh");
136+
client.get("/_cluster/health/.opendistro_security_config_versions?wait_for_status=yellow&timeout=30s");
137+
138+
String bulkPayload = ""
139+
+ "{ \"index\": { \"_index\": \".opendistro_security_config_versions\", \"_id\": \"opendistro_security_config_versions\" } }\n"
140+
+ "{ \"versions\": [ {"
141+
+ " \"version_id\": \"v1\","
142+
+ " \"timestamp\": \"2025-04-03T00:00:00Z\","
143+
+ " \"modified_by\": \"admin\","
144+
+ " \"security_configs\": {"
145+
+ " \"config_type_1\": {"
146+
+ " \"lastUpdated\": \"2025-04-03T00:00:00Z\","
147+
+ " \"configData\": {"
148+
+ " \"key1\": { \"dummy\": \"value1\" }"
149+
+ " }"
150+
+ " }"
151+
+ " }"
152+
+ "} ] }\n";
153+
154+
var bulkResponse = client.postJson("/_bulk?refresh=true", bulkPayload);
155+
assertThat("Bulk insert failed: " + bulkResponse.getBody(), bulkResponse.getStatusCode(), is(200));
156+
157+
client.post("/_refresh");
158+
159+
var response = client.post(RollbackBase());
160+
assertThat(response.getStatusCode(), is(404));
161+
assertThat(response.getBody(), containsString("No previous version available to rollback"));
162+
});
163+
}
164+
165+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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.api;
13+
14+
import java.util.Map;
15+
16+
import org.junit.Before;
17+
import org.junit.Test;
18+
19+
import org.opensearch.test.framework.TestSecurityConfig;
20+
import org.opensearch.test.framework.cluster.TestRestClient;
21+
22+
import static org.hamcrest.MatcherAssert.assertThat;
23+
import static org.hamcrest.Matchers.containsString;
24+
import static org.hamcrest.Matchers.equalTo;
25+
import static org.hamcrest.Matchers.greaterThan;
26+
import static org.hamcrest.Matchers.is;
27+
import static org.hamcrest.Matchers.isOneOf;
28+
import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX;
29+
import static org.opensearch.security.support.ConfigConstants.SECURITY_CONFIG_VERSION_INDEX_ENABLED;
30+
31+
public class ViewVersionApiIntegrationTest extends AbstractApiIntegrationTest {
32+
33+
static {
34+
testSecurityConfig.user(new TestSecurityConfig.User("limitedUser").password("limitedPass"));
35+
}
36+
37+
private String endpointPrefix() {
38+
return PLUGINS_PREFIX + "/api";
39+
}
40+
41+
private String viewVersionBase() {
42+
return endpointPrefix() + "/version";
43+
}
44+
45+
private String viewVersion(String versionId) {
46+
return viewVersionBase() + "/" + versionId;
47+
}
48+
49+
@Override
50+
protected Map<String, Object> getClusterSettings() {
51+
Map<String, Object> settings = super.getClusterSettings();
52+
settings.put(SECURITY_CONFIG_VERSION_INDEX_ENABLED, true);
53+
return settings;
54+
}
55+
56+
@Before
57+
public void setupIndexAndCerts() throws Exception {
58+
try (TestRestClient client = localCluster.getRestClient(ADMIN_USER_NAME, DEFAULT_PASSWORD)) {
59+
client.get("/_cluster/health?wait_for_status=yellow&timeout=10s");
60+
client.delete("/.opendistro_security_config_versions");
61+
client.put("/.opendistro_security_config_versions");
62+
client.post("/_refresh");
63+
client.get("/_cluster/health/.opendistro_security_config_versions?wait_for_status=yellow&timeout=5s");
64+
65+
String bulkPayload = ""
66+
+ "{ \"index\": { \"_index\": \".opendistro_security_config_versions\", \"_id\": \"opendistro_security_config_versions\" } }\n"
67+
+ "{ \"versions\": [ {"
68+
+ " \"version_id\": \"v1\","
69+
+ " \"timestamp\": \"2025-04-03T00:00:00Z\","
70+
+ " \"modified_by\": \"admin\","
71+
+ " \"security_configs\": {"
72+
+ " \"config_type_1\": {"
73+
+ " \"lastUpdated\": \"2025-04-03T00:00:00Z\","
74+
+ " \"configData\": {"
75+
+ " \"key1\": { \"dummy\": \"value1\" }"
76+
+ " }"
77+
+ " }"
78+
+ " }"
79+
+ "} ] }\n";
80+
81+
var bulkResponse = client.postJson("/_bulk?refresh=true", bulkPayload);
82+
assertThat("Failed to insert config versions doc via bulk: " + bulkResponse.getBody(), bulkResponse.getStatusCode(), is(200));
83+
}
84+
}
85+
86+
@Test
87+
public void testViewAllVersions() throws Exception {
88+
withUser(ADMIN_USER_NAME, DEFAULT_PASSWORD, client -> {
89+
var response = ok(() -> client.get(viewVersionBase()));
90+
var json = response.bodyAsJsonNode();
91+
92+
assertThat(json.has("versions"), is(true));
93+
var versions = json.get("versions");
94+
assertThat(versions.isArray(), is(true));
95+
assertThat(versions.size(), greaterThan(0));
96+
});
97+
}
98+
99+
@Test
100+
public void testViewSpecificVersionFound() throws Exception {
101+
withUser(ADMIN_USER_NAME, DEFAULT_PASSWORD, client -> {
102+
var response = ok(() -> client.get(viewVersion("v1")));
103+
var json = response.bodyAsJsonNode();
104+
105+
assertThat(json.has("versions"), is(true));
106+
var versions = json.get("versions");
107+
assertThat(versions.isArray(), is(true));
108+
assertThat(versions.size(), is(1));
109+
110+
var ver = versions.get(0);
111+
assertThat(ver.get("version_id").asText(), equalTo("v1"));
112+
});
113+
}
114+
115+
@Test
116+
public void testViewSpecificVersionNotFound() throws Exception {
117+
withUser(ADMIN_USER_NAME, DEFAULT_PASSWORD, client -> {
118+
var response = notFound(() -> client.get(viewVersion("does-not-exist")));
119+
var json = response.bodyAsJsonNode();
120+
121+
assertThat(json.has("status"), is(true));
122+
assertThat(json.get("status").asText(), equalTo("NOT_FOUND"));
123+
124+
assertThat(json.has("message"), is(true));
125+
assertThat(json.get("message").asText(), containsString("not found"));
126+
});
127+
}
128+
129+
@Test
130+
public void testViewAllVersions_forbiddenWithoutAdminCert() throws Exception {
131+
withUser("limitedUser", "limitedPass", client -> {
132+
var response = client.get(viewVersionBase());
133+
assertThat(response.getStatusCode(), isOneOf(401, 403));
134+
});
135+
}
136+
}

src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,7 @@ protected final RestChannelConsumer prepareRequest(RestRequest request, NodeClie
639639
*/
640640
protected void consumeParameters(final RestRequest request) {
641641
request.param("name");
642+
request.param("versionID");
642643
}
643644

644645
@Override

src/main/java/org/opensearch/security/dlic/rest/api/Endpoint.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,7 @@ public enum Endpoint {
2929
VALIDATE,
3030
ALLOWLIST,
3131
NODESDN,
32-
SSL;
32+
SSL,
33+
VIEW_VERSION,
34+
ROLLBACK_VERSION;
3335
}

0 commit comments

Comments
 (0)