-
Notifications
You must be signed in to change notification settings - Fork 25.6k
Add Support for Providing a custom ServiceAccountTokenStore through SecurityExtensions #126612
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
3dec54b
fcab6e2
0d027db
6a64ba5
cd95dc1
c679f7d
35e379e
5340793
5628f9e
6ff864f
cbe9cec
91ab630
4e2b7cf
9da5400
8dee42d
1375e1c
cebaa8a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| pr: 126612 | ||
| summary: Add Support for Providing a custom `ServiceAccountTokenStore` through `SecurityExtensions` | ||
| area: Authentication | ||
| type: enhancement | ||
| issues: [] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| /* | ||
| * 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.service; | ||
|
|
||
| import org.elasticsearch.xpack.core.security.action.service.TokenInfo; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public interface ReadOnlyServiceAccountTokenStore extends ServiceAccountTokenStore { | ||
| default List<TokenInfo> findNodeLocalTokensFor(ServiceAccount.ServiceAccountId accountId) { | ||
| throw new IllegalStateException("Find node local tokens not supported by [" + this.getClass() + "]"); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -208,6 +208,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.service.ReadOnlyServiceAccountTokenStore; | ||
| import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; | ||
| import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; | ||
| import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine; | ||
|
|
@@ -310,6 +311,7 @@ | |
| import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; | ||
| import org.elasticsearch.xpack.security.authc.jwt.JwtRealm; | ||
| import org.elasticsearch.xpack.security.authc.service.CachingServiceAccountTokenStore; | ||
| import org.elasticsearch.xpack.security.authc.service.CompositeServiceAccountTokenStore; | ||
| import org.elasticsearch.xpack.security.authc.service.FileServiceAccountTokenStore; | ||
| import org.elasticsearch.xpack.security.authc.service.IndexServiceAccountTokenStore; | ||
| import org.elasticsearch.xpack.security.authc.service.ServiceAccountService; | ||
|
|
@@ -915,12 +917,34 @@ Collection<Object> createComponents( | |
| this.realms.set(realms); | ||
|
|
||
| systemIndices.getMainIndexManager().addStateListener(nativeRoleMappingStore::onSecurityIndexStateChange); | ||
|
|
||
| final CacheInvalidatorRegistry cacheInvalidatorRegistry = new CacheInvalidatorRegistry(); | ||
| cacheInvalidatorRegistry.registerAlias("service", Set.of("file_service_account_token", "index_service_account_token")); | ||
| components.add(cacheInvalidatorRegistry); | ||
| systemIndices.getMainIndexManager().addStateListener(cacheInvalidatorRegistry::onSecurityIndexStateChange); | ||
|
|
||
| ServiceAccountService serviceAccountService = createServiceAccountService( | ||
| components, | ||
| cacheInvalidatorRegistry, | ||
| extensionComponents, | ||
| () -> new IndexServiceAccountTokenStore( | ||
| settings, | ||
| threadPool, | ||
| getClock(), | ||
| client, | ||
| systemIndices.getMainIndexManager(), | ||
| clusterService, | ||
| cacheInvalidatorRegistry | ||
| ), | ||
| () -> new FileServiceAccountTokenStore( | ||
| environment, | ||
| resourceWatcherService, | ||
| threadPool, | ||
| clusterService, | ||
| cacheInvalidatorRegistry | ||
| ) | ||
| ); | ||
|
|
||
| components.add(serviceAccountService); | ||
|
|
||
| systemIndices.getMainIndexManager().addStateListener(cacheInvalidatorRegistry::onSecurityIndexStateChange); | ||
| final NativePrivilegeStore privilegeStore = new NativePrivilegeStore( | ||
| settings, | ||
| client, | ||
|
|
@@ -1004,33 +1028,6 @@ Collection<Object> createComponents( | |
| ); | ||
| components.add(apiKeyService); | ||
|
|
||
| final IndexServiceAccountTokenStore indexServiceAccountTokenStore = new IndexServiceAccountTokenStore( | ||
| settings, | ||
| threadPool, | ||
| getClock(), | ||
| client, | ||
| systemIndices.getMainIndexManager(), | ||
| clusterService, | ||
| cacheInvalidatorRegistry | ||
| ); | ||
| components.add(indexServiceAccountTokenStore); | ||
|
|
||
| final FileServiceAccountTokenStore fileServiceAccountTokenStore = new FileServiceAccountTokenStore( | ||
| environment, | ||
| resourceWatcherService, | ||
| threadPool, | ||
| clusterService, | ||
| cacheInvalidatorRegistry | ||
| ); | ||
| components.add(fileServiceAccountTokenStore); | ||
|
|
||
| final ServiceAccountService serviceAccountService = new ServiceAccountService( | ||
| client, | ||
| fileServiceAccountTokenStore, | ||
| indexServiceAccountTokenStore | ||
| ); | ||
| components.add(serviceAccountService); | ||
|
|
||
| final RoleProviders roleProviders = new RoleProviders( | ||
| reservedRolesStore, | ||
| fileRolesStore.get(), | ||
|
|
@@ -1250,6 +1247,56 @@ Collection<Object> createComponents( | |
| return components; | ||
| } | ||
|
|
||
| private ServiceAccountService createServiceAccountService( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add a test for the logic in this method?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added testing for this. Thanks! |
||
| List<Object> components, | ||
| CacheInvalidatorRegistry cacheInvalidatorRegistry, | ||
| SecurityExtension.SecurityComponents extensionComponents, | ||
| Supplier<IndexServiceAccountTokenStore> indexServiceAccountTokenStoreSupplier, | ||
| Supplier<FileServiceAccountTokenStore> fileServiceAccountTokenStoreSupplier | ||
| ) { | ||
| Map<String, ReadOnlyServiceAccountTokenStore> accountTokenStoreByExtension = new HashMap<>(); | ||
|
|
||
| for (var extension : securityExtensions) { | ||
| var serviceAccountTokenStore = extension.getServiceAccountTokenStore(extensionComponents); | ||
| if (serviceAccountTokenStore != null) { | ||
| accountTokenStoreByExtension.put(extension.extensionName(), serviceAccountTokenStore); | ||
| } | ||
| } | ||
|
|
||
| ServiceAccountService serviceAccountService; | ||
| if (accountTokenStoreByExtension.size() > 1) { | ||
| throw new IllegalStateException( | ||
| "More than one ServiceAccountTokenStore provided in extensions: " + accountTokenStoreByExtension.keySet() | ||
| ); | ||
| } | ||
| var extensionStore = accountTokenStoreByExtension.values().stream().findFirst(); | ||
| if (extensionStore.isEmpty()) { | ||
| var fileServiceAccountTokenStore = fileServiceAccountTokenStoreSupplier.get(); | ||
| var indexServiceAccountTokenStore = indexServiceAccountTokenStoreSupplier.get(); | ||
|
|
||
| components.add(new PluginComponentBinding<>(ReadOnlyServiceAccountTokenStore.class, fileServiceAccountTokenStore)); | ||
| components.add(indexServiceAccountTokenStore); | ||
| cacheInvalidatorRegistry.registerAlias("service", Set.of("file_service_account_token", "index_service_account_token")); | ||
|
|
||
| serviceAccountService = new ServiceAccountService( | ||
| client.get(), | ||
| new CompositeServiceAccountTokenStore( | ||
| List.of(fileServiceAccountTokenStore, indexServiceAccountTokenStore), | ||
| client.get().threadPool().getThreadContext() | ||
| ), | ||
| indexServiceAccountTokenStore | ||
| ); | ||
| } else { | ||
| // Completely handover service account token management to the extension if provided, | ||
| // this will disable the index managed | ||
| // service account tokens managed through the service account token API | ||
| components.add(new PluginComponentBinding<>(ReadOnlyServiceAccountTokenStore.class, extensionStore.get())); | ||
|
||
| logger.debug("Service account authentication handled by extension, disabling file and index token stores"); | ||
| serviceAccountService = new ServiceAccountService(client.get(), extensionStore.get()); | ||
| } | ||
| return serviceAccountService; | ||
| } | ||
|
|
||
| @FixForMultiProject | ||
| // TODO : The migration task needs to be project aware | ||
| private void applyPendingSecurityMigrations(ProjectId projectId, SecurityIndexManager.IndexState newState) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These had to move since they're now part of the
SecurityExtensioninterface throughServiceAccountTokenStore.