Skip to content

Commit d5d8cf9

Browse files
authored
Include role names in access denied errors (#69318)
This commit adds a User's list of current role names to access denied error messages to aid in diagnostics. This allows an administrator to know whether the correct course of action is to add another role to the user (e.g. by fixing incorrect role mappings) or by modifying a role to add more privileges. Resolves: #42166
1 parent c3f11f2 commit d5d8cf9

File tree

5 files changed

+37
-14
lines changed

5 files changed

+37
-14
lines changed

x-pack/plugin/ilm/qa/with-security/src/javaRestTest/java/org/elasticsearch/xpack/security/PermissionsIT.java

+1
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ public void testCanManageIndexWithNoPermissions() throws Exception {
154154
assertThat(stepInfo.get("type"), equalTo("security_exception"));
155155
assertThat(stepInfo.get("reason"), equalTo("action [indices:monitor/stats] is unauthorized" +
156156
" for user [test_ilm]" +
157+
" with roles [ilm]" +
157158
" on indices [not-ilm]," +
158159
" this action is granted by the index privileges [monitor,manage,all]"));
159160
}

x-pack/plugin/ml/qa/native-multi-node-tests/src/javaRestTest/java/org/elasticsearch/xpack/ml/integration/DatafeedJobsRestIT.java

+8-3
Original file line numberDiff line numberDiff line change
@@ -819,7 +819,10 @@ public void testLookbackWithoutPermissions() throws Exception {
819819
new Request("GET", NotificationsIndex.NOTIFICATIONS_INDEX + "/_search?size=1000&q=job_id:" + jobId));
820820
String notificationsResponseAsString = EntityUtils.toString(notificationsResponse.getEntity());
821821
assertThat(notificationsResponseAsString, containsString("\"message\":\"Datafeed is encountering errors extracting data: " +
822-
"action [indices:data/read/search] is unauthorized for user [ml_admin_plus_data] on indices [network-data]"));
822+
"action [indices:data/read/search] is unauthorized" +
823+
" for user [ml_admin_plus_data]" +
824+
" with roles [machine_learning_admin,test_data_access]" +
825+
" on indices [network-data]"));
823826
}
824827

825828
public void testLookbackWithPipelineBucketAgg() throws Exception {
@@ -967,8 +970,10 @@ public void testLookbackWithoutPermissionsAndRollup() throws Exception {
967970
new Request("GET", NotificationsIndex.NOTIFICATIONS_INDEX + "/_search?size=1000&q=job_id:" + jobId));
968971
String notificationsResponseAsString = EntityUtils.toString(notificationsResponse.getEntity());
969972
assertThat(notificationsResponseAsString, containsString("\"message\":\"Datafeed is encountering errors extracting data: " +
970-
"action [indices:data/read/xpack/rollup/search] is unauthorized for user [ml_admin_plus_data] " +
971-
"on indices [airline-data-aggs-rollup]"));
973+
"action [indices:data/read/xpack/rollup/search] is unauthorized" +
974+
" for user [ml_admin_plus_data]" +
975+
" with roles [machine_learning_admin,test_data_access]" +
976+
" on indices [airline-data-aggs-rollup]"));
972977
}
973978

974979
public void testLookbackWithSingleBucketAgg() throws Exception {

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

+4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.elasticsearch.cluster.metadata.Metadata;
2929
import org.elasticsearch.cluster.service.ClusterService;
3030
import org.elasticsearch.common.Nullable;
31+
import org.elasticsearch.common.Strings;
3132
import org.elasticsearch.common.collect.Tuple;
3233
import org.elasticsearch.common.settings.Setting;
3334
import org.elasticsearch.common.settings.Setting.Property;
@@ -630,6 +631,9 @@ private ElasticsearchSecurityException denialException(Authentication authentica
630631
final String apiKeyId = (String) authentication.getMetadata().get(ApiKeyService.API_KEY_ID_KEY);
631632
assert apiKeyId != null : "api key id must be present in the metadata";
632633
userText = "API key id [" + apiKeyId + "] of " + userText;
634+
} else {
635+
// Don't print roles for API keys because they're not meaningful
636+
userText = userText + " with roles [" + Strings.arrayToCommaDelimitedString(authentication.getUser().roles()) + "]";
633637
}
634638

635639
String message = "action [" + action + "] is unauthorized for " + userText;

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java

+20-9
Original file line numberDiff line numberDiff line change
@@ -676,8 +676,11 @@ public void testUnknownRoleCausesDenial() throws IOException {
676676

677677
ElasticsearchSecurityException securityException = expectThrows(ElasticsearchSecurityException.class,
678678
() -> authorize(authentication, action, request));
679-
assertThat(securityException,
680-
throwableWithMessage(containsString("[" + action + "] is unauthorized for user [test user] on indices [")));
679+
assertThat(securityException, throwableWithMessage(containsString(
680+
"[" + action + "] is unauthorized" +
681+
" for user [test user]" +
682+
" with roles [non-existent-role]" +
683+
" on indices [")));
681684
assertThat(securityException, throwableWithMessage(containsString("this action is granted by the index privileges [read,all]")));
682685

683686
verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), eq(request), authzInfoRoles(Role.EMPTY.names()));
@@ -715,8 +718,11 @@ public void testThatRoleWithNoIndicesIsDenied() throws IOException {
715718

716719
ElasticsearchSecurityException securityException = expectThrows(ElasticsearchSecurityException.class,
717720
() -> authorize(authentication, action, request));
718-
assertThat(securityException,
719-
throwableWithMessage(containsString("[" + action + "] is unauthorized for user [test user] on indices [")));
721+
assertThat(securityException, throwableWithMessage(containsString(
722+
"[" + action + "] is unauthorized" +
723+
" for user [test user]" +
724+
" with roles [no_indices]" +
725+
" on indices [")));
720726
assertThat(securityException, throwableWithMessage(containsString("this action is granted by the index privileges [read,all]")));
721727

722728
verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), eq(request),
@@ -887,23 +893,28 @@ public void testCreateIndexWithAlias() throws IOException {
887893
}
888894

