-
Notifications
You must be signed in to change notification settings - Fork 25.7k
Do not fetch reserved roles from native store when Get Role API is called #121971
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
slobodanadamovic
merged 16 commits into
elastic:main
from
slobodanadamovic:sa-filter-reserved-roles-from-get-roles-api
Feb 17, 2025
Merged
Changes from 5 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
771aaee
Do not fetch reserved roles from native store when Get Role API is ca…
slobodanadamovic db41a40
Update docs/changelog/121971.yaml
slobodanadamovic 0e5024a
filter reserved roles in other cases and add tests
slobodanadamovic b233731
Merge branch 'main' into sa-filter-reserved-roles-from-get-roles-api
slobodanadamovic bc9f4be
make roles.yml file updates atomic
slobodanadamovic f658590
naming nit
slobodanadamovic e7e60ee
Merge branch 'main' into sa-filter-reserved-roles-from-get-roles-api
slobodanadamovic 104339f
remove unnecessary reserved roles filtering and add an asserition
slobodanadamovic 6907adf
Merge branch 'main' into sa-filter-reserved-roles-from-get-roles-api
slobodanadamovic ad864c7
filter reserved roles
slobodanadamovic 9a21687
fix transport get action tests
slobodanadamovic c6c7b51
use ReservedRoleNameChecker.isReserved consistently
slobodanadamovic 0695590
Merge branch 'main' of github.com:elastic/elasticsearch into sa-filte…
slobodanadamovic d1e1e01
Merge branch 'main' into sa-filter-reserved-roles-from-get-roles-api
slobodanadamovic c1e1abc
Merge branch 'main' into sa-filter-reserved-roles-from-get-roles-api
slobodanadamovic 88b710c
Merge branch 'main' into sa-filter-reserved-roles-from-get-roles-api
slobodanadamovic File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| pr: 121971 | ||
| summary: Do not fetch reserved roles from native store when Get Role API is called | ||
| area: Authorization | ||
| type: enhancement | ||
| issues: [] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
166 changes: 166 additions & 0 deletions
166
.../qa/security-basic/src/javaRestTest/java/org/elasticsearch/xpack/security/GetRolesIT.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,166 @@ | ||
| /* | ||
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| * or more contributor license agreements. Licensed under the Elastic License | ||
| * 2.0; you may not use this file except in compliance with the Elastic License | ||
| * 2.0. | ||
| */ | ||
|
|
||
| package org.elasticsearch.xpack.security; | ||
|
|
||
| import org.elasticsearch.client.Request; | ||
| import org.elasticsearch.client.Response; | ||
| import org.elasticsearch.client.ResponseException; | ||
| import org.elasticsearch.client.RestClient; | ||
| import org.elasticsearch.common.bytes.BytesReference; | ||
| import org.elasticsearch.common.settings.SecureString; | ||
| import org.elasticsearch.common.settings.Settings; | ||
| import org.elasticsearch.common.util.concurrent.ThreadContext; | ||
| import org.elasticsearch.test.cluster.ElasticsearchCluster; | ||
| import org.elasticsearch.test.cluster.local.distribution.DistributionType; | ||
| import org.elasticsearch.test.cluster.local.model.User; | ||
| import org.elasticsearch.test.cluster.util.resource.Resource; | ||
| import org.elasticsearch.xcontent.ObjectPath; | ||
| import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; | ||
| import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore; | ||
| import org.junit.Before; | ||
| import org.junit.ClassRule; | ||
|
|
||
| import java.io.IOException; | ||
| import java.util.HashMap; | ||
| import java.util.HashSet; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
|
|
||
| import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder; | ||
| import static org.hamcrest.Matchers.equalTo; | ||
|
|
||
| public class GetRolesIT extends SecurityInBasicRestTestCase { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added this integration test mostly for sanity check that |
||
|
|
||
| private static final String ADMIN_USER = "admin_user"; | ||
| private static final SecureString ADMIN_PASSWORD = new SecureString("admin-password".toCharArray()); | ||
| protected static final String READ_SECURITY_USER = "read_security_user"; | ||
| private static final SecureString READ_SECURITY_PASSWORD = new SecureString("read-security-password".toCharArray()); | ||
|
|
||
| @Before | ||
| public void initialize() { | ||
| new ReservedRolesStore(); | ||
| } | ||
|
|
||
| @ClassRule | ||
| public static ElasticsearchCluster cluster = ElasticsearchCluster.local() | ||
| .distribution(DistributionType.DEFAULT) | ||
| .nodes(2) | ||
| .setting("xpack.security.enabled", "true") | ||
| .setting("xpack.license.self_generated.type", "basic") | ||
| .rolesFile(Resource.fromClasspath("roles.yml")) | ||
| .user(ADMIN_USER, ADMIN_PASSWORD.toString(), User.ROOT_USER_ROLE, true) | ||
| .user(READ_SECURITY_USER, READ_SECURITY_PASSWORD.toString(), "read_security_user_role", false) | ||
| .build(); | ||
|
|
||
| @Override | ||
| protected Settings restAdminSettings() { | ||
| String token = basicAuthHeaderValue(ADMIN_USER, ADMIN_PASSWORD); | ||
| return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", token).build(); | ||
| } | ||
|
|
||
| @Override | ||
| protected Settings restClientSettings() { | ||
| String token = basicAuthHeaderValue(READ_SECURITY_USER, READ_SECURITY_PASSWORD); | ||
| return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", token).build(); | ||
| } | ||
|
|
||
| @Override | ||
| protected String getTestRestCluster() { | ||
| return cluster.getHttpAddresses(); | ||
| } | ||
|
|
||
| public void testGetAllRolesNoNative() throws Exception { | ||
| // Test get roles API with operator admin_user | ||
| getAllRolesAndAssert(adminClient(), ReservedRolesStore.names()); | ||
| // Test get roles API with read_security_user | ||
| getAllRolesAndAssert(client(), ReservedRolesStore.names()); | ||
| } | ||
|
|
||
| public void testGetAllRolesWithNative() throws Exception { | ||
| createRole("custom_role", "Test custom native role.", Map.of("owner", "test")); | ||
|
|
||
| Set<String> expectedRoles = new HashSet<>(ReservedRolesStore.names()); | ||
| expectedRoles.add("custom_role"); | ||
|
|
||
| // Test get roles API with operator admin_user | ||
| getAllRolesAndAssert(adminClient(), expectedRoles); | ||
| // Test get roles API with read_security_user | ||
| getAllRolesAndAssert(client(), expectedRoles); | ||
| } | ||
|
|
||
| public void testGetReservedOnly() throws Exception { | ||
| createRole("custom_role", "Test custom native role.", Map.of("owner", "test")); | ||
|
|
||
| Set<String> rolesToGet = new HashSet<>(); | ||
| rolesToGet.add("custom_role"); | ||
| rolesToGet.addAll(randomSet(1, 5, () -> randomFrom(ReservedRolesStore.names()))); | ||
|
|
||
| getRolesAndAssert(adminClient(), rolesToGet); | ||
| getRolesAndAssert(client(), rolesToGet); | ||
| } | ||
|
|
||
| public void testGetNativeOnly() throws Exception { | ||
| createRole("custom_role1", "Test custom native role.", Map.of("owner", "test1")); | ||
| createRole("custom_role2", "Test custom native role.", Map.of("owner", "test2")); | ||
|
|
||
| Set<String> rolesToGet = Set.of("custom_role1", "custom_role2"); | ||
|
|
||
| getRolesAndAssert(adminClient(), rolesToGet); | ||
| getRolesAndAssert(client(), rolesToGet); | ||
| } | ||
|
|
||
| public void testGetMixedRoles() throws Exception { | ||
| createRole("custom_role", "Test custom native role.", Map.of("owner", "test")); | ||
|
|
||
| Set<String> rolesToGet = new HashSet<>(); | ||
| rolesToGet.add("custom_role"); | ||
| rolesToGet.addAll(randomSet(1, 5, () -> randomFrom(ReservedRolesStore.names()))); | ||
|
|
||
| getRolesAndAssert(adminClient(), rolesToGet); | ||
| getRolesAndAssert(client(), rolesToGet); | ||
| } | ||
|
|
||
| public void testNonExistentRole() { | ||
| var e = expectThrows( | ||
| ResponseException.class, | ||
| () -> client().performRequest(new Request("GET", "/_security/role/non_existent_role")) | ||
| ); | ||
| assertThat(e.getResponse().getStatusLine().getStatusCode(), equalTo(404)); | ||
| } | ||
|
|
||
| private void createRole(String roleName, String description, Map<String, Object> metadata) throws IOException { | ||
| Request request = new Request("POST", "/_security/role/" + roleName); | ||
| Map<String, Object> requestMap = new HashMap<>(); | ||
| if (description != null) { | ||
| requestMap.put(RoleDescriptor.Fields.DESCRIPTION.getPreferredName(), description); | ||
| } | ||
| if (metadata != null) { | ||
| requestMap.put(RoleDescriptor.Fields.METADATA.getPreferredName(), metadata); | ||
| } | ||
| BytesReference source = BytesReference.bytes(jsonBuilder().map(requestMap)); | ||
| request.setJsonEntity(source.utf8ToString()); | ||
| Response response = adminClient().performRequest(request); | ||
| assertOK(response); | ||
| Map<String, Object> responseMap = responseAsMap(response); | ||
| assertTrue(ObjectPath.eval("role.created", responseMap)); | ||
| } | ||
|
|
||
| private void getAllRolesAndAssert(RestClient client, Set<String> expectedRoles) throws IOException { | ||
| final Response response = client.performRequest(new Request("GET", "/_security/role")); | ||
| assertOK(response); | ||
| final Map<String, Object> responseMap = responseAsMap(response); | ||
| assertThat(responseMap.keySet(), equalTo(expectedRoles)); | ||
| } | ||
|
|
||
| private void getRolesAndAssert(RestClient client, Set<String> rolesToGet) throws IOException { | ||
| final Response response = client.performRequest(new Request("GET", "/_security/role/" + String.join(",", rolesToGet))); | ||
| assertOK(response); | ||
| final Map<String, Object> responseMap = responseAsMap(response); | ||
| assertThat(responseMap.keySet(), equalTo(rolesToGet)); | ||
| } | ||
| } | ||
103 changes: 103 additions & 0 deletions
103
...ty/src/internalClusterTest/java/org/elasticsearch/integration/GeRoleDescriptorsTests.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| /* | ||
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| * or more contributor license agreements. Licensed under the Elastic License | ||
| * 2.0; you may not use this file except in compliance with the Elastic License | ||
| * 2.0. | ||
| */ | ||
| package org.elasticsearch.integration; | ||
|
|
||
| import org.elasticsearch.action.support.PlainActionFuture; | ||
| import org.elasticsearch.test.NativeRealmIntegTestCase; | ||
| import org.elasticsearch.test.TestSecurityClient; | ||
| import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; | ||
| import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore; | ||
| import org.elasticsearch.xpack.core.security.authz.store.RoleRetrievalResult; | ||
| import org.elasticsearch.xpack.security.authz.store.NativeRolesStore; | ||
| import org.elasticsearch.xpack.security.support.SecuritySystemIndices; | ||
| import org.junit.Before; | ||
| import org.junit.BeforeClass; | ||
|
|
||
| import java.io.IOException; | ||
| import java.util.HashSet; | ||
| import java.util.Set; | ||
|
|
||
| import static org.elasticsearch.test.SecuritySettingsSource.SECURITY_REQUEST_OPTIONS; | ||
| import static org.hamcrest.Matchers.containsInAnyOrder; | ||
| import static org.hamcrest.Matchers.empty; | ||
| import static org.hamcrest.Matchers.is; | ||
| import static org.hamcrest.Matchers.notNullValue; | ||
|
|
||
| /** | ||
| * Test for the {@link NativeRolesStore#getRoleDescriptors} method. | ||
| */ | ||
| public class GeRoleDescriptorsTests extends NativeRealmIntegTestCase { | ||
|
|
||
| private static Set<String> customRoles; | ||
|
|
||
| @BeforeClass | ||
| public static void init() throws Exception { | ||
| new ReservedRolesStore(); | ||
|
|
||
| final int numOfRoles = randomIntBetween(5, 10); | ||
| customRoles = new HashSet<>(numOfRoles); | ||
| for (int i = 0; i < numOfRoles; i++) { | ||
| customRoles.add("custom_role_" + randomAlphaOfLength(10) + "_" + i); | ||
| } | ||
| } | ||
|
|
||
| @Before | ||
| public void setup() throws IOException { | ||
| final TestSecurityClient securityClient = new TestSecurityClient(getRestClient(), SECURITY_REQUEST_OPTIONS); | ||
| for (String role : customRoles) { | ||
| final RoleDescriptor descriptor = new RoleDescriptor( | ||
| role, | ||
| new String[0], | ||
| new RoleDescriptor.IndicesPrivileges[] { | ||
| RoleDescriptor.IndicesPrivileges.builder() | ||
| .indices("*") | ||
| .privileges("ALL") | ||
| .allowRestrictedIndices(randomBoolean()) | ||
| .build() }, | ||
| new String[0] | ||
| ); | ||
| securityClient.putRole(descriptor); | ||
| logger.info("--> created role [{}]", role); | ||
| } | ||
|
|
||
| ensureGreen(SecuritySystemIndices.SECURITY_MAIN_ALIAS); | ||
| } | ||
|
|
||
| public void testGetCustomRoles() { | ||
| for (NativeRolesStore rolesStore : internalCluster().getInstances(NativeRolesStore.class)) { | ||
| PlainActionFuture<RoleRetrievalResult> future = new PlainActionFuture<>(); | ||
| rolesStore.getRoleDescriptors(customRoles, future); | ||
| RoleRetrievalResult result = future.actionGet(); | ||
| assertThat(result, notNullValue()); | ||
| assertTrue(result.isSuccess()); | ||
| assertThat(result.getDescriptors().stream().map(RoleDescriptor::getName).toList(), containsInAnyOrder(customRoles.toArray())); | ||
| } | ||
| } | ||
|
|
||
| public void testGetReservedRoles() { | ||
| for (NativeRolesStore rolesStore : internalCluster().getInstances(NativeRolesStore.class)) { | ||
| PlainActionFuture<RoleRetrievalResult> future = new PlainActionFuture<>(); | ||
| Set<String> reservedRoles = randomUnique(() -> randomFrom(ReservedRolesStore.names()), randomIntBetween(1, 5)); | ||
| rolesStore.getRoleDescriptors(reservedRoles, future); | ||
| RoleRetrievalResult result = future.actionGet(); | ||
| assertThat(result, notNullValue()); | ||
| assertTrue(result.isSuccess()); | ||
| assertThat(result.getDescriptors(), is(empty())); | ||
| } | ||
| } | ||
|
|
||
| public void testGetAllRoles() { | ||
| for (NativeRolesStore rolesStore : internalCluster().getInstances(NativeRolesStore.class)) { | ||
| PlainActionFuture<RoleRetrievalResult> future = new PlainActionFuture<>(); | ||
| rolesStore.getRoleDescriptors(randomBoolean() ? null : Set.of(), future); | ||
| RoleRetrievalResult result = future.actionGet(); | ||
| assertThat(result, notNullValue()); | ||
| assertTrue(result.isSuccess()); | ||
| assertThat(result.getDescriptors().stream().map(RoleDescriptor::getName).toList(), containsInAnyOrder(customRoles.toArray())); | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a fix for Serverless tests.