Skip to content

Commit 85094f2

Browse files
ankit--sethielasticsearchmachineslobodanadamovic
authored
Handling access tokens (elastic#133106)
* initial commit - tests pending potentially * [CI] Auto commit changes from spotless * fix syntax * correct javadoc * fix style issue * fix tests * [PoC] Pluggable authenticator chain * [CI] Auto commit changes from spotless * spotless + remove unused method * fix javadoc line lenght * refactor with code review feedback and new validation for cloud-saml-kibana * [CI] Auto commit changes from spotless * code review stuff * [CI] Auto commit changes from spotless --------- Co-authored-by: elasticsearchmachine <[email protected]> Co-authored-by: Slobodan Adamovic <[email protected]>
1 parent 60245c9 commit 85094f2

File tree

11 files changed

+181
-175
lines changed

11 files changed

+181
-175
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.CustomApiKeyAuthenticator;
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 CustomApiKeyAuthenticator getCustomApiKeyAuthenticator(SecurityComponents components) {
132+
default List<CustomAuthenticator> getCustomAuthenticators(SecurityComponents components) {
133133
return null;
134134
}
135135

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,6 +1376,18 @@ public static Authentication newRealmAuthentication(User user, RealmRef realmRef
13761376
return authentication;
13771377
}
13781378

1379+
public static Authentication newCloudAccessTokenAuthentication(
1380+
AuthenticationResult<User> authResult,
1381+
Authentication.RealmRef realmRef
1382+
) {
1383+
assert authResult.isAuthenticated() : "cloud access token authn result must be successful";
1384+
final User user = authResult.getValue();
1385+
return new Authentication(
1386+
new Subject(user, realmRef, TransportVersion.current(), authResult.getMetadata()),
1387+
AuthenticationType.TOKEN
1388+
);
1389+
}
1390+
13791391
public static Authentication newCloudApiKeyAuthentication(AuthenticationResult<User> authResult, String nodeName) {
13801392
assert authResult.isAuthenticated() : "cloud API Key authn result must be successful";
13811393
final User apiKeyUser = authResult.getValue();

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

Lines changed: 0 additions & 51 deletions
This file was deleted.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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+
17+
/**
18+
* An extension point to provide a custom authenticator implementation. For example, a custom API key or a custom OAuth2
19+
* token implementation. The implementation is wrapped by a core `Authenticator` class and included in the authenticator chain
20+
* _before_ the respective "standard" authenticator(s).
21+
*/
22+
public interface CustomAuthenticator {
23+
24+
boolean supports(AuthenticationToken token);
25+
26+
@Nullable
27+
AuthenticationToken extractToken(ThreadContext context);
28+
29+
void authenticate(@Nullable AuthenticationToken token, ActionListener<AuthenticationResult<Authentication>> listener);
30+
31+
}

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

Lines changed: 32 additions & 30 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.CustomApiKeyAuthenticator;
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;
@@ -1080,9 +1080,8 @@ Collection<Object> createComponents(
10801080
operatorPrivilegesService.set(OperatorPrivileges.NOOP_OPERATOR_PRIVILEGES_SERVICE);
10811081
}
10821082

1083-
final CustomApiKeyAuthenticator customApiKeyAuthenticator = createCustomApiKeyAuthenticator(extensionComponents);
1084-
1085-
components.add(customApiKeyAuthenticator);
1083+
final List<CustomAuthenticator> customAuthenticators = getCustomAuthenticatorFromExtensions(extensionComponents);
1084+
components.addAll(customAuthenticators);
10861085

10871086
authcService.set(
10881087
new AuthenticationService(
@@ -1096,7 +1095,7 @@ Collection<Object> createComponents(
10961095
apiKeyService,
10971096
serviceAccountService,
10981097
operatorPrivilegesService.get(),
1099-
customApiKeyAuthenticator,
1098+
customAuthenticators,
11001099
telemetryProvider.getMeterRegistry()
11011100
)
11021101
);
@@ -1232,45 +1231,48 @@ Collection<Object> createComponents(
12321231
return components;
12331232
}
12341233

1235-
private CustomApiKeyAuthenticator createCustomApiKeyAuthenticator(SecurityExtension.SecurityComponents extensionComponents) {
1236-
final Map<String, CustomApiKeyAuthenticator> customApiKeyAuthenticatorByExtension = new HashMap<>();
1237-
for (final SecurityExtension extension : securityExtensions) {
1238-
final CustomApiKeyAuthenticator customApiKeyAuthenticator = extension.getCustomApiKeyAuthenticator(extensionComponents);
1239-
if (customApiKeyAuthenticator != null) {
1240-
if (false == isInternalExtension(extension)) {
1234+
private List<CustomAuthenticator> getCustomAuthenticatorFromExtensions(SecurityExtension.SecurityComponents extensionComponents) {
1235+
final Map<String, List<CustomAuthenticator>> customAuthenticatorsByExtension = new HashMap<>();
1236+
for (final SecurityExtension securityExtension : securityExtensions) {
1237+
final List<CustomAuthenticator> customAuthenticators = securityExtension.getCustomAuthenticators(extensionComponents);
1238+
if (customAuthenticators != null) {
1239+
if (false == isInternalExtension(securityExtension)) {
12411240
throw new IllegalStateException(
12421241
"The ["
1243-
+ extension.extensionName()
1244-
+ "] extension tried to install a custom CustomApiKeyAuthenticator. "
1242+
+ securityExtension.extensionName()
1243+
+ "] extension tried to install a "
1244+
+ CustomAuthenticator.class.getSimpleName()
1245+
+ ". "
12451246
+ "This functionality is not available to external extensions."
12461247
);
12471248
}
1248-
customApiKeyAuthenticatorByExtension.put(extension.extensionName(), customApiKeyAuthenticator);
1249+
customAuthenticatorsByExtension.put(securityExtension.extensionName(), customAuthenticators);
12491250
}
12501251
}
12511252

1252-
if (customApiKeyAuthenticatorByExtension.isEmpty()) {
1253+
if (customAuthenticatorsByExtension.isEmpty()) {
12531254
logger.debug(
1254-
"No custom implementation for [{}]. Falling-back to noop implementation.",
1255-
CustomApiKeyAuthenticator.class.getCanonicalName()
1255+
"No custom implementations for [{}] provided by security extensions.",
1256+
CustomAuthenticator.class.getCanonicalName()
12561257
);
1257-
return new CustomApiKeyAuthenticator.Noop();
1258-
1259-
} else if (customApiKeyAuthenticatorByExtension.size() > 1) {
1258+
return List.of();
1259+
} else if (customAuthenticatorsByExtension.size() > 1) {
12601260
throw new IllegalStateException(
1261-
"Multiple extensions tried to install a custom CustomApiKeyAuthenticator: " + customApiKeyAuthenticatorByExtension.keySet()
1261+
"Multiple extensions tried to install custom authenticators: " + customAuthenticatorsByExtension.keySet()
12621262
);
1263-
12641263
} else {
1265-
final var authenticatorByExtensionEntry = customApiKeyAuthenticatorByExtension.entrySet().iterator().next();
1266-
final CustomApiKeyAuthenticator customApiKeyAuthenticator = authenticatorByExtensionEntry.getValue();
1264+
final var authenticatorByExtensionEntry = customAuthenticatorsByExtension.entrySet().iterator().next();
1265+
final List<CustomAuthenticator> customAuthenticators = authenticatorByExtensionEntry.getValue();
12671266
final String extensionName = authenticatorByExtensionEntry.getKey();
1268-
logger.debug(
1269-
"CustomApiKeyAuthenticator implementation [{}] provided by extension [{}]",
1270-
customApiKeyAuthenticator.getClass().getCanonicalName(),
1271-
extensionName
1272-
);
1273-
return customApiKeyAuthenticator;
1267+
for (CustomAuthenticator authenticator : customAuthenticators) {
1268+
logger.debug(
1269+
"{} implementation [{}] provided by extension [{}]",
1270+
CustomAuthenticator.class.getSimpleName(),
1271+
authenticator.getClass().getCanonicalName(),
1272+
extensionName
1273+
);
1274+
}
1275+
return customAuthenticators;
12741276
}
12751277
}
12761278

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

Lines changed: 4 additions & 3 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.CustomApiKeyAuthenticator;
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;
@@ -93,7 +93,7 @@ public AuthenticationService(
9393
ApiKeyService apiKeyService,
9494
ServiceAccountService serviceAccountService,
9595
OperatorPrivilegesService operatorPrivilegesService,
96-
CustomApiKeyAuthenticator customApiKeyAuthenticator,
96+
List<CustomAuthenticator> customAuthenticators,
9797
MeterRegistry meterRegistry
9898
) {
9999
this.realms = realms;
@@ -110,14 +110,15 @@ public AuthenticationService(
110110
}
111111

112112
final String nodeName = Node.NODE_NAME_SETTING.get(settings);
113+
113114
this.authenticatorChain = new AuthenticatorChain(
114115
settings,
115116
operatorPrivilegesService,
116117
anonymousUser,
117118
new AuthenticationContextSerializer(),
119+
new PluggableAuthenticatorChain(customAuthenticators),
118120
new ServiceAccountAuthenticator(serviceAccountService, nodeName, meterRegistry),
119121
new OAuth2TokenAuthenticator(tokenService, meterRegistry),
120-
new PluggableApiKeyAuthenticator(customApiKeyAuthenticator),
121122
new ApiKeyAuthenticator(apiKeyService, nodeName, meterRegistry),
122123
new RealmsAuthenticator(numInvalidation, lastSuccessfulAuthCache, meterRegistry)
123124
);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public interface Authenticator {
3737
/**
3838
* Attempt to Extract an {@link AuthenticationToken} from the given {@link Context}.
3939
* @param context The context object encapsulating current request and other information relevant for authentication.
40-
*
40+
4141
* @return An {@link AuthenticationToken} if one can be extracted or null if this Authenticator cannot
4242
* extract one.
4343
*/

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

Lines changed: 13 additions & 8 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,9 +54,9 @@ class AuthenticatorChain {
5254
OperatorPrivilegesService operatorPrivilegesService,
5355
AnonymousUser anonymousUser,
5456
AuthenticationContextSerializer authenticationSerializer,
57+
PluggableAuthenticatorChain pluggableAuthenticatorChain,
5558
ServiceAccountAuthenticator serviceAccountAuthenticator,
5659
OAuth2TokenAuthenticator oAuth2TokenAuthenticator,
57-
PluggableApiKeyAuthenticator pluggableApiKeyAuthenticator,
5860
ApiKeyAuthenticator apiKeyAuthenticator,
5961
RealmsAuthenticator realmsAuthenticator
6062
) {
@@ -65,13 +67,16 @@ class AuthenticatorChain {
6567
this.isAnonymousUserEnabled = AnonymousUser.isAnonymousEnabled(settings);
6668
this.authenticationSerializer = authenticationSerializer;
6769
this.realmsAuthenticator = realmsAuthenticator;
68-
this.allAuthenticators = List.of(
69-
serviceAccountAuthenticator,
70-
oAuth2TokenAuthenticator,
71-
pluggableApiKeyAuthenticator,
72-
apiKeyAuthenticator,
73-
realmsAuthenticator
74-
);
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);
7580
}
7681

7782
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 & 56 deletions
This file was deleted.

0 commit comments

Comments
 (0)