Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
2739f57
initial commit - tests pending potentially
ankit--sethi Aug 19, 2025
832f469
[CI] Auto commit changes from spotless
Aug 19, 2025
1bd116a
Merge branch 'main' into feature/session-tokens
ankit--sethi Aug 19, 2025
aa9b2fb
Merge branch 'main' into feature/session-tokens
ankit--sethi Aug 20, 2025
6a8fbb2
fix syntax
ankit--sethi Aug 20, 2025
767c34c
Merge branch 'main' into feature/session-tokens
ankit--sethi Aug 20, 2025
e4fa7d0
Merge remote-tracking branch 'origin/feature/session-tokens' into fea…
ankit--sethi Aug 20, 2025
118705f
correct javadoc
ankit--sethi Aug 20, 2025
e466371
Merge branch 'main' into feature/session-tokens
ankit--sethi Aug 20, 2025
239d510
Merge branch 'main' into feature/session-tokens
ankit--sethi Aug 21, 2025
b174e5a
fix style issue
ankit--sethi Aug 21, 2025
7c3c8a3
Merge remote-tracking branch 'origin/feature/session-tokens' into fea…
ankit--sethi Aug 21, 2025
0f28ac0
fix tests
ankit--sethi Aug 21, 2025
2a03dc9
Merge branch 'main' into feature/session-tokens
ankit--sethi Aug 21, 2025
fac7f3b
Merge branch 'main' into feature/session-tokens
ankit--sethi Aug 21, 2025
113b4ba
[PoC] Pluggable authenticator chain
slobodanadamovic Aug 22, 2025
41f3714
Merge branch 'main' of github.com:elastic/elasticsearch into poc-cust…
slobodanadamovic Aug 22, 2025
040a9aa
[CI] Auto commit changes from spotless
Aug 22, 2025
b2b6404
spotless + remove unused method
slobodanadamovic Aug 25, 2025
c782a2c
fix javadoc line lenght
slobodanadamovic Aug 25, 2025
b2d3938
Merge branch 'main' of github.com:elastic/elasticsearch into poc-cust…
slobodanadamovic Aug 25, 2025
692d8e3
Merge branch 'main' of github.com:ankit--sethi/elasticsearch into fea…
ankit--sethi Aug 25, 2025
2c74a18
Merge remote-tracking branch 'slobodan/poc-custom-authenticator-chain…
ankit--sethi Aug 25, 2025
cf543eb
refactor with code review feedback and new validation for cloud-saml-…
ankit--sethi Aug 25, 2025
ebd4188
[CI] Auto commit changes from spotless
Aug 25, 2025
f22bf54
Merge branch 'main' into feature/session-tokens
ankit--sethi Aug 26, 2025
31b6b56
Merge branch 'main' into feature/session-tokens
ankit--sethi Aug 27, 2025
b7411f2
Merge branch 'main' of github.com:ankit--sethi/elasticsearch into fea…
ankit--sethi Aug 28, 2025
49e4d66
Merge branch 'main' of github.com:ankit--sethi/elasticsearch into fea…
ankit--sethi Aug 28, 2025
01a3f18
Merge branch 'main' of github.com:ankit--sethi/elasticsearch into fea…
ankit--sethi Sep 3, 2025
4a32400
code review stuff
ankit--sethi Sep 3, 2025
14ccac1
Merge branch 'main' into feature/session-tokens
ankit--sethi Sep 3, 2025
2d716c6
[CI] Auto commit changes from spotless
Sep 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.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;
Expand Down Expand Up @@ -129,7 +129,7 @@ default ServiceAccountTokenStore getServiceAccountTokenStore(SecurityComponents
return null;
}

default CustomApiKeyAuthenticator getCustomApiKeyAuthenticator(SecurityComponents components) {
default List<CustomAuthenticator> getCustomAuthenticators(SecurityComponents components) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1376,6 +1376,18 @@ public static Authentication newRealmAuthentication(User user, RealmRef realmRef
return authentication;
}

public static Authentication newCloudAccessTokenAuthentication(
AuthenticationResult<User> authResult,
Authentication.RealmRef realmRef
) {
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
);
}

