From c40272394831c0e78e0a4aef11c36649bbc36838 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Tue, 13 Sep 2022 20:09:31 +0300 Subject: [PATCH] Introduce new `read_security` cluster privilege (#89790) This introduces a new built-in cluster privilege that allows all read-only security-related operations. It also allows checking the user and user profile privileges with the "has privilege" APIs. Resolves #89245 --- docs/changelog/89790.yaml | 6 ++ .../rest-api/security/get-api-keys.asciidoc | 5 +- .../security/get-app-privileges.asciidoc | 6 +- .../security/get-builtin-privileges.asciidoc | 5 +- .../security/get-role-mappings.asciidoc | 14 +-- .../en/rest-api/security/get-roles.asciidoc | 11 ++- .../security/get-service-credentials.asciidoc | 9 +- .../security/get-user-profile.asciidoc | 5 +- .../en/rest-api/security/get-users.asciidoc | 12 +-- .../has-privileges-user-profile.asciidoc | 4 +- .../rest-api/security/query-api-key.asciidoc | 6 +- .../security/suggest-user-profile.asciidoc | 4 +- .../authorization/privileges.asciidoc | 16 +++- .../privilege/ClusterPrivilegeResolver.java | 33 +++++++ .../user/HasPrivilegesRequestTests.java | 1 + .../permission/ClusterPermissionTests.java | 24 ++++- .../ClusterPrivilegeResolverTests.java | 10 ++- .../authz/privilege/PrivilegeTests.java | 90 +++++++++++++++++++ .../security/authc/ApiKeyIntegTests.java | 37 ++++++-- .../test/privileges/11_builtin.yml | 2 +- 20 files changed, 248 insertions(+), 52 deletions(-) create mode 100644 docs/changelog/89790.yaml diff --git a/docs/changelog/89790.yaml b/docs/changelog/89790.yaml new file mode 100644 index 0000000000000..bd66dda689164 --- /dev/null +++ b/docs/changelog/89790.yaml @@ -0,0 +1,6 @@ +pr: 89790 +summary: Introduce the new `read_security` cluster privilege +area: Authorization +type: feature +issues: + - 89245 diff --git a/x-pack/docs/en/rest-api/security/get-api-keys.asciidoc b/x-pack/docs/en/rest-api/security/get-api-keys.asciidoc index a2e72b7841f3c..a6dc4b2ab390d 100644 --- a/x-pack/docs/en/rest-api/security/get-api-keys.asciidoc +++ b/x-pack/docs/en/rest-api/security/get-api-keys.asciidoc @@ -15,9 +15,10 @@ Retrieves information for one or more API keys. [[security-api-get-api-key-prereqs]] ==== {api-prereq-title} -* To use this API, you must have at least the `manage_own_api_key` cluster privilege. +* To use this API, you must have at least the `manage_own_api_key` or the `read_security` +cluster privileges. * If you have only the `manage_own_api_key` privilege, this API returns only -the API keys that you own. If you have the `manage_api_key` or greater +the API keys that you own. If you have `read_security`, `manage_api_key` or greater privileges (including `manage_security`), this API returns all API keys regardless of ownership. diff --git a/x-pack/docs/en/rest-api/security/get-app-privileges.asciidoc b/x-pack/docs/en/rest-api/security/get-app-privileges.asciidoc index 98020b1558d9f..f0f3f1b69071c 100644 --- a/x-pack/docs/en/rest-api/security/get-app-privileges.asciidoc +++ b/x-pack/docs/en/rest-api/security/get-app-privileges.asciidoc @@ -14,7 +14,7 @@ Retrieves <>. `GET /_security/privilege/` + -`GET /_security/privilege//` +`GET /_security/privilege//` [[security-api-get-privileges-prereqs]] @@ -22,7 +22,7 @@ Retrieves <>. To use this API, you must have either: -- the `manage_security` cluster privilege (or a greater privilege such as `all`); _or_ +- the `read_security` cluster privilege (or a greater privilege such as `manage_security` or `all`); _or_ - the _"Manage Application Privileges"_ global privilege for the application being referenced in the request @@ -51,7 +51,7 @@ To check a user's application privileges, use the [[security-api-get-privileges-example]] ==== {api-examples-title} -The following example retrieves information about the `read` privilege for the +The following example retrieves information about the `read` privilege for the `app01` application: [source,console] diff --git a/x-pack/docs/en/rest-api/security/get-builtin-privileges.asciidoc b/x-pack/docs/en/rest-api/security/get-builtin-privileges.asciidoc index 17fbb90b6d086..b044cb89a968f 100644 --- a/x-pack/docs/en/rest-api/security/get-builtin-privileges.asciidoc +++ b/x-pack/docs/en/rest-api/security/get-builtin-privileges.asciidoc @@ -19,8 +19,8 @@ available in this version of {es}. [[security-api-get-builtin-privileges-prereqs]] ==== {api-prereq-title} -* To use this API, you must have - the `manage_security` cluster privilege -(or a greater privilege such as `all`). +* To use this API, you must have the `read_security` cluster privilege +(or a greater privilege such as `manage_security` or `all`). [[security-api-get-builtin-privileges-desc]] ==== {api-description-title} @@ -102,6 +102,7 @@ A successful call returns an object with "cluster" and "index" fields. "read_ccr", "read_ilm", "read_pipeline", + "read_security", "read_slm", "transport_client" ], diff --git a/x-pack/docs/en/rest-api/security/get-role-mappings.asciidoc b/x-pack/docs/en/rest-api/security/get-role-mappings.asciidoc index 6fa79fe8a03f8..8272ec4d015a8 100644 --- a/x-pack/docs/en/rest-api/security/get-role-mappings.asciidoc +++ b/x-pack/docs/en/rest-api/security/get-role-mappings.asciidoc @@ -12,17 +12,17 @@ Retrieves role mappings. `GET /_security/role_mapping` + -`GET /_security/role_mapping/` +`GET /_security/role_mapping/` [[security-api-get-role-mapping-prereqs]] ==== {api-prereq-title} -* To use this API, you must have at least the `manage_security` cluster privilege. +* To use this API, you must have at least the `read_security` cluster privilege. [[security-api-get-role-mapping-desc]] ==== {api-description-title} -Role mappings define which roles are assigned to each user. For more information, +Role mappings define which roles are assigned to each user. For more information, see <>. The role mapping APIs are generally the preferred way to manage role mappings @@ -36,16 +36,16 @@ in role mapping files. `name`:: (Optional, string) The distinct name that identifies the role mapping. The name is used solely as an identifier to facilitate interaction via the API; it does - not affect the behavior of the mapping in any way. You can specify multiple + not affect the behavior of the mapping in any way. You can specify multiple mapping names as a comma-separated list. If you do not specify this - parameter, the API returns information about all role mappings. + parameter, the API returns information about all role mappings. [[security-api-get-role-mapping-response-body]] ==== {api-response-body-title} A successful call retrieves an object, where the keys are the -names of the request mappings, and the values are the JSON representation of -those mappings. For more information, see +names of the request mappings, and the values are the JSON representation of +those mappings. For more information, see <>. [[security-api-get-role-mapping-response-codes]] diff --git a/x-pack/docs/en/rest-api/security/get-roles.asciidoc b/x-pack/docs/en/rest-api/security/get-roles.asciidoc index 46016a1d9d72f..80f0fd587aae8 100644 --- a/x-pack/docs/en/rest-api/security/get-roles.asciidoc +++ b/x-pack/docs/en/rest-api/security/get-roles.asciidoc @@ -17,8 +17,7 @@ Retrieves roles in the native realm. [[security-api-get-role-prereqs]] ==== {api-prereq-title} -* To use this API, you must have at least the `manage_security` cluster -privilege. +* To use this API, you must have at least the `read_security` cluster privilege. [[security-api-get-role-desc]] ==== {api-description-title} @@ -31,10 +30,10 @@ API cannot retrieve roles that are defined in roles files. ==== {api-path-parms-title} `name`:: - (Optional, string) The name of the role. You can specify multiple roles as a - comma-separated list. If you do not specify this parameter, the API + (Optional, string) The name of the role. You can specify multiple roles as a + comma-separated list. If you do not specify this parameter, the API returns information about all roles. - + [[security-api-get-role-response-body]] ==== {api-response-body-title} @@ -49,7 +48,7 @@ If the role is not defined in the native realm, the request returns 404. [[security-api-get-role-example]] ==== {api-examples-title} -The following example retrieves information about the `my_admin_role` role in +The following example retrieves information about the `my_admin_role` role in the native realm: [source,console] diff --git a/x-pack/docs/en/rest-api/security/get-service-credentials.asciidoc b/x-pack/docs/en/rest-api/security/get-service-credentials.asciidoc index e30a4d18c67ee..3da6c3d860558 100644 --- a/x-pack/docs/en/rest-api/security/get-service-credentials.asciidoc +++ b/x-pack/docs/en/rest-api/security/get-service-credentials.asciidoc @@ -16,16 +16,17 @@ Retrieves all service credentials for a <>. [[security-api-get-service-credentials-prereqs]] ==== {api-prereq-title} -* To use this API, you must have at least the `manage_service_account` -<>. +* To use this API, you must have at least the `read_security` +<> (or a greater privilege +such as `manage_service_account` or `manage_security`). [[security-api-get-service-credentials-desc]] ==== {api-description-title} Use this API to retrieve a list of credentials for a service account. The response includes service account tokens that were created with the -<< create service account API >> as well as file-backed tokens from all -nodes of the cluster. +<> +as well as file-backed tokens from all nodes of the cluster. NOTE: For tokens backed by the `service_tokens` file, the API collects them from all nodes of the cluster. Tokens with the same name from diff --git a/x-pack/docs/en/rest-api/security/get-user-profile.asciidoc b/x-pack/docs/en/rest-api/security/get-user-profile.asciidoc index 91966a7137e74..ff8b3d278795a 100644 --- a/x-pack/docs/en/rest-api/security/get-user-profile.asciidoc +++ b/x-pack/docs/en/rest-api/security/get-user-profile.asciidoc @@ -17,8 +17,9 @@ Retrieves user profiles using a list of unique profile ID. [[security-api-get-user-profile-prereqs]] ==== {api-prereq-title} -* To use this API, you must have _at least_ the `manage_user_profile` cluster privilege. - +To use this API, you must have _at least_ the `read_security` +<> (or a greater privilege +such as `manage_user_profile` or `manage_security`). [[security-api-get-user-profile-desc]] ==== {api-description-title} diff --git a/x-pack/docs/en/rest-api/security/get-users.asciidoc b/x-pack/docs/en/rest-api/security/get-users.asciidoc index f60082be0fc0c..05c4488e524c0 100644 --- a/x-pack/docs/en/rest-api/security/get-users.asciidoc +++ b/x-pack/docs/en/rest-api/security/get-users.asciidoc @@ -5,7 +5,7 @@ Get users ++++ -Retrieves information about users in the native realm and built-in users. +Retrieves information about users in the native realm and built-in users. [[security-api-get-user-request]] @@ -13,19 +13,19 @@ Retrieves information about users in the native realm and built-in users. `GET /_security/user` + -`GET /_security/user/` +`GET /_security/user/` [[security-api-get-user-prereqs]] ==== {api-prereq-title} -* To use this API, you must have at least the `manage_security` cluster privilege. +* To use this API, you must have at least the `read_security` cluster privilege. [[security-api-get-user-desc]] ==== {api-description-title} -For more information about the native realm, see -<> and <>. +For more information about the native realm, see +<> and <>. [[security-api-get-user-path-params]] ==== {api-path-parms-title} @@ -60,7 +60,7 @@ GET /_security/user/jacknich [source,console-result] -------------------------------------------------- -{ +{ "jacknich": { "username": "jacknich", "roles": [ diff --git a/x-pack/docs/en/rest-api/security/has-privileges-user-profile.asciidoc b/x-pack/docs/en/rest-api/security/has-privileges-user-profile.asciidoc index 3bb4a19787952..e87d620c11c82 100644 --- a/x-pack/docs/en/rest-api/security/has-privileges-user-profile.asciidoc +++ b/x-pack/docs/en/rest-api/security/has-privileges-user-profile.asciidoc @@ -21,7 +21,9 @@ have all the requested privileges. [[security-api-has-privileges-user-profile-prereqs]] ==== {api-prereq-title} -To use this API, you must have the `manage_user_profile` cluster privilege. +To use this API, you must have _at least_ the `read_security` +<> (or a greater privilege +such as `manage_user_profile` or `manage_security`). [[security-api-has-privileges-user-profile-desc]] ==== {api-description-title} diff --git a/x-pack/docs/en/rest-api/security/query-api-key.asciidoc b/x-pack/docs/en/rest-api/security/query-api-key.asciidoc index 163e6937903a9..f7b315d5db904 100644 --- a/x-pack/docs/en/rest-api/security/query-api-key.asciidoc +++ b/x-pack/docs/en/rest-api/security/query-api-key.asciidoc @@ -19,10 +19,10 @@ in a <> fashion. [[security-api-query-api-key-prereqs]] ==== {api-prereq-title} -* To use this API, you must have at least the `manage_own_api_key` cluster -privilege. +* To use this API, you must have at least the `manage_own_api_key` or the `read_security` +cluster privileges. * If you have only the `manage_own_api_key` privilege, this API returns only -the API keys that you own. If you have the `manage_api_key` or greater +the API keys that you own. If you have the `read_security`, `manage_api_key` or greater privileges (including `manage_security`), this API returns all API keys regardless of ownership. diff --git a/x-pack/docs/en/rest-api/security/suggest-user-profile.asciidoc b/x-pack/docs/en/rest-api/security/suggest-user-profile.asciidoc index 5c3f71f641d73..3c99701792434 100644 --- a/x-pack/docs/en/rest-api/security/suggest-user-profile.asciidoc +++ b/x-pack/docs/en/rest-api/security/suggest-user-profile.asciidoc @@ -19,7 +19,9 @@ Get suggestions for user profiles that match specified search criteria. [[security-api-suggest-user-profile-prereqs]] ==== {api-prereq-title} -To use this API, you must have the `manage_user_profile` cluster privilege. +To use this API, you must have _at least_ the `read_security` +<> (or a greater privilege +such as `manage_user_profile` or `manage_security`). [[security-api-suggest-user-profile-query-params]] ==== {api-query-parms-title} diff --git a/x-pack/docs/en/security/authorization/privileges.asciidoc b/x-pack/docs/en/security/authorization/privileges.asciidoc index f6e7982ab2005..65f7d87bc76b5 100644 --- a/x-pack/docs/en/security/authorization/privileges.asciidoc +++ b/x-pack/docs/en/security/authorization/privileges.asciidoc @@ -32,7 +32,10 @@ ability to manage security. `manage_api_key`:: All security-related operations on {es} API keys including <>, -<>, and +<>, +<>, +<>, +<>, and <>. + -- @@ -89,7 +92,10 @@ to initiate and manage OpenID Connect authentication on behalf of other users. All security-related operations on {es} API keys that are owned by the current authenticated user. The operations include <>, -<>, and +<>, +<>, +<>, +<>, and <>. `manage_pipeline`:: @@ -176,6 +182,12 @@ Read-only access to ingest pipline (get, simulate). All read-only {slm-init} actions, such as getting policies and checking the {slm-init} status. +`read_security`:: +All read-only security-related operations, such as getting users, user profiles, +{es} API keys, {es} service accounts, roles and role mappings. +Allows <> and <> +on all {es} API keys. + `transport_client`:: All privileges necessary for a transport client to connect. Required by the remote cluster to enable <>. 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 7a66e6c1d476d..aba28556a4d96 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 @@ -28,11 +28,24 @@ import org.elasticsearch.xpack.core.ilm.action.StartILMAction; import org.elasticsearch.xpack.core.ilm.action.StopILMAction; import org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationAction; +import org.elasticsearch.xpack.core.security.action.apikey.GetApiKeyAction; import org.elasticsearch.xpack.core.security.action.apikey.GrantApiKeyAction; +import org.elasticsearch.xpack.core.security.action.apikey.QueryApiKeyAction; +import org.elasticsearch.xpack.core.security.action.privilege.GetBuiltinPrivilegesAction; +import org.elasticsearch.xpack.core.security.action.privilege.GetPrivilegesAction; +import org.elasticsearch.xpack.core.security.action.profile.GetProfilesAction; +import org.elasticsearch.xpack.core.security.action.profile.SuggestProfilesAction; +import org.elasticsearch.xpack.core.security.action.role.GetRolesAction; +import org.elasticsearch.xpack.core.security.action.rolemapping.GetRoleMappingsAction; import org.elasticsearch.xpack.core.security.action.saml.SamlSpMetadataAction; +import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountAction; +import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountCredentialsAction; import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenAction; import org.elasticsearch.xpack.core.security.action.token.RefreshTokenAction; +import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesAction; +import org.elasticsearch.xpack.core.security.action.user.GetUsersAction; import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesAction; +import org.elasticsearch.xpack.core.security.action.user.ProfileHasPrivilegesAction; import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.slm.action.GetSnapshotLifecycleAction; @@ -174,6 +187,25 @@ public class ClusterPrivilegeResolver { ALL_SECURITY_PATTERN, Set.of(DelegatePkiAuthenticationAction.NAME) ); + public static final NamedClusterPrivilege READ_SECURITY = new ActionClusterPrivilege( + "read_security", + Set.of( + GetApiKeyAction.NAME, + QueryApiKeyAction.NAME, + GetBuiltinPrivilegesAction.NAME, + GetPrivilegesAction.NAME, + GetProfilesAction.NAME, + ProfileHasPrivilegesAction.NAME, + SuggestProfilesAction.NAME, + GetRolesAction.NAME, + GetRoleMappingsAction.NAME, + GetServiceAccountAction.NAME, + GetServiceAccountCredentialsAction.NAME + "*", + GetUsersAction.NAME, + GetUserPrivilegesAction.NAME, // normally authorized under the "same-user" authz check, but added here for uniformity + HasPrivilegesAction.NAME + ) + ); public static final NamedClusterPrivilege MANAGE_SAML = new ActionClusterPrivilege("manage_saml", MANAGE_SAML_PATTERN); public static final NamedClusterPrivilege MANAGE_OIDC = new ActionClusterPrivilege("manage_oidc", MANAGE_OIDC_PATTERN); public static final NamedClusterPrivilege MANAGE_API_KEY = new ActionClusterPrivilege("manage_api_key", MANAGE_API_KEY_PATTERN); @@ -239,6 +271,7 @@ public class ClusterPrivilegeResolver { READ_PIPELINE, TRANSPORT_CLIENT, MANAGE_SECURITY, + READ_SECURITY, MANAGE_SAML, MANAGE_OIDC, MANAGE_API_KEY, diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/user/HasPrivilegesRequestTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/user/HasPrivilegesRequestTests.java index a912423241177..55ca8e420e3ba 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/user/HasPrivilegesRequestTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/user/HasPrivilegesRequestTests.java @@ -94,6 +94,7 @@ private HasPrivilegesRequest randomRequest() { ClusterPrivilegeResolver.MANAGE, ClusterPrivilegeResolver.MANAGE_ML, ClusterPrivilegeResolver.MANAGE_SECURITY, + ClusterPrivilegeResolver.READ_SECURITY, ClusterPrivilegeResolver.MANAGE_PIPELINE, ClusterPrivilegeResolver.ALL ) diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermissionTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermissionTests.java index 50948c8b59824..7ff09da2f83f5 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermissionTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermissionTests.java @@ -51,7 +51,7 @@ public void testClusterPermissionBuilder() { assertNotNull(builder); assertThat(builder.build(), is(ClusterPermission.NONE)); - builder = ClusterPrivilegeResolver.MANAGE_SECURITY.buildPermission(builder); + builder = ClusterPrivilegeResolver.READ_SECURITY.buildPermission(builder); builder = ClusterPrivilegeResolver.MANAGE_ILM.buildPermission(builder); final MockConfigurableClusterPrivilege mockConfigurableClusterPrivilege1 = new MockConfigurableClusterPrivilege( r -> r == mockTransportRequest @@ -69,7 +69,7 @@ public void testClusterPermissionBuilder() { assertThat( privileges, containsInAnyOrder( - ClusterPrivilegeResolver.MANAGE_SECURITY, + ClusterPrivilegeResolver.READ_SECURITY, ClusterPrivilegeResolver.MANAGE_ILM, mockConfigurableClusterPrivilege1, mockConfigurableClusterPrivilege2 @@ -156,7 +156,7 @@ public void testNoneClusterPermissionIsImpliedByNone() { public void testNoneClusterPermissionIsImpliedByAny() { ClusterPermission.Builder builder = ClusterPermission.builder(); - builder = ClusterPrivilegeResolver.MANAGE_SECURITY.buildPermission(builder); + builder = ClusterPrivilegeResolver.READ_SECURITY.buildPermission(builder); builder = ClusterPrivilegeResolver.MANAGE_ILM.buildPermission(builder); final MockConfigurableClusterPrivilege mockConfigurableClusterPrivilege1 = new MockConfigurableClusterPrivilege( r -> r == mockTransportRequest @@ -253,14 +253,30 @@ public void testClusterPermissionSubsetIsImpliedByAllClusterPermission() { assertThat(allClusterPermission.implies(otherClusterPermission), is(true)); } + public void testReadSecurityPrivilegeNoImplyApiKeyManagement() { + assertFalse(ClusterPrivilegeResolver.READ_SECURITY.permission().implies(ClusterPrivilegeResolver.MANAGE_API_KEY.permission())); + assertFalse(ClusterPrivilegeResolver.MANAGE_API_KEY.permission().implies(ClusterPrivilegeResolver.READ_SECURITY.permission())); + assertFalse(ClusterPrivilegeResolver.READ_SECURITY.permission().implies(ClusterPrivilegeResolver.MANAGE_OWN_API_KEY.permission())); + assertFalse(ClusterPrivilegeResolver.MANAGE_OWN_API_KEY.permission().implies(ClusterPrivilegeResolver.READ_SECURITY.permission())); + } + public void testImpliesOnSecurityPrivilegeHierarchy() { - final List highToLow = List.of( + List highToLow = List.of( ClusterPrivilegeResolver.ALL.permission(), ClusterPrivilegeResolver.MANAGE_SECURITY.permission(), ClusterPrivilegeResolver.MANAGE_API_KEY.permission(), ClusterPrivilegeResolver.MANAGE_OWN_API_KEY.permission() ); + assertImpliesHierarchy(highToLow); + highToLow = List.of( + ClusterPrivilegeResolver.ALL.permission(), + ClusterPrivilegeResolver.MANAGE_SECURITY.permission(), + ClusterPrivilegeResolver.READ_SECURITY.permission() + ); + assertImpliesHierarchy(highToLow); + } + private void assertImpliesHierarchy(List highToLow) { for (int i = 0; i < highToLow.size(); i++) { ClusterPermission high = highToLow.get(i); for (int j = i; j < highToLow.size(); j++) { diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolverTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolverTests.java index 17d75427dbb8c..0bc3d5d8eab46 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolverTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolverTests.java @@ -18,7 +18,7 @@ public class ClusterPrivilegeResolverTests extends ESTestCase { - public void testSortByAccessLevel() throws Exception { + public void testSortByAccessLevel() { final List privileges = new ArrayList<>( List.of( ClusterPrivilegeResolver.ALL, @@ -26,16 +26,20 @@ public void testSortByAccessLevel() throws Exception { ClusterPrivilegeResolver.MANAGE, ClusterPrivilegeResolver.MANAGE_OWN_API_KEY, ClusterPrivilegeResolver.MANAGE_API_KEY, + ClusterPrivilegeResolver.READ_SECURITY, ClusterPrivilegeResolver.MANAGE_SECURITY ) ); Collections.shuffle(privileges, random()); final SortedMap sorted = ClusterPrivilegeResolver.sortByAccessLevel(privileges); // This is: - // "manage_own_api_key", "monitor" (neither of which grant anything else in the list), sorted by name + // "manage_own_api_key", "monitor", "read_security" (neither of which grant anything else in the list), sorted by name // "manage" and "manage_api_key",(which each grant 1 other privilege in the list), sorted by name // "manage_security" and "all", sorted by access level ("all" implies "manage_security") - assertThat(sorted.keySet(), contains("manage_own_api_key", "monitor", "manage", "manage_api_key", "manage_security", "all")); + assertThat( + sorted.keySet(), + contains("manage_own_api_key", "monitor", "read_security", "manage", "manage_api_key", "manage_security", "all") + ); } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/PrivilegeTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/PrivilegeTests.java index a22718d323e9e..930386bef0e74 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/PrivilegeTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/PrivilegeTests.java @@ -22,6 +22,41 @@ import org.elasticsearch.xpack.core.enrich.action.ExecuteEnrichPolicyAction; import org.elasticsearch.xpack.core.enrich.action.GetEnrichPolicyAction; import org.elasticsearch.xpack.core.enrich.action.PutEnrichPolicyAction; +import org.elasticsearch.xpack.core.security.action.ClearSecurityCacheAction; +import org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationAction; +import org.elasticsearch.xpack.core.security.action.apikey.BulkUpdateApiKeyAction; +import org.elasticsearch.xpack.core.security.action.apikey.CreateApiKeyAction; +import org.elasticsearch.xpack.core.security.action.apikey.GetApiKeyAction; +import org.elasticsearch.xpack.core.security.action.apikey.InvalidateApiKeyAction; +import org.elasticsearch.xpack.core.security.action.apikey.QueryApiKeyAction; +import org.elasticsearch.xpack.core.security.action.apikey.UpdateApiKeyAction; +import org.elasticsearch.xpack.core.security.action.enrollment.KibanaEnrollmentAction; +import org.elasticsearch.xpack.core.security.action.enrollment.NodeEnrollmentAction; +import org.elasticsearch.xpack.core.security.action.privilege.GetBuiltinPrivilegesAction; +import org.elasticsearch.xpack.core.security.action.privilege.GetPrivilegesAction; +import org.elasticsearch.xpack.core.security.action.profile.ActivateProfileAction; +import org.elasticsearch.xpack.core.security.action.profile.GetProfilesAction; +import org.elasticsearch.xpack.core.security.action.profile.SetProfileEnabledAction; +import org.elasticsearch.xpack.core.security.action.profile.SuggestProfilesAction; +import org.elasticsearch.xpack.core.security.action.profile.UpdateProfileDataAction; +import org.elasticsearch.xpack.core.security.action.realm.ClearRealmCacheAction; +import org.elasticsearch.xpack.core.security.action.role.ClearRolesCacheAction; +import org.elasticsearch.xpack.core.security.action.role.DeleteRoleAction; +import org.elasticsearch.xpack.core.security.action.role.GetRolesAction; +import org.elasticsearch.xpack.core.security.action.role.PutRoleAction; +import org.elasticsearch.xpack.core.security.action.rolemapping.DeleteRoleMappingAction; +import org.elasticsearch.xpack.core.security.action.rolemapping.GetRoleMappingsAction; +import org.elasticsearch.xpack.core.security.action.rolemapping.PutRoleMappingAction; +import org.elasticsearch.xpack.core.security.action.service.CreateServiceAccountTokenAction; +import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountAction; +import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountCredentialsAction; +import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountNodesCredentialsAction; +import org.elasticsearch.xpack.core.security.action.user.DeleteUserAction; +import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesAction; +import org.elasticsearch.xpack.core.security.action.user.GetUsersAction; +import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesAction; +import org.elasticsearch.xpack.core.security.action.user.ProfileHasPrivilegesAction; +import org.elasticsearch.xpack.core.security.action.user.PutUserAction; import org.elasticsearch.xpack.core.security.authc.AuthenticationTestHelper; import org.elasticsearch.xpack.core.security.authz.permission.ClusterPermission; import org.elasticsearch.xpack.core.security.support.Automatons; @@ -197,6 +232,61 @@ public void testManageAutoscalingPrivilege() { verifyClusterActionAllowed(ClusterPrivilegeResolver.MANAGE_AUTOSCALING, "cluster:admin/autoscaling/get_decision"); } + public void testReadSecurityPrivilege() { + verifyClusterActionAllowed( + ClusterPrivilegeResolver.READ_SECURITY, + GetApiKeyAction.NAME, + QueryApiKeyAction.NAME, + GetBuiltinPrivilegesAction.NAME, + GetPrivilegesAction.NAME, + GetProfilesAction.NAME, + ProfileHasPrivilegesAction.NAME, + SuggestProfilesAction.NAME, + GetRolesAction.NAME, + GetRoleMappingsAction.NAME, + GetServiceAccountAction.NAME, + GetServiceAccountCredentialsAction.NAME, + GetUsersAction.NAME, + HasPrivilegesAction.NAME, + GetUserPrivilegesAction.NAME + ); + verifyClusterActionAllowed( + ClusterPrivilegeResolver.READ_SECURITY, + GetServiceAccountNodesCredentialsAction.NAME, + GetServiceAccountCredentialsAction.NAME + randomFrom("", "whatever") + ); + verifyClusterActionDenied( + ClusterPrivilegeResolver.READ_SECURITY, + PutUserAction.NAME, + DeleteUserAction.NAME, + PutRoleAction.NAME, + DeleteRoleAction.NAME, + PutRoleMappingAction.NAME, + DeleteRoleMappingAction.NAME, + CreateServiceAccountTokenAction.NAME, + CreateApiKeyAction.NAME, + InvalidateApiKeyAction.NAME, + ClusterHealthAction.NAME, + ClusterStateAction.NAME, + ClusterStatsAction.NAME, + NodeEnrollmentAction.NAME, + KibanaEnrollmentAction.NAME, + PutIndexTemplateAction.NAME, + GetIndexTemplatesAction.NAME, + ClusterRerouteAction.NAME, + ClusterUpdateSettingsAction.NAME, + ClearRealmCacheAction.NAME, + ClearSecurityCacheAction.NAME, + ClearRolesCacheAction.NAME, + UpdateApiKeyAction.NAME, + BulkUpdateApiKeyAction.NAME, + DelegatePkiAuthenticationAction.NAME, + ActivateProfileAction.NAME, + SetProfileEnabledAction.NAME, + UpdateProfileDataAction.NAME + ); + } + public void testManageUserProfilePrivilege() { verifyClusterActionAllowed( ClusterPrivilegeResolver.MANAGE_USER_PROFILE, diff --git a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/ApiKeyIntegTests.java b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/ApiKeyIntegTests.java index 93e5834b1e3e8..aa74814171b50 100644 --- a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/ApiKeyIntegTests.java +++ b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/ApiKeyIntegTests.java @@ -198,6 +198,8 @@ public String configRoles() { no_api_key_role: cluster: ["manage_token"] + read_security_role: + cluster: ["read_security"] manage_api_key_role: cluster: ["manage_api_key"] manage_own_api_key_role: @@ -214,6 +216,9 @@ public String configUsers() { + "user_with_no_api_key_role:" + usersPasswdHashed + "\n" + + "user_with_read_security_role:" + + usersPasswdHashed + + "\n" + "user_with_manage_api_key_role:" + usersPasswdHashed + "\n" @@ -226,6 +231,7 @@ public String configUsers() { public String configUsersRoles() { return super.configUsersRoles() + """ no_api_key_role:user_with_no_api_key_role + read_security_role:user_with_read_security_role manage_api_key_role:user_with_manage_api_key_role manage_own_api_key_role:user_with_manage_own_api_key_role """; @@ -1092,12 +1098,15 @@ public void testGetAllApiKeys() throws InterruptedException, ExecutionException List userWithManageOwnApiKeyRoleApiKeys = userWithManageOwnTuple.v1(); final Client client = client().filterWithHeader( - Collections.singletonMap("Authorization", basicAuthHeaderValue("user_with_manage_api_key_role", TEST_PASSWORD_SECURE_STRING)) + Collections.singletonMap( + "Authorization", + basicAuthHeaderValue( + randomFrom("user_with_read_security_role", "user_with_manage_api_key_role"), + TEST_PASSWORD_SECURE_STRING + ) + ) ); final boolean withLimitedBy = randomBoolean(); - PlainActionFuture listener = new PlainActionFuture<>(); - client.execute(GetApiKeyAction.INSTANCE, GetApiKeyRequest.builder().withLimitedBy(withLimitedBy).build(), listener); - GetApiKeyResponse response = listener.get(); int totalApiKeys = noOfSuperuserApiKeys + noOfApiKeysForUserWithManageApiKeyRole + noOfApiKeysForUserWithManageOwnApiKeyRole; List allApiKeys = new ArrayList<>(); Stream.of(defaultUserCreatedKeys, userWithManageApiKeyRoleApiKeys, userWithManageOwnApiKeyRoleApiKeys).forEach(allApiKeys::addAll); @@ -1128,7 +1137,7 @@ public void testGetAllApiKeys() throws InterruptedException, ExecutionException metadatas, List.of(DEFAULT_API_KEY_ROLE_DESCRIPTOR), expectedLimitedByRoleDescriptorsLookup, - response.getApiKeyInfos(), + getAllApiKeyInfo(client, withLimitedBy), allApiKeys.stream().map(CreateApiKeyResponse::getId).collect(Collectors.toSet()), null ); @@ -2682,6 +2691,24 @@ private ApiKey getApiKeyInfo(Client client, String apiKeyId, boolean withLimited } } + private ApiKey[] getAllApiKeyInfo(Client client, boolean withLimitedBy) { + if (randomBoolean()) { + final PlainActionFuture future = new PlainActionFuture<>(); + client.execute(GetApiKeyAction.INSTANCE, GetApiKeyRequest.builder().withLimitedBy(withLimitedBy).build(), future); + final GetApiKeyResponse getApiKeyResponse = future.actionGet(); + return getApiKeyResponse.getApiKeyInfos(); + } else { + final PlainActionFuture future = new PlainActionFuture<>(); + client.execute( + QueryApiKeyAction.INSTANCE, + new QueryApiKeyRequest(QueryBuilders.matchAllQuery(), null, 1000, null, null, withLimitedBy), + future + ); + final QueryApiKeyResponse queryApiKeyResponse = future.actionGet(); + return Arrays.stream(queryApiKeyResponse.getItems()).map(QueryApiKeyResponse.Item::getApiKey).toArray(ApiKey[]::new); + } + } + private ServiceWithNodeName getServiceWithNodeName() { final var nodeName = randomFrom(internalCluster().getNodeNames()); final var service = internalCluster().getInstance(ApiKeyService.class, nodeName); 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 0c8f990e4051b..be7d28af3ae42 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" : 42 } + - length: { "cluster" : 43 } - length: { "index" : 19 }