Skip to content

Commit 9f28d47

Browse files
committed
Security tests
1 parent 5ad90ff commit 9f28d47

File tree

3 files changed

+288
-0
lines changed

3 files changed

+288
-0
lines changed

.github/workflows/multi-node-test-workflow.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,49 @@ jobs:
2525
uses: actions/checkout@v2
2626
- name: Run integration tests with multi node config
2727
run: ./gradlew integTest -PnumNodes=3 -Dopensearch.version=1.2.0-SNAPSHOT
28+
- name: Pull and Run Docker
29+
run: |
30+
plugin=`ls index-management/build/distributions/*.zip`
31+
list_of_files=`ls`
32+
list_of_all_files=`ls index-management/build/distributions/`
33+
version=`echo $plugin|awk -F- '{print $3}'| cut -d. -f 1-3`
34+
plugin_version=`echo $plugin|awk -F- '{print $3}'| cut -d. -f 1-4`
35+
candidate_version=`echo $plugin|awk -F- '{print $4}'| cut -d. -f 1-1`
36+
echo $version $plugin_version $candidate_version
37+
echo $ls $list_of_all_files
38+
39+
if docker pull opensearchstaging/opensearch:$version-$candidate_version
40+
then
41+
echo "FROM opensearchstaging/opensearch:$version-$candidate_version" >> Dockerfile
42+
echo "RUN if [ -d /usr/share/opensearch/plugins/opensearch-index-management ]; then /usr/share/opensearch/bin/opensearch-plugin remove opensearch-index-management; fi" >> Dockerfile
43+
echo "ADD alerting/build/distributions/opensearch-index-management-$plugin_version-$candidate_version.zip /tmp/" >> Dockerfile
44+
echo "RUN /usr/share/opensearch/bin/opensearch-plugin install --batch file:/tmp/opensearch-index-management-$plugin_version-$candidate_version.zip" >> Dockerfile
45+
46+
docker build -t opensearch-index-management:test .
47+
echo "imagePresent=true" >> $GITHUB_ENV
48+
else
49+
echo "imagePresent=false" >> $GITHUB_ENV
50+
fi
51+
- name: Run Docker Image
52+
if: env.imagePresent == 'true'
53+
run: |
54+
cd ..
55+
docker run -p 9200:9200 -d -p 9600:9600 -e "discovery.type=single-node" opensearch-alerting:test
56+
sleep 120
57+
- name: Run IndexManagement Test for security enabled test cases
58+
if: env.imagePresent == 'true'
59+
run: |
60+
cluster_running=`curl -XGET https://localhost:9200/_cat/plugins -u admin:admin --insecure`
61+
echo $cluster_running
62+
security=`curl -XGET https://localhost:9200/_cat/plugins -u admin:admin --insecure |grep opensearch-security|wc -l`
63+
echo $security
64+
if [ $security -gt 0 ]
65+
then
66+
echo "Security plugin is available"
67+
./gradlew :integTest -Dtests.rest.cluster=localhost:9200 -Dtests.cluster=localhost:9200 -Dtests.clustername=docker-cluster -Dsecurity=true -Dhttps=true -Duser=admin -Dpassword=admin
68+
else
69+
echo "Security plugin is NOT available skipping this run as tests without security have already been run"
70+
fi
2871
- name: Upload failed logs
2972
uses: actions/upload-artifact@v2
3073
if: failure()

build.gradle

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,13 @@ integTest {
313313
}
314314
}
315315