public static Authentication newCloudApiKeyAuthentication(AuthenticationResult<User> authResult, String nodeName) {
assert authResult.isAuthenticated() : "cloud API Key authn result must be successful";
final User apiKeyUser = authResult.getValue();
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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;

/**
* 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 {

boolean supports(AuthenticationToken token);

@Nullable
AuthenticationToken extractToken(ThreadContext context);

void authenticate(@Nullable AuthenticationToken token, ActionListener<AuthenticationResult<Authentication>> listener);

}
Original file line number Diff line number Diff line change
Expand Up @@ -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.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;
Expand Down Expand Up @@ -1080,9 +1080,8 @@ Collection<Object> createComponents(
operatorPrivilegesService.set(OperatorPrivileges.NOOP_OPERATOR_PRIVILEGES_SERVICE);
}

final CustomApiKeyAuthenticator customApiKeyAuthenticator = createCustomApiKeyAuthenticator(extensionComponents);

components.add(customApiKeyAuthenticator);
final List<CustomAuthenticator> customAuthenticators = getCustomAuthenticatorFromExtensions(extensionComponents);
components.addAll(customAuthenticators);

authcService.set(
new AuthenticationService(
Expand All @@ -1096,7 +1095,7 @@ Collection<Object> createComponents(
apiKeyService,
serviceAccountService,
operatorPrivilegesService.get(),
customApiKeyAuthenticator,
customAuthenticators,
telemetryProvider.getMeterRegistry()
)
);
Expand Down Expand Up @@ -1232,45 +1231,48 @@ Collection<Object> createComponents(
return components;
}

private CustomApiKeyAuthenticator createCustomApiKeyAuthenticator(SecurityExtension.SecurityComponents extensionComponents) {
final Map<String, CustomApiKeyAuthenticator> customApiKeyAuthenticatorByExtension = new HashMap<>();
for (final SecurityExtension extension : securityExtensions) {
final CustomApiKeyAuthenticator customApiKeyAuthenticator = extension.getCustomApiKeyAuthenticator(extensionComponents);
if (customApiKeyAuthenticator != null) {
if (false == isInternalExtension(extension)) {
private List<CustomAuthenticator> getCustomAuthenticatorFromExtensions(SecurityExtension.SecurityComponents extensionComponents) {
final Map<String, List<CustomAuthenticator>> customAuthenticatorsByExtension = new HashMap<>();
for (final SecurityExtension securityExtension : securityExtensions) {
final List<CustomAuthenticator> 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(), customApiKeyAuthenticator);
customAuthenticatorsByExtension.put(securityExtension.extensionName(), customAuthenticators);
}
}

if (customApiKeyAuthenticatorByExtension.isEmpty()) {
if (customAuthenticatorsByExtension.isEmpty()) {
logger.debug(
"No custom implementation for [{}]. Falling-back to noop implementation.",
CustomApiKeyAuthenticator.class.getCanonicalName()
"No custom implementations for [{}] provided by security extensions.",
CustomAuthenticator.class.getCanonicalName()
);
return new CustomApiKeyAuthenticator.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 CustomApiKeyAuthenticator customApiKeyAuthenticator = authenticatorByExtensionEntry.getValue();
final var authenticatorByExtensionEntry = customAuthenticatorsByExtension.entrySet().iterator().next();
final List<CustomAuthenticator> customAuthenticators = authenticatorByExtensionEntry.getValue();
final String extensionName = authenticatorByExtensionEntry.getKey();
logger.debug(
"CustomApiKeyAuthenticator implementation [{}] provided by extension [{}]",
customApiKeyAuthenticator.getClass().getCanonicalName(),
extensionName
);
return customApiKeyAuthenticator;
for (CustomAuthenticator authenticator : customAuthenticators) {
logger.debug(
"{} implementation [{}] provided by extension [{}]",
CustomAuthenticator.class.getSimpleName(),
authenticator.getClass().getCanonicalName(),
extensionName
);
}
return customAuthenticators;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.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;
Expand Down Expand Up @@ -93,7 +93,7 @@ public AuthenticationService(
ApiKeyService apiKeyService,
ServiceAccountService serviceAccountService,
OperatorPrivilegesService operatorPrivilegesService,
CustomApiKeyAuthenticator customApiKeyAuthenticator,
List<CustomAuthenticator> customAuthenticators,
MeterRegistry meterRegistry
) {
this.realms = realms;
Expand All @@ -110,14 +110,15 @@ public AuthenticationService(
}

final String nodeName = Node.NODE_NAME_SETTING.get(settings);

this.authenticatorChain = new AuthenticatorChain(
settings,
operatorPrivilegesService,
anonymousUser,
new AuthenticationContextSerializer(),
new PluggableAuthenticatorChain(customAuthenticators),
new ServiceAccountAuthenticator(serviceAccountService, nodeName, meterRegistry),
new OAuth2TokenAuthenticator(tokenService, meterRegistry),
new PluggableApiKeyAuthenticator(customApiKeyAuthenticator),
new ApiKeyAuthenticator(apiKeyService, nodeName, meterRegistry),
new RealmsAuthenticator(numInvalidation, lastSuccessfulAuthCache, meterRegistry)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -52,9 +54,9 @@ class AuthenticatorChain {
OperatorPrivilegesService operatorPrivilegesService,
AnonymousUser anonymousUser,
AuthenticationContextSerializer authenticationSerializer,
PluggableAuthenticatorChain pluggableAuthenticatorChain,
ServiceAccountAuthenticator serviceAccountAuthenticator,
OAuth2TokenAuthenticator oAuth2TokenAuthenticator,
PluggableApiKeyAuthenticator pluggableApiKeyAuthenticator,
ApiKeyAuthenticator apiKeyAuthenticator,
RealmsAuthenticator realmsAuthenticator
) {
Expand All @@ -65,13 +67,16 @@ class AuthenticatorChain {
this.isAnonymousUserEnabled = AnonymousUser.isAnonymousEnabled(settings);
this.authenticationSerializer = authenticationSerializer;
this.realmsAuthenticator = realmsAuthenticator;
this.allAuthenticators = List.of(
serviceAccountAuthenticator,
oAuth2TokenAuthenticator,
pluggableApiKeyAuthenticator,
apiKeyAuthenticator,
realmsAuthenticator
);

List<Authenticator> 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<Authentication> originalListener) {
Expand Down

This file was deleted.

Loading