Skip to content

Commit 98aa7e4

Browse files
committed
Add Project Service Account Auth
1 parent 1e7c6ab commit 98aa7e4

31 files changed

+211
-123
lines changed

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

+5
Original file line numberDiff line numberDiff line change
@@ -16,6 +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.service.ServiceAccountTokenStore;
1920
import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper;
2021
import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine;
2122
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
@@ -114,6 +115,10 @@ default List<BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>>> getRo
114115
return Collections.emptyList();
115116
}
116117

118+
default ServiceAccountTokenStore getServiceAccountTokenStore(SecurityComponents components) {
119+
return null;
120+
}
121+
117122
/**
118123
* Returns a authorization engine for authorizing requests, or null to use the default authorization mechanism.
119124
*

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/service/TokenInfo.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ public int compareTo(TokenInfo o) {
105105

106106
public enum TokenSource {
107107
INDEX,
108-
FILE;
108+
FILE,
109+
EXTENSION
109110
}
110111
}
+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* 2.0.
66
*/
77

8-
package org.elasticsearch.xpack.security.authc.service;
8+
package org.elasticsearch.xpack.core.security.authc.service;
99

1010
import org.apache.logging.log4j.util.Strings;
1111
import org.elasticsearch.common.io.stream.StreamInput;
+2-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* 2.0.
66
*/
77

8-
package org.elasticsearch.xpack.security.authc.service;
8+
package org.elasticsearch.xpack.core.security.authc.service;
99

1010
import org.apache.logging.log4j.LogManager;
1111
import org.apache.logging.log4j.Logger;
@@ -14,9 +14,9 @@
1414
import org.elasticsearch.common.settings.SecureString;
1515
import org.elasticsearch.core.CharArrays;
1616
import org.elasticsearch.xpack.core.security.authc.AuthenticationToken;
17+
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount.ServiceAccountId;
1718
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
1819
import org.elasticsearch.xpack.core.security.support.Validation;
19-
import org.elasticsearch.xpack.security.authc.service.ServiceAccount.ServiceAccountId;
2020