316+
// run the test only if security is enabled
317+
if (!securityEnabled) {
318+
filter {
319+
excludeTestsMatching "org.opensearch.indexmanagement.SecurityBehaviorIT"
320+
}
321+
}
322+
316323
// TODO: raise issue in Core, this is because of the test framework
317324
if (System.getProperty("tests.clustername") != null) {
318325
exclude 'org/opensearch/indexmanagement/indexstatemanagement/MetadataRegressionIT.class'
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
package org.opensearch.indexmanagement
2+
3+
import org.apache.http.entity.ContentType
4+
import org.apache.http.entity.StringEntity
5+
import org.junit.Assert
6+
import org.opensearch.client.Request
7+
import org.opensearch.client.Response
8+
import org.opensearch.client.RestClient
9+
import org.opensearch.commons.rest.SecureRestClientBuilder
10+
import org.opensearch.rest.RestStatus
11+
import org.opensearch.test.junit.annotations.TestLogging
12+
13+
@TestLogging("level:DEBUG", reason = "Debug for tests.")
14+
class SecurityBehaviorIT: IndexManagementRestTestCase() {
15+
16+
var financeUserClient: RestClient? = null
17+
var hrUserClient: RestClient? = null
18+
var adminUserClient: RestClient? = null
19+
var noAuthUserClient: RestClient? = null
20+
21+
fun `test security behavior for ISM`() {
22+
setupUsersAndRoles()
23+
24+
disableFilterBy()
25+
var financeResponse = createPolicy("finance-policy", 10, financeUserClient)
26+
var hrResponse = createPolicy("hr-policy", 15, hrUserClient)
27+
var adminResponse = createPolicy("admin-policy", 0, adminUserClient)
28+
var noAuthResponse = createPolicy("noauth-policy", 100, noAuthUserClient)
29+
30+
assertEquals("User jane failed to create policy", RestStatus.CREATED, financeResponse?.restStatus())
31+
assertEquals("User jack failed to create policy", RestStatus.CREATED, hrResponse?.restStatus())
32+
assertEquals("User sam failed to create policy", RestStatus.CREATED, adminResponse?.restStatus())
33+
assertEquals("User noauth didn't fail to create policy", RestStatus.FORBIDDEN, noAuthResponse?.restStatus())
34+
35+
financeResponse = getPolicies(financeUserClient)
36+
hrResponse = getPolicies(hrUserClient)
37+
adminResponse = getPolicies(adminUserClient)
38+
noAuthResponse = getPolicies(noAuthUserClient)
39+
40+
assertEquals("User jane cannot get policies", RestStatus.OK, financeResponse?.restStatus())
41+
assertEquals("User jack cannot get policies", RestStatus.OK, hrResponse?.restStatus())
42+
assertEquals("User sam cannot get policies", RestStatus.OK, adminResponse?.restStatus())
43+
assertEquals("User noauth can get policies", RestStatus.FORBIDDEN, noAuthResponse?.restStatus())
44+
45+
// Ensure all users can see each other policies
46+
assertEquals("User jane not able to see all policies", 3, financeResponse?.asMap()?.get("total_policies"))
47+
assertEquals("User jack not able to see all policies", 3, hrResponse?.asMap()?.get("total_policies"))
48+
assertEquals("User sam not able to see all policies", 3, adminResponse?.asMap()?.get("total_policies"))
49+
50+
client().performRequest(Request("PUT", "/finance-1"))
51+
client().performRequest(Request("PUT", "/hr-1"))
52+
client().performRequest(Request("PUT", "/marketing-1"))
53+
54+
financeResponse = explainManagedIndices(financeUserClient)
55+
hrResponse = explainManagedIndices(hrUserClient)
56+
adminResponse = explainManagedIndices(adminUserClient)
57+
noAuthResponse = explainManagedIndices(noAuthUserClient)
58+
59+
assertEquals("User jane cannot get managed indices", RestStatus.OK, financeResponse?.restStatus())
60+
assertEquals("User jack cannot get managed indices", RestStatus.OK, hrResponse?.restStatus())
61+
assertEquals("User sam cannot get managed indices", RestStatus.OK, adminResponse?.restStatus())
62+
assertEquals("User noauth can get managed indices", RestStatus.FORBIDDEN, noAuthResponse?.restStatus())
63+
64+
assertEquals("User jane seeing more managed indices than allowed", 1, financeResponse?.asMap()?.get("total_managed_indices"))
65+
assertEquals("User jack seeing more managed indices than allowed", 1, hrResponse?.asMap()?.get("total_managed_indices"))
66+
assertEquals("User sam seeing more managed indices than allowed", 3, adminResponse?.asMap()?.get("total_managed_indices"))
67+
68+
// Enabling backend role filtering
69+
enableFilterBy()
70+
financeResponse = getPolicies(financeUserClient)
71+
hrResponse = getPolicies(hrUserClient)
72+
adminResponse = getPolicies(adminUserClient)
73+
74+
// Only admin can all policies other users only can see intersecting policies
75+
assertEquals("User jane not able to see all policies", 2, financeResponse?.asMap()?.get("total_policies"))
76+
assertEquals("User jack not able to see all policies", 1, hrResponse?.asMap()?.get("total_policies"))
77+
assertEquals("User sam not able to see all policies", 3, adminResponse?.asMap()?.get("total_policies"))
78+
79+
disableFilterBy()
80+
}
81+
82+
private fun createPolicy(name: String, priority: Int, userClient: RestClient?): Response? {
83+
val request = Request("PUT", "_plugins/_ism/policies/$name")
84+
val json = """
85+
{
86+
"policy": {
87+
"description": "test policy",
88+
"default_state": "start",
89+
"states": [
90+
{
91+
"name": "start",
92+
"actions": [
93+
{
94+
"replica_count": {
95+
"number_of_replicas": 5
96+
}
97+
}
98+
],
99+
"transitions": []
100+
}
101+
],
102+
"ism_template": {
103+
"index_patterns": ["*"],
104+
"priority": $priority
105+
}
106+
}
107+
}
108+
""".trimIndent()
109+
request.setJsonEntity(json)
110+
return userClient?.performRequest(request)
111+
}
112+
113+
private fun getPolicies(userClient: RestClient?): Response? {
114+
val request = Request("GET", "_plugins/_ism/policies")
115+
return userClient?.performRequest(request)
116+
}
117+
118+
private fun explainManagedIndices(userClient: RestClient?): Response? {
119+
val request = Request("GET", "_plugins/_ism/explain")
120+
return userClient?.performRequest(request)
121+
}
122+
123+
private fun createUser(name: String, pwd: String = "Test123!", backendRoles: List<String> = listOf()) {
124+
val request = Request("PUT", "_plugins/_security/api/internalusers/$name")
125+
val backendRolesStr = backendRoles.joinToString(",")
126+
val json = """
127+
{
128+
"password": $pwd,
129+
"backend_roles": [$backendRolesStr],
130+
"attributes":{}
131+
}
132+
""".trimIndent()
133+
request.setJsonEntity(json)
134+
client().performRequest(request)
135+
}
136+
137+
private fun enableFilterBy() {
138+
val setting = """
139+
{
140+
"persistent": {
141+
"plugins.index_management.filter_by_backend_roles": "true"
142+
}
143+
}
144+
""".trimIndent()
145+
val updateResponse = client().makeRequest("PUT", "_cluster/settings", emptyMap(), StringEntity(setting, ContentType.APPLICATION_JSON))
146+
assertEquals(updateResponse.statusLine.toString(), 200, updateResponse.statusLine.statusCode)
147+
}
148+
149+
private fun disableFilterBy() {
150+
val setting = """
151+
{
152+
"persistent": {
153+
"plugins.index_management.filter_by_backend_roles": "false"
154+
}
155+
}
156+
""".trimIndent()
157+
val updateResponse = client().makeRequest("PUT", "_cluster/settings", emptyMap(), StringEntity(setting, ContentType.APPLICATION_JSON))
158+
Assert.assertEquals(updateResponse.statusLine.toString(), 200, updateResponse.statusLine.statusCode)
159+
}
160+
161+
private fun addUsersToRole(role: String, users: List<String>) {
162+
val request = Request("PUT", "/_plugins/_security/api/rolesmapping/$role")
163+
val usersStr = users.joinToString(",")
164+
var entity = """
165+
{
166+
"backend_roles": [],
167+
"hosts": [],
168+
"users": [$usersStr]
169+
}
170+
""".trimIndent()
171+
request.setJsonEntity(entity)
172+
client().performRequest(request)
173+
}
174+
175+
private fun addRole(name: String, clusterPermissions: List<String>, indexPatterns: List<String>, indexPermissions: List<String>) {
176+
val request = Request("PUT", "/_plugins/_security/api/roles/$name")
177+
val indexPatternsStr = indexPatterns.joinToString(",")
178+
val clusterPermissionsStr = clusterPermissions.joinToString(",")
179+
val indexPermissionsStr = indexPermissions.joinToString(",")
180+
val entity = """
181+
{
182+
"cluster_permissions": [$clusterPermissionsStr],
183+
"index_permissions": [
184+
{
185+
"fls": [],
186+
"masked_fields": [],
187+
"allowed_actions": [$indexPermissionsStr],
188+
"index_patterns": [$indexPatternsStr]
189+
}
190+
],
191+
"tenant_permissions": []
192+
}
193+
""".trimIndent()
194+
195+
request.setJsonEntity(entity)
196+
client().performRequest(request)
197+
}
198+
199+
private fun setupUsersAndRoles() {
200+
// Create user jane with backend roles - ["finance", "general"]
201+
createUser("jane", backendRoles = listOf("finance", "hr"))
202+
203+
// Create user jack with backend roles - ["hr"]
204+
createUser("jack", backendRoles = listOf("hr"))
205+
206+
// Create user sam with backend roles - ["general"]
207+
createUser("sam", backendRoles = listOf("general"))
208+
209+
// Create user auth with no backend roles
210+
createUser("noauth")
211+
212+
val clusterPermissions = listOf(
213+
"cluster:admin/opendistro/ism/*",
214+
"cluster:admin/opendistro/rollup/*",
215+
"cluster:admin/opendistro/transform/*",
216+
)
217+
val indexPermissions = listOf(
218+
"indices:admin/opensearch/ism/*",
219+
"indices:admin/mappings/get",
220+
"indices:data/read/search"
221+
)
222+
// Create role - "finance_im_role"
223+
addRole("finance_im_role", clusterPermissions, listOf("finance-*"), indexPermissions)
224+
225+
// Create role - "hr_im_role"
226+
addRole("hr_im_role", clusterPermissions, listOf("hr-*"), indexPermissions)
227+
228+
// add roles to all the users
229+
addUsersToRole("finance_im_role", listOf("jane"))
230+
addUsersToRole("hr_im_role", listOf("jack"))
231+
addUsersToRole("all_access", listOf("sam"))
232+
233+
financeUserClient = SecureRestClientBuilder(clusterHosts.toTypedArray(), isHttps(), "jane", "Test123!").setSocketTimeout(60000).build()
234+
hrUserClient = SecureRestClientBuilder(clusterHosts.toTypedArray(), isHttps(), "jack", "Test123!").setSocketTimeout(60000).build()
235+
adminUserClient = SecureRestClientBuilder(clusterHosts.toTypedArray(), isHttps(), "sam", "Test123!").setSocketTimeout(60000).build()
236+
noAuthUserClient = SecureRestClientBuilder(clusterHosts.toTypedArray(), isHttps(), "noauth", "Test123!").setSocketTimeout(60000).build()
237+
}
238+
}

0 commit comments

Comments
 (0)