Skip to content

Commit 771aaee

Browse files
Do not fetch reserved roles from native store when Get Role API is called
The reserved roles are already returned from the `ReservedRolesStore` in `TransportGetRolesAction`. There is no need to query and deserialize reserved roles from the `.security` index when Get Roles API is called.
1 parent e24489f commit 771aaee

File tree

2 files changed

+169
-1
lines changed
  • x-pack/plugin/security

2 files changed

+169
-1
lines changed
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.security;
9+
10+
import org.elasticsearch.client.Request;
11+
import org.elasticsearch.client.Response;
12+
import org.elasticsearch.client.ResponseException;
13+
import org.elasticsearch.client.RestClient;
14+
import org.elasticsearch.common.bytes.BytesReference;
15+
import org.elasticsearch.common.settings.SecureString;
16+
import org.elasticsearch.common.settings.Settings;
17+
import org.elasticsearch.common.util.concurrent.ThreadContext;
18+
import org.elasticsearch.test.cluster.ElasticsearchCluster;
19+
import org.elasticsearch.test.cluster.local.distribution.DistributionType;
20+
import org.elasticsearch.test.cluster.local.model.User;
21+
import org.elasticsearch.test.cluster.util.resource.Resource;
22+
import org.elasticsearch.xcontent.ObjectPath;
23+
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
24+
import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
25+
import org.junit.Before;
26+
import org.junit.ClassRule;
27+
28+
import java.io.IOException;
29+
import java.util.HashMap;
30+
import java.util.HashSet;
31+
import java.util.Map;
32+
import java.util.Set;
33+
34+
import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder;
35+
import static org.hamcrest.Matchers.equalTo;
36+
37+
public class GetRolesIT extends SecurityInBasicRestTestCase {
38+
39+
private static final String ADMIN_USER = "admin_user";
40+
private static final SecureString ADMIN_PASSWORD = new SecureString("admin-password".toCharArray());
41+
protected static final String READ_SECURITY_USER = "read_security_user";
42+
private static final SecureString READ_SECURITY_PASSWORD = new SecureString("read-security-password".toCharArray());
43+
44+
@Before
45+
public void initialize() {
46+
new ReservedRolesStore();
47+
}
48+
49+
@ClassRule
50+
public static ElasticsearchCluster cluster = ElasticsearchCluster.local()
51+
.distribution(DistributionType.DEFAULT)
52+
.nodes(2)
53+
.setting("xpack.security.enabled", "true")
54+
.setting("xpack.license.self_generated.type", "basic")
55+
.rolesFile(Resource.fromClasspath("roles.yml"))
56+
.user(ADMIN_USER, ADMIN_PASSWORD.toString(), User.ROOT_USER_ROLE, true)
57+
.user(READ_SECURITY_USER, READ_SECURITY_PASSWORD.toString(), "read_security_user_role", false)
58+
.build();
59+
60+
@Override
61+
protected Settings restAdminSettings() {
62+
String token = basicAuthHeaderValue(ADMIN_USER, ADMIN_PASSWORD);
63+
return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", token).build();
64+
}
65+
66+
@Override
67+
protected Settings restClientSettings() {
68+
String token = basicAuthHeaderValue(READ_SECURITY_USER, READ_SECURITY_PASSWORD);
69+
return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", token).build();
70+
}
71+
72+
@Override
73+
protected String getTestRestCluster() {
74+
return cluster.getHttpAddresses();
75+
}
76+
77+
public void testGetAllRolesNoNative() throws Exception {
78+
// Test get roles API with operator admin_user
79+
getAllRolesAndAssert(adminClient(), ReservedRolesStore.names());
80+
// Test get roles API with read_security_user
81+
getAllRolesAndAssert(client(), ReservedRolesStore.names());
82+
}
83+
84+
public void testGetAllRolesWithNative() throws Exception {
85+
createRole("custom_role", "Test custom native role.", Map.of("owner", "test"));
86+
87+
Set<String> expectedRoles = new HashSet<>(ReservedRolesStore.names());
88+
expectedRoles.add("custom_role");
89+
90+
// Test get roles API with operator admin_user
91+
getAllRolesAndAssert(adminClient(), expectedRoles);
92+
// Test get roles API with read_security_user
93+
getAllRolesAndAssert(client(), expectedRoles);
94+
}
95+
96+
public void testGetReservedOnly() throws Exception {
97+
createRole("custom_role", "Test custom native role.", Map.of("owner", "test"));
98+
99+
Set<String> rolesToGet = new HashSet<>();
100+
rolesToGet.add("custom_role");
101+
rolesToGet.addAll(randomSet(1, 5, () -> randomFrom(ReservedRolesStore.names())));
102+
103+
getRolesAndAssert(adminClient(), rolesToGet);
104+
getRolesAndAssert(client(), rolesToGet);
105+
}
106+
107+
public void testGetNativeOnly() throws Exception {
108+
createRole("custom_role1", "Test custom native role.", Map.of("owner", "test1"));
109+
createRole("custom_role2", "Test custom native role.", Map.of("owner", "test2"));
110+
111+
Set<String> rolesToGet = Set.of("custom_role1", "custom_role2");
112+
113+
getRolesAndAssert(adminClient(), rolesToGet);
114+
getRolesAndAssert(client(), rolesToGet);
115+
}
116+
117+
public void testGetMixedRoles() throws Exception {
118+
createRole("custom_role", "Test custom native role.", Map.of("owner", "test"));
119+
120+
Set<String> rolesToGet = new HashSet<>();
121+
rolesToGet.add("custom_role");
122+
rolesToGet.addAll(randomSet(1, 5, () -> randomFrom(ReservedRolesStore.names())));
123+
124+
getRolesAndAssert(adminClient(), rolesToGet);
125+
getRolesAndAssert(client(), rolesToGet);
126+
}
127+
128+
public void testNonExistentRole() {
129+
var e = expectThrows(
130+
ResponseException.class,
131+
() -> client().performRequest(new Request("GET", "/_security/role/non_existent_role"))
132+
);
133+
assertThat(e.getResponse().getStatusLine().getStatusCode(), equalTo(404));
134+
}
135+
136+
private void createRole(String roleName, String description, Map<String, Object> metadata) throws IOException {
137+
Request request = new Request("POST", "/_security/role/" + roleName);
138+
Map<String, Object> requestMap = new HashMap<>();
139+
if (description != null) {
140+
requestMap.put(RoleDescriptor.Fields.DESCRIPTION.getPreferredName(), description);
141+
}
142+
if (metadata != null) {
143+
requestMap.put(RoleDescriptor.Fields.METADATA.getPreferredName(), metadata);
144+
}
145+
BytesReference source = BytesReference.bytes(jsonBuilder().map(requestMap));
146+
request.setJsonEntity(source.utf8ToString());
147+
Response response = adminClient().performRequest(request);
148+
assertOK(response);
149+
Map<String, Object> responseMap = responseAsMap(response);
150+
assertTrue(ObjectPath.eval("role.created", responseMap));
151+
}
152+
153+
private void getAllRolesAndAssert(RestClient client, Set<String> expectedRoles) throws IOException {
154+
final Response response = client.performRequest(new Request("GET", "/_security/role"));
155+
assertOK(response);
156+
final Map<String, Object> responseMap = responseAsMap(response);
157+
assertThat(responseMap.keySet(), equalTo(expectedRoles));
158+
}
159+
160+
private void getRolesAndAssert(RestClient client, Set<String> rolesToGet) throws IOException {
161+
final Response response = client.performRequest(new Request("GET", "/_security/role/" + String.join(",", rolesToGet)));
162+
assertOK(response);
163+
final Map<String, Object> responseMap = responseAsMap(response);
164+
assertThat(responseMap.keySet(), equalTo(rolesToGet));
165+
}
166+
}

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,9 @@ public void getRoleDescriptors(Set<String> names, final ActionListener<RoleRetri
194194
listener.onResponse(RoleRetrievalResult.failure(frozenSecurityIndex.getUnavailableReason(SEARCH_SHARDS)));
195195
} else if (names == null || names.isEmpty()) {
196196
securityIndex.checkIndexVersionThenExecute(listener::onFailure, () -> {
197-
QueryBuilder query = QueryBuilders.termQuery(RoleDescriptor.Fields.TYPE.getPreferredName(), ROLE_TYPE);
197+
QueryBuilder query = QueryBuilders.boolQuery()
198+
.must(QueryBuilders.termQuery(RoleDescriptor.Fields.TYPE.getPreferredName(), ROLE_TYPE))
199+
.mustNot(QueryBuilders.termQuery("metadata_flattened._reserved", true));
198200
final Supplier<ThreadContext.StoredContext> supplier = client.threadPool().getThreadContext().newRestorableContext(false);
199201
try (ThreadContext.StoredContext ignore = client.threadPool().getThreadContext().stashWithOrigin(SECURITY_ORIGIN)) {
200202
SearchRequest request = client.prepareSearch(SECURITY_MAIN_ALIAS)

0 commit comments

Comments
 (0)