2121
import java.io.ByteArrayInputStream;
2222
import java.io.ByteArrayOutputStream;
@@ -51,7 +51,6 @@ public class ServiceAccountToken implements AuthenticationToken, Closeable {
5151
private final ServiceAccountTokenId tokenId;
5252
private final SecureString secret;
5353

54-
// pkg private for testing
5554
ServiceAccountToken(ServiceAccountId accountId, String tokenName, SecureString secret) {
5655
tokenId = new ServiceAccountTokenId(accountId, tokenName);
5756
this.secret = Objects.requireNonNull(secret, "service account token secret cannot be null");
+16-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* 2.0.
66
*/
77

8-
package org.elasticsearch.xpack.security.authc.service;
8+
package org.elasticsearch.xpack.core.security.authc.service;
99

1010
import org.elasticsearch.action.ActionListener;
1111
import org.elasticsearch.xpack.core.security.action.service.TokenInfo.TokenSource;
@@ -21,12 +21,24 @@ public interface ServiceAccountTokenStore {
2121
void authenticate(ServiceAccountToken token, ActionListener<StoreAuthenticationResult> listener);
2222

2323
class StoreAuthenticationResult {
24-
private final boolean success;
2524
private final TokenSource tokenSource;
25+
private final boolean success;
2626

27-
public StoreAuthenticationResult(boolean success, TokenSource tokenSource) {
28-
this.success = success;
27+
private StoreAuthenticationResult(TokenSource tokenSource, boolean success) {
2928
this.tokenSource = tokenSource;
29+
this.success = success;
30+
}
31+
32+
public static StoreAuthenticationResult successful(TokenSource tokenSource) {
33+
return new StoreAuthenticationResult(tokenSource, true);
34+
}
35+
36+
public static StoreAuthenticationResult failed(TokenSource tokenSource) {
37+
return new StoreAuthenticationResult(tokenSource, false);
38+
}
39+
40+
public static StoreAuthenticationResult fromBooleanResult(TokenSource tokenSource, boolean result) {
41+
return result ? successful(tokenSource) : failed(tokenSource);
3042
}
3143

3244
public boolean isSuccess() {
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
* 2.0.
66
*/
77

8-
package org.elasticsearch.xpack.security.authc.service;
8+
package org.elasticsearch.xpack.core.security.authc.service;
99

1010
import org.elasticsearch.common.settings.SecureString;
1111
import org.elasticsearch.test.ESTestCase;
12+
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount.ServiceAccountId;
1213
import org.elasticsearch.xpack.core.security.support.Validation;
1314
import org.elasticsearch.xpack.core.security.support.ValidationTests;
14-
import org.elasticsearch.xpack.security.authc.service.ServiceAccount.ServiceAccountId;
1515

1616
import java.io.IOException;
1717

x-pack/plugin/security/src/main/java/module-info.java

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
exports org.elasticsearch.xpack.security.rest.action.apikey to org.elasticsearch.internal.security;
7575
exports org.elasticsearch.xpack.security.support to org.elasticsearch.internal.security;
7676
exports org.elasticsearch.xpack.security.authz.store to org.elasticsearch.internal.security;
77+
exports org.elasticsearch.xpack.security.authc.service;
7778

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

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

+46-30
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@
208208
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
209209
import org.elasticsearch.xpack.core.security.authc.RealmSettings;
210210
import org.elasticsearch.xpack.core.security.authc.Subject;
211+
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountTokenStore;
211212
import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper;
212213
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
213214
import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine;
@@ -310,6 +311,7 @@
310311
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
311312
import org.elasticsearch.xpack.security.authc.jwt.JwtRealm;
312313
import org.elasticsearch.xpack.security.authc.service.CachingServiceAccountTokenStore;
314+
import org.elasticsearch.xpack.security.authc.service.CompositeServiceAccountTokenStore;
313315
import org.elasticsearch.xpack.security.authc.service.FileServiceAccountTokenStore;
314316
import org.elasticsearch.xpack.security.authc.service.IndexServiceAccountTokenStore;
315317
import org.elasticsearch.xpack.security.authc.service.ServiceAccountService;
@@ -915,12 +917,53 @@ Collection<Object> createComponents(
915917
this.realms.set(realms);
916918

917919
systemIndices.getMainIndexManager().addStateListener(nativeRoleMappingStore::onSecurityIndexStateChange);
918-
919920
final CacheInvalidatorRegistry cacheInvalidatorRegistry = new CacheInvalidatorRegistry();
920-
cacheInvalidatorRegistry.registerAlias("service", Set.of("file_service_account_token", "index_service_account_token"));
921921
components.add(cacheInvalidatorRegistry);
922-
systemIndices.getMainIndexManager().addStateListener(cacheInvalidatorRegistry::onSecurityIndexStateChange);
923922

923+
final IndexServiceAccountTokenStore indexServiceAccountTokenStore = new IndexServiceAccountTokenStore(
924+
settings,
925+
threadPool,
926+
getClock(),
927+
client,
928+
systemIndices.getMainIndexManager(),
929+
clusterService,
930+
cacheInvalidatorRegistry
931+
);
932+
components.add(indexServiceAccountTokenStore);
933+
934+
final FileServiceAccountTokenStore fileServiceAccountTokenStore = new FileServiceAccountTokenStore(
935+
environment,
936+
resourceWatcherService,
937+
threadPool,
938+
clusterService,
939+
cacheInvalidatorRegistry
940+
);
941+
components.add(fileServiceAccountTokenStore);
942+
cacheInvalidatorRegistry.registerAlias("service", Set.of("file_service_account_token", "index_service_account_token"));
943+
944+
List<ServiceAccountTokenStore> extensionTokenStores = securityExtensions.stream()
945+
.map(extension -> extension.getServiceAccountTokenStore(extensionComponents))
946+
.toList();
947+
948+
ServiceAccountService serviceAccountService;
949+
950+
if (extensionTokenStores.isEmpty()) {
951+
serviceAccountService = new ServiceAccountService(client, fileServiceAccountTokenStore, indexServiceAccountTokenStore);
952+
} else {
953+
// Completely handover service account token management to the extension if provided, this will disable the index managed
954+
// service account tokens managed through the service account token API
955+
logger.debug("Service account authentication handled by extension, disabling file and index token stores");
956+
components.addAll(extensionTokenStores);
957+
serviceAccountService = new ServiceAccountService(
958+
client,
959+
new CompositeServiceAccountTokenStore(extensionTokenStores, client.threadPool().getThreadContext())
960+
);
961+
// TODO Should this also register with the cacheInvalidatorRegistry?
962+
}
963+
964+
components.add(serviceAccountService);
965+
966+
systemIndices.getMainIndexManager().addStateListener(cacheInvalidatorRegistry::onSecurityIndexStateChange);
924967
final NativePrivilegeStore privilegeStore = new NativePrivilegeStore(
925968
settings,
926969
client,
@@ -1004,33 +1047,6 @@ Collection<Object> createComponents(
10041047
);
10051048
components.add(apiKeyService);
10061049

1007-
final IndexServiceAccountTokenStore indexServiceAccountTokenStore = new IndexServiceAccountTokenStore(
1008-
settings,
1009-
threadPool,
1010-
getClock(),
1011-
client,
1012-
systemIndices.getMainIndexManager(),
1013-
clusterService,
1014-
cacheInvalidatorRegistry
1015-
);
1016-
components.add(indexServiceAccountTokenStore);
1017-
1018-
final FileServiceAccountTokenStore fileServiceAccountTokenStore = new FileServiceAccountTokenStore(
1019-
environment,
1020-
resourceWatcherService,
1021-
threadPool,
1022-
clusterService,
1023-
cacheInvalidatorRegistry
1024-
);
1025-
components.add(fileServiceAccountTokenStore);
1026-
1027-
final ServiceAccountService serviceAccountService = new ServiceAccountService(
1028-
client,
1029-
fileServiceAccountTokenStore,
1030-
indexServiceAccountTokenStore
1031-
);
1032-
components.add(serviceAccountService);
1033-
10341050
final RoleProviders roleProviders = new RoleProviders(
10351051
reservedRolesStore,
10361052
fileRolesStore.get(),

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/service/TransportGetServiceAccountAction.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountRequest;
2020
import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountResponse;
2121
import org.elasticsearch.xpack.core.security.action.service.ServiceAccountInfo;
22-
import org.elasticsearch.xpack.security.authc.service.ServiceAccount;
22+
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount;
2323
import org.elasticsearch.xpack.security.authc.service.ServiceAccountService;
2424

2525
import java.util.function.Predicate;

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/service/TransportGetServiceAccountNodesCredentialsAction.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountCredentialsNodesResponse;
2222
import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountNodesCredentialsAction;
2323
import org.elasticsearch.xpack.core.security.action.service.TokenInfo;
24+
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount.ServiceAccountId;
2425
import org.elasticsearch.xpack.security.authc.service.FileServiceAccountTokenStore;
25-
import org.elasticsearch.xpack.security.authc.service.ServiceAccount.ServiceAccountId;
2626

2727
import java.io.IOException;
2828
import java.util.List;

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
import org.elasticsearch.xpack.core.security.authc.AuthenticationField;
9898
import org.elasticsearch.xpack.core.security.authc.AuthenticationToken;
9999
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountSettings;
100+
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken;
100101
import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine.AuthorizationInfo;
101102
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
102103
import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableClusterPrivileges;
@@ -110,7 +111,6 @@
110111
import org.elasticsearch.xpack.security.audit.AuditTrail;
111112
import org.elasticsearch.xpack.security.audit.AuditUtil;
112113
import org.elasticsearch.xpack.security.authc.ApiKeyService;
113-
import org.elasticsearch.xpack.security.authc.service.ServiceAccountToken;
114114
import org.elasticsearch.xpack.security.rest.RemoteHostHeader;
115115
import org.elasticsearch.xpack.security.transport.filter.IPFilter;
116116
import org.elasticsearch.xpack.security.transport.filter.SecurityIpFilterRule;

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
import org.elasticsearch.xpack.core.security.authc.Authentication;
1616
import org.elasticsearch.xpack.core.security.authc.AuthenticationResult;
1717
import org.elasticsearch.xpack.core.security.authc.AuthenticationToken;
18+
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken;
1819
import org.elasticsearch.xpack.security.authc.service.ServiceAccountService;
19-
import org.elasticsearch.xpack.security.authc.service.ServiceAccountToken;
2020
import org.elasticsearch.xpack.security.metric.InstrumentedSecurityActionListener;
2121
import org.elasticsearch.xpack.security.metric.SecurityMetricType;
2222
import org.elasticsearch.xpack.security.metric.SecurityMetrics;

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import org.elasticsearch.core.TimeValue;
2020
import org.elasticsearch.threadpool.ThreadPool;
2121
import org.elasticsearch.xpack.core.security.action.service.TokenInfo.TokenSource;
22+
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken;
23+
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountTokenStore;
2224
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
2325
import org.elasticsearch.xpack.security.support.CacheInvalidatorRegistry;
2426

@@ -97,10 +99,10 @@ private void authenticateWithCache(ServiceAccountToken token, ActionListener<Sto
9799
if (valueAlreadyInCache.get()) {
98100
listenableCacheEntry.addListener(listener.delegateFailureAndWrap((l, result) -> {
99101
if (result.success) {
100-
l.onResponse(new StoreAuthenticationResult(result.verify(token), getTokenSource()));
102+
l.onResponse(StoreAuthenticationResult.fromBooleanResult(getTokenSource(), result.verify(token)));
101103
} else if (result.verify(token)) {
102104
// same wrong token
103-
l.onResponse(new StoreAuthenticationResult(false, getTokenSource()));
105+
l.onResponse(StoreAuthenticationResult.failed(getTokenSource()));
104106
} else {
105107
cache.invalidate(token.getQualifiedName(), listenableCacheEntry);
106108
authenticateWithCache(token, l);

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import org.elasticsearch.action.ActionListener;
1313
import org.elasticsearch.common.util.concurrent.ThreadContext;
1414
import org.elasticsearch.xpack.core.common.IteratingActionListener;
15+
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken;
16+
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountTokenStore;
1517

1618
import java.util.List;
1719
import java.util.function.Function;
@@ -38,7 +40,7 @@ public void authenticate(ServiceAccountToken token, ActionListener<StoreAuthenti
3840
stores,
3941
threadContext,
4042
Function.identity(),
41-
storeAuthenticationResult -> false == storeAuthenticationResult.isSuccess()
43+
storeAuthenticationResult -> storeAuthenticationResult.isSuccess() == false
4244
);
4345
try {
4446
authenticatingListener.run();

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

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package org.elasticsearch.xpack.security.authc.service;
99

1010
import org.elasticsearch.common.Strings;
11+
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount;
1112
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
1213
import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
1314
import org.elasticsearch.xpack.core.security.user.User;

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

+5-3
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@
2222
import org.elasticsearch.xpack.core.XPackPlugin;
2323
import org.elasticsearch.xpack.core.security.action.service.TokenInfo;
2424
import org.elasticsearch.xpack.core.security.action.service.TokenInfo.TokenSource;
25+
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount.ServiceAccountId;
26+
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken;
2527
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
2628
import org.elasticsearch.xpack.core.security.support.NoOpLogger;
2729
import org.elasticsearch.xpack.security.PrivilegedFileWatcher;
28-
import org.elasticsearch.xpack.security.authc.service.ServiceAccount.ServiceAccountId;
2930
import org.elasticsearch.xpack.security.support.CacheInvalidatorRegistry;
3031
import org.elasticsearch.xpack.security.support.FileLineParser;
3132
import org.elasticsearch.xpack.security.support.FileReloadListener;
@@ -50,6 +51,7 @@ public class FileServiceAccountTokenStore extends CachingServiceAccountTokenStor
5051
private final CopyOnWriteArrayList<Runnable> refreshListeners;
5152
private volatile Map<String, char[]> tokenHashes;
5253

54+
@SuppressWarnings("this-escape")
5355
public FileServiceAccountTokenStore(
5456
Environment env,
5557
ResourceWatcherService resourceWatcherService,
@@ -82,8 +84,8 @@ public void doAuthenticate(ServiceAccountToken token, ActionListener<StoreAuthen
8284
// because it is not expected to have a large number of service tokens.
8385
listener.onResponse(
8486
Optional.ofNullable(tokenHashes.get(token.getQualifiedName()))
85-
.map(hash -> new StoreAuthenticationResult(Hasher.verifyHash(token.getSecret(), hash), getTokenSource()))
86-
.orElse(new StoreAuthenticationResult(false, getTokenSource()))
87+
.map(hash -> StoreAuthenticationResult.fromBooleanResult(getTokenSource(), Hasher.verifyHash(token.getSecret(), hash)))
88+
.orElse(StoreAuthenticationResult.failed(getTokenSource()))
8789
);
8890
}
8991

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@
2121
import org.elasticsearch.core.Predicates;
2222
import org.elasticsearch.env.Environment;
2323
import org.elasticsearch.xpack.core.XPackSettings;
24+
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount.ServiceAccountId;
25+
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken;
26+
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken.ServiceAccountTokenId;
2427
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
2528
import org.elasticsearch.xpack.core.security.support.Validation;
26-
import org.elasticsearch.xpack.security.authc.service.ServiceAccount.ServiceAccountId;
27-
import org.elasticsearch.xpack.security.authc.service.ServiceAccountToken.ServiceAccountTokenId;
2829
import org.elasticsearch.xpack.security.support.FileAttributesChecker;
2930

3031
import java.nio.file.Path;

0 commit comments

Comments
 (0)