Skip to content

Commit b690154

Browse files
authored
Ignore app priv failures when resolving superuser (#85519) (#85588)
In #81400 we changed `superuser` to no longer have _every_ privilege. Consequently, we also removed the special case code that existed that would ignore all other roles for any user that had superuser role. However, we added some special handling so that failing to resolve those other roles would not block superuser access - when a user has superuser role, any failures in role resolution will be effectively ignored, and the user will be given the superuser role only. However, this failure handling did not account for the loading of application privileges. If application privileges needed to be loaded, but failed, this could prevent resolution of the superuser role. This change extends the failure handling to encompass the full resolution of roles, and fallback to superuser only, whenever other roles or application privileges are unavailable Relates: #85312
1 parent 1683e90 commit b690154

File tree

3 files changed

+91
-25
lines changed

3 files changed

+91
-25
lines changed

docs/changelog/85519.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 85519
2+
summary: Ignore app priv failures when resolving superuser
3+
area: Authorization
4+
type: bug
5+
issues: []

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

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -240,22 +240,7 @@ public void buildRoleFromRoleReference(RoleReference roleReference, ActionListen
240240
final Role existing = roleCache.get(roleKey);
241241
if (existing == null) {
242242
final long invalidationCounter = numInvalidation.get();
243-
roleReference.resolve(roleReferenceResolver, ActionListener.wrap(rolesRetrievalResult -> {
244-
if (RolesRetrievalResult.EMPTY == rolesRetrievalResult) {
245-
roleActionListener.onResponse(Role.EMPTY);
246-
} else if (RolesRetrievalResult.SUPERUSER == rolesRetrievalResult) {
247-
roleActionListener.onResponse(superuserRole);
248-
} else {
249-
buildThenMaybeCacheRole(
250-
roleKey,
251-
rolesRetrievalResult.getRoleDescriptors(),
252-
rolesRetrievalResult.getMissingRoles(),
253-
rolesRetrievalResult.isSuccess(),
254-
invalidationCounter,
255-
roleActionListener
256-
);
257-
}
258-
}, e -> {
243+
final Consumer<Exception> failureHandler = e -> {
259244
// Because superuser does not have write access to restricted indices, it is valid to mix superuser with other roles to
260245
// gain addition access. However, if retrieving those roles fails for some reason, then that could leave admins in a
261246
// situation where they are unable to administer their cluster (in order to resolve the problem that is leading to failures
@@ -274,7 +259,23 @@ public void buildRoleFromRoleReference(RoleReference roleReference, ActionListen
274259
} else {
275260
roleActionListener.onFailure(e);
276261
}
277-
}));
262+
};
263+
roleReference.resolve(roleReferenceResolver, ActionListener.wrap(rolesRetrievalResult -> {
264+
if (RolesRetrievalResult.EMPTY == rolesRetrievalResult) {
265+
roleActionListener.onResponse(Role.EMPTY);
266+
} else if (RolesRetrievalResult.SUPERUSER == rolesRetrievalResult) {
267+
roleActionListener.onResponse(superuserRole);
268+
} else {
269+
buildThenMaybeCacheRole(
270+
roleKey,
271+
rolesRetrievalResult.getRoleDescriptors(),
272+
rolesRetrievalResult.getMissingRoles(),
273+
rolesRetrievalResult.isSuccess(),
274+
invalidationCounter,
275+
ActionListener.wrap(roleActionListener::onResponse, failureHandler)
276+
);
277+
}
278+
}, failureHandler));
278279
} else {
279280
roleActionListener.onResponse(existing);
280281
}

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

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
import org.elasticsearch.xpack.security.authc.service.ServiceAccountService;
9292
import org.elasticsearch.xpack.security.support.CacheInvalidatorRegistry;
9393
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
94+
import org.hamcrest.Matcher;
9495
import org.hamcrest.Matchers;
9596

9697
import java.io.IOException;
@@ -117,6 +118,7 @@
117118
import java.util.function.Predicate;
118119

119120
import static org.elasticsearch.test.ActionListenerUtils.anyActionListener;
121+
import static org.elasticsearch.test.TestMatchers.throwableWithMessage;
120122
import static org.elasticsearch.xpack.core.security.SecurityField.DOCUMENT_LEVEL_SECURITY_FEATURE;
121123
import static org.elasticsearch.xpack.core.security.authc.AuthenticationField.API_KEY_ID_KEY;
122124
import static org.elasticsearch.xpack.core.security.authc.AuthenticationField.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY;
@@ -324,6 +326,59 @@ public void testRolesWhenDlsFlsLicensed() throws IOException {
324326
}
325327

326328
public void testSuperuserIsEffectiveWhenOtherRolesUnavailable() {
329+
final boolean criticalFailure = randomBoolean();
330+
final Consumer<ActionListener<RoleRetrievalResult>> rolesHandler = callback -> {
331+
final RuntimeException exception = new RuntimeException("Test(" + getTestName() + ") - native roles unavailable");
332+
if (criticalFailure) {
333+
callback.onFailure(exception);
334+
} else {
335+
callback.onResponse(RoleRetrievalResult.failure(exception));
336+
}
337+
};
338+
final Consumer<ActionListener<Collection<ApplicationPrivilegeDescriptor>>> privilegesHandler = callback -> callback.onResponse(
339+
Collections.emptyList()
340+
);
341+
342+
final CompositeRolesStore compositeRolesStore = setupRolesStore(rolesHandler, privilegesHandler);
343+
trySuccessfullyLoadSuperuserRole(compositeRolesStore);
344+
if (criticalFailure) {
345+
// A failure RoleRetrievalResult doesn't block role building, only a throw exception does
346+
tryFailOnNonSuperuserRole(compositeRolesStore, throwableWithMessage(containsString("native roles unavailable")));
347+
}
348+
}
349+
350+
public void testSuperuserIsEffectiveWhenApplicationPrivilegesAreUnavailable() {
351+
final RoleDescriptor role = new RoleDescriptor(
352+
"_mock_role",
353+
new String[0],
354+
new IndicesPrivileges[0],
355+
new RoleDescriptor.ApplicationResourcePrivileges[] {
356+
RoleDescriptor.ApplicationResourcePrivileges.builder()
357+
.application(randomAlphaOfLengthBetween(5, 12))
358+
.privileges("all")
359+
.resources("*")
360+
.build() },
361+
new ConfigurableClusterPrivilege[0],
362+
new String[0],
363+
Map.of(),
364+
Map.of()
365+
);
366+
final Consumer<ActionListener<RoleRetrievalResult>> rolesHandler = callback -> callback.onResponse(
367+
RoleRetrievalResult.success(Set.of(role))
368+
);
369+
final Consumer<ActionListener<Collection<ApplicationPrivilegeDescriptor>>> privilegesHandler = callback -> callback.onFailure(
370+
new RuntimeException("No privileges for you!")
371+
);
372+
373+
final CompositeRolesStore compositeRolesStore = setupRolesStore(rolesHandler, privilegesHandler);
374+
trySuccessfullyLoadSuperuserRole(compositeRolesStore);
375+
tryFailOnNonSuperuserRole(compositeRolesStore, throwableWithMessage(containsString("No privileges for you!")));
376+
}
377+
378+
private CompositeRolesStore setupRolesStore(
379+
Consumer<ActionListener<RoleRetrievalResult>> rolesHandler,
380+
Consumer<ActionListener<Collection<ApplicationPrivilegeDescriptor>>> privilegesHandler
381+
) {
327382
final FileRolesStore fileRolesStore = mock(FileRolesStore.class);
328383
doCallRealMethod().when(fileRolesStore).accept(anySet(), anyActionListener());
329384
when(fileRolesStore.roleDescriptors(anySet())).thenReturn(Collections.emptySet());
@@ -333,13 +388,7 @@ public void testSuperuserIsEffectiveWhenOtherRolesUnavailable() {
333388
doAnswer((invocationOnMock) -> {
334389
@SuppressWarnings("unchecked")
335390
ActionListener<RoleRetrievalResult> callback = (ActionListener<RoleRetrievalResult>) invocationOnMock.getArguments()[1];
336-
final RuntimeException exception = new RuntimeException("Test(" + getTestName() + ") - native roles unavailable");
337-
if (randomBoolean()) {
338-
callback.onFailure(exception);
339-
} else {
340-
callback.onResponse(RoleRetrievalResult.failure(exception));
341-
342-
}
391+
rolesHandler.accept(callback);
343392
return null;
344393
}).when(nativeRolesStore).getRoleDescriptors(isASet(), anyActionListener());
345394

@@ -349,7 +398,7 @@ public void testSuperuserIsEffectiveWhenOtherRolesUnavailable() {
349398
@SuppressWarnings("unchecked")
350399
ActionListener<Collection<ApplicationPrivilegeDescriptor>> callback = (ActionListener<
351400
Collection<ApplicationPrivilegeDescriptor>>) invocationOnMock.getArguments()[2];
352-
callback.onResponse(Collections.emptyList());
401+
privilegesHandler.accept(callback);
353402
return null;
354403
}).when(nativePrivilegeStore).getPrivileges(anySet(), anySet(), anyActionListener());
355404

@@ -365,7 +414,10 @@ public void testSuperuserIsEffectiveWhenOtherRolesUnavailable() {
365414
null,
366415
null
367416
);
417+
return compositeRolesStore;
418+
}
368419

420+
private void trySuccessfullyLoadSuperuserRole(CompositeRolesStore compositeRolesStore) {
369421
final Set<String> roles = Set.of(randomAlphaOfLengthBetween(1, 6), "superuser", randomAlphaOfLengthBetween(7, 12));
370422
PlainActionFuture<Role> future = new PlainActionFuture<>();
371423
getRoleForRoleNames(compositeRolesStore, roles, future);
@@ -388,6 +440,14 @@ public void testSuperuserIsEffectiveWhenOtherRolesUnavailable() {
388440
assertThat(securityActionPredicate.test(IndexAction.NAME), is(false));
389441
}
390442

443+
private void tryFailOnNonSuperuserRole(CompositeRolesStore compositeRolesStore, Matcher<? super Exception> exceptionMatcher) {
444+
final Set<String> roles = Set.of(randomAlphaOfLengthBetween(1, 6), randomAlphaOfLengthBetween(7, 12));
445+
PlainActionFuture<Role> future = new PlainActionFuture<>();
446+
getRoleForRoleNames(compositeRolesStore, roles, future);
447+
final Exception exception = expectThrows(Exception.class, future::actionGet);
448+
assertThat(exception, exceptionMatcher);
449+
}
450+
391451
public void testNegativeLookupsAreCached() {
392452
final FileRolesStore fileRolesStore = mock(FileRolesStore.class);
393453
doCallRealMethod().when(fileRolesStore).accept(anySet(), anyActionListener());

0 commit comments

Comments
 (0)