Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@
import org.elasticsearch.xpack.security.authc.support.mapper.ProjectStateRoleMapper;
import org.elasticsearch.xpack.security.authz.AuthorizationDenialMessages;
import org.elasticsearch.xpack.security.authz.AuthorizationService;
import org.elasticsearch.xpack.security.authz.CustomActionAuthorizationStep;
import org.elasticsearch.xpack.security.authz.DlsFlsRequestCacheDifferentiator;
import org.elasticsearch.xpack.security.authz.FileRoleValidator;
import org.elasticsearch.xpack.security.authz.ReservedRoleNameChecker;
Expand Down Expand Up @@ -643,6 +644,7 @@ public class Security extends Plugin
private final SetOnce<RemoteClusterSecurityExtension.Provider> remoteClusterSecurityExtensionProvider = new SetOnce<>();
private final SetOnce<RemoteClusterSecurityExtension> remoteClusterSecurityExtension = new SetOnce<>();
private final SetOnce<RemoteClusterAuthenticationService> remoteClusterAuthenticationService = new SetOnce<>();
private final SetOnce<CustomActionAuthorizationStep.Factory> esqlAuthorizationStep = new SetOnce<>();

private final SetOnce<SecurityMigrations.Manager> migrationManager = new SetOnce<>();
private final SetOnce<List<Closeable>> closableComponents = new SetOnce<>();
Expand Down Expand Up @@ -1143,6 +1145,9 @@ Collection<Object> createComponents(
if (authorizationDenialMessages.get() == null) {
authorizationDenialMessages.set(new AuthorizationDenialMessages.Default());
}
if (esqlAuthorizationStep.get() == null) {
esqlAuthorizationStep.set(new CustomActionAuthorizationStep.Factory.Default());
}
final AuthorizationService authzService = new AuthorizationService(
settings,
allRolesStore,
Expand All @@ -1160,7 +1165,8 @@ Collection<Object> createComponents(
restrictedIndices,
authorizationDenialMessages.get(),
linkedProjectConfigService,
projectResolver
projectResolver,
esqlAuthorizationStep.get().create(settings, linkedProjectConfigService)
);

components.add(nativeRolesStore); // used by roles actions
Expand Down Expand Up @@ -2524,6 +2530,7 @@ public void loadExtensions(ExtensionLoader loader) {
RemoteClusterSecurityExtension.Provider.class,
CrossClusterAccessSecurityExtension.Provider::new
);
loadSingletonExtensionAndSetOnce(loader, esqlAuthorizationStep, CustomActionAuthorizationStep.Factory.class);
}

private <T> void loadSingletonExtensionAndSetOnce(ExtensionLoader loader, SetOnce<T> setOnce, Class<T> clazz) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ public AuthorizationService(
RestrictedIndices restrictedIndices,
AuthorizationDenialMessages authorizationDenialMessages,
LinkedProjectConfigService linkedProjectConfigService,
ProjectResolver projectResolver
ProjectResolver projectResolver,
CustomActionAuthorizationStep esqlAuthorizationStep
) {
this.clusterService = clusterService;
this.auditTrailService = auditTrailService;
Expand All @@ -182,7 +183,8 @@ public AuthorizationService(
settings,
rolesStore,
fieldPermissionsCache,
new LoadAuthorizedIndicesTimeChecker.Factory(logger, settings, clusterService.getClusterSettings())
new LoadAuthorizedIndicesTimeChecker.Factory(logger, settings, clusterService.getClusterSettings()),
esqlAuthorizationStep
);
this.authorizationEngine = authorizationEngine == null ? this.rbacEngine : authorizationEngine;
this.requestInterceptors = requestInterceptors;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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.authz;

import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.transport.LinkedProjectConfigService;
import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine;

public interface CustomActionAuthorizationStep {
boolean authorize(AuthorizationEngine.RequestInfo requestInfo);

class Default implements CustomActionAuthorizationStep {
@Override
public boolean authorize(AuthorizationEngine.RequestInfo requestInfo) {
return false;
}
}

interface Factory {
CustomActionAuthorizationStep create(Settings settings, LinkedProjectConfigService linkedProjectConfigService);

class Default implements Factory {
@Override
public CustomActionAuthorizationStep create(Settings settings, LinkedProjectConfigService linkedProjectConfigService) {
return new CustomActionAuthorizationStep.Default();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,17 +153,20 @@ public class RBACEngine implements AuthorizationEngine {
private final CompositeRolesStore rolesStore;
private final FieldPermissionsCache fieldPermissionsCache;
private final LoadAuthorizedIndicesTimeChecker.Factory authzIndicesTimerFactory;
private final CustomActionAuthorizationStep esqlStep;

public RBACEngine(
Settings settings,
CompositeRolesStore rolesStore,
FieldPermissionsCache fieldPermissionsCache,
LoadAuthorizedIndicesTimeChecker.Factory authzIndicesTimerFactory
LoadAuthorizedIndicesTimeChecker.Factory authzIndicesTimerFactory,
CustomActionAuthorizationStep esqlStep
) {
this.settings = settings;
this.rolesStore = rolesStore;
this.fieldPermissionsCache = fieldPermissionsCache;
this.authzIndicesTimerFactory = authzIndicesTimerFactory;
this.esqlStep = esqlStep;
}

@Override
Expand Down Expand Up @@ -334,6 +337,9 @@ public SubscribableListener<IndexAuthorizationResult> authorizeIndexAction(
} catch (Exception e) {
return SubscribableListener.newFailed(e);
}
if (esqlStep.authorize(requestInfo)) {
return SubscribableListener.newSucceeded(IndexAuthorizationResult.EMPTY);
}
if (TransportActionProxy.isProxyAction(action) || shouldAuthorizeIndexActionNameOnly(action, request)) {
// we've already validated that the request is a proxy request so we can skip that but we still
// need to validate that the action is allowed and then move on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,8 @@ public void setup() {
RESTRICTED_INDICES,
new AuthorizationDenialMessages.Default(),
linkedProjectConfigService,
projectResolver
projectResolver,
new CustomActionAuthorizationStep.Default()
);
}

Expand Down Expand Up @@ -1775,7 +1776,8 @@ public void testDenialForAnonymousUser() {
RESTRICTED_INDICES,
new AuthorizationDenialMessages.Default(),
linkedProjectConfigService,
projectResolver
projectResolver,
new CustomActionAuthorizationStep.Default()
);

RoleDescriptor role = new RoleDescriptor(
Expand Down Expand Up @@ -1826,7 +1828,8 @@ public void testDenialForAnonymousUserAuthorizationExceptionDisabled() {
RESTRICTED_INDICES,
new AuthorizationDenialMessages.Default(),
linkedProjectConfigService,
projectResolver
projectResolver,
new CustomActionAuthorizationStep.Default()
);

RoleDescriptor role = new RoleDescriptor(
Expand Down Expand Up @@ -3365,7 +3368,8 @@ public void testAuthorizationEngineSelectionForCheckPrivileges() throws Exceptio
RESTRICTED_INDICES,
new AuthorizationDenialMessages.Default(),
linkedProjectConfigService,
projectResolver
projectResolver,
new CustomActionAuthorizationStep.Default()
);

Subject subject = new Subject(new User("test", "a role"), mock(RealmRef.class));
Expand Down Expand Up @@ -3522,7 +3526,8 @@ public void getUserPrivileges(AuthorizationInfo authorizationInfo, ActionListene
RESTRICTED_INDICES,
new AuthorizationDenialMessages.Default(),
linkedProjectConfigService,
projectResolver
projectResolver,
new CustomActionAuthorizationStep.Default()
);
Authentication authentication;
try (StoredContext ignore = threadContext.stashContext()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,13 @@ public void createEngine() {
final LoadAuthorizedIndicesTimeChecker.Factory timerFactory = mock(LoadAuthorizedIndicesTimeChecker.Factory.class);
when(timerFactory.newTimer(any())).thenReturn(LoadAuthorizedIndicesTimeChecker.NO_OP_CONSUMER);
rolesStore = mock(CompositeRolesStore.class);
engine = new RBACEngine(Settings.EMPTY, rolesStore, new FieldPermissionsCache(Settings.EMPTY), timerFactory);
engine = new RBACEngine(
Settings.EMPTY,
rolesStore,
new FieldPermissionsCache(Settings.EMPTY),
timerFactory,
new CustomActionAuthorizationStep.Default()
);
}

public void testResolveAuthorizationInfoForEmptyRolesWithAuthentication() {
Expand Down