diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolver.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolver.java index 9b9d69ba7461f..bbdd0d7cd658f 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolver.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolver.java @@ -23,6 +23,7 @@ import org.elasticsearch.action.ingest.SimulatePipelineAction; import org.elasticsearch.common.Strings; import org.elasticsearch.common.util.Maps; +import org.elasticsearch.synonyms.SynonymsAPI; import org.elasticsearch.transport.RemoteClusterService; import org.elasticsearch.transport.TcpTransport; import org.elasticsearch.transport.TransportRequest; @@ -154,6 +155,11 @@ public class ClusterPrivilegeResolver { private static final Set READ_SLM_PATTERN = Set.of(GetSnapshotLifecycleAction.NAME, GetStatusAction.NAME); private static final Set MANAGE_SEARCH_APPLICATION_PATTERN = Set.of("cluster:admin/xpack/application/search_application/*"); + private static final Set MANAGE_SEARCH_SYNONYMS_PATTERN = Set.of( + "cluster:admin/synonyms/*", + "cluster:admin/synonyms_sets/*", + "cluster:admin/synonym_rules/*" + ); private static final Set CROSS_CLUSTER_SEARCH_PATTERN = Set.of( RemoteClusterService.REMOTE_CLUSTER_HANDSHAKE_ACTION_NAME, @@ -277,6 +283,10 @@ public class ClusterPrivilegeResolver { "manage_search_application", MANAGE_SEARCH_APPLICATION_PATTERN ); + public static final NamedClusterPrivilege MANAGE_SEARCH_SYNONYMS = new ActionClusterPrivilege( + "manage_search_synonyms", + MANAGE_SEARCH_SYNONYMS_PATTERN + ); public static final NamedClusterPrivilege MANAGE_BEHAVIORAL_ANALYTICS = new ActionClusterPrivilege( "manage_behavioral_analytics", MANAGE_BEHAVIORAL_ANALYTICS_PATTERN @@ -343,6 +353,7 @@ public class ClusterPrivilegeResolver { MANAGE_LOGSTASH_PIPELINES, CANCEL_TASK, MANAGE_SEARCH_APPLICATION, + SynonymsAPI.isEnabled() ? MANAGE_SEARCH_SYNONYMS : null, MANAGE_BEHAVIORAL_ANALYTICS, POST_BEHAVIORAL_ANALYTICS_EVENT, TcpTransport.isUntrustedRemoteClusterEnabled() ? CROSS_CLUSTER_SEARCH : null, diff --git a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/privileges/11_builtin.yml b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/privileges/11_builtin.yml index e17d429e35248..7467d1760efc2 100644 --- a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/privileges/11_builtin.yml +++ b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/privileges/11_builtin.yml @@ -15,5 +15,5 @@ setup: # This is fragile - it needs to be updated every time we add a new cluster/index privilege # I would much prefer we could just check that specific entries are in the array, but we don't have # an assertion for that - - length: { "cluster" : 48 } + - length: { "cluster" : 49 } - length: { "index" : 22 } diff --git a/x-pack/qa/core-rest-tests-with-security/build.gradle b/x-pack/qa/core-rest-tests-with-security/build.gradle index 55476fe2f2c71..778af00403ad0 100644 --- a/x-pack/qa/core-rest-tests-with-security/build.gradle +++ b/x-pack/qa/core-rest-tests-with-security/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'elasticsearch.internal-yaml-rest-test' +import org.elasticsearch.gradle.internal.info.BuildParams dependencies { testImplementation project(':x-pack:qa') @@ -20,9 +21,15 @@ restResources { } tasks.named("yamlRestTest").configure { - systemProperty 'tests.rest.blacklist', - [ - 'index/10_with_id/Index with ID', - 'indices.get_alias/10_basic/Get alias against closed indices' - ].join(',') + ArrayList blacklist = [ + 'index/10_with_id/Index with ID', + 'indices.get_alias/10_basic/Get alias against closed indices' + ]; + if (BuildParams.isSnapshotBuild() == false) { + blacklist += [ + 'synonyms_privileges/10_synonyms_with_privileges/*', + 'synonyms_privileges/20_synonyms_no_privileges/*' + ]; + } + systemProperty 'tests.rest.blacklist', blacklist.join(',') } diff --git a/x-pack/qa/core-rest-tests-with-security/src/yamlRestTest/resources/rest-api-spec/test/synonyms_privileges/10_synonyms_with_privileges.yml b/x-pack/qa/core-rest-tests-with-security/src/yamlRestTest/resources/rest-api-spec/test/synonyms_privileges/10_synonyms_with_privileges.yml new file mode 100644 index 0000000000000..df6511736d7cb --- /dev/null +++ b/x-pack/qa/core-rest-tests-with-security/src/yamlRestTest/resources/rest-api-spec/test/synonyms_privileges/10_synonyms_with_privileges.yml @@ -0,0 +1,89 @@ +setup: + - skip: + features: headers + version: " - 8.9.99" + reason: Introduced in 8.10.0 + + - do: + security.put_user: + username: "synonyms-user" + body: + password: "synonyms-user-password" + roles : [ "synonyms-role" ] + + - do: + security.put_role: + name: "synonyms-role" + body: + cluster: ["manage_search_synonyms"] + +--- +teardown: + - do: + security.delete_user: + username: "synonyms-user" + ignore: 404 + - do: + security.delete_role: + name: "synonyms-role" + ignore: 404 + +--- +"Check synonyms set operations - with manage_search_synonyms privilege": + - do: + headers: { Authorization: "Basic c3lub255bXMtdXNlcjpzeW5vbnltcy11c2VyLXBhc3N3b3Jk" } # synonyms-user + synonyms.put: + synonyms_set: test-synonyms + body: + synonyms_set: + - synonyms: "hello, hi" + id: "test-id" + + - match: { result: "created" } + + - do: + headers: { Authorization: "Basic c3lub255bXMtdXNlcjpzeW5vbnltcy11c2VyLXBhc3N3b3Jk" } # synonyms-user + synonyms.get: + synonyms_set: test-synonyms + + - match: + count: 1 + + - do: + headers: { Authorization: "Basic c3lub255bXMtdXNlcjpzeW5vbnltcy11c2VyLXBhc3N3b3Jk" } # synonyms-user + synonyms_sets.get: { } + + - match: + count: 1 + + - do: + headers: { Authorization: "Basic c3lub255bXMtdXNlcjpzeW5vbnltcy11c2VyLXBhc3N3b3Jk" } # synonyms-user + synonym_rule.put: + synonyms_set: "test-synonyms" + synonym_rule: "test-id-0" + body: + synonyms: "i-phone, iphone" + + - match: { result: "created" } + + - do: + headers: { Authorization: "Basic c3lub255bXMtdXNlcjpzeW5vbnltcy11c2VyLXBhc3N3b3Jk" } # synonyms-user + synonym_rule.get: + synonyms_set: "test-synonyms" + synonym_rule: "test-id-0" + + - match: {id: "test-id-0"} + + - do: + headers: { Authorization: "Basic c3lub255bXMtdXNlcjpzeW5vbnltcy11c2VyLXBhc3N3b3Jk" } # synonyms-user + synonym_rule.delete: + synonyms_set: test-synonyms + synonym_rule: test-id-0 + + - do: + headers: { Authorization: "Basic c3lub255bXMtdXNlcjpzeW5vbnltcy11c2VyLXBhc3N3b3Jk" } # synonyms-user + synonyms.delete: + synonyms_set: test-synonyms + + - match: + acknowledged: true diff --git a/x-pack/qa/core-rest-tests-with-security/src/yamlRestTest/resources/rest-api-spec/test/synonyms_privileges/20_synonyms_no_privileges.yml b/x-pack/qa/core-rest-tests-with-security/src/yamlRestTest/resources/rest-api-spec/test/synonyms_privileges/20_synonyms_no_privileges.yml new file mode 100644 index 0000000000000..5489a6f02fff0 --- /dev/null +++ b/x-pack/qa/core-rest-tests-with-security/src/yamlRestTest/resources/rest-api-spec/test/synonyms_privileges/20_synonyms_no_privileges.yml @@ -0,0 +1,93 @@ +setup: + - skip: + features: headers + version: " - 8.9.99" + reason: Introduced in 8.10.0 + + - do: + security.put_user: + username: "non-synonyms-user" + body: + password: "non-synonyms-user-password" + roles : [ "non-synonyms-role" ] + + - do: + security.put_role: + name: "non-synonyms-role" + body: + indices: + - names: ["*"] + privileges: [ "manage", "write", "read" ] +--- +teardown: + - do: + security.delete_user: + username: "non-synonyms-user" + ignore: 404 + - do: + security.delete_role: + name: "non-synonyms-role" + ignore: 404 + +--- +"Create synonyms set - no manage_search_synonyms privilege": + - do: + catch: "forbidden" + headers: { Authorization: "Basic bm9uLXN5bm9ueW1zLXVzZXI6bm9uLXN5bm9ueW1zLXVzZXItcGFzc3dvcmQ=" } # non-synonyms-user + synonyms.put: + synonyms_set: test-synonyms + body: + synonyms_set: + - synonyms: "hello, hi" + id: "test-id" + +--- +"Get synonyms set - no manage_search_synonyms privilege": + - do: + catch: "forbidden" + headers: { Authorization: "Basic bm9uLXN5bm9ueW1zLXVzZXI6bm9uLXN5bm9ueW1zLXVzZXItcGFzc3dvcmQ=" } # non-synonyms-user + synonyms.get: + synonyms_set: test-synonyms + +--- +"Delete synonyms set - no manage_search_synonyms privilege": + - do: + catch: "forbidden" + headers: { Authorization: "Basic bm9uLXN5bm9ueW1zLXVzZXI6bm9uLXN5bm9ueW1zLXVzZXItcGFzc3dvcmQ=" } # non-synonyms-user + synonyms.delete: + synonyms_set: test-synonyms +--- +"List synonyms sets - no manage_search_synonyms privilege": + - do: + catch: "forbidden" + headers: { Authorization: "Basic bm9uLXN5bm9ueW1zLXVzZXI6bm9uLXN5bm9ueW1zLXVzZXItcGFzc3dvcmQ=" } # non-synonyms-user + synonyms_sets.get: { } + +--- +"Update a synonyms rule - no manage_search_synonyms privilege": + - do: + catch: "forbidden" + headers: { Authorization: "Basic bm9uLXN5bm9ueW1zLXVzZXI6bm9uLXN5bm9ueW1zLXVzZXItcGFzc3dvcmQ=" } # non-synonyms-user + synonym_rule.put: + synonyms_set: "test-synonyms" + synonym_rule: "test-id-2" + body: + synonyms: "bye, goodbye, seeya" + +--- +"Get a synonym rule - no manage_search_synonyms privilege": + - do: + catch: "forbidden" + headers: { Authorization: "Basic bm9uLXN5bm9ueW1zLXVzZXI6bm9uLXN5bm9ueW1zLXVzZXItcGFzc3dvcmQ=" } # non-synonyms-user + synonym_rule.get: + synonyms_set: "test-synonyms" + synonym_rule: "test-id-2" + +--- +"Delete synonym rule - no manage_search_synonyms privilege": + - do: + catch: "forbidden" + headers: { Authorization: "Basic bm9uLXN5bm9ueW1zLXVzZXI6bm9uLXN5bm9ueW1zLXVzZXItcGFzc3dvcmQ=" } # non-synonyms-user + synonym_rule.delete: + synonyms_set: test-synonyms + synonym_rule: test-id-2