diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 37e4ea024a858..9b33cbab6b094 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -323,6 +323,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; @@ -644,6 +645,7 @@ public class Security extends Plugin private final SetOnce remoteClusterSecurityExtensionProvider = new SetOnce<>(); private final SetOnce remoteClusterSecurityExtension = new SetOnce<>(); private final SetOnce remoteClusterAuthenticationService = new SetOnce<>(); + private final SetOnce esqlAuthorizationStep = new SetOnce<>(); private final SetOnce migrationManager = new SetOnce<>(); private final SetOnce> closableComponents = new SetOnce<>(); @@ -1144,6 +1146,9 @@ Collection 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, @@ -1162,7 +1167,8 @@ Collection createComponents( authorizationDenialMessages.get(), linkedProjectConfigService, projectResolver, - getCustomAuthorizedProjectsResolverOrDefault(extensionComponents) + getCustomAuthorizedProjectsResolverOrDefault(extensionComponents), + esqlAuthorizationStep.get().create(settings, linkedProjectConfigService) ); components.add(nativeRolesStore); // used by roles actions @@ -2549,6 +2555,7 @@ public void loadExtensions(ExtensionLoader loader) { RemoteClusterSecurityExtension.Provider.class, CrossClusterAccessSecurityExtension.Provider::new ); + loadSingletonExtensionAndSetOnce(loader, esqlAuthorizationStep, CustomActionAuthorizationStep.Factory.class); } private void loadSingletonExtensionAndSetOnce(ExtensionLoader loader, SetOnce setOnce, Class clazz) { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java index 9297fce4326ca..a0411e631fcd6 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java @@ -172,7 +172,8 @@ public AuthorizationService( AuthorizationDenialMessages authorizationDenialMessages, LinkedProjectConfigService linkedProjectConfigService, ProjectResolver projectResolver, - AuthorizedProjectsResolver authorizedProjectsResolver + AuthorizedProjectsResolver authorizedProjectsResolver, + CustomActionAuthorizationStep esqlAuthorizationStep ) { this.clusterService = clusterService; this.auditTrailService = auditTrailService; @@ -193,7 +194,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; diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/CustomActionAuthorizationStep.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/CustomActionAuthorizationStep.java new file mode 100644 index 0000000000000..130bad8056bbc --- /dev/null +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/CustomActionAuthorizationStep.java @@ -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(); + } + } + } +} diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java index ebea576ae7075..46fe34e871151 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java @@ -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 @@ -334,6 +337,9 @@ public SubscribableListener 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 diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java index cb11095a5aad8..25d8ec03e4dfa 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java @@ -343,7 +343,8 @@ public void setup() { new AuthorizationDenialMessages.Default(), linkedProjectConfigService, projectResolver, - new AuthorizedProjectsResolver.Default() + new AuthorizedProjectsResolver.Default(), + new CustomActionAuthorizationStep.Default() ); } @@ -1778,7 +1779,8 @@ public void testDenialForAnonymousUser() { new AuthorizationDenialMessages.Default(), linkedProjectConfigService, projectResolver, - new AuthorizedProjectsResolver.Default() + new AuthorizedProjectsResolver.Default(), + new CustomActionAuthorizationStep.Default() ); RoleDescriptor role = new RoleDescriptor( @@ -1830,7 +1832,8 @@ public void testDenialForAnonymousUserAuthorizationExceptionDisabled() { new AuthorizationDenialMessages.Default(), linkedProjectConfigService, projectResolver, - new AuthorizedProjectsResolver.Default() + new AuthorizedProjectsResolver.Default(), + new CustomActionAuthorizationStep.Default() ); RoleDescriptor role = new RoleDescriptor( @@ -3370,7 +3373,8 @@ public void testAuthorizationEngineSelectionForCheckPrivileges() throws Exceptio new AuthorizationDenialMessages.Default(), linkedProjectConfigService, projectResolver, - new AuthorizedProjectsResolver.Default() + new AuthorizedProjectsResolver.Default(), + new CustomActionAuthorizationStep.Default() ); Subject subject = new Subject(new User("test", "a role"), mock(RealmRef.class)); @@ -3528,7 +3532,8 @@ public void getUserPrivileges(AuthorizationInfo authorizationInfo, ActionListene new AuthorizationDenialMessages.Default(), linkedProjectConfigService, projectResolver, - new AuthorizedProjectsResolver.Default() + new AuthorizedProjectsResolver.Default(), + new CustomActionAuthorizationStep.Default() ); Authentication authentication; try (StoredContext ignore = threadContext.stashContext()) { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RBACEngineTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RBACEngineTests.java index 9bd9938c19dff..d967211b5994d 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RBACEngineTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RBACEngineTests.java @@ -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() {