Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ afterEvaluate {
plugins.add(firstPlugin)

if (securityEnabled) {
node.extraConfigFile("kirk.pem", file("src/test/resources/security/kirk.pem"))
/*node.extraConfigFile("kirk.pem", file("src/test/resources/security/kirk.pem"))
node.extraConfigFile("kirk-key.pem", file("src/test/resources/security/kirk-key.pem"))
node.extraConfigFile("esnode.pem", file("src/test/resources/security/esnode.pem"))
node.extraConfigFile("esnode-key.pem", file("src/test/resources/security/esnode-key.pem"))
Expand All @@ -236,7 +236,7 @@ afterEvaluate {
node.setting("plugins.security.check_snapshot_restore_write_privileges", "true")
node.setting("plugins.security.restapi.roles_enabled", "[\"all_access\", \"security_rest_api_access\"]")
node.setting("plugins.security.system_indices.enabled", "true")
// node.setting("plugins.security.system_indices.indices", "[\".opendistro-ism-config\"]")
node.setting("plugins.security.system_indices.indices", "[\".opendistro-ism-config\"]")*/
}
}
}
Expand Down Expand Up @@ -313,6 +313,13 @@ integTest {
}
}

// run the test only if security is enabled
if (!securityEnabled) {
filter {
excludeTestsMatching "org.opensearch.indexmanagement.SecurityBehaviorIT"
}
}

