diff --git a/build.gradle b/build.gradle index 79e37cbcb..b57610b5d 100644 --- a/build.gradle +++ b/build.gradle @@ -391,6 +391,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.user_attribute_serialization.enabled", "true") // node.setting("plugins.security.system_indices.indices", "[\".opendistro-ism-config\"]") } } diff --git a/src/main/resources/mappings/opendistro-ism-config.json b/src/main/resources/mappings/opendistro-ism-config.json index 845bfe34c..68958f5e4 100644 --- a/src/main/resources/mappings/opendistro-ism-config.json +++ b/src/main/resources/mappings/opendistro-ism-config.json @@ -1,6 +1,6 @@ { "_meta" : { - "schema_version": 24 + "schema_version": 25 }, "dynamic": "strict", "properties": { @@ -644,6 +644,10 @@ "type" : "keyword" } } + }, + "custom_attributes": { + "type": "object", + "dynamic": "true" } } } diff --git a/src/main/resources/mappings/opensearch-control-center.json b/src/main/resources/mappings/opensearch-control-center.json index 992f5da72..7ac36c824 100644 --- a/src/main/resources/mappings/opensearch-control-center.json +++ b/src/main/resources/mappings/opensearch-control-center.json @@ -1,6 +1,6 @@ { "_meta": { - "schema_version": 1 + "schema_version": 2 }, "dynamic": "strict", "properties": { @@ -53,6 +53,10 @@ "type" : "keyword" } } + }, + "custom_attributes": { + "type": "object", + "dynamic": "true" } } }, diff --git a/src/test/kotlin/org/opensearch/indexmanagement/IndexManagementRestTestCase.kt b/src/test/kotlin/org/opensearch/indexmanagement/IndexManagementRestTestCase.kt index 4a18d5c9a..ee9d4d2a5 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/IndexManagementRestTestCase.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/IndexManagementRestTestCase.kt @@ -41,7 +41,7 @@ import javax.management.remote.JMXConnectorFactory import javax.management.remote.JMXServiceURL abstract class IndexManagementRestTestCase : ODFERestTestCase() { - val configSchemaVersion = 24 + val configSchemaVersion = 25 val historySchemaVersion = 7 // Having issues with tests leaking into other tests and mappings being incorrect and they are not caught by any pending task wait check as diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/IndexStateManagementRestTestCase.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/IndexStateManagementRestTestCase.kt index 2992a2ab2..a98158aee 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/IndexStateManagementRestTestCase.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/IndexStateManagementRestTestCase.kt @@ -88,6 +88,66 @@ abstract class IndexStateManagementRestTestCase : IndexManagementRestTestCase() val explainResponseOpendistroPolicyIdSetting = "index.opendistro.index_state_management.policy_id" val explainResponseOpenSearchPolicyIdSetting = "index.plugins.index_state_management.policy_id" + @Before + protected fun attemptAddAttributesToAdmin() { + val attrs = mapOf( + "department" to "engineering", + "env" to "itest", + ) + try { + addAttributesToAdmin(attrs) + } catch (e: Exception) { + logger.warn("Skipping admin attribute setup: ${e.message}") + } + } + + private fun buildAttributesPatch(op: String, attributes: Map): StringEntity { + val payload = buildString { + append("[{\"op\":\"").append(op).append("\",\"path\":\"/attributes\",\"value\":{") + append( + attributes.entries.joinToString(",") { (k, v) -> + "\"${k.replace("\"", "\\\"")}\":\"${v.replace("\"", "\\\"")}\"" + }, + ) + append("}}]") + } + return StringEntity(payload, ContentType.APPLICATION_JSON) + } + + /** Returns true if the PATCH succeeded (2xx), false if the route isn’t usable, and throws on fatal errors. */ + private fun tryPatchAttributes(endpoint: String, entity: StringEntity): Boolean = try { + val resp = adminClient().makeRequest("PATCH", endpoint, emptyMap(), entity) + resp.restStatus().status in 200..299 + } catch (e: ResponseException) { + // Treat common “route not usable / blocked” statuses as a soft failure (false), rethrow other errors. + val softStatuses = setOf( + RestStatus.NOT_FOUND, RestStatus.METHOD_NOT_ALLOWED, RestStatus.FORBIDDEN, RestStatus.UNAUTHORIZED, RestStatus.BAD_REQUEST, + ) + if (e.response.restStatus() in softStatuses) false else throw e + } catch (_: Exception) { + // admin client couldn't reach security, treat as soft failure to try the next route + false + } + + protected fun addAttributesToAdmin(attributes: Map) { + val endpoints = listOf( + "/_plugins/_security/api/internalusers/admin", + "/_opendistro/_security/api/internalusers/admin", + ) + + val addBody = buildAttributesPatch("add", attributes) + val replaceBody = buildAttributesPatch("replace", attributes) + + for (endpoint in endpoints) { + // First try ADD (creates /attributes if missing) + if (tryPatchAttributes(endpoint, addBody)) return + // Then try REPLACE (updates if it already exists) + if (tryPatchAttributes(endpoint, replaceBody)) return + } + + logger.info("Could not set admin attributes via any Security route; continuing without attributes.") + } + @Before protected fun disableIndexStateManagementJitter() { // jitter would add a test-breaking delay to the integration tests diff --git a/src/test/resources/mappings/cached-opendistro-ism-config.json b/src/test/resources/mappings/cached-opendistro-ism-config.json index 845bfe34c..68958f5e4 100644 --- a/src/test/resources/mappings/cached-opendistro-ism-config.json +++ b/src/test/resources/mappings/cached-opendistro-ism-config.json @@ -1,6 +1,6 @@ { "_meta" : { - "schema_version": 24 + "schema_version": 25 }, "dynamic": "strict", "properties": { @@ -644,6 +644,10 @@ "type" : "keyword" } } + }, + "custom_attributes": { + "type": "object", + "dynamic": "true" } } }