diff --git a/docs/changelog/89333.yaml b/docs/changelog/89333.yaml new file mode 100644 index 0000000000000..72ff86e5fa742 --- /dev/null +++ b/docs/changelog/89333.yaml @@ -0,0 +1,5 @@ +pr: 89333 +summary: Return 400 error for `GetUserPrivileges` call with API keys +area: Security +type: enhancement +issues: [] diff --git a/x-pack/plugin/security/qa/security-trial/src/javaRestTest/java/org/elasticsearch/xpack/security/apikey/ApiKeyRestIT.java b/x-pack/plugin/security/qa/security-trial/src/javaRestTest/java/org/elasticsearch/xpack/security/apikey/ApiKeyRestIT.java index ebc35a9b5a11d..880a4dcade3f0 100644 --- a/x-pack/plugin/security/qa/security-trial/src/javaRestTest/java/org/elasticsearch/xpack/security/apikey/ApiKeyRestIT.java +++ b/x-pack/plugin/security/qa/security-trial/src/javaRestTest/java/org/elasticsearch/xpack/security/apikey/ApiKeyRestIT.java @@ -12,10 +12,12 @@ import org.elasticsearch.client.Response; import org.elasticsearch.client.ResponseException; import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.core.Tuple; import org.elasticsearch.test.XContentTestUtils; import org.elasticsearch.xcontent.XContentParser; import org.elasticsearch.xcontent.XContentType; +import org.elasticsearch.xcontent.json.JsonXContent; import org.elasticsearch.xpack.core.security.action.apikey.ApiKey; import org.elasticsearch.xpack.core.security.action.apikey.GetApiKeyResponse; import org.elasticsearch.xpack.core.security.action.apikey.GrantApiKeyAction; @@ -490,6 +492,87 @@ public void testGrantorCannotUpdateApiKeyOfGrantTarget() throws IOException { ); } + public void testGetPrivilegesForApiKeyWorksIfItDoesNotHaveAssignedPrivileges() throws IOException { + final Request createApiKeyRequest = new Request("POST", "_security/api_key"); + if (randomBoolean()) { + createApiKeyRequest.setJsonEntity(""" + { "name": "k1" }"""); + } else { + createApiKeyRequest.setJsonEntity(""" + { + "name": "k1", + "role_descriptors": { } + }"""); + } + final Response createApiKeyResponse = adminClient().performRequest(createApiKeyRequest); + assertOK(createApiKeyResponse); + + final Request getPrivilegesRequest = new Request("GET", "_security/user/_privileges"); + getPrivilegesRequest.setOptions( + RequestOptions.DEFAULT.toBuilder().addHeader("Authorization", "ApiKey " + responseAsMap(createApiKeyResponse).get("encoded")) + ); + final Response getPrivilegesResponse = client().performRequest(getPrivilegesRequest); + assertOK(getPrivilegesResponse); + + assertThat(responseAsMap(getPrivilegesResponse), equalTo(XContentHelper.convertToMap(JsonXContent.jsonXContent, """ + { + "cluster": [ + "all" + ], + "global": [], + "indices": [ + { + "names": [ + "*" + ], + "privileges": [ + "all" + ], + "allow_restricted_indices": true + } + ], + "applications": [ + { + "application": "*", + "privileges": [ + "*" + ], + "resources": [ + "*" + ] + } + ], + "run_as": [ + "*" + ] + }""", false))); + } + + public void testGetPrivilegesForApiKeyThrows400IfItHasAssignedPrivileges() throws IOException { + final Request createApiKeyRequest = new Request("POST", "_security/api_key"); + createApiKeyRequest.setJsonEntity(""" + { + "name": "k1", + "role_descriptors": { "a": { "cluster": ["monitor"] } } + }"""); + final Response createApiKeyResponse = adminClient().performRequest(createApiKeyRequest); + assertOK(createApiKeyResponse); + + final Request getPrivilegesRequest = new Request("GET", "_security/user/_privileges"); + getPrivilegesRequest.setOptions( + RequestOptions.DEFAULT.toBuilder().addHeader("Authorization", "ApiKey " + responseAsMap(createApiKeyResponse).get("encoded")) + ); + final ResponseException e = expectThrows(ResponseException.class, () -> client().performRequest(getPrivilegesRequest)); + assertThat(e.getResponse().getStatusLine().getStatusCode(), equalTo(400)); + assertThat( + e.getMessage(), + containsString( + "Cannot retrieve privileges for API keys with assigned role descriptors. " + + "Please use the Get API key information API https://ela.st/es-api-get-api-key" + ) + ); + } + private void doTestAuthenticationWithApiKey(final String apiKeyName, final String apiKeyId, final String apiKeyEncoded) throws IOException { final var authenticateRequest = new Request("GET", "_security/_authenticate"); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java index 839d3a5437c39..e4c2c050d3d45 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java @@ -658,7 +658,20 @@ public void getUserPrivileges(AuthorizationInfo authorizationInfo, ActionListene ); } else { final Role role = ((RBACAuthorizationInfo) authorizationInfo).getRole(); - listener.onResponse(buildUserPrivilegesResponseObject(role)); + final GetUserPrivilegesResponse getUserPrivilegesResponse; + try { + getUserPrivilegesResponse = buildUserPrivilegesResponseObject(role); + } catch (UnsupportedOperationException e) { + listener.onFailure( + new IllegalArgumentException( + "Cannot retrieve privileges for API keys with assigned role descriptors. " + + "Please use the Get API key information API https://ela.st/es-api-get-api-key", + e + ) + ); + return; + } + listener.onResponse(getUserPrivilegesResponse); } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RBACEngineTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RBACEngineTests.java index ec4a6c7e6d0f0..d2376a0172023 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RBACEngineTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RBACEngineTests.java @@ -58,10 +58,14 @@ import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine.PrivilegesToCheck; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor.ApplicationResourcePrivileges; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor.IndicesPrivileges; +import org.elasticsearch.xpack.core.security.authz.permission.ApplicationPermission; +import org.elasticsearch.xpack.core.security.authz.permission.ClusterPermission; import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissions; import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsDefinition; +import org.elasticsearch.xpack.core.security.authz.permission.IndicesPermission; import org.elasticsearch.xpack.core.security.authz.permission.ResourcePrivileges; import org.elasticsearch.xpack.core.security.authz.permission.Role; +import org.elasticsearch.xpack.core.security.authz.permission.RunAsPermission; import org.elasticsearch.xpack.core.security.authz.permission.SimpleRole; import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor; @@ -105,6 +109,7 @@ import static org.hamcrest.Matchers.iterableWithSize; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.sameInstance; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -1505,6 +1510,39 @@ public void testLazinessForAuthorizedIndicesSet() { verify(supplier, never()).get(); } + public void testGetUserPrivilegesThrowsIaeForUnsupportedOperation() { + final RBACAuthorizationInfo authorizationInfo = mock(RBACAuthorizationInfo.class); + final Role role = mock(Role.class); + when(authorizationInfo.getRole()).thenReturn(role); + when(role.cluster()).thenReturn(ClusterPermission.NONE); + when(role.indices()).thenReturn(IndicesPermission.NONE); + when(role.application()).thenReturn(ApplicationPermission.NONE); + when(role.runAs()).thenReturn(RunAsPermission.NONE); + + final UnsupportedOperationException unsupportedOperationException = new UnsupportedOperationException(); + switch (randomIntBetween(0, 3)) { + case 0 -> when(role.cluster()).thenThrow(unsupportedOperationException); + case 1 -> when(role.indices()).thenThrow(unsupportedOperationException); + case 2 -> when(role.application()).thenThrow(unsupportedOperationException); + case 3 -> when(role.runAs()).thenThrow(unsupportedOperationException); + default -> throw new IllegalStateException("unknown case number"); + } + + final PlainActionFuture future = new PlainActionFuture<>(); + engine.getUserPrivileges(authorizationInfo, future); + + final IllegalArgumentException e = expectThrows(IllegalArgumentException.class, future::actionGet); + + assertThat( + e.getMessage(), + equalTo( + "Cannot retrieve privileges for API keys with assigned role descriptors. " + + "Please use the Get API key information API https://ela.st/es-api-get-api-key" + ) + ); + assertThat(e.getCause(), sameInstance(unsupportedOperationException)); + } + private GetUserPrivilegesResponse.Indices findIndexPrivilege(Set indices, String name) { return indices.stream().filter(i -> i.getIndices().contains(name)).findFirst().get(); }