889895
public void testDenialErrorMessagesForSearchAction() throws IOException {
890-
RoleDescriptor role = new RoleDescriptor("some_indices_" + randomAlphaOfLengthBetween(3, 6), null, new IndicesPrivileges[]{
896+
RoleDescriptor indexRole = new RoleDescriptor("some_indices_" + randomAlphaOfLengthBetween(3, 6), null, new IndicesPrivileges[]{
891897
IndicesPrivileges.builder().indices("all*").privileges("all").build(),
892898
IndicesPrivileges.builder().indices("read*").privileges("read").build(),
893899
IndicesPrivileges.builder().indices("write*").privileges("write").build()
894900
}, null);
895-
User user = new User(randomAlphaOfLengthBetween(6, 8), role.getName());
901+
RoleDescriptor emptyRole = new RoleDescriptor("empty_role_" + randomAlphaOfLengthBetween(1,4), null, null, null);
902+
User user = new User(randomAlphaOfLengthBetween(6, 8), indexRole.getName(), emptyRole.getName());
896903
final Authentication authentication = createAuthentication(user);
897-
roleMap.put(role.getName(), role);
904+
roleMap.put(indexRole.getName(), indexRole);
905+
roleMap.put(emptyRole.getName(), emptyRole);
898906

899907
AuditUtil.getOrGenerateRequestId(threadContext);
900908

901909
TransportRequest request = new SearchRequest("all-1", "read-2", "write-3", "other-4");
902910

903911
ElasticsearchSecurityException securityException = expectThrows(ElasticsearchSecurityException.class,
904912
() -> authorize(authentication, SearchAction.NAME, request));
905-
assertThat(securityException, throwableWithMessage(
906-
containsString("[" + SearchAction.NAME + "] is unauthorized for user [" + user.principal() + "] on indices [")));
913+
assertThat(securityException, throwableWithMessage(containsString(
914+
"[" + SearchAction.NAME + "] is unauthorized" +
915+
" for user [" + user.principal() + "]" +
916+
" with roles [" + indexRole.getName() + "," + emptyRole.getName() + "]" +
917+
" on indices [")));
907918
assertThat(securityException, throwableWithMessage(containsString("write-3")));
908919
assertThat(securityException, throwableWithMessage(containsString("other-4")));
909920
assertThat(securityException, throwableWithMessage(not(containsString("all-1"))));

x-pack/plugin/src/test/resources/rest-api-spec/test/api_key/11_invalidation.yml

+4-2
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ teardown:
126126
"username": "api_key_manager"
127127
}
128128
- match: { "error.type": "security_exception" }
129-
- match: { "error.reason": "action [cluster:admin/xpack/security/api_key/invalidate] is unauthorized for user [api_key_user_1], this action is granted by the cluster privileges [manage_api_key,manage_security,all]" }
129+
- match:
130+
"error.reason": "action [cluster:admin/xpack/security/api_key/invalidate] is unauthorized for user [api_key_user_1] with roles [user_role], this action is granted by the cluster privileges [manage_api_key,manage_security,all]"
130131

131132
- do:
132133
headers:
@@ -189,7 +190,8 @@ teardown:
189190
"realm_name": "default_native"
190191
}
191192
- match: { "error.type": "security_exception" }
192-
- match: { "error.reason": "action [cluster:admin/xpack/security/api_key/invalidate] is unauthorized for user [api_key_user_1], this action is granted by the cluster privileges [manage_api_key,manage_security,all]" }
193+
- match:
194+
"error.reason": "action [cluster:admin/xpack/security/api_key/invalidate] is unauthorized for user [api_key_user_1] with roles [user_role], this action is granted by the cluster privileges [manage_api_key,manage_security,all]"
193195

194196
- do:
195197
headers:

0 commit comments

Comments
 (0)