Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 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
808556a
Merge branch 'main' of github.com:ankit--sethi/elasticsearch into fea…
ankit--sethi Sep 3, 2025
72d3d0f
followups from previous PR -
ankit--sethi Sep 5, 2025
d98bebb
Merge branch 'main' into feature/session-tokens
ankit--sethi Sep 5, 2025
491e378
[CI] Auto commit changes from spotless
Sep 5, 2025
720bc14
Merge branch 'main' of github.com:ankit--sethi/elasticsearch into fea…
ankit--sethi Sep 5, 2025
8b33379
Merge branch 'feature/session-tokens' of github.com:ankit--sethi/elas…
ankit--sethi Sep 5, 2025
90ef203
revert bad change
ankit--sethi Sep 5, 2025
ce7074c
[CI] Auto commit changes from spotless
Sep 5, 2025
ff65077
Merge branch 'main' of github.com:ankit--sethi/elasticsearch into fea…
ankit--sethi Sep 8, 2025
495c53b
merging
ankit--sethi Sep 8, 2025
765895e
Merge remote-tracking branch 'origin/feature/session-tokens' into fea…
ankit--sethi Sep 8, 2025
56082e9
Merge branch 'main' of github.com:ankit--sethi/elasticsearch into fea…
ankit--sethi Sep 11, 2025
10d9eb8
update interfaces to allow access to meter registry within cloud
ankit--sethi Sep 11, 2025
c10487c
[CI] Auto commit changes from spotless
Sep 11, 2025
cf5c58c
Merge branch 'main' into feature/session-tokens
ankit--sethi Sep 12, 2025
f5c922d
Merge branch 'main' of github.com:ankit--sethi/elasticsearch into fea…
ankit--sethi Sep 12, 2025
14857d0
fix imports
ankit--sethi Sep 12, 2025
186cbdc
Merge branch 'main' of github.com:ankit--sethi/elasticsearch into fea…
ankit--sethi Sep 12, 2025
ca1e571
Merge branch 'main' of github.com:ankit--sethi/elasticsearch into fea…
ankit--sethi Sep 12, 2025
0d25b3b
Merge branch 'feature/session-tokens' into feature/cloud-metrics
ankit--sethi Sep 12, 2025
9b2fc5a
Merge branch 'feature/cloud-metrics' of github.com:ankit--sethi/elast…
ankit--sethi Sep 12, 2025
8d2fa10
Merge branch 'main' of github.com:ankit--sethi/elasticsearch into fea…
ankit--sethi Sep 15, 2025
1a86294
added tests and refactored method back into existing listener class
ankit--sethi Sep 15, 2025
dc225b8
Merge branch 'main' of github.com:ankit--sethi/elasticsearch into fea…
ankit--sethi Sep 15, 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 @@ -12,6 +12,7 @@
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.telemetry.TelemetryProvider;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.core.security.authc.AuthenticationFailureHandler;
Expand Down Expand Up @@ -63,6 +64,9 @@ interface SecurityComponents {

/** Provides the ability to access project-scoped data from the global scope **/
ProjectResolver projectResolver();

/** Provides the ability to access the APM tracer and meter registry **/
TelemetryProvider telemetryProvider();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -28,4 +29,6 @@ public interface CustomAuthenticator {

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

Authentication getAuthentication(AuthenticationResult<User> result, String nodeName);

}
1 change: 1 addition & 0 deletions x-pack/plugin/security/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
exports org.elasticsearch.xpack.security.support to org.elasticsearch.internal.security;
exports org.elasticsearch.xpack.security.authz.store to org.elasticsearch.internal.security;
exports org.elasticsearch.xpack.security.authc.service;
exports org.elasticsearch.xpack.security.metric;

provides org.elasticsearch.index.SlowLogFieldProvider with org.elasticsearch.xpack.security.slowlog.SecuritySlowLogFieldProvider;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -854,7 +854,8 @@ Collection<Object> createComponents(
clusterService,
resourceWatcherService,
userRoleMapper,
projectResolver
projectResolver,
telemetryProvider
);
Map<String, Realm.Factory> realmFactories = new HashMap<>(
InternalRealms.getFactories(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
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;
import org.elasticsearch.xpack.core.security.authc.apikey.CustomAuthenticator;

import java.util.List;
import java.util.Objects;
import java.util.function.BiConsumer;

public class PluggableAuthenticatorChain implements Authenticator {

Expand Down Expand Up @@ -55,28 +57,48 @@ public void authenticate(Context context, ActionListener<AuthenticationResult<Au
}
AuthenticationToken token = context.getMostRecentAuthenticationToken();
if (token != null) {
// TODO switch to IteratingActionListener
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;
}
}
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<CustomAuthenticator, ActionListener<AuthenticationResult<Authentication>>> 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
}
};
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,31 @@ public static <R, C> ActionListener<AuthenticationResult<R>> wrapForAuthc(
}), () -> metrics.recordTime(context, startTimeNano));
}

