From 2739f57ef89e7a4e10401fd597bed82e28fd63bd Mon Sep 17 00:00:00 2001 From: ankitsethi Date: Mon, 18 Aug 2025 23:18:58 -0500 Subject: [PATCH 01/25] initial commit - tests pending potentially --- .../core/security/SecurityExtension.java | 4 +- .../core/security/authc/Authentication.java | 11 +++++ ...tor.java => CustomTokenAuthenticator.java} | 13 +++--- .../xpack/security/Security.java | 38 +++++++++--------- .../authc/AbstractPluggableAuthenticator.java | 35 ++++++++++++++++ .../security/authc/AuthenticationService.java | 18 +++++++-- .../security/authc/AuthenticatorChain.java | 2 + .../authc/PluggableApiKeyAuthenticator.java | 40 ++++++------------- .../PluggableOAuth2TokenAuthenticator.java | 23 +++++++++++ 9 files changed, 128 insertions(+), 56 deletions(-) rename x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/{CustomApiKeyAuthenticator.java => CustomTokenAuthenticator.java} (82%) create mode 100644 x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AbstractPluggableAuthenticator.java create mode 100644 x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableOAuth2TokenAuthenticator.java diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java index f41b19de95272..eaf74d672a1e9 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java @@ -16,7 +16,7 @@ import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.core.security.authc.AuthenticationFailureHandler; import org.elasticsearch.xpack.core.security.authc.Realm; -import org.elasticsearch.xpack.core.security.authc.apikey.CustomApiKeyAuthenticator; +import org.elasticsearch.xpack.core.security.authc.apikey.CustomTokenAuthenticator; import org.elasticsearch.xpack.core.security.authc.service.NodeLocalServiceAccountTokenStore; import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountTokenStore; import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; @@ -129,7 +129,7 @@ default ServiceAccountTokenStore getServiceAccountTokenStore(SecurityComponents return null; } - default CustomApiKeyAuthenticator getCustomApiKeyAuthenticator(SecurityComponents components) { + default List getCustomApiKeyAuthenticator(SecurityComponents components) { return null; } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java index 20a02139aa17e..f2c9faf112358 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java @@ -1376,6 +1376,17 @@ public static Authentication newRealmAuthentication(User user, RealmRef realmRef return authentication; } + public static Authentication newCloudAccessTokenAuthentication(AuthenticationResult authResult, String nodeName) { + assert authResult.isAuthenticated() : "cloud token authn result must be successful"; + final User user = authResult.getValue(); + //#TODO is this right? + final Authentication.RealmRef authenticatedBy = new RealmRef("cloud-saml-kibana", "saml", nodeName, null); + return new Authentication( + new Subject(user, authenticatedBy, TransportVersion.current(), authResult.getMetadata()), + AuthenticationType.TOKEN + ); + } + public static Authentication newCloudApiKeyAuthentication(AuthenticationResult authResult, String nodeName) { assert authResult.isAuthenticated() : "cloud API Key authn result must be successful"; final User apiKeyUser = authResult.getValue(); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomApiKeyAuthenticator.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomTokenAuthenticator.java similarity index 82% rename from x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomApiKeyAuthenticator.java rename to x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomTokenAuthenticator.java index 4f5d05e720715..f410fd0d708ea 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomApiKeyAuthenticator.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomTokenAuthenticator.java @@ -19,24 +19,27 @@ * The implementation is wrapped by a core `Authenticator` class and included in the authenticator chain _before_ the * default API key authenticator. */ -public interface CustomApiKeyAuthenticator { +public interface CustomTokenAuthenticator { + + String CLIENT_AUTHENTICATION_HEADER = "X-Client-Authentication"; + String name(); - AuthenticationToken extractCredentials(@Nullable SecureString apiKeyCredentials); + AuthenticationToken extractCredentials(@Nullable SecureString tokenCredentials); void authenticate(@Nullable AuthenticationToken authenticationToken, ActionListener> listener); /** - * A no-op implementation of {@link CustomApiKeyAuthenticator} that is effectively skipped in the authenticator chain. + * A no-op implementation of {@link CustomTokenAuthenticator} that is effectively skipped in the authenticator chain. */ - class Noop implements CustomApiKeyAuthenticator { + class Noop implements CustomTokenAuthenticator { @Override public String name() { return "noop"; } @Override - public AuthenticationToken extractCredentials(@Nullable SecureString apiKeyCredentials) { + public AuthenticationToken extractCredentials(@Nullable SecureString tokenCredentials) { return null; } 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 a82200aadac2d..bf56c46075039 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 @@ -201,7 +201,7 @@ import org.elasticsearch.xpack.core.security.authc.RealmConfig; import org.elasticsearch.xpack.core.security.authc.RealmSettings; import org.elasticsearch.xpack.core.security.authc.Subject; -import org.elasticsearch.xpack.core.security.authc.apikey.CustomApiKeyAuthenticator; +import org.elasticsearch.xpack.core.security.authc.apikey.CustomTokenAuthenticator; import org.elasticsearch.xpack.core.security.authc.service.NodeLocalServiceAccountTokenStore; import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountTokenStore; import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; @@ -1068,9 +1068,9 @@ Collection createComponents( operatorPrivilegesService.set(OperatorPrivileges.NOOP_OPERATOR_PRIVILEGES_SERVICE); } - final CustomApiKeyAuthenticator customApiKeyAuthenticator = createCustomApiKeyAuthenticator(extensionComponents); + final Collection customTokenAuthenticator = createCustomApiKeyAuthenticator(extensionComponents); - components.add(customApiKeyAuthenticator); + components.add(customTokenAuthenticator); authcService.set( new AuthenticationService( @@ -1084,7 +1084,7 @@ Collection createComponents( apiKeyService, serviceAccountService, operatorPrivilegesService.get(), - customApiKeyAuthenticator, + customTokenAuthenticator, telemetryProvider.getMeterRegistry() ) ); @@ -1220,11 +1220,11 @@ Collection createComponents( return components; } - private CustomApiKeyAuthenticator createCustomApiKeyAuthenticator(SecurityExtension.SecurityComponents extensionComponents) { - final Map customApiKeyAuthenticatorByExtension = new HashMap<>(); + private List createCustomApiKeyAuthenticator(SecurityExtension.SecurityComponents extensionComponents) { + final Map> customApiKeyAuthenticatorByExtension = new HashMap<>(); for (final SecurityExtension extension : securityExtensions) { - final CustomApiKeyAuthenticator customApiKeyAuthenticator = extension.getCustomApiKeyAuthenticator(extensionComponents); - if (customApiKeyAuthenticator != null) { + final List customTokenAuthenticator = extension.getCustomApiKeyAuthenticator(extensionComponents); + if (customTokenAuthenticator != null) { if (false == isInternalExtension(extension)) { throw new IllegalStateException( "The [" @@ -1233,16 +1233,16 @@ private CustomApiKeyAuthenticator createCustomApiKeyAuthenticator(SecurityExtens + "This functionality is not available to external extensions." ); } - customApiKeyAuthenticatorByExtension.put(extension.extensionName(), customApiKeyAuthenticator); + customApiKeyAuthenticatorByExtension.put(extension.extensionName(), customTokenAuthenticator); } } if (customApiKeyAuthenticatorByExtension.isEmpty()) { logger.debug( "No custom implementation for [{}]. Falling-back to noop implementation.", - CustomApiKeyAuthenticator.class.getCanonicalName() + CustomTokenAuthenticator.class.getCanonicalName() ); - return new CustomApiKeyAuthenticator.Noop(); + return List.of(new CustomTokenAuthenticator.Noop()); } else if (customApiKeyAuthenticatorByExtension.size() > 1) { throw new IllegalStateException( @@ -1251,14 +1251,16 @@ private CustomApiKeyAuthenticator createCustomApiKeyAuthenticator(SecurityExtens } else { final var authenticatorByExtensionEntry = customApiKeyAuthenticatorByExtension.entrySet().iterator().next(); - final CustomApiKeyAuthenticator customApiKeyAuthenticator = authenticatorByExtensionEntry.getValue(); + final List customTokenAuthenticators = authenticatorByExtensionEntry.getValue(); final String extensionName = authenticatorByExtensionEntry.getKey(); - logger.debug( - "CustomApiKeyAuthenticator implementation [{}] provided by extension [{}]", - customApiKeyAuthenticator.getClass().getCanonicalName(), - extensionName - ); - return customApiKeyAuthenticator; + for (CustomTokenAuthenticator authenticator : customTokenAuthenticators) { + logger.debug( + "CustomApiKeyAuthenticator implementation [{}] provided by extension [{}]", + authenticator.getClass().getCanonicalName(), + extensionName + ); + } + return customTokenAuthenticators; } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AbstractPluggableAuthenticator.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AbstractPluggableAuthenticator.java new file mode 100644 index 0000000000000..335f823d42100 --- /dev/null +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AbstractPluggableAuthenticator.java @@ -0,0 +1,35 @@ +package org.elasticsearch.xpack.security.authc; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.xpack.core.security.authc.Authentication; +import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; +import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; +import org.elasticsearch.xpack.core.security.authc.apikey.CustomTokenAuthenticator; + +public abstract class AbstractPluggableAuthenticator implements Authenticator { + + @Override + public String name() { + return getAuthenticator().name(); + } + + public void authenticate(Authenticator.Context context, ActionListener> listener) { + final AuthenticationToken authenticationToken = context.getMostRecentAuthenticationToken(); + getAuthenticator().authenticate(authenticationToken, ActionListener.wrap(response -> { + if (response.isAuthenticated()) { + listener.onResponse(response); + } else if (response.getStatus() == AuthenticationResult.Status.TERMINATE) { + final Exception ex = response.getException(); + if (ex == null) { + listener.onFailure(context.getRequest().authenticationFailed(authenticationToken)); + } else { + listener.onFailure(context.getRequest().exceptionProcessingRequest(ex, authenticationToken)); + } + } else if (response.getStatus() == AuthenticationResult.Status.CONTINUE) { + listener.onResponse(AuthenticationResult.notHandled()); + } + }, ex -> listener.onFailure(context.getRequest().exceptionProcessingRequest(ex, authenticationToken)))); + } + + public abstract CustomTokenAuthenticator getAuthenticator(); +} diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java index a9c513a605fe8..12ee0a0f017c4 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java @@ -30,7 +30,7 @@ import org.elasticsearch.xpack.core.security.authc.AuthenticationServiceField; import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; import org.elasticsearch.xpack.core.security.authc.Realm; -import org.elasticsearch.xpack.core.security.authc.apikey.CustomApiKeyAuthenticator; +import org.elasticsearch.xpack.core.security.authc.apikey.CustomTokenAuthenticator; import org.elasticsearch.xpack.core.security.authc.support.AuthenticationContextSerializer; import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine.EmptyAuthorizationInfo; import org.elasticsearch.xpack.core.security.user.AnonymousUser; @@ -42,6 +42,7 @@ import org.elasticsearch.xpack.security.operator.OperatorPrivileges.OperatorPrivilegesService; import org.elasticsearch.xpack.security.support.SecurityIndexManager; +import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicLong; @@ -93,7 +94,7 @@ public AuthenticationService( ApiKeyService apiKeyService, ServiceAccountService serviceAccountService, OperatorPrivilegesService operatorPrivilegesService, - CustomApiKeyAuthenticator customApiKeyAuthenticator, + Collection customTokenAuthenticators, MeterRegistry meterRegistry ) { this.realms = realms; @@ -110,14 +111,25 @@ public AuthenticationService( } final String nodeName = Node.NODE_NAME_SETTING.get(settings); + CustomTokenAuthenticator oauth2Authenticator = customTokenAuthenticators + .stream() + .filter(t -> t.name().contains("oauth2") || t instanceof CustomTokenAuthenticator.Noop) + .findAny() + .orElseThrow(); + CustomTokenAuthenticator apiKeyAuthenticator = customTokenAuthenticators + .stream() + .filter(t -> t.name().contains("api key") || t instanceof CustomTokenAuthenticator.Noop) + .findAny() + .orElseThrow(); this.authenticatorChain = new AuthenticatorChain( settings, operatorPrivilegesService, anonymousUser, new AuthenticationContextSerializer(), new ServiceAccountAuthenticator(serviceAccountService, nodeName, meterRegistry), + new PluggableOAuth2TokenAuthenticator(oauth2Authenticator), new OAuth2TokenAuthenticator(tokenService, meterRegistry), - new PluggableApiKeyAuthenticator(customApiKeyAuthenticator), + new PluggableApiKeyAuthenticator(apiKeyAuthenticator), new ApiKeyAuthenticator(apiKeyService, nodeName, meterRegistry), new RealmsAuthenticator(numInvalidation, lastSuccessfulAuthCache, meterRegistry) ); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticatorChain.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticatorChain.java index f3532dc4c6270..c00571c10baf8 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticatorChain.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticatorChain.java @@ -53,6 +53,7 @@ class AuthenticatorChain { AnonymousUser anonymousUser, AuthenticationContextSerializer authenticationSerializer, ServiceAccountAuthenticator serviceAccountAuthenticator, + PluggableOAuth2TokenAuthenticator pluggableOAuth2TokenAuthenticator, OAuth2TokenAuthenticator oAuth2TokenAuthenticator, PluggableApiKeyAuthenticator pluggableApiKeyAuthenticator, ApiKeyAuthenticator apiKeyAuthenticator, @@ -67,6 +68,7 @@ class AuthenticatorChain { this.realmsAuthenticator = realmsAuthenticator; this.allAuthenticators = List.of( serviceAccountAuthenticator, + pluggableOAuth2TokenAuthenticator, oAuth2TokenAuthenticator, pluggableApiKeyAuthenticator, apiKeyAuthenticator, diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableApiKeyAuthenticator.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableApiKeyAuthenticator.java index 0637efbc5e89a..e8d60e49f1915 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableApiKeyAuthenticator.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableApiKeyAuthenticator.java @@ -7,50 +7,34 @@ package org.elasticsearch.xpack.security.authc; -import org.elasticsearch.action.ActionListener; -import org.elasticsearch.xpack.core.security.authc.Authentication; -import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; -import org.elasticsearch.xpack.core.security.authc.apikey.CustomApiKeyAuthenticator; +import org.elasticsearch.xpack.core.security.authc.apikey.CustomTokenAuthenticator; /** - * An adapter for {@link CustomApiKeyAuthenticator} that implements the {@link Authenticator} interface, so the custom API key authenticator + * An adapter for {@link CustomTokenAuthenticator} that implements the {@link Authenticator} interface, so the custom API key authenticator * can be plugged into the authenticator chain. Module dependencies prevent us from introducing a direct extension point for * an {@link Authenticator}. */ -public class PluggableApiKeyAuthenticator implements Authenticator { - private final CustomApiKeyAuthenticator authenticator; +public class PluggableApiKeyAuthenticator extends AbstractPluggableAuthenticator { + private final CustomTokenAuthenticator apiKeyAuthenticator; - public PluggableApiKeyAuthenticator(CustomApiKeyAuthenticator authenticator) { - this.authenticator = authenticator; + public PluggableApiKeyAuthenticator(CustomTokenAuthenticator apiKeyAuthenticator) { + this.apiKeyAuthenticator = apiKeyAuthenticator; } @Override public String name() { - return authenticator.name(); + return apiKeyAuthenticator.name(); } @Override - public AuthenticationToken extractCredentials(Context context) { - return authenticator.extractCredentials(context.getApiKeyString()); + public AuthenticationToken extractCredentials(Authenticator.Context context) { + return apiKeyAuthenticator.extractCredentials(context.getApiKeyString()); + } @Override - public void authenticate(Context context, ActionListener> listener) { - final AuthenticationToken authenticationToken = context.getMostRecentAuthenticationToken(); - authenticator.authenticate(authenticationToken, ActionListener.wrap(response -> { - if (response.isAuthenticated()) { - listener.onResponse(response); - } else if (response.getStatus() == AuthenticationResult.Status.TERMINATE) { - final Exception ex = response.getException(); - if (ex == null) { - listener.onFailure(context.getRequest().authenticationFailed(authenticationToken)); - } else { - listener.onFailure(context.getRequest().exceptionProcessingRequest(ex, authenticationToken)); - } - } else if (response.getStatus() == AuthenticationResult.Status.CONTINUE) { - listener.onResponse(AuthenticationResult.notHandled()); - } - }, ex -> listener.onFailure(context.getRequest().exceptionProcessingRequest(ex, authenticationToken)))); + public CustomTokenAuthenticator getAuthenticator() { + return apiKeyAuthenticator; } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableOAuth2TokenAuthenticator.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableOAuth2TokenAuthenticator.java new file mode 100644 index 0000000000000..14e1b3622c4f5 --- /dev/null +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableOAuth2TokenAuthenticator.java @@ -0,0 +1,23 @@ +package org.elasticsearch.xpack.security.authc; + +import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; +import org.elasticsearch.xpack.core.security.authc.apikey.CustomTokenAuthenticator; + +public class PluggableOAuth2TokenAuthenticator extends AbstractPluggableAuthenticator { + + private CustomTokenAuthenticator customOAuth2TokenAuthenticator; + + public PluggableOAuth2TokenAuthenticator(CustomTokenAuthenticator authenticator) { + this.customOAuth2TokenAuthenticator = authenticator; + } + + @Override + public AuthenticationToken extractCredentials(Context context) { + return customOAuth2TokenAuthenticator.extractCredentials(context.getBearerString()); + } + + @Override + public CustomTokenAuthenticator getAuthenticator() { + return customOAuth2TokenAuthenticator; + } +} From 832f4699ef857d98e83e7331c95ed151572dc7d7 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Tue, 19 Aug 2025 04:32:45 +0000 Subject: [PATCH 02/25] [CI] Auto commit changes from spotless --- .../xpack/core/security/authc/Authentication.java | 2 +- .../xpack/security/authc/AuthenticationService.java | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java index f2c9faf112358..e6c8ddaadb5d8 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java @@ -1379,7 +1379,7 @@ public static Authentication newRealmAuthentication(User user, RealmRef realmRef public static Authentication newCloudAccessTokenAuthentication(AuthenticationResult authResult, String nodeName) { assert authResult.isAuthenticated() : "cloud token authn result must be successful"; final User user = authResult.getValue(); - //#TODO is this right? + // #TODO is this right? final Authentication.RealmRef authenticatedBy = new RealmRef("cloud-saml-kibana", "saml", nodeName, null); return new Authentication( new Subject(user, authenticatedBy, TransportVersion.current(), authResult.getMetadata()), diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java index 12ee0a0f017c4..62c80a76116b8 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java @@ -111,13 +111,11 @@ public AuthenticationService( } final String nodeName = Node.NODE_NAME_SETTING.get(settings); - CustomTokenAuthenticator oauth2Authenticator = customTokenAuthenticators - .stream() + CustomTokenAuthenticator oauth2Authenticator = customTokenAuthenticators.stream() .filter(t -> t.name().contains("oauth2") || t instanceof CustomTokenAuthenticator.Noop) .findAny() .orElseThrow(); - CustomTokenAuthenticator apiKeyAuthenticator = customTokenAuthenticators - .stream() + CustomTokenAuthenticator apiKeyAuthenticator = customTokenAuthenticators.stream() .filter(t -> t.name().contains("api key") || t instanceof CustomTokenAuthenticator.Noop) .findAny() .orElseThrow(); From 6a8fbb2a9ab80112a0b6a8a25fdbb8d0072cb755 Mon Sep 17 00:00:00 2001 From: ankitsethi Date: Wed, 20 Aug 2025 14:38:19 -0500 Subject: [PATCH 03/25] fix syntax --- .../xpack/security/authc/AuthenticatorChainTests.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticatorChainTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticatorChainTests.java index bfd122655768b..da56e7d30806a 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticatorChainTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticatorChainTests.java @@ -68,6 +68,7 @@ public class AuthenticatorChainTests extends ESTestCase { private OAuth2TokenAuthenticator oAuth2TokenAuthenticator; private ApiKeyAuthenticator apiKeyAuthenticator; private PluggableApiKeyAuthenticator pluggableApiKeyAuthenticator; + private PluggableOAuth2TokenAuthenticator pluggableOAuth2TokenAuthenticator; private RealmsAuthenticator realmsAuthenticator; private Authentication authentication; private User fallbackUser; @@ -104,6 +105,7 @@ public void init() { anonymousUser, authenticationContextSerializer, serviceAccountAuthenticator, + pluggableOAuth2TokenAuthenticator, oAuth2TokenAuthenticator, pluggableApiKeyAuthenticator, apiKeyAuthenticator, From 118705fa0122b5e6a0d68f785550e8313ec8c5d8 Mon Sep 17 00:00:00 2001 From: ankitsethi Date: Wed, 20 Aug 2025 15:23:25 -0500 Subject: [PATCH 04/25] correct javadoc --- .../authc/apikey/CustomTokenAuthenticator.java | 4 ++-- .../authc/AbstractPluggableAuthenticator.java | 7 +++++++ .../authc/PluggableOAuth2TokenAuthenticator.java | 12 ++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomTokenAuthenticator.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomTokenAuthenticator.java index f410fd0d708ea..ff5ca8e4cda87 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomTokenAuthenticator.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomTokenAuthenticator.java @@ -15,9 +15,9 @@ import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; /** - * An extension point to provide a custom API key authenticator implementation. + * An extension point to provide a custom token authenticator implementation. For example, a custom API key or a custom OAuth2 token implementation. * The implementation is wrapped by a core `Authenticator` class and included in the authenticator chain _before_ the - * default API key authenticator. + * respective "standard" authenticator(s). */ public interface CustomTokenAuthenticator { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AbstractPluggableAuthenticator.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AbstractPluggableAuthenticator.java index 335f823d42100..cc080d628e637 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AbstractPluggableAuthenticator.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AbstractPluggableAuthenticator.java @@ -1,3 +1,10 @@ +/* + * 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.authc; import org.elasticsearch.action.ActionListener; diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableOAuth2TokenAuthenticator.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableOAuth2TokenAuthenticator.java index 14e1b3622c4f5..69c1b9230d01a 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableOAuth2TokenAuthenticator.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableOAuth2TokenAuthenticator.java @@ -1,8 +1,20 @@ +/* + * 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.authc; import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; import org.elasticsearch.xpack.core.security.authc.apikey.CustomTokenAuthenticator; +/** + * An adapter for {@link CustomTokenAuthenticator} that implements the {@link Authenticator} interface, so the custom API key authenticator + * can be plugged into the authenticator chain. Module dependencies prevent us from introducing a direct extension point for + * an {@link Authenticator}. + */ public class PluggableOAuth2TokenAuthenticator extends AbstractPluggableAuthenticator { private CustomTokenAuthenticator customOAuth2TokenAuthenticator; From b174e5a72f3800d7b45c9993bd131a73d86b4ddd Mon Sep 17 00:00:00 2001 From: ankitsethi Date: Thu, 21 Aug 2025 11:24:23 -0500 Subject: [PATCH 05/25] fix style issue --- .../core/security/authc/apikey/CustomTokenAuthenticator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomTokenAuthenticator.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomTokenAuthenticator.java index ff5ca8e4cda87..5a5e37d432650 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomTokenAuthenticator.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomTokenAuthenticator.java @@ -15,8 +15,8 @@ import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; /** - * An extension point to provide a custom token authenticator implementation. For example, a custom API key or a custom OAuth2 token implementation. - * The implementation is wrapped by a core `Authenticator` class and included in the authenticator chain _before_ the + * An extension point to provide a custom token authenticator implementation. For example, a custom API key or a custom OAuth2 token + * implementation. The implementation is wrapped by a core `Authenticator` class and included in the authenticator chain _before_ the * respective "standard" authenticator(s). */ public interface CustomTokenAuthenticator { From 0f28ac0bd8f7a0d644e29d2ae763134309689d18 Mon Sep 17 00:00:00 2001 From: ankitsethi Date: Thu, 21 Aug 2025 12:52:17 -0500 Subject: [PATCH 06/25] fix tests --- .../xpack/security/authc/AuthenticationService.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java index 62c80a76116b8..cf439e71cc786 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java @@ -112,13 +112,13 @@ public AuthenticationService( final String nodeName = Node.NODE_NAME_SETTING.get(settings); CustomTokenAuthenticator oauth2Authenticator = customTokenAuthenticators.stream() - .filter(t -> t.name().contains("oauth2") || t instanceof CustomTokenAuthenticator.Noop) + .filter(t -> t.name().contains("oauth2")) .findAny() - .orElseThrow(); + .orElse(new CustomTokenAuthenticator.Noop()); CustomTokenAuthenticator apiKeyAuthenticator = customTokenAuthenticators.stream() - .filter(t -> t.name().contains("api key") || t instanceof CustomTokenAuthenticator.Noop) + .filter(t -> t.name().contains("api key")) .findAny() - .orElseThrow(); + .orElse(new CustomTokenAuthenticator.Noop()); this.authenticatorChain = new AuthenticatorChain( settings, operatorPrivilegesService, From 113b4bade448982ddc7a94b07bd36a2ac4d1a8c9 Mon Sep 17 00:00:00 2001 From: Slobodan Adamovic Date: Fri, 22 Aug 2025 16:04:21 +0200 Subject: [PATCH 07/25] [PoC] Pluggable authenticator chain --- .../core/security/SecurityExtension.java | 4 +- .../authc/apikey/CustomAuthenticator.java | 32 ++++++++ .../apikey/CustomTokenAuthenticator.java | 54 ------------ .../xpack/security/Security.java | 53 ++++++------ .../authc/AbstractPluggableAuthenticator.java | 42 ---------- .../security/authc/AuthenticationService.java | 16 +--- .../security/authc/AuthenticatorChain.java | 23 +++--- .../authc/PluggableApiKeyAuthenticator.java | 40 --------- .../authc/PluggableAuthenticatorChain.java | 82 +++++++++++++++++++ .../PluggableOAuth2TokenAuthenticator.java | 35 -------- .../authc/AuthenticatorChainTests.java | 29 +------ 11 files changed, 162 insertions(+), 248 deletions(-) create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomAuthenticator.java delete mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomTokenAuthenticator.java delete mode 100644 x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AbstractPluggableAuthenticator.java delete mode 100644 x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableApiKeyAuthenticator.java create mode 100644 x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java delete mode 100644 x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableOAuth2TokenAuthenticator.java diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java index eaf74d672a1e9..449246fbb5c92 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java @@ -16,7 +16,7 @@ import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.core.security.authc.AuthenticationFailureHandler; import org.elasticsearch.xpack.core.security.authc.Realm; -import org.elasticsearch.xpack.core.security.authc.apikey.CustomTokenAuthenticator; +import org.elasticsearch.xpack.core.security.authc.apikey.CustomAuthenticator; import org.elasticsearch.xpack.core.security.authc.service.NodeLocalServiceAccountTokenStore; import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountTokenStore; import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; @@ -129,7 +129,7 @@ default ServiceAccountTokenStore getServiceAccountTokenStore(SecurityComponents return null; } - default List getCustomApiKeyAuthenticator(SecurityComponents components) { + default List getCustomAuthenticators(SecurityComponents components) { return null; } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomAuthenticator.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomAuthenticator.java new file mode 100644 index 0000000000000..2d873218afb69 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomAuthenticator.java @@ -0,0 +1,32 @@ +/* + * 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.core.security.authc.apikey; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.core.Nullable; +import org.elasticsearch.xpack.core.security.authc.Authentication; +import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; +import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; +import org.elasticsearch.xpack.core.security.user.User; + +/** + * An extension point to provide a custom token authenticator implementation. For example, a custom API key or a custom OAuth2 token implementation. + * The implementation is wrapped by a core `Authenticator` class and included in the authenticator chain _before_ the + * respective "standard" authenticator(s). + */ +public interface CustomAuthenticator { + + boolean supports(AuthenticationToken token); + + @Nullable + AuthenticationToken extractToken(ThreadContext context); + + void authenticate(@Nullable AuthenticationToken token, ActionListener> listener); + +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomTokenAuthenticator.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomTokenAuthenticator.java deleted file mode 100644 index ff5ca8e4cda87..0000000000000 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomTokenAuthenticator.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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.core.security.authc.apikey; - -import org.elasticsearch.action.ActionListener; -import org.elasticsearch.common.settings.SecureString; -import org.elasticsearch.core.Nullable; -import org.elasticsearch.xpack.core.security.authc.Authentication; -import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; -import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; - -/** - * An extension point to provide a custom token authenticator implementation. For example, a custom API key or a custom OAuth2 token implementation. - * The implementation is wrapped by a core `Authenticator` class and included in the authenticator chain _before_ the - * respective "standard" authenticator(s). - */ -public interface CustomTokenAuthenticator { - - String CLIENT_AUTHENTICATION_HEADER = "X-Client-Authentication"; - - String name(); - - AuthenticationToken extractCredentials(@Nullable SecureString tokenCredentials); - - void authenticate(@Nullable AuthenticationToken authenticationToken, ActionListener> listener); - - /** - * A no-op implementation of {@link CustomTokenAuthenticator} that is effectively skipped in the authenticator chain. - */ - class Noop implements CustomTokenAuthenticator { - @Override - public String name() { - return "noop"; - } - - @Override - public AuthenticationToken extractCredentials(@Nullable SecureString tokenCredentials) { - return null; - } - - @Override - public void authenticate( - @Nullable AuthenticationToken authenticationToken, - ActionListener> listener - ) { - listener.onResponse(AuthenticationResult.notHandled()); - } - } -} 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 3448427ccaccf..7a4a80dcfc412 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 @@ -201,7 +201,7 @@ import org.elasticsearch.xpack.core.security.authc.RealmConfig; import org.elasticsearch.xpack.core.security.authc.RealmSettings; import org.elasticsearch.xpack.core.security.authc.Subject; -import org.elasticsearch.xpack.core.security.authc.apikey.CustomTokenAuthenticator; +import org.elasticsearch.xpack.core.security.authc.apikey.CustomAuthenticator; import org.elasticsearch.xpack.core.security.authc.service.NodeLocalServiceAccountTokenStore; import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountTokenStore; import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; @@ -1068,9 +1068,7 @@ Collection createComponents( operatorPrivilegesService.set(OperatorPrivileges.NOOP_OPERATOR_PRIVILEGES_SERVICE); } - final Collection customTokenAuthenticator = createCustomApiKeyAuthenticator(extensionComponents); - - components.add(customTokenAuthenticator); + final List customAuthenticators = getCustomAuthenticatorFromExtensions(extensionComponents); authcService.set( new AuthenticationService( @@ -1084,7 +1082,7 @@ Collection createComponents( apiKeyService, serviceAccountService, operatorPrivilegesService.get(), - customTokenAuthenticator, + customAuthenticators, telemetryProvider.getMeterRegistry() ) ); @@ -1220,47 +1218,48 @@ Collection createComponents( return components; } - private List createCustomApiKeyAuthenticator(SecurityExtension.SecurityComponents extensionComponents) { - final Map> customApiKeyAuthenticatorByExtension = new HashMap<>(); - for (final SecurityExtension extension : securityExtensions) { - final List customTokenAuthenticator = extension.getCustomApiKeyAuthenticator(extensionComponents); - if (customTokenAuthenticator != null) { - if (false == isInternalExtension(extension)) { + private List getCustomAuthenticatorFromExtensions(SecurityExtension.SecurityComponents extensionComponents) { + final Map> customAuthenticatorsByExtension = new HashMap<>(); + for (final SecurityExtension securityExtension : securityExtensions) { + final List customAuthenticators = securityExtension.getCustomAuthenticators(extensionComponents); + if (customAuthenticators != null) { + if (false == isInternalExtension(securityExtension)) { throw new IllegalStateException( "The [" - + extension.extensionName() - + "] extension tried to install a custom CustomApiKeyAuthenticator. " + + securityExtension.extensionName() + + "] extension tried to install a " + + CustomAuthenticator.class.getSimpleName() + + ". " + "This functionality is not available to external extensions." ); } - customApiKeyAuthenticatorByExtension.put(extension.extensionName(), customTokenAuthenticator); + customAuthenticatorsByExtension.put(securityExtension.extensionName(), customAuthenticators); } } - if (customApiKeyAuthenticatorByExtension.isEmpty()) { + if (customAuthenticatorsByExtension.isEmpty()) { logger.debug( - "No custom implementation for [{}]. Falling-back to noop implementation.", - CustomTokenAuthenticator.class.getCanonicalName() + "No custom implementations for [{}] provided by security extensions.", + CustomAuthenticator.class.getCanonicalName() ); - return List.of(new CustomTokenAuthenticator.Noop()); - - } else if (customApiKeyAuthenticatorByExtension.size() > 1) { + return List.of(); + } else if (customAuthenticatorsByExtension.size() > 1) { throw new IllegalStateException( - "Multiple extensions tried to install a custom CustomApiKeyAuthenticator: " + customApiKeyAuthenticatorByExtension.keySet() + "Multiple extensions tried to install custom authenticators: " + customAuthenticatorsByExtension.keySet() ); - } else { - final var authenticatorByExtensionEntry = customApiKeyAuthenticatorByExtension.entrySet().iterator().next(); - final List customTokenAuthenticators = authenticatorByExtensionEntry.getValue(); + final var authenticatorByExtensionEntry = customAuthenticatorsByExtension.entrySet().iterator().next(); + final List customAuthenticators = authenticatorByExtensionEntry.getValue(); final String extensionName = authenticatorByExtensionEntry.getKey(); - for (CustomTokenAuthenticator authenticator : customTokenAuthenticators) { + for (CustomAuthenticator authenticator : customAuthenticators) { logger.debug( - "CustomApiKeyAuthenticator implementation [{}] provided by extension [{}]", + "{} implementation [{}] provided by extension [{}]", + CustomAuthenticator.class.getSimpleName(), authenticator.getClass().getCanonicalName(), extensionName ); } - return customTokenAuthenticators; + return customAuthenticators; } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AbstractPluggableAuthenticator.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AbstractPluggableAuthenticator.java deleted file mode 100644 index cc080d628e637..0000000000000 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AbstractPluggableAuthenticator.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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.authc; - -import org.elasticsearch.action.ActionListener; -import org.elasticsearch.xpack.core.security.authc.Authentication; -import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; -import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; -import org.elasticsearch.xpack.core.security.authc.apikey.CustomTokenAuthenticator; - -public abstract class AbstractPluggableAuthenticator implements Authenticator { - - @Override - public String name() { - return getAuthenticator().name(); - } - - public void authenticate(Authenticator.Context context, ActionListener> listener) { - final AuthenticationToken authenticationToken = context.getMostRecentAuthenticationToken(); - getAuthenticator().authenticate(authenticationToken, ActionListener.wrap(response -> { - if (response.isAuthenticated()) { - listener.onResponse(response); - } else if (response.getStatus() == AuthenticationResult.Status.TERMINATE) { - final Exception ex = response.getException(); - if (ex == null) { - listener.onFailure(context.getRequest().authenticationFailed(authenticationToken)); - } else { - listener.onFailure(context.getRequest().exceptionProcessingRequest(ex, authenticationToken)); - } - } else if (response.getStatus() == AuthenticationResult.Status.CONTINUE) { - listener.onResponse(AuthenticationResult.notHandled()); - } - }, ex -> listener.onFailure(context.getRequest().exceptionProcessingRequest(ex, authenticationToken)))); - } - - public abstract CustomTokenAuthenticator getAuthenticator(); -} diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java index 62c80a76116b8..cb34d4cf9b450 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java @@ -30,7 +30,7 @@ import org.elasticsearch.xpack.core.security.authc.AuthenticationServiceField; import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; import org.elasticsearch.xpack.core.security.authc.Realm; -import org.elasticsearch.xpack.core.security.authc.apikey.CustomTokenAuthenticator; +import org.elasticsearch.xpack.core.security.authc.apikey.CustomAuthenticator; import org.elasticsearch.xpack.core.security.authc.support.AuthenticationContextSerializer; import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine.EmptyAuthorizationInfo; import org.elasticsearch.xpack.core.security.user.AnonymousUser; @@ -94,7 +94,7 @@ public AuthenticationService( ApiKeyService apiKeyService, ServiceAccountService serviceAccountService, OperatorPrivilegesService operatorPrivilegesService, - Collection customTokenAuthenticators, + List customAuthenticators, MeterRegistry meterRegistry ) { this.realms = realms; @@ -111,23 +111,15 @@ public AuthenticationService( } final String nodeName = Node.NODE_NAME_SETTING.get(settings); - CustomTokenAuthenticator oauth2Authenticator = customTokenAuthenticators.stream() - .filter(t -> t.name().contains("oauth2") || t instanceof CustomTokenAuthenticator.Noop) - .findAny() - .orElseThrow(); - CustomTokenAuthenticator apiKeyAuthenticator = customTokenAuthenticators.stream() - .filter(t -> t.name().contains("api key") || t instanceof CustomTokenAuthenticator.Noop) - .findAny() - .orElseThrow(); + this.authenticatorChain = new AuthenticatorChain( settings, operatorPrivilegesService, anonymousUser, new AuthenticationContextSerializer(), + new PluggableAuthenticatorChain(customAuthenticators), new ServiceAccountAuthenticator(serviceAccountService, nodeName, meterRegistry), - new PluggableOAuth2TokenAuthenticator(oauth2Authenticator), new OAuth2TokenAuthenticator(tokenService, meterRegistry), - new PluggableApiKeyAuthenticator(apiKeyAuthenticator), new ApiKeyAuthenticator(apiKeyService, nodeName, meterRegistry), new RealmsAuthenticator(numInvalidation, lastSuccessfulAuthCache, meterRegistry) ); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticatorChain.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticatorChain.java index c00571c10baf8..a87225c9f7682 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticatorChain.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticatorChain.java @@ -26,6 +26,8 @@ import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.operator.OperatorPrivileges.OperatorPrivilegesService; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.function.BiConsumer; @@ -52,10 +54,9 @@ class AuthenticatorChain { OperatorPrivilegesService operatorPrivilegesService, AnonymousUser anonymousUser, AuthenticationContextSerializer authenticationSerializer, + PluggableAuthenticatorChain pluggableAuthenticatorChain, ServiceAccountAuthenticator serviceAccountAuthenticator, - PluggableOAuth2TokenAuthenticator pluggableOAuth2TokenAuthenticator, OAuth2TokenAuthenticator oAuth2TokenAuthenticator, - PluggableApiKeyAuthenticator pluggableApiKeyAuthenticator, ApiKeyAuthenticator apiKeyAuthenticator, RealmsAuthenticator realmsAuthenticator ) { @@ -66,14 +67,16 @@ class AuthenticatorChain { this.isAnonymousUserEnabled = AnonymousUser.isAnonymousEnabled(settings); this.authenticationSerializer = authenticationSerializer; this.realmsAuthenticator = realmsAuthenticator; - this.allAuthenticators = List.of( - serviceAccountAuthenticator, - pluggableOAuth2TokenAuthenticator, - oAuth2TokenAuthenticator, - pluggableApiKeyAuthenticator, - apiKeyAuthenticator, - realmsAuthenticator - ); + + List authenticators = new ArrayList<>(); + if (pluggableAuthenticatorChain.hasCustomAuthenticators()) { + authenticators.add(pluggableAuthenticatorChain); + } + authenticators.add(serviceAccountAuthenticator); + authenticators.add(oAuth2TokenAuthenticator); + authenticators.add(apiKeyAuthenticator); + authenticators.add(realmsAuthenticator); + this.allAuthenticators = Collections.unmodifiableList(authenticators); } void authenticate(Authenticator.Context context, ActionListener originalListener) { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableApiKeyAuthenticator.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableApiKeyAuthenticator.java deleted file mode 100644 index e8d60e49f1915..0000000000000 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableApiKeyAuthenticator.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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.authc; - -import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; -import org.elasticsearch.xpack.core.security.authc.apikey.CustomTokenAuthenticator; - -/** - * An adapter for {@link CustomTokenAuthenticator} that implements the {@link Authenticator} interface, so the custom API key authenticator - * can be plugged into the authenticator chain. Module dependencies prevent us from introducing a direct extension point for - * an {@link Authenticator}. - */ -public class PluggableApiKeyAuthenticator extends AbstractPluggableAuthenticator { - private final CustomTokenAuthenticator apiKeyAuthenticator; - - public PluggableApiKeyAuthenticator(CustomTokenAuthenticator apiKeyAuthenticator) { - this.apiKeyAuthenticator = apiKeyAuthenticator; - } - - @Override - public String name() { - return apiKeyAuthenticator.name(); - } - - @Override - public AuthenticationToken extractCredentials(Authenticator.Context context) { - return apiKeyAuthenticator.extractCredentials(context.getApiKeyString()); - - } - - @Override - public CustomTokenAuthenticator getAuthenticator() { - return apiKeyAuthenticator; - } -} diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java new file mode 100644 index 0000000000000..9780b4ae43f93 --- /dev/null +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java @@ -0,0 +1,82 @@ +/* + * 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.authc; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.xpack.core.security.authc.Authentication; +import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; +import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; +import org.elasticsearch.xpack.core.security.authc.apikey.CustomAuthenticator; + +import java.util.List; + +public class PluggableAuthenticatorChain implements Authenticator { + + public static final PluggableAuthenticatorChain EMPTY = new PluggableAuthenticatorChain(List.of()); + + private final List customAuthenticators; + + public PluggableAuthenticatorChain(List customAuthenticators) { + this.customAuthenticators = customAuthenticators; + } + + @Override + public String name() { + return "pluggable custom authenticator chain"; + } + + public boolean hasCustomAuthenticators() { + return customAuthenticators.size() > 0; + } + + @Override + public AuthenticationToken extractCredentials(Context context) { + if (false == hasCustomAuthenticators()) { + return null; + } + for (CustomAuthenticator customAuthenticator : customAuthenticators) { + AuthenticationToken token = customAuthenticator.extractToken(context.getThreadContext()); + if (token != null) { + return token; + } + } + return null; + } + + @Override + public void authenticate(Context context, ActionListener> listener) { + if (false == hasCustomAuthenticators()) { + listener.onResponse(AuthenticationResult.notHandled()); + return; + } + AuthenticationToken token = context.getMostRecentAuthenticationToken(); + if (token != null) { + for (CustomAuthenticator customAuthenticator : customAuthenticators) { + if (customAuthenticator.supports(token)) { + customAuthenticator.authenticate(token, ActionListener.wrap(response -> { + if (response.isAuthenticated()) { + listener.onResponse(response); + } else if (response.getStatus() == AuthenticationResult.Status.TERMINATE) { + final Exception ex = response.getException(); + if (ex == null) { + listener.onFailure(context.getRequest().authenticationFailed(token)); + } else { + listener.onFailure(context.getRequest().exceptionProcessingRequest(ex, token)); + } + } else if (response.getStatus() == AuthenticationResult.Status.CONTINUE) { + listener.onResponse(AuthenticationResult.notHandled()); + } + }, ex -> listener.onFailure(context.getRequest().exceptionProcessingRequest(ex, token)))); + return; + } + } + } + listener.onResponse(AuthenticationResult.notHandled()); + } + +} diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableOAuth2TokenAuthenticator.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableOAuth2TokenAuthenticator.java deleted file mode 100644 index 69c1b9230d01a..0000000000000 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableOAuth2TokenAuthenticator.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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.authc; - -import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; -import org.elasticsearch.xpack.core.security.authc.apikey.CustomTokenAuthenticator; - -/** - * An adapter for {@link CustomTokenAuthenticator} that implements the {@link Authenticator} interface, so the custom API key authenticator - * can be plugged into the authenticator chain. Module dependencies prevent us from introducing a direct extension point for - * an {@link Authenticator}. - */ -public class PluggableOAuth2TokenAuthenticator extends AbstractPluggableAuthenticator { - - private CustomTokenAuthenticator customOAuth2TokenAuthenticator; - - public PluggableOAuth2TokenAuthenticator(CustomTokenAuthenticator authenticator) { - this.customOAuth2TokenAuthenticator = authenticator; - } - - @Override - public AuthenticationToken extractCredentials(Context context) { - return customOAuth2TokenAuthenticator.extractCredentials(context.getBearerString()); - } - - @Override - public CustomTokenAuthenticator getAuthenticator() { - return customOAuth2TokenAuthenticator; - } -} diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticatorChainTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticatorChainTests.java index da56e7d30806a..ce5ca298a9d75 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticatorChainTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticatorChainTests.java @@ -67,8 +67,6 @@ public class AuthenticatorChainTests extends ESTestCase { private ServiceAccountAuthenticator serviceAccountAuthenticator; private OAuth2TokenAuthenticator oAuth2TokenAuthenticator; private ApiKeyAuthenticator apiKeyAuthenticator; - private PluggableApiKeyAuthenticator pluggableApiKeyAuthenticator; - private PluggableOAuth2TokenAuthenticator pluggableOAuth2TokenAuthenticator; private RealmsAuthenticator realmsAuthenticator; private Authentication authentication; private User fallbackUser; @@ -93,7 +91,8 @@ public void init() { oAuth2TokenAuthenticator = mock(OAuth2TokenAuthenticator.class); apiKeyAuthenticator = mock(ApiKeyAuthenticator.class); realmsAuthenticator = mock(RealmsAuthenticator.class); - pluggableApiKeyAuthenticator = mock(PluggableApiKeyAuthenticator.class); + PluggableAuthenticatorChain pluggableAuthenticatorChain = PluggableAuthenticatorChain.EMPTY; + when(realms.getActiveRealms()).thenReturn(List.of(mock(Realm.class))); when(realms.getUnlicensedRealms()).thenReturn(List.of()); final User user = new User(randomAlphaOfLength(8)); @@ -104,10 +103,9 @@ public void init() { operatorPrivilegesService, anonymousUser, authenticationContextSerializer, + pluggableAuthenticatorChain, serviceAccountAuthenticator, - pluggableOAuth2TokenAuthenticator, oAuth2TokenAuthenticator, - pluggableApiKeyAuthenticator, apiKeyAuthenticator, realmsAuthenticator ); @@ -222,13 +220,6 @@ public void testAuthenticateWithApiKey() throws IOException { new ApiKeyCredentials(randomAlphaOfLength(20), apiKeySecret, randomFrom(ApiKey.Type.values())) ); doCallRealMethod().when(serviceAccountAuthenticator).authenticate(eq(context), anyActionListener()); - doAnswer(invocationOnMock -> { - @SuppressWarnings("unchecked") - final ActionListener> listener = (ActionListener< - AuthenticationResult>) invocationOnMock.getArguments()[1]; - listener.onResponse(AuthenticationResult.notHandled()); - return null; - }).when(pluggableApiKeyAuthenticator).authenticate(eq(context), any()); doCallRealMethod().when(oAuth2TokenAuthenticator).authenticate(eq(context), anyActionListener()); } doAnswer(invocationOnMock -> { @@ -271,13 +262,6 @@ public void testAuthenticateWithRealms() throws IOException { doCallRealMethod().when(serviceAccountAuthenticator).authenticate(eq(context), anyActionListener()); doCallRealMethod().when(oAuth2TokenAuthenticator).authenticate(eq(context), anyActionListener()); doCallRealMethod().when(apiKeyAuthenticator).authenticate(eq(context), anyActionListener()); - doAnswer(invocationOnMock -> { - @SuppressWarnings("unchecked") - final ActionListener> listener = (ActionListener< - AuthenticationResult>) invocationOnMock.getArguments()[1]; - listener.onResponse(AuthenticationResult.notHandled()); - return null; - }).when(pluggableApiKeyAuthenticator).authenticate(eq(context), any()); } doAnswer(invocationOnMock -> { @SuppressWarnings("unchecked") @@ -340,13 +324,6 @@ public void testContextWithDirectWrongTokenFailsAuthn() { doCallRealMethod().when(serviceAccountAuthenticator).authenticate(eq(context), anyActionListener()); doCallRealMethod().when(oAuth2TokenAuthenticator).authenticate(eq(context), anyActionListener()); doCallRealMethod().when(apiKeyAuthenticator).authenticate(eq(context), anyActionListener()); - doAnswer(invocationOnMock -> { - @SuppressWarnings("unchecked") - final ActionListener> listener = (ActionListener< - AuthenticationResult>) invocationOnMock.getArguments()[1]; - listener.onResponse(AuthenticationResult.notHandled()); - return null; - }).when(pluggableApiKeyAuthenticator).authenticate(eq(context), any()); // 1. realms do not consume the token doAnswer(invocationOnMock -> { From 040a9aa9e067bcc4fa04e46e69d43ffc37c913c0 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Fri, 22 Aug 2025 14:28:47 +0000 Subject: [PATCH 08/25] [CI] Auto commit changes from spotless --- .../xpack/core/security/authc/apikey/CustomAuthenticator.java | 1 - .../xpack/security/authc/AuthenticationService.java | 1 - 2 files changed, 2 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomAuthenticator.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomAuthenticator.java index 2d873218afb69..39f5b22357b73 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomAuthenticator.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomAuthenticator.java @@ -13,7 +13,6 @@ import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; -import org.elasticsearch.xpack.core.security.user.User; /** * An extension point to provide a custom token authenticator implementation. For example, a custom API key or a custom OAuth2 token implementation. diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java index cb34d4cf9b450..5fb930ceb3324 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java @@ -42,7 +42,6 @@ import org.elasticsearch.xpack.security.operator.OperatorPrivileges.OperatorPrivilegesService; import org.elasticsearch.xpack.security.support.SecurityIndexManager; -import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicLong; From b2b640412cbe158fbd440a0bf1c47f1e7e3f1e0a Mon Sep 17 00:00:00 2001 From: Slobodan Adamovic Date: Mon, 25 Aug 2025 09:37:25 +0200 Subject: [PATCH 09/25] spotless + remove unused method --- .../xpack/core/security/authc/Authentication.java | 11 ----------- .../xpack/security/authc/Authenticator.java | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java index e6c8ddaadb5d8..20a02139aa17e 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java @@ -1376,17 +1376,6 @@ public static Authentication newRealmAuthentication(User user, RealmRef realmRef return authentication; } - public static Authentication newCloudAccessTokenAuthentication(AuthenticationResult authResult, String nodeName) { - assert authResult.isAuthenticated() : "cloud token authn result must be successful"; - final User user = authResult.getValue(); - // #TODO is this right? - final Authentication.RealmRef authenticatedBy = new RealmRef("cloud-saml-kibana", "saml", nodeName, null); - return new Authentication( - new Subject(user, authenticatedBy, TransportVersion.current(), authResult.getMetadata()), - AuthenticationType.TOKEN - ); - } - public static Authentication newCloudApiKeyAuthentication(AuthenticationResult authResult, String nodeName) { assert authResult.isAuthenticated() : "cloud API Key authn result must be successful"; final User apiKeyUser = authResult.getValue(); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/Authenticator.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/Authenticator.java index ed218c3eba58d..6433cfacc77bb 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/Authenticator.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/Authenticator.java @@ -37,7 +37,7 @@ public interface Authenticator { /** * Attempt to Extract an {@link AuthenticationToken} from the given {@link Context}. * @param context The context object encapsulating current request and other information relevant for authentication. - * + * @return An {@link AuthenticationToken} if one can be extracted or null if this Authenticator cannot * extract one. */ From c782a2cc37b612b433dc26a3a941816e02420a7c Mon Sep 17 00:00:00 2001 From: Slobodan Adamovic Date: Mon, 25 Aug 2025 09:47:59 +0200 Subject: [PATCH 10/25] fix javadoc line lenght --- .../core/security/authc/apikey/CustomAuthenticator.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomAuthenticator.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomAuthenticator.java index 39f5b22357b73..5415714bb3114 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomAuthenticator.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomAuthenticator.java @@ -15,9 +15,9 @@ import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; /** - * An extension point to provide a custom token authenticator implementation. For example, a custom API key or a custom OAuth2 token implementation. - * The implementation is wrapped by a core `Authenticator` class and included in the authenticator chain _before_ the - * respective "standard" authenticator(s). + * An extension point to provide a custom authenticator implementation. For example, a custom API key or a custom OAuth2 + * token implementation. The implementation is wrapped by a core `Authenticator` class and included in the authenticator chain + * _before_ the respective "standard" authenticator(s). */ public interface CustomAuthenticator { From cf543eb833f3d0739e61d0a9389f21300263ccbe Mon Sep 17 00:00:00 2001 From: ankitsethi Date: Mon, 25 Aug 2025 15:46:47 -0500 Subject: [PATCH 11/25] refactor with code review feedback and new validation for cloud-saml-kibana --- .../xpack/core/security/authc/Authentication.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java index 20a02139aa17e..6fd3bd0f99e15 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java @@ -1376,6 +1376,16 @@ public static Authentication newRealmAuthentication(User user, RealmRef realmRef return authentication; } + public static Authentication newCloudAccessTokenAuthentication(AuthenticationResult authResult, + Authentication.RealmRef realmRef) { + assert authResult.isAuthenticated() : "cloud access token authn result must be successful"; + final User apiKeyUser = authResult.getValue(); + return new Authentication( + new Subject(apiKeyUser, realmRef, TransportVersion.current(), authResult.getMetadata()), + AuthenticationType.TOKEN + ); + } + public static Authentication newCloudApiKeyAuthentication(AuthenticationResult authResult, String nodeName) { assert authResult.isAuthenticated() : "cloud API Key authn result must be successful"; final User apiKeyUser = authResult.getValue(); From ebd41888765d5944c136907cb0440f2d95935441 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Mon, 25 Aug 2025 20:54:41 +0000 Subject: [PATCH 12/25] [CI] Auto commit changes from spotless --- .../xpack/core/security/authc/Authentication.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java index 6fd3bd0f99e15..591ee667c1e01 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java @@ -1376,8 +1376,10 @@ public static Authentication newRealmAuthentication(User user, RealmRef realmRef return authentication; } - public static Authentication newCloudAccessTokenAuthentication(AuthenticationResult authResult, - Authentication.RealmRef realmRef) { + public static Authentication newCloudAccessTokenAuthentication( + AuthenticationResult authResult, + Authentication.RealmRef realmRef + ) { assert authResult.isAuthenticated() : "cloud access token authn result must be successful"; final User apiKeyUser = authResult.getValue(); return new Authentication( From 4a32400a9cb71d4273c23454d327667d8f2bf3a4 Mon Sep 17 00:00:00 2001 From: ankitsethi Date: Tue, 2 Sep 2025 22:47:56 -0500 Subject: [PATCH 13/25] code review stuff --- .../xpack/core/security/authc/Authentication.java | 4 ++-- .../java/org/elasticsearch/xpack/security/Security.java | 1 + .../xpack/security/authc/PluggableAuthenticatorChain.java | 6 +++--- .../xpack/security/authc/AuthenticatorChainTests.java | 3 ++- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java index 591ee667c1e01..ac543c4f81cf3 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java @@ -1381,9 +1381,9 @@ public static Authentication newCloudAccessTokenAuthentication( Authentication.RealmRef realmRef ) { assert authResult.isAuthenticated() : "cloud access token authn result must be successful"; - final User apiKeyUser = authResult.getValue(); + final User user = authResult.getValue(); return new Authentication( - new Subject(apiKeyUser, realmRef, TransportVersion.current(), authResult.getMetadata()), + new Subject(user, realmRef, TransportVersion.current(), authResult.getMetadata()), AuthenticationType.TOKEN ); } 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 ba7283125d26e..7e0b5befbf529 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 @@ -1081,6 +1081,7 @@ Collection createComponents( } final List customAuthenticators = getCustomAuthenticatorFromExtensions(extensionComponents); + components.addAll(customAuthenticators); authcService.set( new AuthenticationService( diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java index 9780b4ae43f93..6ecd26d224313 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java @@ -14,15 +14,14 @@ import org.elasticsearch.xpack.core.security.authc.apikey.CustomAuthenticator; import java.util.List; +import java.util.Objects; public class PluggableAuthenticatorChain implements Authenticator { - public static final PluggableAuthenticatorChain EMPTY = new PluggableAuthenticatorChain(List.of()); - private final List customAuthenticators; public PluggableAuthenticatorChain(List customAuthenticators) { - this.customAuthenticators = customAuthenticators; + this.customAuthenticators = Objects.requireNonNull(customAuthenticators); } @Override @@ -56,6 +55,7 @@ public void authenticate(Context context, ActionListener { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticatorChainTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticatorChainTests.java index ce5ca298a9d75..87bafcd2da01f 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticatorChainTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticatorChainTests.java @@ -38,6 +38,7 @@ import org.junit.Before; import java.io.IOException; +import java.util.Collections; import java.util.List; import static org.elasticsearch.test.ActionListenerUtils.anyActionListener; @@ -91,7 +92,7 @@ public void init() { oAuth2TokenAuthenticator = mock(OAuth2TokenAuthenticator.class); apiKeyAuthenticator = mock(ApiKeyAuthenticator.class); realmsAuthenticator = mock(RealmsAuthenticator.class); - PluggableAuthenticatorChain pluggableAuthenticatorChain = PluggableAuthenticatorChain.EMPTY; + PluggableAuthenticatorChain pluggableAuthenticatorChain = new PluggableAuthenticatorChain(Collections.emptyList()); when(realms.getActiveRealms()).thenReturn(List.of(mock(Realm.class))); when(realms.getUnlicensedRealms()).thenReturn(List.of()); From 2d716c66ee0e31896699ce00b078b8d66ff3911e Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Wed, 3 Sep 2025 03:58:44 +0000 Subject: [PATCH 14/25] [CI] Auto commit changes from spotless --- .../xpack/security/authc/PluggableAuthenticatorChain.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java index 6ecd26d224313..41a8412ce4685 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java @@ -55,7 +55,7 @@ public void authenticate(Context context, ActionListener { From 72d3d0ff9210c57e25a16b1347d3f5c9fd8a85c2 Mon Sep 17 00:00:00 2001 From: Ankit Sethi Date: Fri, 5 Sep 2025 12:23:58 -0500 Subject: [PATCH 15/25] followups from previous PR - 1. Using IteratingActionListener instead of a for-loop in the main flow of PluggableAuthenticatorChain 2. Adding unit test coverage for PluggableAuthenticatorChain --- .../core/security/authc/Authentication.java | 19 +- .../authc/apikey/CustomAuthenticator.java | 3 + .../authc/PluggableAuthenticatorChain.java | 61 ++- .../PluggableAuthenticatorChainTests.java | 361 ++++++++++++++++++ 4 files changed, 417 insertions(+), 27 deletions(-) create mode 100644 x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChainTests.java diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java index ac543c4f81cf3..f1840037c90a1 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java @@ -59,6 +59,7 @@ import static org.elasticsearch.transport.RemoteClusterPortSettings.TRANSPORT_VERSION_ADVANCED_REMOTE_CLUSTER_SECURITY; import static org.elasticsearch.xcontent.ConstructingObjectParser.constructorArg; import static org.elasticsearch.xcontent.ConstructingObjectParser.optionalConstructorArg; +import static org.elasticsearch.xpack.core.security.authc.Authentication.AuthenticationType.TOKEN; import static org.elasticsearch.xpack.core.security.authc.Authentication.RealmRef.newAnonymousRealmRef; import static org.elasticsearch.xpack.core.security.authc.Authentication.RealmRef.newApiKeyRealmRef; import static org.elasticsearch.xpack.core.security.authc.Authentication.RealmRef.newCloudApiKeyRealmRef; @@ -83,6 +84,7 @@ import static org.elasticsearch.xpack.core.security.authc.AuthenticationField.FALLBACK_REALM_NAME; import static org.elasticsearch.xpack.core.security.authc.AuthenticationField.FALLBACK_REALM_TYPE; import static org.elasticsearch.xpack.core.security.authc.RealmDomain.REALM_DOMAIN_PARSER; +import static org.elasticsearch.xpack.core.security.authc.Subject.Type.USER; import static org.elasticsearch.xpack.core.security.authz.RoleDescriptor.Fields.REMOTE_CLUSTER; import static org.elasticsearch.xpack.core.security.authz.permission.RemoteClusterPermissions.ROLE_REMOTE_CLUSTER_PRIVS; @@ -424,7 +426,7 @@ public Authentication token() { assert false == isAuthenticatedInternally(); assert false == isServiceAccount(); assert false == isCrossClusterAccess(); - final Authentication newTokenAuthentication = new Authentication(effectiveSubject, authenticatingSubject, AuthenticationType.TOKEN); + final Authentication newTokenAuthentication = new Authentication(effectiveSubject, authenticatingSubject, TOKEN); return newTokenAuthentication; } @@ -603,7 +605,7 @@ public boolean supportsRunAs(@Nullable AnonymousUser anonymousUser) { // Run-as is supported for authentication with realm, api_key or token. if (AuthenticationType.REALM == getAuthenticationType() || AuthenticationType.API_KEY == getAuthenticationType() - || AuthenticationType.TOKEN == getAuthenticationType()) { + || TOKEN == getAuthenticationType()) { return true; } @@ -717,7 +719,7 @@ public boolean canAccessResourcesOf(Authentication resourceCreatorAuthentication assert EnumSet.of( Authentication.AuthenticationType.REALM, Authentication.AuthenticationType.API_KEY, - Authentication.AuthenticationType.TOKEN, + TOKEN, Authentication.AuthenticationType.ANONYMOUS, Authentication.AuthenticationType.INTERNAL ).containsAll(EnumSet.of(getAuthenticationType(), resourceCreatorAuthentication.getAuthenticationType())) @@ -823,6 +825,9 @@ public void toXContentFragment(XContentBuilder builder) throws IOException { apiKeyField.put("managed_by", CredentialManagedBy.CLOUD.getDisplayName()); builder.field("api_key", Collections.unmodifiableMap(apiKeyField)); } + if (metadata.containsKey("managed_by")) { + builder.field("managed_by", metadata.get("managed_by")); + } } public static Authentication getAuthenticationFromCrossClusterAccessMetadata(Authentication authentication) { @@ -982,7 +987,7 @@ private void checkConsistencyForApiKeyAuthenticationType() { } private void checkConsistencyForRealmAuthenticationType() { - if (Subject.Type.USER != authenticatingSubject.getType()) { + if (USER != authenticatingSubject.getType()) { throw new IllegalArgumentException("Realm authentication must have subject type of user"); } if (isRunAs()) { @@ -1025,7 +1030,7 @@ private static void checkRunAsConsistency(Subject effectiveSubject, Subject auth ) ); } - if (Subject.Type.USER != effectiveSubject.getType()) { + if (USER != effectiveSubject.getType()) { throw new IllegalArgumentException(Strings.format("Run-as subject type cannot be [%s]", effectiveSubject.getType())); } if (false == effectiveSubject.getMetadata().isEmpty()) { @@ -1357,7 +1362,7 @@ public static Authentication newServiceAccountAuthentication(User serviceAccount final Authentication.RealmRef authenticatedBy = newServiceAccountRealmRef(nodeName); Authentication authentication = new Authentication( new Subject(serviceAccountUser, authenticatedBy, TransportVersion.current(), metadata), - AuthenticationType.TOKEN + TOKEN ); return authentication; } @@ -1384,7 +1389,7 @@ public static Authentication newCloudAccessTokenAuthentication( final User user = authResult.getValue(); return new Authentication( new Subject(user, realmRef, TransportVersion.current(), authResult.getMetadata()), - AuthenticationType.TOKEN + TOKEN ); } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomAuthenticator.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomAuthenticator.java index 5415714bb3114..25789bad921f5 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomAuthenticator.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomAuthenticator.java @@ -13,6 +13,7 @@ import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; +import org.elasticsearch.xpack.core.security.user.User; /** * An extension point to provide a custom authenticator implementation. For example, a custom API key or a custom OAuth2 @@ -28,4 +29,6 @@ public interface CustomAuthenticator { void authenticate(@Nullable AuthenticationToken token, ActionListener> listener); + Authentication getAuthentication(AuthenticationResult result, String nodeName); + } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java index 41a8412ce4685..4d656283ffd2f 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java @@ -8,6 +8,7 @@ package org.elasticsearch.xpack.security.authc; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.xpack.core.common.IteratingActionListener; import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; @@ -15,6 +16,7 @@ import java.util.List; import java.util.Objects; +import java.util.function.BiConsumer; public class PluggableAuthenticatorChain implements Authenticator { @@ -55,28 +57,47 @@ public void authenticate(Context context, ActionListener { - if (response.isAuthenticated()) { - listener.onResponse(response); - } else if (response.getStatus() == AuthenticationResult.Status.TERMINATE) { - final Exception ex = response.getException(); - if (ex == null) { - listener.onFailure(context.getRequest().authenticationFailed(token)); - } else { - listener.onFailure(context.getRequest().exceptionProcessingRequest(ex, token)); - } - } else if (response.getStatus() == AuthenticationResult.Status.CONTINUE) { - listener.onResponse(AuthenticationResult.notHandled()); - } - }, ex -> listener.onFailure(context.getRequest().exceptionProcessingRequest(ex, token)))); - return; - } - } + var lis = new IteratingActionListener<>(listener, + getAuthConsumer(context), + customAuthenticators, + context.getThreadContext(), + result -> { + if (result == null) { + // all custom authenticators left the token unhandled + return AuthenticationResult.notHandled(); + } + return result; + }, + result -> result == null || result.getStatus() == AuthenticationResult.Status.CONTINUE); + lis.run(); + return; } listener.onResponse(AuthenticationResult.notHandled()); } + private BiConsumer>> getAuthConsumer(Context context) { + AuthenticationToken token = context.getMostRecentAuthenticationToken(); + return (authenticator, iteratingListener) -> { + if (authenticator.supports(token)) { + authenticator.authenticate(token, ActionListener.wrap(response -> { + if (response.isAuthenticated()) { + iteratingListener.onResponse(response); + } else if (response.getStatus() == AuthenticationResult.Status.TERMINATE) { + final Exception ex = response.getException(); + if (ex == null) { + iteratingListener.onFailure(context.getRequest().authenticationFailed(token)); + } else { + iteratingListener.onFailure(context.getRequest().exceptionProcessingRequest(ex, token)); + } + } else if (response.getStatus() == AuthenticationResult.Status.CONTINUE) { + iteratingListener.onResponse(AuthenticationResult.notHandled()); + } + }, ex -> iteratingListener.onFailure(context.getRequest().exceptionProcessingRequest(ex, token)))); + } + else { + iteratingListener.onResponse(null); // try the next custom authenticator + } + }; + } + } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChainTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChainTests.java new file mode 100644 index 0000000000000..961dc46827475 --- /dev/null +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChainTests.java @@ -0,0 +1,361 @@ +/* + * 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.authc; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.core.Nullable; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.core.security.authc.Authentication; +import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; +import org.elasticsearch.xpack.core.security.authc.AuthenticationTestHelper; +import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; +import org.elasticsearch.xpack.core.security.authc.apikey.CustomAuthenticator; +import org.elasticsearch.xpack.core.security.user.User; +import org.junit.Before; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.notNullValue; + +public class PluggableAuthenticatorChainTests extends ESTestCase { + + private ThreadContext threadContext; + + private static class TestTokenA implements AuthenticationToken { + private final String value; + + TestTokenA(String value) { + this.value = value; + } + + @Override + public String principal() { + return "user-" + value; + } + + @Override + public Object credentials() { + return value; + } + + @Override + public void clearCredentials() { + // no-op + } + + public String getValue() { + return value; + } + } + + private static class TestTokenB implements AuthenticationToken { + private final String value; + + TestTokenB(String value) { + this.value = value; + } + + @Override + public String principal() { + return "user-" + value; + } + + @Override + public Object credentials() { + return value; + } + + @Override + public void clearCredentials() { + // no-op + } + + public String getValue() { + return value; + } + } + + public class TokenAAuthenticator implements CustomAuthenticator { + + private final String id; + + public TokenAAuthenticator() { + id = "1"; + } + + public TokenAAuthenticator(String id) { + this.id = id; + } + + @Override + public boolean supports(AuthenticationToken token) { + return token instanceof TestTokenA; + } + + @Override + public @Nullable AuthenticationToken extractToken(ThreadContext context) { + return new TestTokenA("foo"); + } + + @Override + public void authenticate(@Nullable AuthenticationToken token, ActionListener> listener) { + if (token instanceof TestTokenA testToken) { + User user = new User("token-a-auth-user-" + id + "-" + testToken.getValue()); + Authentication auth = AuthenticationTestHelper.builder().user(user).build(false); + listener.onResponse(AuthenticationResult.success(auth)); + } else { + listener.onResponse(AuthenticationResult.notHandled()); + } + } + + @Override + public Authentication getAuthentication(AuthenticationResult result, String nodeName) { + return AuthenticationTestHelper.builder().user(result.getValue()).build(false); + } + } + + public class TokenBAuthenticator implements CustomAuthenticator { + + private final String id; + + public TokenBAuthenticator() { + id = "1"; + } + + public TokenBAuthenticator(String id) { + this.id = id; + } + + @Override + public boolean supports(AuthenticationToken token) { + return token instanceof TestTokenB; + } + + @Override + public @Nullable AuthenticationToken extractToken(ThreadContext context) { + return new TestTokenB("foo"); + } + + @Override + public void authenticate(@Nullable AuthenticationToken token, ActionListener> listener) { + if (token instanceof TestTokenB testToken) { + User user = new User("token-b-auth-user-" + id + "-" + testToken.getValue()); + Authentication auth = AuthenticationTestHelper.builder().user(user).build(false); + listener.onResponse(AuthenticationResult.success(auth)); + } else { + listener.onResponse(AuthenticationResult.notHandled()); + } + } + + @Override + public Authentication getAuthentication(AuthenticationResult result, String nodeName) { + return AuthenticationTestHelper.builder().user(result.getValue()).build(false); + } + } + + @Before + public void init() { + final Settings settings = Settings.builder() + .build(); + threadContext = new ThreadContext(settings); + } + + public void testAuthenticateWithTokenAPickedUpByTokenAAuthenticatorInCustomChain() throws Exception { + + PluggableAuthenticatorChain chain = new PluggableAuthenticatorChain(List.of(new TokenAAuthenticator(), new TokenBAuthenticator())); + TestTokenA testToken = new TestTokenA("test-value"); + + Authenticator.Context context = createContext(); + context.addAuthenticationToken(testToken); + + CountDownLatch latch = new CountDownLatch(1); + AtomicReference> resultRef = new AtomicReference<>(); + AtomicReference exceptionRef = new AtomicReference<>(); + + ActionListener> listener = new ActionListener<>() { + @Override + public void onResponse(AuthenticationResult result) { + resultRef.set(result); + latch.countDown(); + } + + @Override + public void onFailure(Exception e) { + exceptionRef.set(e); + latch.countDown(); + } + }; + + chain.authenticate(context, listener); + latch.await(); + + if (exceptionRef.get() != null) { + throw new AssertionError("Authentication failed with exception", exceptionRef.get()); + } + + AuthenticationResult result = resultRef.get(); + assertThat(result, notNullValue()); + assertThat(result.isAuthenticated(), equalTo(true)); + + Authentication auth = result.getValue(); + assertThat(auth.getEffectiveSubject().getUser().principal(), equalTo("token-a-auth-user-1-test-value")); + } + + public void testAuthenticateWithTokenAPickedUpByTokenAAuthenticatorInCustomChainWithChainOrderFlipped() throws Exception { + + PluggableAuthenticatorChain chain = new PluggableAuthenticatorChain(List.of(new TokenBAuthenticator(), new TokenAAuthenticator())); + TestTokenA testToken = new TestTokenA("test-value"); + + Authenticator.Context context = createContext(); + context.addAuthenticationToken(testToken); + + CountDownLatch latch = new CountDownLatch(1); + AtomicReference> resultRef = new AtomicReference<>(); + AtomicReference exceptionRef = new AtomicReference<>(); + + ActionListener> listener = new ActionListener<>() { + @Override + public void onResponse(AuthenticationResult result) { + resultRef.set(result); + latch.countDown(); + } + + @Override + public void onFailure(Exception e) { + exceptionRef.set(e); + latch.countDown(); + } + }; + + chain.authenticate(context, listener); + latch.await(); + + if (exceptionRef.get() != null) { + throw new AssertionError("Authentication failed with exception", exceptionRef.get()); + } + + AuthenticationResult result = resultRef.get(); + assertThat(result, notNullValue()); + assertThat(result.isAuthenticated(), equalTo(true)); + + Authentication auth = result.getValue(); + assertThat(auth.getEffectiveSubject().getUser().principal(), equalTo("token-a-auth-user-1-test-value")); + } + + public void testAuthenticateWhenTokenSupportedByBothAuthenticatorsInChain() throws Exception { + + PluggableAuthenticatorChain chain = new PluggableAuthenticatorChain(List.of(new TokenAAuthenticator("foo"), + new TokenAAuthenticator("bar"))); + TestTokenA testToken = new TestTokenA("test-value"); + + Authenticator.Context context = createContext(); + context.addAuthenticationToken(testToken); + + CountDownLatch latch = new CountDownLatch(1); + AtomicReference> resultRef = new AtomicReference<>(); + AtomicReference exceptionRef = new AtomicReference<>(); + + ActionListener> listener = new ActionListener<>() { + @Override + public void onResponse(AuthenticationResult result) { + resultRef.set(result); + latch.countDown(); + } + + @Override + public void onFailure(Exception e) { + exceptionRef.set(e); + latch.countDown(); + } + }; + + chain.authenticate(context, listener); + latch.await(); + + if (exceptionRef.get() != null) { + throw new AssertionError("Authentication failed with exception", exceptionRef.get()); + } + + AuthenticationResult result = resultRef.get(); + assertThat(result, notNullValue()); + assertThat(result.isAuthenticated(), equalTo(true)); + + Authentication auth = result.getValue(); + assertThat(auth.getEffectiveSubject().getUser().principal(), equalTo("token-a-auth-user-foo-test-value")); //id of first + } + + public void testAuthenticateWhenTokenSupportedByNoAuthenticatorsInChain() throws Exception { + + PluggableAuthenticatorChain chain = new PluggableAuthenticatorChain(List.of(new TokenAAuthenticator("foo"), + new TokenAAuthenticator("bar"))); + AuthenticationToken unknownToken = new AuthenticationToken() { + @Override + public String principal() { + return "unknown"; + } + + @Override + public Object credentials() { + return null; + } + + @Override + public void clearCredentials() { + // no-op + } + }; + + Authenticator.Context context = createContext(); + context.addAuthenticationToken(unknownToken); + + CountDownLatch latch = new CountDownLatch(1); + AtomicReference> resultRef = new AtomicReference<>(); + AtomicReference exceptionRef = new AtomicReference<>(); + + ActionListener> listener = new ActionListener<>() { + @Override + public void onResponse(AuthenticationResult result) { + resultRef.set(result); + latch.countDown(); + } + + @Override + public void onFailure(Exception e) { + exceptionRef.set(e); + latch.countDown(); + } + }; + + chain.authenticate(context, listener); + latch.await(); + + if (exceptionRef.get() != null) { + throw new AssertionError("Authentication failed with exception", exceptionRef.get()); + } + + AuthenticationResult result = resultRef.get(); + assertThat(result, notNullValue()); + assertThat(result.getStatus(), equalTo(AuthenticationResult.Status.CONTINUE)); + } + + + + private Authenticator.Context createContext() { + return new Authenticator.Context( + threadContext, + null, + null, + true, + null + ); + } +} From 491e3781b66f34a04f194f5aca1026a7720236fc Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Fri, 5 Sep 2025 18:54:24 +0000 Subject: [PATCH 16/25] [CI] Auto commit changes from spotless --- .../core/security/authc/Authentication.java | 5 +--- .../authc/PluggableAuthenticatorChain.java | 9 ++++--- .../PluggableAuthenticatorChainTests.java | 25 +++++++------------ 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java index f1840037c90a1..52b5a1557f52e 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java @@ -1387,10 +1387,7 @@ public static Authentication newCloudAccessTokenAuthentication( ) { assert authResult.isAuthenticated() : "cloud access token authn result must be successful"; final User user = authResult.getValue(); - return new Authentication( - new Subject(user, realmRef, TransportVersion.current(), authResult.getMetadata()), - TOKEN - ); + return new Authentication(new Subject(user, realmRef, TransportVersion.current(), authResult.getMetadata()), TOKEN); } public static Authentication newCloudApiKeyAuthentication(AuthenticationResult authResult, String nodeName) { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java index 4d656283ffd2f..e798e8db671fc 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java @@ -57,7 +57,8 @@ public void authenticate(Context context, ActionListener(listener, + var lis = new IteratingActionListener<>( + listener, getAuthConsumer(context), customAuthenticators, context.getThreadContext(), @@ -68,7 +69,8 @@ public void authenticate(Context context, ActionListener result == null || result.getStatus() == AuthenticationResult.Status.CONTINUE); + result -> result == null || result.getStatus() == AuthenticationResult.Status.CONTINUE + ); lis.run(); return; } @@ -93,8 +95,7 @@ private BiConsumer iteratingListener.onFailure(context.getRequest().exceptionProcessingRequest(ex, token)))); - } - else { + } else { iteratingListener.onResponse(null); // try the next custom authenticator } }; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChainTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChainTests.java index 961dc46827475..96cdeee7deadf 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChainTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChainTests.java @@ -164,8 +164,7 @@ public Authentication getAuthentication(AuthenticationResult result, Strin @Before public void init() { - final Settings settings = Settings.builder() - .build(); + final Settings settings = Settings.builder().build(); threadContext = new ThreadContext(settings); } @@ -253,8 +252,9 @@ public void onFailure(Exception e) { public void testAuthenticateWhenTokenSupportedByBothAuthenticatorsInChain() throws Exception { - PluggableAuthenticatorChain chain = new PluggableAuthenticatorChain(List.of(new TokenAAuthenticator("foo"), - new TokenAAuthenticator("bar"))); + PluggableAuthenticatorChain chain = new PluggableAuthenticatorChain( + List.of(new TokenAAuthenticator("foo"), new TokenAAuthenticator("bar")) + ); TestTokenA testToken = new TestTokenA("test-value"); Authenticator.Context context = createContext(); @@ -290,13 +290,14 @@ public void onFailure(Exception e) { assertThat(result.isAuthenticated(), equalTo(true)); Authentication auth = result.getValue(); - assertThat(auth.getEffectiveSubject().getUser().principal(), equalTo("token-a-auth-user-foo-test-value")); //id of first + assertThat(auth.getEffectiveSubject().getUser().principal(), equalTo("token-a-auth-user-foo-test-value")); // id of first } public void testAuthenticateWhenTokenSupportedByNoAuthenticatorsInChain() throws Exception { - PluggableAuthenticatorChain chain = new PluggableAuthenticatorChain(List.of(new TokenAAuthenticator("foo"), - new TokenAAuthenticator("bar"))); + PluggableAuthenticatorChain chain = new PluggableAuthenticatorChain( + List.of(new TokenAAuthenticator("foo"), new TokenAAuthenticator("bar")) + ); AuthenticationToken unknownToken = new AuthenticationToken() { @Override public String principal() { @@ -347,15 +348,7 @@ public void onFailure(Exception e) { assertThat(result.getStatus(), equalTo(AuthenticationResult.Status.CONTINUE)); } - - private Authenticator.Context createContext() { - return new Authenticator.Context( - threadContext, - null, - null, - true, - null - ); + return new Authenticator.Context(threadContext, null, null, true, null); } } From 90ef203c1b801dd76ec6d830ba1f1eb1bcf3872f Mon Sep 17 00:00:00 2001 From: Ankit Sethi Date: Fri, 5 Sep 2025 14:05:53 -0500 Subject: [PATCH 17/25] revert bad change --- .../xpack/core/security/authc/Authentication.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java index 52b5a1557f52e..f16131ec4fc9a 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java @@ -59,7 +59,6 @@ import static org.elasticsearch.transport.RemoteClusterPortSettings.TRANSPORT_VERSION_ADVANCED_REMOTE_CLUSTER_SECURITY; import static org.elasticsearch.xcontent.ConstructingObjectParser.constructorArg; import static org.elasticsearch.xcontent.ConstructingObjectParser.optionalConstructorArg; -import static org.elasticsearch.xpack.core.security.authc.Authentication.AuthenticationType.TOKEN; import static org.elasticsearch.xpack.core.security.authc.Authentication.RealmRef.newAnonymousRealmRef; import static org.elasticsearch.xpack.core.security.authc.Authentication.RealmRef.newApiKeyRealmRef; import static org.elasticsearch.xpack.core.security.authc.Authentication.RealmRef.newCloudApiKeyRealmRef; @@ -426,7 +425,7 @@ public Authentication token() { assert false == isAuthenticatedInternally(); assert false == isServiceAccount(); assert false == isCrossClusterAccess(); - final Authentication newTokenAuthentication = new Authentication(effectiveSubject, authenticatingSubject, TOKEN); + final Authentication newTokenAuthentication = new Authentication(effectiveSubject, authenticatingSubject, AuthenticationType.TOKEN); return newTokenAuthentication; } @@ -605,7 +604,7 @@ public boolean supportsRunAs(@Nullable AnonymousUser anonymousUser) { // Run-as is supported for authentication with realm, api_key or token. if (AuthenticationType.REALM == getAuthenticationType() || AuthenticationType.API_KEY == getAuthenticationType() - || TOKEN == getAuthenticationType()) { + || AuthenticationType.TOKEN == getAuthenticationType()) { return true; } @@ -719,7 +718,7 @@ public boolean canAccessResourcesOf(Authentication resourceCreatorAuthentication assert EnumSet.of( Authentication.AuthenticationType.REALM, Authentication.AuthenticationType.API_KEY, - TOKEN, + AuthenticationType.TOKEN, Authentication.AuthenticationType.ANONYMOUS, Authentication.AuthenticationType.INTERNAL ).containsAll(EnumSet.of(getAuthenticationType(), resourceCreatorAuthentication.getAuthenticationType())) @@ -1362,7 +1361,7 @@ public static Authentication newServiceAccountAuthentication(User serviceAccount final Authentication.RealmRef authenticatedBy = newServiceAccountRealmRef(nodeName); Authentication authentication = new Authentication( new Subject(serviceAccountUser, authenticatedBy, TransportVersion.current(), metadata), - TOKEN + AuthenticationType.TOKEN ); return authentication; } @@ -1387,7 +1386,8 @@ public static Authentication newCloudAccessTokenAuthentication( ) { assert authResult.isAuthenticated() : "cloud access token authn result must be successful"; final User user = authResult.getValue(); - return new Authentication(new Subject(user, realmRef, TransportVersion.current(), authResult.getMetadata()), TOKEN); + return new Authentication(new Subject(user, realmRef, TransportVersion.current(), authResult.getMetadata()), + AuthenticationType.TOKEN); } public static Authentication newCloudApiKeyAuthentication(AuthenticationResult authResult, String nodeName) { From ce7074c20c32056345b7dde8dc91485824244f6b Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Fri, 5 Sep 2025 19:12:42 +0000 Subject: [PATCH 18/25] [CI] Auto commit changes from spotless --- .../xpack/core/security/authc/Authentication.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java index f16131ec4fc9a..82e5092f3c44a 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java @@ -1386,8 +1386,10 @@ public static Authentication newCloudAccessTokenAuthentication( ) { assert authResult.isAuthenticated() : "cloud access token authn result must be successful"; final User user = authResult.getValue(); - return new Authentication(new Subject(user, realmRef, TransportVersion.current(), authResult.getMetadata()), - AuthenticationType.TOKEN); + return new Authentication( + new Subject(user, realmRef, TransportVersion.current(), authResult.getMetadata()), + AuthenticationType.TOKEN + ); } public static Authentication newCloudApiKeyAuthentication(AuthenticationResult authResult, String nodeName) { From 14857d08d73607435441a1bf58bed24450876f6c Mon Sep 17 00:00:00 2001 From: Ankit Sethi Date: Fri, 12 Sep 2025 11:18:35 -0500 Subject: [PATCH 19/25] fix imports --- .../xpack/core/security/authc/Authentication.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java index 82e5092f3c44a..879d313e0f232 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java @@ -83,7 +83,6 @@ import static org.elasticsearch.xpack.core.security.authc.AuthenticationField.FALLBACK_REALM_NAME; import static org.elasticsearch.xpack.core.security.authc.AuthenticationField.FALLBACK_REALM_TYPE; import static org.elasticsearch.xpack.core.security.authc.RealmDomain.REALM_DOMAIN_PARSER; -import static org.elasticsearch.xpack.core.security.authc.Subject.Type.USER; import static org.elasticsearch.xpack.core.security.authz.RoleDescriptor.Fields.REMOTE_CLUSTER; import static org.elasticsearch.xpack.core.security.authz.permission.RemoteClusterPermissions.ROLE_REMOTE_CLUSTER_PRIVS; @@ -718,7 +717,7 @@ public boolean canAccessResourcesOf(Authentication resourceCreatorAuthentication assert EnumSet.of( Authentication.AuthenticationType.REALM, Authentication.AuthenticationType.API_KEY, - AuthenticationType.TOKEN, + Authentication.AuthenticationType.TOKEN, Authentication.AuthenticationType.ANONYMOUS, Authentication.AuthenticationType.INTERNAL ).containsAll(EnumSet.of(getAuthenticationType(), resourceCreatorAuthentication.getAuthenticationType())) @@ -986,7 +985,7 @@ private void checkConsistencyForApiKeyAuthenticationType() { } private void checkConsistencyForRealmAuthenticationType() { - if (USER != authenticatingSubject.getType()) { + if (Subject.Type.USER != authenticatingSubject.getType()) { throw new IllegalArgumentException("Realm authentication must have subject type of user"); } if (isRunAs()) { @@ -1029,7 +1028,7 @@ private static void checkRunAsConsistency(Subject effectiveSubject, Subject auth ) ); } - if (USER != effectiveSubject.getType()) { + if (Subject.Type.USER != effectiveSubject.getType()) { throw new IllegalArgumentException(Strings.format("Run-as subject type cannot be [%s]", effectiveSubject.getType())); } if (false == effectiveSubject.getMetadata().isEmpty()) { From a745f16fbf587640d56a7939170438b975d36e0b Mon Sep 17 00:00:00 2001 From: Ankit Sethi Date: Wed, 24 Sep 2025 15:20:22 -0500 Subject: [PATCH 20/25] fix import --- .../xpack/security/authc/PluggableAuthenticatorChainTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChainTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChainTests.java index 96cdeee7deadf..864c5838b12dd 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChainTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChainTests.java @@ -15,7 +15,7 @@ import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; import org.elasticsearch.xpack.core.security.authc.AuthenticationTestHelper; import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; -import org.elasticsearch.xpack.core.security.authc.apikey.CustomAuthenticator; +import org.elasticsearch.xpack.core.security.authc.CustomAuthenticator; import org.elasticsearch.xpack.core.security.user.User; import org.junit.Before; From 22ea22df8ad82f2d0b6c8584a627e3e7b5508546 Mon Sep 17 00:00:00 2001 From: Ankit Sethi Date: Wed, 24 Sep 2025 17:03:33 -0500 Subject: [PATCH 21/25] don't need this any more --- .../xpack/core/security/authc/CustomAuthenticator.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/CustomAuthenticator.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/CustomAuthenticator.java index af527e7265179..ad55e6a24c040 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/CustomAuthenticator.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/CustomAuthenticator.java @@ -10,7 +10,6 @@ import org.elasticsearch.action.ActionListener; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.core.Nullable; -import org.elasticsearch.xpack.core.security.user.User; /** * An extension point to provide a custom authenticator implementation. For example, a custom API key or a custom OAuth2 @@ -26,6 +25,4 @@ public interface CustomAuthenticator { void authenticate(@Nullable AuthenticationToken token, ActionListener> listener); - Authentication getAuthentication(AuthenticationResult result, String nodeName); - } From 58826d42993775373a325601982827f3e5c3d9f6 Mon Sep 17 00:00:00 2001 From: Ankit Sethi Date: Fri, 26 Sep 2025 13:25:08 -0500 Subject: [PATCH 22/25] code review changes + update pointer --- .../xpack/core/security/authc/Authentication.java | 3 --- .../xpack/security/authc/PluggableAuthenticatorChain.java | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java index 71b43bc26a48b..55f1d33d97b17 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java @@ -823,9 +823,6 @@ public void toXContentFragment(XContentBuilder builder) throws IOException { apiKeyField.put("managed_by", CredentialManagedBy.CLOUD.getDisplayName()); builder.field("api_key", Collections.unmodifiableMap(apiKeyField)); } - if (metadata.containsKey("managed_by")) { - builder.field("managed_by", metadata.get("managed_by")); - } } public static Authentication getAuthenticationFromCrossClusterAccessMetadata(Authentication authentication) { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java index 64374fca6bd8d..1479d00d57fc6 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java @@ -100,7 +100,7 @@ private BiConsumer iteratingListener.onFailure(context.getRequest().exceptionProcessingRequest(ex, token)))); } else { - iteratingListener.onResponse(null); // try the next custom authenticator + iteratingListener.onResponse(AuthenticationResult.notHandled()); // try the next custom authenticator } }; } From 52259553ee38ca8c9d559a55406d61fb62ce7925 Mon Sep 17 00:00:00 2001 From: Ankit Sethi Date: Fri, 26 Sep 2025 13:27:48 -0500 Subject: [PATCH 23/25] fix test --- .../authc/PluggableAuthenticatorChainTests.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChainTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChainTests.java index 864c5838b12dd..16bf0f43c0d8e 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChainTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChainTests.java @@ -116,11 +116,6 @@ public void authenticate(@Nullable AuthenticationToken token, ActionListener result, String nodeName) { - return AuthenticationTestHelper.builder().user(result.getValue()).build(false); - } } public class TokenBAuthenticator implements CustomAuthenticator { @@ -155,11 +150,6 @@ public void authenticate(@Nullable AuthenticationToken token, ActionListener result, String nodeName) { - return AuthenticationTestHelper.builder().user(result.getValue()).build(false); - } } @Before From 88ca9e5430b297c503aed0e0c6eac9a9446c322c Mon Sep 17 00:00:00 2001 From: Ankit Sethi <9.ankitsethi@gmail.com> Date: Mon, 29 Sep 2025 10:33:35 -0500 Subject: [PATCH 24/25] Update x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Slobodan Adamović --- .../authc/PluggableAuthenticatorChain.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java index 1479d00d57fc6..8783d792e1c7d 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java @@ -61,21 +61,20 @@ public void authenticate(Context context, ActionListener( + var iteratingListener = new IteratingActionListener<>( listener, getAuthConsumer(context), customAuthenticators, context.getThreadContext(), - result -> { - if (result == null) { - // all custom authenticators left the token unhandled - return AuthenticationResult.notHandled(); - } - return result; - }, - result -> result == null || result.getStatus() == AuthenticationResult.Status.CONTINUE + Function.identity(), + result -> result.getStatus() == AuthenticationResult.Status.CONTINUE ); - lis.run(); + try { + iteratingListener.run(); + } catch (Exception e) { + logger.debug(() -> format("Authentication of token [%s] failed", token.getClass().getName()), e); + listener.onFailure(context.getRequest().exceptionProcessingRequest(e, token)); + } return; } listener.onResponse(AuthenticationResult.notHandled()); From c150e242107d056739a568aa137ea6127a14926b Mon Sep 17 00:00:00 2001 From: Ankit Sethi Date: Mon, 29 Sep 2025 10:37:48 -0500 Subject: [PATCH 25/25] code review stuff --- .../xpack/security/authc/PluggableAuthenticatorChain.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java index 8783d792e1c7d..10a4a82c216fa 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableAuthenticatorChain.java @@ -7,6 +7,8 @@ package org.elasticsearch.xpack.security.authc; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.elasticsearch.action.ActionListener; import org.elasticsearch.xpack.core.common.IteratingActionListener; import org.elasticsearch.xpack.core.security.authc.Authentication; @@ -17,9 +19,14 @@ import java.util.Collections; import java.util.List; import java.util.function.BiConsumer; +import java.util.function.Function; + +import static org.elasticsearch.common.Strings.format; public class PluggableAuthenticatorChain implements Authenticator { + private static final Logger logger = LogManager.getLogger(PluggableAuthenticatorChain.class); + private final List customAuthenticators; public PluggableAuthenticatorChain(List customAuthenticators) {