// TODO: raise issue in Core, this is because of the test framework
if (System.getProperty("tests.clustername") != null) {
exclude 'org/opensearch/indexmanagement/indexstatemanagement/MetadataRegressionIT.class'
Expand Down
280 changes: 280 additions & 0 deletions src/test/kotlin/org/opensearch/indexmanagement/SecurityBehaviorIT.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

package org.opensearch.indexmanagement

import org.apache.http.entity.ContentType
import org.apache.http.entity.StringEntity
import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.opensearch.client.Request
import org.opensearch.client.Response
import org.opensearch.client.RestClient
import org.opensearch.commons.rest.SecureRestClientBuilder
import org.opensearch.rest.RestStatus
import org.opensearch.test.junit.annotations.TestLogging

@TestLogging("level:DEBUG", reason = "Debug for tests.")
class SecurityBehaviorIT : IndexManagementRestTestCase() {

var financeUserClient: RestClient? = null
var hrUserClient: RestClient? = null
var adminUserClient: RestClient? = null
var noAuthUserClient: RestClient? = null

override fun preserveIndicesUponCompletion(): Boolean {
return true
}

@Before
fun setupUsersAndRoles() {
// Create user jane with backend roles - ["finance", "general"]
createUser("jane", backendRoles = listOf("finance", "hr"))

// Create user jack with backend roles - ["hr"]
createUser("jack", backendRoles = listOf("hr"))

// Create user sam with backend roles - ["general"]
createUser("sam", backendRoles = listOf("general"))

// Create user auth with no backend roles
createUser("noauth")

val clusterPermissions = listOf(
"cluster:admin/opendistro/ism/*",
"cluster:admin/opendistro/rollup/*",
"cluster:admin/opendistro/transform/*",
)
val indexPermissions = listOf(
"indices:admin/opensearch/ism/*",
"indices:admin/mappings/get",
"indices:data/read/search"
)
// Create role - "finance_im_role"
addRole("finance_im_role", clusterPermissions, listOf("finance-*"), indexPermissions)

// Create role - "hr_im_role"
addRole("hr_im_role", clusterPermissions, listOf("hr-*"), indexPermissions)

// add roles to all the users
addUsersToRole("finance_im_role", listOf("jane"))
addUsersToRole("hr_im_role", listOf("jack"))
addUsersToRole("all_access", listOf("sam", "admin"))

financeUserClient = SecureRestClientBuilder(clusterHosts.toTypedArray(), isHttps(), "jane", "Test123!").setSocketTimeout(60000).build()
hrUserClient = SecureRestClientBuilder(clusterHosts.toTypedArray(), isHttps(), "jack", "Test123!").setSocketTimeout(60000).build()
adminUserClient = SecureRestClientBuilder(clusterHosts.toTypedArray(), isHttps(), "sam", "Test123!").setSocketTimeout(60000).build()
noAuthUserClient = SecureRestClientBuilder(clusterHosts.toTypedArray(), isHttps(), "noauth", "Test123!").setSocketTimeout(60000).build()
}

@After
fun cleanup() {
financeUserClient?.close()
hrUserClient?.close()
adminUserClient?.close()
noAuthUserClient?.close()

deleteUser("jack")
deleteUser("jane")
deleteUser("sam")
deleteUser("noauth")

deleteRole("finance_im_role")
deleteRole("hr_im_role")

deleteIndex(".opendistro-ism-config")
deleteIndex("finance-1")
deleteIndex("marketing-1")
deleteIndex("hr-1")

disableFilterBy()
}

fun `test security behavior`() {
disableFilterBy()

var financeResponse = createPolicy("finance-policy", 10, financeUserClient)
var hrResponse = createPolicy("hr-policy", 15, hrUserClient)
var adminResponse = createPolicy("admin-policy", 0, adminUserClient)
// var noAuthResponse = createPolicy("noauth-policy", 100, noAuthUserClient)

assertEquals("User jane failed to create policy", RestStatus.CREATED, financeResponse?.restStatus())
assertEquals("User jack failed to create policy", RestStatus.CREATED, hrResponse?.restStatus())
assertEquals("User sam failed to create policy", RestStatus.CREATED, adminResponse?.restStatus())
// assertEquals("User noauth didn't fail to create policy", RestStatus.FORBIDDEN, noAuthResponse?.restStatus())

financeResponse = getPolicies(financeUserClient)
hrResponse = getPolicies(hrUserClient)
adminResponse = getPolicies(adminUserClient)
// noAuthResponse = getPolicies(noAuthUserClient)

assertEquals("User jane cannot get policies", RestStatus.OK, financeResponse?.restStatus())
assertEquals("User jack cannot get policies", RestStatus.OK, hrResponse?.restStatus())
assertEquals("User sam cannot get policies", RestStatus.OK, adminResponse?.restStatus())
// assertEquals("User noauth can get policies", RestStatus.FORBIDDEN, noAuthResponse?.restStatus())

// Ensure all users can see each other policies
assertEquals("User jane not able to see all policies", 3, financeResponse?.asMap()?.get("total_policies"))
assertEquals("User jack not able to see all policies", 3, hrResponse?.asMap()?.get("total_policies"))
assertEquals("User sam not able to see all policies", 3, adminResponse?.asMap()?.get("total_policies"))

client().makeRequest("PUT", "/finance-1")
client().makeRequest("PUT", "/hr-1")
client().makeRequest("PUT", "/marketing-1")

waitFor {
financeResponse = explainManagedIndices(financeUserClient)
hrResponse = explainManagedIndices(hrUserClient)
adminResponse = explainManagedIndices(adminUserClient)
assertEquals("User jane cannot get managed indices", RestStatus.OK, financeResponse?.restStatus())
assertEquals("User jack cannot get managed indices", RestStatus.OK, hrResponse?.restStatus())
assertEquals("User sam cannot get managed indices", RestStatus.OK, adminResponse?.restStatus())
assertEquals("User jane seeing more managed indices than allowed", 1, financeResponse?.asMap()?.get("total_managed_indices"))
assertEquals("User jack seeing more managed indices than allowed", 1, hrResponse?.asMap()?.get("total_managed_indices"))
assertEquals("User sam seeing more managed indices than allowed", 3, adminResponse?.asMap()?.get("total_managed_indices"))
}

// Enabling backend role filtering
enableFilterBy()
financeResponse = getPolicies(financeUserClient)
hrResponse = getPolicies(hrUserClient)
adminResponse = getPolicies(adminUserClient)

// Only admin can all policies other users only can see intersecting policies
assertEquals("User jane not able to see all policies", 2, financeResponse?.asMap()?.get("total_policies"))
assertEquals("User jack not able to see all policies", 2, hrResponse?.asMap()?.get("total_policies"))
assertEquals("User sam not able to see all policies", 3, adminResponse?.asMap()?.get("total_policies"))
}

private fun createPolicy(name: String, priority: Int, userClient: RestClient?): Response? {
val request = Request("PUT", "_plugins/_ism/policies/$name")
val json = """
{
"policy": {
"description": "test policy",
"default_state": "start",
"states": [
{
"name": "start",
"actions": [
{
"replica_count": {
"number_of_replicas": 5
}
}
],
"transitions": []
}
],
"ism_template": {
"index_patterns": ["*"],
"priority": $priority
}
}
}
""".trimIndent()
request.setJsonEntity(json)
return userClient?.performRequest(request)
}

private fun getPolicies(userClient: RestClient?): Response? {
return userClient?.makeRequest("GET", "_plugins/_ism/policies")
}

private fun explainManagedIndices(userClient: RestClient?): Response? {
return userClient?.makeRequest("GET", "_plugins/_ism/explain")
}

private fun createUser(name: String, pwd: String = "Test123!", backendRoles: List<String> = listOf()) {
val request = Request("PUT", "_plugins/_security/api/internalusers/$name")
val backendRolesStr = backendRoles.joinToString { "\"$it\"" }
val json = """
{
"password": "$pwd",
"backend_roles": [$backendRolesStr],
"attributes":{}
}
""".trimIndent()
request.setJsonEntity(json)
client().performRequest(request)
}

private fun enableFilterBy() {
val setting = """
{
"persistent": {
"plugins.index_management.filter_by_backend_roles": "true"
}
}
""".trimIndent()
val updateResponse = client().makeRequest("PUT", "_cluster/settings", emptyMap(), StringEntity(setting, ContentType.APPLICATION_JSON))
assertEquals(updateResponse.statusLine.toString(), 200, updateResponse.statusLine.statusCode)
}

private fun disableFilterBy() {
val setting = """
{
"persistent": {
"plugins.index_management.filter_by_backend_roles": "false"
}
}
""".trimIndent()
val updateResponse = client().makeRequest("PUT", "_cluster/settings", emptyMap(), StringEntity(setting, ContentType.APPLICATION_JSON))
Assert.assertEquals(updateResponse.statusLine.toString(), 200, updateResponse.statusLine.statusCode)
}

private fun addUsersToRole(role: String, users: List<String>) {
val request = Request("PUT", "/_plugins/_security/api/rolesmapping/$role")
val usersStr = users.joinToString { "\"$it\"" }
var entity = """
{
"backend_roles": [],
"hosts": [],
"users": [$usersStr]
}
""".trimIndent()
request.setJsonEntity(entity)
client().performRequest(request)
}

private fun addRole(name: String, clusterPermissions: List<String>, indexPatterns: List<String>, indexPermissions: List<String>) {
val request = Request("PUT", "/_plugins/_security/api/roles/$name")
val indexPatternsStr = indexPatterns.joinToString { "\"$it\"" }
val clusterPermissionsStr = clusterPermissions.joinToString { "\"$it\"" }
val indexPermissionsStr = indexPermissions.joinToString { "\"$it\"" }
val entity = """
{
"cluster_permissions": [$clusterPermissionsStr],
"index_permissions": [
{
"fls": [],
"masked_fields": [],
"allowed_actions": [$indexPermissionsStr],
"index_patterns": [$indexPatternsStr]
}
],
"tenant_permissions": []
}
""".trimIndent()

request.setJsonEntity(entity)
client().performRequest(request)
}

private fun deleteUser(name: String) {
client().makeRequest("DELETE", "/_plugins/_security/api/internalusers/$name")
}

private fun deleteRole(name: String) {
client().makeRequest("DELETE", "/_plugins/_security/api/roles/$name")
}
}