/**
* A simpler variant that re-uses the Authentication Result as the context. This can be handy in situations when the attributes that are
* of interest are only available after the authentication is completed and not before.
* As a natural consequence, there will be no context available at the point of recording start time and in cases of exceptional failure
* @param metrics
* @param listener
* @param <R>
* @return
*/
public static <R> ActionListener<AuthenticationResult<R>> wrapForAuthc(
final SecurityMetrics<AuthenticationResult<R>> metrics,
final ActionListener<AuthenticationResult<R>> listener
) {
assert metrics.type().group() == SecurityMetricGroup.AUTHC;
final long startTimeNano = metrics.relativeTimeInNanos();
return ActionListener.runBefore(ActionListener.wrap(result -> {
if (result.isAuthenticated()) {
metrics.recordSuccess(result);
} else {
metrics.recordFailure(result, result.getMessage());
}
listener.onResponse(result);
}, e -> {
metrics.recordFailure(null, e.getMessage());
listener.onFailure(e);
}), () -> metrics.recordTime(null, startTimeNano));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,25 @@ public enum SecurityMetricType {
new SecurityMetricInfo("es.security.authc.api_key.time", "Time it took (in nanoseconds) to execute API key authentication.", "ns")
),

CLOUD_AUTHC_API_KEY(
SecurityMetricGroup.AUTHC,
new SecurityMetricInfo(
"es.security.authc.cloud_api_key.success.total",
"Number of successful cloud API key authentications.",
"count"
),
new SecurityMetricInfo(
"es.security.authc.cloud_api_key.failures.total",
"Number of failed cloud API key authentications.",
"count"
),
new SecurityMetricInfo(
"es.security.authc.cloud_api_key.time",
"Time it took (in nanoseconds) to execute cloud API key authentication.",
"ns"
)
),

AUTHC_SERVICE_ACCOUNT(
SecurityMetricGroup.AUTHC,
new SecurityMetricInfo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.telemetry.TelemetryProvider;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.core.security.SecurityExtension;
Expand All @@ -27,21 +28,24 @@ public final class ExtensionComponents implements SecurityExtension.SecurityComp
private final ResourceWatcherService resourceWatcherService;
private final UserRoleMapper roleMapper;
private final ProjectResolver projectResolver;
private final TelemetryProvider telemetryProvider;

public ExtensionComponents(
Environment environment,
Client client,
ClusterService clusterService,
ResourceWatcherService resourceWatcherService,
UserRoleMapper roleMapper,
ProjectResolver projectResolver
ProjectResolver projectResolver,
TelemetryProvider telemetryProvider
) {
this.environment = environment;
this.client = client;
this.clusterService = clusterService;
this.resourceWatcherService = resourceWatcherService;
this.roleMapper = roleMapper;
this.projectResolver = projectResolver;
this.telemetryProvider = telemetryProvider;
}

@Override
Expand Down Expand Up @@ -83,4 +87,9 @@ public UserRoleMapper roleMapper() {
public ProjectResolver projectResolver() {
return projectResolver;
}

@Override
public TelemetryProvider telemetryProvider() {
return telemetryProvider;
}
}
Loading