Skip to content

Commit 113b4ba

Browse files
[PoC] Pluggable authenticator chain
1 parent e466371 commit 113b4ba

File tree

11 files changed

+162
-248
lines changed

11 files changed

+162
-248
lines changed

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import org.elasticsearch.watcher.ResourceWatcherService;
1717
import org.elasticsearch.xpack.core.security.authc.AuthenticationFailureHandler;
1818
import org.elasticsearch.xpack.core.security.authc.Realm;
19-
import org.elasticsearch.xpack.core.security.authc.apikey.CustomTokenAuthenticator;
19+
import org.elasticsearch.xpack.core.security.authc.apikey.CustomAuthenticator;
2020
import org.elasticsearch.xpack.core.security.authc.service.NodeLocalServiceAccountTokenStore;
2121
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountTokenStore;
2222
import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper;
@@ -129,7 +129,7 @@ default ServiceAccountTokenStore getServiceAccountTokenStore(SecurityComponents
129129
return null;
130130
}
131131

132-
default List<CustomTokenAuthenticator> getCustomApiKeyAuthenticator(SecurityComponents components) {
132+
default List<CustomAuthenticator> getCustomAuthenticators(SecurityComponents components) {
133133
return null;
134134
}
135135

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.core.security.authc.apikey;
9+
10+
import org.elasticsearch.action.ActionListener;
11+
import org.elasticsearch.common.util.concurrent.ThreadContext;
12+
import org.elasticsearch.core.Nullable;
13+
import org.elasticsearch.xpack.core.security.authc.Authentication;
14+
import org.elasticsearch.xpack.core.security.authc.AuthenticationResult;
15+
import org.elasticsearch.xpack.core.security.authc.AuthenticationToken;
16+
import org.elasticsearch.xpack.core.security.user.User;
17+
18+
/**
19+
* An extension point to provide a custom token authenticator implementation. For example, a custom API key or a custom OAuth2 token implementation.
20+
* The implementation is wrapped by a core `Authenticator` class and included in the authenticator chain _before_ the
21+
* respective "standard" authenticator(s).
22+
*/
23+
public interface CustomAuthenticator {
24+
25+
boolean supports(AuthenticationToken token);
26+
27+
@Nullable
28+
AuthenticationToken extractToken(ThreadContext context);
29+
30+
void authenticate(@Nullable AuthenticationToken token, ActionListener<AuthenticationResult<Authentication>> listener);
31+
32+
}

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomTokenAuthenticator.java

Lines changed: 0 additions & 54 deletions
This file was deleted.

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@
201201
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
202202
import org.elasticsearch.xpack.core.security.authc.RealmSettings;
203203
import org.elasticsearch.xpack.core.security.authc.Subject;
204-
import org.elasticsearch.xpack.core.security.authc.apikey.CustomTokenAuthenticator;
204+
import org.elasticsearch.xpack.core.security.authc.apikey.CustomAuthenticator;
205205
import org.elasticsearch.xpack.core.security.authc.service.NodeLocalServiceAccountTokenStore;
206206
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountTokenStore;
207207
import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper;
@@ -1068,9 +1068,7 @@ Collection<Object> createComponents(
10681068
operatorPrivilegesService.set(OperatorPrivileges.NOOP_OPERATOR_PRIVILEGES_SERVICE);
10691069
}
10701070

1071-
final Collection<CustomTokenAuthenticator> customTokenAuthenticator = createCustomApiKeyAuthenticator(extensionComponents);
1072-
1073-
components.add(customTokenAuthenticator);
1071+
final List<CustomAuthenticator> customAuthenticators = getCustomAuthenticatorFromExtensions(extensionComponents);
10741072

10751073
authcService.set(
10761074
new AuthenticationService(
@@ -1084,7 +1082,7 @@ Collection<Object> createComponents(
10841082
apiKeyService,
10851083
serviceAccountService,
10861084
operatorPrivilegesService.get(),
1087-
customTokenAuthenticator,
1085+
customAuthenticators,
10881086
telemetryProvider.getMeterRegistry()
10891087
)
10901088
);
@@ -1220,47 +1218,48 @@ Collection<Object> createComponents(
12201218
return components;
12211219
}
12221220

1223-
private List<CustomTokenAuthenticator> createCustomApiKeyAuthenticator(SecurityExtension.SecurityComponents extensionComponents) {
1224-
final Map<String, List<CustomTokenAuthenticator>> customApiKeyAuthenticatorByExtension = new HashMap<>();
1225-
for (final SecurityExtension extension : securityExtensions) {
1226-
final List<CustomTokenAuthenticator> customTokenAuthenticator = extension.getCustomApiKeyAuthenticator(extensionComponents);
1227-
if (customTokenAuthenticator != null) {
1228-
if (false == isInternalExtension(extension)) {
1221+
private List<CustomAuthenticator> getCustomAuthenticatorFromExtensions(SecurityExtension.SecurityComponents extensionComponents) {
1222+
final Map<String, List<CustomAuthenticator>> customAuthenticatorsByExtension = new HashMap<>();
1223+
for (final SecurityExtension securityExtension : securityExtensions) {
1224+
final List<CustomAuthenticator> customAuthenticators = securityExtension.getCustomAuthenticators(extensionComponents);
1225+
if (customAuthenticators != null) {
1226+
if (false == isInternalExtension(securityExtension)) {
12291227
throw new IllegalStateException(
12301228
"The ["
1231-
+ extension.extensionName()
1232-
+ "] extension tried to install a custom CustomApiKeyAuthenticator. "
1229+
+ securityExtension.extensionName()
1230+
+ "] extension tried to install a "
1231+
+ CustomAuthenticator.class.getSimpleName()
1232+
+ ". "
12331233
+ "This functionality is not available to external extensions."
12341234
);
12351235
}
1236-
customApiKeyAuthenticatorByExtension.put(extension.extensionName(), customTokenAuthenticator);
1236+
customAuthenticatorsByExtension.put(securityExtension.extensionName(), customAuthenticators);
12371237
}
12381238
}
12391239

1240-
if (customApiKeyAuthenticatorByExtension.isEmpty()) {
1240+
if (customAuthenticatorsByExtension.isEmpty()) {
12411241
logger.debug(
1242-
"No custom implementation for [{}]. Falling-back to noop implementation.",
1243-
CustomTokenAuthenticator.class.getCanonicalName()
1242+
"No custom implementations for [{}] provided by security extensions.",
1243+
CustomAuthenticator.class.getCanonicalName()
12441244
);
1245-
return List.of(new CustomTokenAuthenticator.Noop());
1246-
1247-
} else if (customApiKeyAuthenticatorByExtension.size() > 1) {
1245+
return List.of();
1246+
} else if (customAuthenticatorsByExtension.size() > 1) {
12481247
throw new IllegalStateException(
1249-
"Multiple extensions tried to install a custom CustomApiKeyAuthenticator: " + customApiKeyAuthenticatorByExtension.keySet()
1248+
"Multiple extensions tried to install custom authenticators: " + customAuthenticatorsByExtension.keySet()
12501249
);
1251-
12521250
} else {
1253-
final var authenticatorByExtensionEntry = customApiKeyAuthenticatorByExtension.entrySet().iterator().next();
1254-
final List<CustomTokenAuthenticator> customTokenAuthenticators = authenticatorByExtensionEntry.getValue();
1251+
final var authenticatorByExtensionEntry = customAuthenticatorsByExtension.entrySet().iterator().next();
1252+
final List<CustomAuthenticator> customAuthenticators = authenticatorByExtensionEntry.getValue();
12551253
final String extensionName = authenticatorByExtensionEntry.getKey();
1256-
for (CustomTokenAuthenticator authenticator : customTokenAuthenticators) {
1254+
for (CustomAuthenticator authenticator : customAuthenticators) {
12571255
logger.debug(
1258-
"CustomApiKeyAuthenticator implementation [{}] provided by extension [{}]",
1256+
"{} implementation [{}] provided by extension [{}]",
1257+
CustomAuthenticator.class.getSimpleName(),
12591258
authenticator.getClass().getCanonicalName(),
12601259
extensionName
12611260
);
12621261
}
1263-
return customTokenAuthenticators;
1262+
return customAuthenticators;
12641263
}
12651264
}
12661265

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AbstractPluggableAuthenticator.java

Lines changed: 0 additions & 42 deletions
This file was deleted.

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
import org.elasticsearch.xpack.core.security.authc.AuthenticationServiceField;
3131
import org.elasticsearch.xpack.core.security.authc.AuthenticationToken;
3232
import org.elasticsearch.xpack.core.security.authc.Realm;
33-
import org.elasticsearch.xpack.core.security.authc.apikey.CustomTokenAuthenticator;
33+
import org.elasticsearch.xpack.core.security.authc.apikey.CustomAuthenticator;
3434
import org.elasticsearch.xpack.core.security.authc.support.AuthenticationContextSerializer;
3535
import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine.EmptyAuthorizationInfo;
3636
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
@@ -94,7 +94,7 @@ public AuthenticationService(
9494
ApiKeyService apiKeyService,
9595
ServiceAccountService serviceAccountService,
9696
OperatorPrivilegesService operatorPrivilegesService,
97-
Collection<CustomTokenAuthenticator> customTokenAuthenticators,
97+
List<CustomAuthenticator> customAuthenticators,
9898
MeterRegistry meterRegistry
9999
) {
100100
this.realms = realms;
@@ -111,23 +111,15 @@ public AuthenticationService(
111111
}
112112

113113
final String nodeName = Node.NODE_NAME_SETTING.get(settings);
114-
CustomTokenAuthenticator oauth2Authenticator = customTokenAuthenticators.stream()
115-
.filter(t -> t.name().contains("oauth2") || t instanceof CustomTokenAuthenticator.Noop)
116-
.findAny()
117-
.orElseThrow();
118-
CustomTokenAuthenticator apiKeyAuthenticator = customTokenAuthenticators.stream()
119-
.filter(t -> t.name().contains("api key") || t instanceof CustomTokenAuthenticator.Noop)
120-
.findAny()
121-
.orElseThrow();
114+
122115
this.authenticatorChain = new AuthenticatorChain(
123116
settings,
124117
operatorPrivilegesService,
125118
anonymousUser,
126119
new AuthenticationContextSerializer(),
120+
new PluggableAuthenticatorChain(customAuthenticators),
127121
new ServiceAccountAuthenticator(serviceAccountService, nodeName, meterRegistry),
128-
new PluggableOAuth2TokenAuthenticator(oauth2Authenticator),
129122
new OAuth2TokenAuthenticator(tokenService, meterRegistry),
130-
new PluggableApiKeyAuthenticator(apiKeyAuthenticator),
131123
new ApiKeyAuthenticator(apiKeyService, nodeName, meterRegistry),
132124
new RealmsAuthenticator(numInvalidation, lastSuccessfulAuthCache, meterRegistry)
133125
);

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticatorChain.java

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import org.elasticsearch.xpack.core.security.user.User;
2727
import org.elasticsearch.xpack.security.operator.OperatorPrivileges.OperatorPrivilegesService;
2828

29+
import java.util.ArrayList;
30+
import java.util.Collections;
2931
import java.util.List;
3032
import java.util.Map;
3133
import java.util.function.BiConsumer;
@@ -52,10 +54,9 @@ class AuthenticatorChain {
5254
OperatorPrivilegesService operatorPrivilegesService,
5355
AnonymousUser anonymousUser,
5456
AuthenticationContextSerializer authenticationSerializer,
57+
PluggableAuthenticatorChain pluggableAuthenticatorChain,
5558
ServiceAccountAuthenticator serviceAccountAuthenticator,
56-
PluggableOAuth2TokenAuthenticator pluggableOAuth2TokenAuthenticator,
5759
OAuth2TokenAuthenticator oAuth2TokenAuthenticator,
58-
PluggableApiKeyAuthenticator pluggableApiKeyAuthenticator,
5960
ApiKeyAuthenticator apiKeyAuthenticator,
6061
RealmsAuthenticator realmsAuthenticator
6162
) {
@@ -66,14 +67,16 @@ class AuthenticatorChain {
6667
this.isAnonymousUserEnabled = AnonymousUser.isAnonymousEnabled(settings);
6768
this.authenticationSerializer = authenticationSerializer;
6869
this.realmsAuthenticator = realmsAuthenticator;
69-
this.allAuthenticators = List.of(
70-
serviceAccountAuthenticator,
71-
pluggableOAuth2TokenAuthenticator,
72-
oAuth2TokenAuthenticator,
73-
pluggableApiKeyAuthenticator,
74-
apiKeyAuthenticator,
75-
realmsAuthenticator
76-
);
70+
71+
List<Authenticator> authenticators = new ArrayList<>();
72+
if (pluggableAuthenticatorChain.hasCustomAuthenticators()) {
73+
authenticators.add(pluggableAuthenticatorChain);
74+
}
75+
authenticators.add(serviceAccountAuthenticator);
76+
authenticators.add(oAuth2TokenAuthenticator);
77+
authenticators.add(apiKeyAuthenticator);
78+
authenticators.add(realmsAuthenticator);
79+
this.allAuthenticators = Collections.unmodifiableList(authenticators);
7780
}
7881

7982
void authenticate(Authenticator.Context context, ActionListener<Authentication> originalListener) {

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/PluggableApiKeyAuthenticator.java

Lines changed: 0 additions & 40 deletions
This file was deleted.

0 commit comments

Comments
 (0)