Add Support for Providing a custom ServiceAccountTokenStore through SecurityExtensions (#126612)
* Add Project Service Account Auth
This commit is contained in:
parent
e9fe219067
commit
bb9d1d6232
|
@ -0,0 +1,5 @@
|
|||
pr: 126612
|
||||
summary: Add Support for Providing a custom `ServiceAccountTokenStore` through `SecurityExtensions`
|
||||
area: Authentication
|
||||
type: enhancement
|
||||
issues: []
|
|
@ -16,6 +16,8 @@ import org.elasticsearch.threadpool.ThreadPool;
|
|||
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||
import org.elasticsearch.xpack.core.security.authc.AuthenticationFailureHandler;
|
||||
import org.elasticsearch.xpack.core.security.authc.Realm;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.NodeLocalServiceAccountTokenStore;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountTokenStore;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper;
|
||||
import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine;
|
||||
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
|
||||
|
@ -114,6 +116,18 @@ public interface SecurityExtension {
|
|||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link NodeLocalServiceAccountTokenStore} used to authenticate service account tokens.
|
||||
* If {@code null} is returned, the default service account token stores will be used.
|
||||
*
|
||||
* Providing a custom {@link NodeLocalServiceAccountTokenStore} here overrides the default implementation.
|
||||
*
|
||||
* @param components Access to components that can be used to authenticate service account tokens
|
||||
*/
|
||||
default ServiceAccountTokenStore getServiceAccountTokenStore(SecurityComponents components) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a authorization engine for authorizing requests, or null to use the default authorization mechanism.
|
||||
*
|
||||
|
|
|
@ -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 NodeLocalServiceAccountTokenStore extends ServiceAccountTokenStore {
|
||||
default List<TokenInfo> findNodeLocalTokensFor(ServiceAccount.ServiceAccountId accountId) {
|
||||
throw new IllegalStateException("Find node local tokens not supported by [" + this.getClass() + "]");
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.xpack.security.authc.service;
|
||||
package org.elasticsearch.xpack.core.security.authc.service;
|
||||
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.xpack.security.authc.service;
|
||||
package org.elasticsearch.xpack.core.security.authc.service;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
@ -14,9 +14,9 @@ import org.elasticsearch.common.UUIDs;
|
|||
import org.elasticsearch.common.settings.SecureString;
|
||||
import org.elasticsearch.core.CharArrays;
|
||||
import org.elasticsearch.xpack.core.security.authc.AuthenticationToken;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
|
||||
import org.elasticsearch.xpack.core.security.support.Validation;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
@ -51,7 +51,6 @@ public class ServiceAccountToken implements AuthenticationToken, Closeable {
|
|||
private final ServiceAccountTokenId tokenId;
|
||||
private final SecureString secret;
|
||||
|
||||
// pkg private for testing
|
||||
ServiceAccountToken(ServiceAccountId accountId, String tokenName, SecureString secret) {
|
||||
tokenId = new ServiceAccountTokenId(accountId, tokenName);
|
||||
this.secret = Objects.requireNonNull(secret, "service account token secret cannot be null");
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.xpack.security.authc.service;
|
||||
package org.elasticsearch.xpack.core.security.authc.service;
|
||||
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.xpack.core.security.action.service.TokenInfo.TokenSource;
|
||||
|
@ -24,11 +24,23 @@ public interface ServiceAccountTokenStore {
|
|||
private final boolean success;
|
||||
private final TokenSource tokenSource;
|
||||
|
||||
public StoreAuthenticationResult(boolean success, TokenSource tokenSource) {
|
||||
private StoreAuthenticationResult(TokenSource tokenSource, boolean success) {
|
||||
this.success = success;
|
||||
this.tokenSource = tokenSource;
|
||||
}
|
||||
|
||||
public static StoreAuthenticationResult successful(TokenSource tokenSource) {
|
||||
return new StoreAuthenticationResult(tokenSource, true);
|
||||
}
|
||||
|
||||
public static StoreAuthenticationResult failed(TokenSource tokenSource) {
|
||||
return new StoreAuthenticationResult(tokenSource, false);
|
||||
}
|
||||
|
||||
public static StoreAuthenticationResult fromBooleanResult(TokenSource tokenSource, boolean result) {
|
||||
return result ? successful(tokenSource) : failed(tokenSource);
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
|
@ -5,13 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.xpack.security.authc.service;
|
||||
package org.elasticsearch.xpack.core.security.authc.service;
|
||||
|
||||
import org.elasticsearch.common.settings.SecureString;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
import org.elasticsearch.xpack.core.security.support.Validation;
|
||||
import org.elasticsearch.xpack.core.security.support.ValidationTests;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
|
||||
import java.io.IOException;
|
||||
|
|
@ -74,6 +74,7 @@ module org.elasticsearch.security {
|
|||
exports org.elasticsearch.xpack.security.rest.action.apikey to org.elasticsearch.internal.security;
|
||||
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;
|
||||
|
||||
provides org.elasticsearch.index.SlowLogFieldProvider with org.elasticsearch.xpack.security.slowlog.SecuritySlowLogFieldProvider;
|
||||
|
||||
|
|
|
@ -208,6 +208,8 @@ import org.elasticsearch.xpack.core.security.authc.Realm;
|
|||
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.NodeLocalServiceAccountTokenStore;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountTokenStore;
|
||||
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 +312,7 @@ import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;
|
|||
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 +918,34 @@ public class Security extends Plugin
|
|||
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 +1029,6 @@ public class Security extends Plugin
|
|||
);
|
||||
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 +1248,74 @@ public class Security extends Plugin
|
|||
return components;
|
||||
}
|
||||
|
||||
private ServiceAccountService createServiceAccountService(
|
||||
List<Object> components,
|
||||
CacheInvalidatorRegistry cacheInvalidatorRegistry,
|
||||
SecurityExtension.SecurityComponents extensionComponents,
|
||||
Supplier<IndexServiceAccountTokenStore> indexServiceAccountTokenStoreSupplier,
|
||||
Supplier<FileServiceAccountTokenStore> fileServiceAccountTokenStoreSupplier
|
||||
) {
|
||||
Map<String, ServiceAccountTokenStore> accountTokenStoreByExtension = new HashMap<>();
|
||||
|
||||
for (var extension : securityExtensions) {
|
||||
var serviceAccountTokenStore = extension.getServiceAccountTokenStore(extensionComponents);
|
||||
if (serviceAccountTokenStore != null) {
|
||||
if (isInternalExtension(extension) == false) {
|
||||
throw new IllegalStateException(
|
||||
"The ["
|
||||
+ extension.getClass().getName()
|
||||
+ "] extension tried to install a custom ServiceAccountTokenStore. This functionality is not available to "
|
||||
+ "external extensions."
|
||||
);
|
||||
}
|
||||
accountTokenStoreByExtension.put(extension.extensionName(), serviceAccountTokenStore);
|
||||
}
|
||||
}
|
||||
|
||||
if (accountTokenStoreByExtension.size() > 1) {
|
||||
throw new IllegalStateException(
|
||||
"More than one extension provided a ServiceAccountTokenStore override: " + accountTokenStoreByExtension.keySet()
|
||||
);
|
||||
}
|
||||
|
||||
if (accountTokenStoreByExtension.isEmpty()) {
|
||||
var fileServiceAccountTokenStore = fileServiceAccountTokenStoreSupplier.get();
|
||||
var indexServiceAccountTokenStore = indexServiceAccountTokenStoreSupplier.get();
|
||||
|
||||
components.add(new PluginComponentBinding<>(NodeLocalServiceAccountTokenStore.class, fileServiceAccountTokenStore));
|
||||
components.add(fileServiceAccountTokenStore);
|
||||
components.add(indexServiceAccountTokenStore);
|
||||
cacheInvalidatorRegistry.registerAlias("service", Set.of("file_service_account_token", "index_service_account_token"));
|
||||
|
||||
return new ServiceAccountService(
|
||||
client.get(),
|
||||
new CompositeServiceAccountTokenStore(
|
||||
List.of(fileServiceAccountTokenStore, indexServiceAccountTokenStore),
|
||||
client.get().threadPool().getThreadContext()
|
||||
),
|
||||
indexServiceAccountTokenStore
|
||||
);
|
||||
}
|
||||
// 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
|
||||
var extensionStore = accountTokenStoreByExtension.values().stream().findFirst();
|
||||
components.add(new PluginComponentBinding<>(NodeLocalServiceAccountTokenStore.class, (token, listener) -> {
|
||||
throw new IllegalStateException("Node local config not supported by [" + extensionStore.get().getClass() + "]");
|
||||
}));
|
||||
components.add(extensionStore);
|
||||
logger.debug("Service account authentication handled by extension, disabling file and index token stores");
|
||||
return new ServiceAccountService(client.get(), extensionStore.get());
|
||||
}
|
||||
|
||||
private static boolean isInternalExtension(SecurityExtension extension) {
|
||||
final String canonicalName = extension.getClass().getCanonicalName();
|
||||
if (canonicalName == null) {
|
||||
return false;
|
||||
}
|
||||
return canonicalName.startsWith("org.elasticsearch.xpack.") || canonicalName.startsWith("co.elastic.elasticsearch.");
|
||||
}
|
||||
|
||||
@FixForMultiProject
|
||||
// TODO : The migration task needs to be project aware
|
||||
private void applyPendingSecurityMigrations(ProjectId projectId, SecurityIndexManager.IndexState newState) {
|
||||
|
|
|
@ -19,7 +19,7 @@ import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountAct
|
|||
import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountRequest;
|
||||
import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountResponse;
|
||||
import org.elasticsearch.xpack.core.security.action.service.ServiceAccountInfo;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccount;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccountService;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
|
|
@ -21,8 +21,8 @@ import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountCre
|
|||
import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountCredentialsNodesResponse;
|
||||
import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountNodesCredentialsAction;
|
||||
import org.elasticsearch.xpack.core.security.action.service.TokenInfo;
|
||||
import org.elasticsearch.xpack.security.authc.service.FileServiceAccountTokenStore;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.NodeLocalServiceAccountTokenStore;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
@ -38,7 +38,7 @@ public class TransportGetServiceAccountNodesCredentialsAction extends TransportN
|
|||
GetServiceAccountCredentialsNodesResponse.Node,
|
||||
Void> {
|
||||
|
||||
private final FileServiceAccountTokenStore fileServiceAccountTokenStore;
|
||||
private final NodeLocalServiceAccountTokenStore readOnlyServiceAccountTokenStore;
|
||||
|
||||
@Inject
|
||||
public TransportGetServiceAccountNodesCredentialsAction(
|
||||
|
@ -46,7 +46,7 @@ public class TransportGetServiceAccountNodesCredentialsAction extends TransportN
|
|||
ClusterService clusterService,
|
||||
TransportService transportService,
|
||||
ActionFilters actionFilters,
|
||||
FileServiceAccountTokenStore fileServiceAccountTokenStore
|
||||
NodeLocalServiceAccountTokenStore readOnlyServiceAccountTokenStore
|
||||
) {
|
||||
super(
|
||||
GetServiceAccountNodesCredentialsAction.NAME,
|
||||
|
@ -56,7 +56,7 @@ public class TransportGetServiceAccountNodesCredentialsAction extends TransportN
|
|||
GetServiceAccountCredentialsNodesRequest.Node::new,
|
||||
threadPool.executor(ThreadPool.Names.GENERIC)
|
||||
);
|
||||
this.fileServiceAccountTokenStore = fileServiceAccountTokenStore;
|
||||
this.readOnlyServiceAccountTokenStore = readOnlyServiceAccountTokenStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -84,7 +84,7 @@ public class TransportGetServiceAccountNodesCredentialsAction extends TransportN
|
|||
Task task
|
||||
) {
|
||||
final ServiceAccountId accountId = new ServiceAccountId(request.getNamespace(), request.getServiceName());
|
||||
final List<TokenInfo> tokenInfos = fileServiceAccountTokenStore.findTokensFor(accountId);
|
||||
final List<TokenInfo> tokenInfos = readOnlyServiceAccountTokenStore.findNodeLocalTokensFor(accountId);
|
||||
return new GetServiceAccountCredentialsNodesResponse.Node(
|
||||
clusterService.localNode(),
|
||||
tokenInfos.stream().map(TokenInfo::getName).toArray(String[]::new)
|
||||
|
|
|
@ -97,6 +97,7 @@ import org.elasticsearch.xpack.core.security.authc.Authentication;
|
|||
import org.elasticsearch.xpack.core.security.authc.AuthenticationField;
|
||||
import org.elasticsearch.xpack.core.security.authc.AuthenticationToken;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountSettings;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken;
|
||||
import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine.AuthorizationInfo;
|
||||
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
|
||||
import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableClusterPrivileges;
|
||||
|
@ -110,7 +111,6 @@ import org.elasticsearch.xpack.security.audit.AuditLevel;
|
|||
import org.elasticsearch.xpack.security.audit.AuditTrail;
|
||||
import org.elasticsearch.xpack.security.audit.AuditUtil;
|
||||
import org.elasticsearch.xpack.security.authc.ApiKeyService;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccountToken;
|
||||
import org.elasticsearch.xpack.security.rest.RemoteHostHeader;
|
||||
import org.elasticsearch.xpack.security.transport.filter.IPFilter;
|
||||
import org.elasticsearch.xpack.security.transport.filter.SecurityIpFilterRule;
|
||||
|
|
|
@ -15,8 +15,8 @@ import org.elasticsearch.telemetry.metric.MeterRegistry;
|
|||
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.service.ServiceAccountToken;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccountService;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccountToken;
|
||||
import org.elasticsearch.xpack.security.metric.InstrumentedSecurityActionListener;
|
||||
import org.elasticsearch.xpack.security.metric.SecurityMetricType;
|
||||
import org.elasticsearch.xpack.security.metric.SecurityMetrics;
|
||||
|
|
|
@ -19,6 +19,8 @@ import org.elasticsearch.common.util.concurrent.ListenableFuture;
|
|||
import org.elasticsearch.core.TimeValue;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.xpack.core.security.action.service.TokenInfo.TokenSource;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountTokenStore;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
|
||||
import org.elasticsearch.xpack.security.support.CacheInvalidatorRegistry;
|
||||
|
||||
|
@ -97,10 +99,10 @@ public abstract class CachingServiceAccountTokenStore implements ServiceAccountT
|
|||
if (valueAlreadyInCache.get()) {
|
||||
listenableCacheEntry.addListener(listener.delegateFailureAndWrap((l, result) -> {
|
||||
if (result.success) {
|
||||
l.onResponse(new StoreAuthenticationResult(result.verify(token), getTokenSource()));
|
||||
l.onResponse(StoreAuthenticationResult.fromBooleanResult(getTokenSource(), result.verify(token)));
|
||||
} else if (result.verify(token)) {
|
||||
// same wrong token
|
||||
l.onResponse(new StoreAuthenticationResult(false, getTokenSource()));
|
||||
l.onResponse(StoreAuthenticationResult.failed(getTokenSource()));
|
||||
} else {
|
||||
cache.invalidate(token.getQualifiedName(), listenableCacheEntry);
|
||||
authenticateWithCache(token, l);
|
||||
|
|
|
@ -12,6 +12,8 @@ import org.apache.logging.log4j.Logger;
|
|||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.xpack.core.common.IteratingActionListener;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountTokenStore;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
@ -38,7 +40,7 @@ public final class CompositeServiceAccountTokenStore implements ServiceAccountTo
|
|||
stores,
|
||||
threadContext,
|
||||
Function.identity(),
|
||||
storeAuthenticationResult -> false == storeAuthenticationResult.isSuccess()
|
||||
storeAuthenticationResult -> storeAuthenticationResult.isSuccess() == false
|
||||
);
|
||||
try {
|
||||
authenticatingListener.run();
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
package org.elasticsearch.xpack.security.authc.service;
|
||||
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount;
|
||||
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
|
||||
import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
|
||||
import org.elasticsearch.xpack.core.security.user.User;
|
||||
|
|
|
@ -22,10 +22,12 @@ import org.elasticsearch.watcher.ResourceWatcherService;
|
|||
import org.elasticsearch.xpack.core.XPackPlugin;
|
||||
import org.elasticsearch.xpack.core.security.action.service.TokenInfo;
|
||||
import org.elasticsearch.xpack.core.security.action.service.TokenInfo.TokenSource;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.NodeLocalServiceAccountTokenStore;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
|
||||
import org.elasticsearch.xpack.core.security.support.NoOpLogger;
|
||||
import org.elasticsearch.xpack.security.PrivilegedFileWatcher;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
import org.elasticsearch.xpack.security.support.CacheInvalidatorRegistry;
|
||||
import org.elasticsearch.xpack.security.support.FileLineParser;
|
||||
import org.elasticsearch.xpack.security.support.FileReloadListener;
|
||||
|
@ -41,7 +43,7 @@ import java.util.Map;
|
|||
import java.util.Optional;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public class FileServiceAccountTokenStore extends CachingServiceAccountTokenStore {
|
||||
public class FileServiceAccountTokenStore extends CachingServiceAccountTokenStore implements NodeLocalServiceAccountTokenStore {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(FileServiceAccountTokenStore.class);
|
||||
|
||||
|
@ -50,6 +52,7 @@ public class FileServiceAccountTokenStore extends CachingServiceAccountTokenStor
|
|||
private final CopyOnWriteArrayList<Runnable> refreshListeners;
|
||||
private volatile Map<String, char[]> tokenHashes;
|
||||
|
||||
@SuppressWarnings("this-escape")
|
||||
public FileServiceAccountTokenStore(
|
||||
Environment env,
|
||||
ResourceWatcherService resourceWatcherService,
|
||||
|
@ -82,8 +85,8 @@ public class FileServiceAccountTokenStore extends CachingServiceAccountTokenStor
|
|||
// because it is not expected to have a large number of service tokens.
|
||||
listener.onResponse(
|
||||
Optional.ofNullable(tokenHashes.get(token.getQualifiedName()))
|
||||
.map(hash -> new StoreAuthenticationResult(Hasher.verifyHash(token.getSecret(), hash), getTokenSource()))
|
||||
.orElse(new StoreAuthenticationResult(false, getTokenSource()))
|
||||
.map(hash -> StoreAuthenticationResult.fromBooleanResult(getTokenSource(), Hasher.verifyHash(token.getSecret(), hash)))
|
||||
.orElse(StoreAuthenticationResult.failed(getTokenSource()))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -92,7 +95,8 @@ public class FileServiceAccountTokenStore extends CachingServiceAccountTokenStor
|
|||
return TokenSource.FILE;
|
||||
}
|
||||
|
||||
public List<TokenInfo> findTokensFor(ServiceAccountId accountId) {
|
||||
@Override
|
||||
public List<TokenInfo> findNodeLocalTokensFor(ServiceAccountId accountId) {
|
||||
final String principal = accountId.asPrincipal();
|
||||
return tokenHashes.keySet()
|
||||
.stream()
|
||||
|
|
|
@ -21,10 +21,11 @@ import org.elasticsearch.common.settings.Settings;
|
|||
import org.elasticsearch.core.Predicates;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.xpack.core.XPackSettings;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken.ServiceAccountTokenId;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
|
||||
import org.elasticsearch.xpack.core.security.support.Validation;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccountToken.ServiceAccountTokenId;
|
||||
import org.elasticsearch.xpack.security.support.FileAttributesChecker;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
|
|
@ -47,9 +47,10 @@ import org.elasticsearch.xpack.core.security.action.service.TokenInfo;
|
|||
import org.elasticsearch.xpack.core.security.action.service.TokenInfo.TokenSource;
|
||||
import org.elasticsearch.xpack.core.security.authc.Authentication;
|
||||
import org.elasticsearch.xpack.core.security.authc.Subject;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken.ServiceAccountTokenId;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccountToken.ServiceAccountTokenId;
|
||||
import org.elasticsearch.xpack.security.support.CacheInvalidatorRegistry;
|
||||
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
|
||||
import org.elasticsearch.xpack.security.support.SecurityIndexManager.IndexState;
|
||||
|
@ -80,6 +81,7 @@ public class IndexServiceAccountTokenStore extends CachingServiceAccountTokenSto
|
|||
private final ClusterService clusterService;
|
||||
private final Hasher hasher;
|
||||
|
||||
@SuppressWarnings("this-escape")
|
||||
public IndexServiceAccountTokenStore(
|
||||
Settings settings,
|
||||
ThreadPool threadPool,
|
||||
|
@ -116,14 +118,14 @@ public class IndexServiceAccountTokenStore extends CachingServiceAccountTokenSto
|
|||
final String tokenHash = (String) response.getSource().get("password");
|
||||
assert tokenHash != null : "service account token hash cannot be null";
|
||||
listener.onResponse(
|
||||
new StoreAuthenticationResult(
|
||||
Hasher.verifyHash(token.getSecret(), tokenHash.toCharArray()),
|
||||
getTokenSource()
|
||||
StoreAuthenticationResult.fromBooleanResult(
|
||||
getTokenSource(),
|
||||
Hasher.verifyHash(token.getSecret(), tokenHash.toCharArray())
|
||||
)
|
||||
);
|
||||
} else {
|
||||
logger.trace("service account token [{}] not found in index", token.getQualifiedName());
|
||||
listener.onResponse(new StoreAuthenticationResult(false, getTokenSource()));
|
||||
listener.onResponse(StoreAuthenticationResult.failed(getTokenSource()));
|
||||
}
|
||||
}, listener::onFailure)
|
||||
)
|
||||
|
|
|
@ -13,6 +13,7 @@ import org.elasticsearch.ElasticsearchSecurityException;
|
|||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.client.internal.Client;
|
||||
import org.elasticsearch.common.settings.SecureString;
|
||||
import org.elasticsearch.core.Nullable;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
import org.elasticsearch.xpack.core.security.action.service.CreateServiceAccountTokenRequest;
|
||||
import org.elasticsearch.xpack.core.security.action.service.CreateServiceAccountTokenResponse;
|
||||
|
@ -24,12 +25,14 @@ import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountNod
|
|||
import org.elasticsearch.xpack.core.security.action.service.TokenInfo;
|
||||
import org.elasticsearch.xpack.core.security.action.service.TokenInfo.TokenSource;
|
||||
import org.elasticsearch.xpack.core.security.authc.Authentication;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountTokenStore;
|
||||
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
|
||||
import org.elasticsearch.xpack.core.security.user.User;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -46,19 +49,20 @@ public class ServiceAccountService {
|
|||
|
||||
private final Client client;
|
||||
private final IndexServiceAccountTokenStore indexServiceAccountTokenStore;
|
||||
private final CompositeServiceAccountTokenStore compositeServiceAccountTokenStore;
|
||||
private final ServiceAccountTokenStore readOnlyServiceAccountTokenStore;
|
||||
|
||||
public ServiceAccountService(Client client, ServiceAccountTokenStore readOnlyServiceAccountTokenStore) {
|
||||
this(client, readOnlyServiceAccountTokenStore, null);
|
||||
}
|
||||
|
||||
public ServiceAccountService(
|
||||
Client client,
|
||||
FileServiceAccountTokenStore fileServiceAccountTokenStore,
|
||||
IndexServiceAccountTokenStore indexServiceAccountTokenStore
|
||||
ServiceAccountTokenStore readOnlyServiceAccountTokenStore,
|
||||
@Nullable IndexServiceAccountTokenStore indexServiceAccountTokenStore
|
||||
) {
|
||||
this.client = client;
|
||||
this.readOnlyServiceAccountTokenStore = readOnlyServiceAccountTokenStore;
|
||||
this.indexServiceAccountTokenStore = indexServiceAccountTokenStore;
|
||||
this.compositeServiceAccountTokenStore = new CompositeServiceAccountTokenStore(
|
||||
List.of(fileServiceAccountTokenStore, indexServiceAccountTokenStore),
|
||||
client.threadPool().getThreadContext()
|
||||
);
|
||||
}
|
||||
|
||||
public static boolean isServiceAccountPrincipal(String principal) {
|
||||
|
@ -131,7 +135,7 @@ public class ServiceAccountService {
|
|||
return;
|
||||
}
|
||||
|
||||
compositeServiceAccountTokenStore.authenticate(serviceAccountToken, ActionListener.wrap(storeAuthenticationResult -> {
|
||||
readOnlyServiceAccountTokenStore.authenticate(serviceAccountToken, ActionListener.wrap(storeAuthenticationResult -> {
|
||||
if (storeAuthenticationResult.isSuccess()) {
|
||||
listener.onResponse(
|
||||
createAuthentication(account, serviceAccountToken, storeAuthenticationResult.getTokenSource(), nodeName)
|
||||
|
@ -149,14 +153,23 @@ public class ServiceAccountService {
|
|||
CreateServiceAccountTokenRequest request,
|
||||
ActionListener<CreateServiceAccountTokenResponse> listener
|
||||
) {
|
||||
if (indexServiceAccountTokenStore == null) {
|
||||
throw new IllegalStateException("Can't create token because index service account token store not configured");
|
||||
}
|
||||
indexServiceAccountTokenStore.createToken(authentication, request, listener);
|
||||
}
|
||||
|
||||
public void deleteIndexToken(DeleteServiceAccountTokenRequest request, ActionListener<Boolean> listener) {
|
||||
if (indexServiceAccountTokenStore == null) {
|
||||
throw new IllegalStateException("Can't delete token because index service account token store not configured");
|
||||
}
|
||||
indexServiceAccountTokenStore.deleteToken(request, listener);
|
||||
}
|
||||
|
||||
public void findTokensFor(GetServiceAccountCredentialsRequest request, ActionListener<GetServiceAccountCredentialsResponse> listener) {
|
||||
if (indexServiceAccountTokenStore == null) {
|
||||
throw new IllegalStateException("Can't find tokens because index service account token store not configured");
|
||||
}
|
||||
final ServiceAccountId accountId = new ServiceAccountId(request.getNamespace(), request.getServiceName());
|
||||
findIndexTokens(accountId, listener);
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import org.elasticsearch.action.ActionListener;
|
|||
import org.elasticsearch.action.ActionModule;
|
||||
import org.elasticsearch.action.ActionResponse;
|
||||
import org.elasticsearch.action.bulk.IncrementalBulkService;
|
||||
import org.elasticsearch.action.support.PlainActionFuture;
|
||||
import org.elasticsearch.client.internal.Client;
|
||||
import org.elasticsearch.cluster.ClusterName;
|
||||
import org.elasticsearch.cluster.ClusterState;
|
||||
|
@ -32,6 +33,7 @@ import org.elasticsearch.common.settings.Setting;
|
|||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.settings.SettingsModule;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.core.Nullable;
|
||||
import org.elasticsearch.core.TimeValue;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.env.TestEnvironment;
|
||||
|
@ -77,12 +79,15 @@ import org.elasticsearch.xpack.core.security.SecurityContext;
|
|||
import org.elasticsearch.xpack.core.security.SecurityExtension;
|
||||
import org.elasticsearch.xpack.core.security.SecurityField;
|
||||
import org.elasticsearch.xpack.core.security.action.ActionTypes;
|
||||
import org.elasticsearch.xpack.core.security.action.service.TokenInfo;
|
||||
import org.elasticsearch.xpack.core.security.authc.Authentication;
|
||||
import org.elasticsearch.xpack.core.security.authc.AuthenticationTestHelper;
|
||||
import org.elasticsearch.xpack.core.security.authc.Realm;
|
||||
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
|
||||
import org.elasticsearch.xpack.core.security.authc.RealmSettings;
|
||||
import org.elasticsearch.xpack.core.security.authc.file.FileRealmSettings;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountTokenStore;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.CachingUsernamePasswordRealmSettings;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
|
||||
import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
|
||||
|
@ -99,6 +104,9 @@ import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;
|
|||
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.FileServiceAccountTokenStore;
|
||||
import org.elasticsearch.xpack.security.authc.service.IndexServiceAccountTokenStore;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccountService;
|
||||
import org.elasticsearch.xpack.security.operator.DefaultOperatorOnlyRegistry;
|
||||
import org.elasticsearch.xpack.security.operator.OperatorOnlyRegistry;
|
||||
import org.elasticsearch.xpack.security.operator.OperatorPrivileges;
|
||||
|
@ -157,16 +165,34 @@ public class SecurityTests extends ESTestCase {
|
|||
private TestUtils.UpdatableLicenseState licenseState;
|
||||
|
||||
public static class DummyExtension implements SecurityExtension {
|
||||
private String realmType;
|
||||
private final String realmType;
|
||||
private final ServiceAccountTokenStore serviceAccountTokenStore;
|
||||
private final String extensionName;
|
||||
|
||||
DummyExtension(String realmType) {
|
||||
this(realmType, "DummyExtension", null);
|
||||
}
|
||||
|
||||
DummyExtension(String realmType, String extensionName, @Nullable ServiceAccountTokenStore serviceAccountTokenStore) {
|
||||
this.realmType = realmType;
|
||||
this.extensionName = extensionName;
|
||||
this.serviceAccountTokenStore = serviceAccountTokenStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String extensionName() {
|
||||
return extensionName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Realm.Factory> getRealms(SecurityComponents components) {
|
||||
return Collections.singletonMap(realmType, config -> null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServiceAccountTokenStore getServiceAccountTokenStore(SecurityComponents components) {
|
||||
return serviceAccountTokenStore;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DummyOperatorOnlyRegistry implements OperatorOnlyRegistry {
|
||||
|
@ -266,7 +292,7 @@ public class SecurityTests extends ESTestCase {
|
|||
assertNotNull(realms.realmFactory("myrealm"));
|
||||
}
|
||||
|
||||
public void testCustomRealmExtensionConflict() throws Exception {
|
||||
public void testCustomRealmExtensionConflict() {
|
||||
IllegalArgumentException e = expectThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> createComponents(Settings.EMPTY, new DummyExtension(FileRealmSettings.TYPE))
|
||||
|
@ -274,6 +300,64 @@ public class SecurityTests extends ESTestCase {
|
|||
assertEquals("Realm type [" + FileRealmSettings.TYPE + "] is already registered", e.getMessage());
|
||||
}
|
||||
|
||||
public void testServiceAccountTokenStoreExtensionSuccess() throws Exception {
|
||||
Collection<Object> components = createComponents(
|
||||
Settings.EMPTY,
|
||||
new DummyExtension(
|
||||
"test_realm",
|
||||
"DummyExtension",
|
||||
(token, listener) -> listener.onResponse(
|
||||
ServiceAccountTokenStore.StoreAuthenticationResult.successful(TokenInfo.TokenSource.FILE)
|
||||
)
|
||||
)
|
||||
);
|
||||
ServiceAccountService serviceAccountService = findComponent(ServiceAccountService.class, components);
|
||||
assertNotNull(serviceAccountService);
|
||||
FileServiceAccountTokenStore fileServiceAccountTokenStore = findComponent(FileServiceAccountTokenStore.class, components);
|
||||
assertNull(fileServiceAccountTokenStore);
|
||||
IndexServiceAccountTokenStore indexServiceAccountTokenStore = findComponent(IndexServiceAccountTokenStore.class, components);
|
||||
assertNull(indexServiceAccountTokenStore);
|
||||
var account = randomFrom(ServiceAccountService.getServiceAccounts().values());
|
||||
assertThrows(IllegalStateException.class, () -> serviceAccountService.createIndexToken(null, null, null));
|
||||
var future = new PlainActionFuture<Authentication>();
|
||||
serviceAccountService.authenticateToken(ServiceAccountToken.newToken(account.id(), "test"), "test", future);
|
||||
assertTrue(future.get().isServiceAccount());
|
||||
}
|
||||
|
||||
public void testSeveralServiceAccountTokenStoreExtensionFail() {
|
||||
IllegalStateException exception = assertThrows(
|
||||
IllegalStateException.class,
|
||||
() -> createComponents(
|
||||
Settings.EMPTY,
|
||||
new DummyExtension(
|
||||
"test_realm_1",
|
||||
"DummyExtension1",
|
||||
(token, listener) -> listener.onResponse(
|
||||
ServiceAccountTokenStore.StoreAuthenticationResult.successful(TokenInfo.TokenSource.FILE)
|
||||
)
|
||||
),
|
||||
new DummyExtension(
|
||||
"test_realm_2",
|
||||
"DummyExtension2",
|
||||
(token, listener) -> listener.onResponse(
|
||||
ServiceAccountTokenStore.StoreAuthenticationResult.successful(TokenInfo.TokenSource.FILE)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
assertThat(exception.getMessage(), containsString("More than one extension provided a ServiceAccountTokenStore override: "));
|
||||
}
|
||||
|
||||
public void testNoServiceAccountTokenStoreExtension() throws Exception {
|
||||
Collection<Object> components = createComponents(Settings.EMPTY);
|
||||
ServiceAccountService serviceAccountService = findComponent(ServiceAccountService.class, components);
|
||||
assertNotNull(serviceAccountService);
|
||||
FileServiceAccountTokenStore fileServiceAccountTokenStore = findComponent(FileServiceAccountTokenStore.class, components);
|
||||
assertNotNull(fileServiceAccountTokenStore);
|
||||
IndexServiceAccountTokenStore indexServiceAccountTokenStore = findComponent(IndexServiceAccountTokenStore.class, components);
|
||||
assertNotNull(indexServiceAccountTokenStore);
|
||||
}
|
||||
|
||||
public void testAuditEnabled() throws Exception {
|
||||
Settings settings = Settings.builder().put(XPackSettings.AUDIT_ENABLED.getKey(), true).build();
|
||||
Collection<Object> components = createComponents(settings);
|
||||
|
|
|
@ -102,7 +102,9 @@ import org.elasticsearch.xpack.core.security.authc.AuthenticationField;
|
|||
import org.elasticsearch.xpack.core.security.authc.AuthenticationTestHelper;
|
||||
import org.elasticsearch.xpack.core.security.authc.AuthenticationToken;
|
||||
import org.elasticsearch.xpack.core.security.authc.CrossClusterAccessSubjectInfo;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountSettings;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.TemplateRoleName;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.ExpressionModel;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.RoleMapperExpression;
|
||||
|
@ -124,8 +126,6 @@ import org.elasticsearch.xpack.security.audit.AuditLevel;
|
|||
import org.elasticsearch.xpack.security.audit.AuditTrail;
|
||||
import org.elasticsearch.xpack.security.audit.AuditUtil;
|
||||
import org.elasticsearch.xpack.security.authc.ApiKeyService;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccountToken;
|
||||
import org.elasticsearch.xpack.security.rest.RemoteHostHeader;
|
||||
import org.elasticsearch.xpack.security.support.CacheInvalidatorRegistry;
|
||||
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
|
||||
|
|
|
@ -81,6 +81,7 @@ import org.elasticsearch.xpack.core.security.authc.RealmConfig;
|
|||
import org.elasticsearch.xpack.core.security.authc.RealmDomain;
|
||||
import org.elasticsearch.xpack.core.security.authc.esnative.NativeRealmSettings;
|
||||
import org.elasticsearch.xpack.core.security.authc.file.FileRealmSettings;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.AuthenticationContextSerializer;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
|
||||
|
@ -98,7 +99,6 @@ import org.elasticsearch.xpack.security.authc.esnative.NativeRealm;
|
|||
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
|
||||
import org.elasticsearch.xpack.security.authc.file.FileRealm;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccountService;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccountToken;
|
||||
import org.elasticsearch.xpack.security.operator.OperatorPrivileges;
|
||||
import org.elasticsearch.xpack.security.support.CacheInvalidatorRegistry;
|
||||
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
|
||||
|
|
|
@ -28,12 +28,12 @@ import org.elasticsearch.xpack.core.security.authc.AuthenticationServiceField;
|
|||
import org.elasticsearch.xpack.core.security.authc.AuthenticationTestHelper;
|
||||
import org.elasticsearch.xpack.core.security.authc.AuthenticationToken;
|
||||
import org.elasticsearch.xpack.core.security.authc.Realm;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.AuthenticationContextSerializer;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.BearerToken;
|
||||
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
|
||||
import org.elasticsearch.xpack.core.security.user.User;
|
||||
import org.elasticsearch.xpack.security.authc.ApiKeyService.ApiKeyCredentials;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccountToken;
|
||||
import org.elasticsearch.xpack.security.operator.OperatorPrivileges.OperatorPrivilegesService;
|
||||
import org.junit.Before;
|
||||
|
||||
|
|
|
@ -16,10 +16,10 @@ import org.elasticsearch.rest.RestStatus;
|
|||
import org.elasticsearch.telemetry.TestTelemetryPlugin;
|
||||
import org.elasticsearch.xpack.core.security.authc.Authentication;
|
||||
import org.elasticsearch.xpack.core.security.authc.AuthenticationResult;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken;
|
||||
import org.elasticsearch.xpack.core.security.user.User;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccountService;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccountToken;
|
||||
import org.elasticsearch.xpack.security.metric.SecurityMetricType;
|
||||
|
||||
import java.util.Map;
|
||||
|
|
|
@ -17,9 +17,10 @@ import org.elasticsearch.test.ESTestCase;
|
|||
import org.elasticsearch.threadpool.TestThreadPool;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.xpack.core.security.action.service.TokenInfo.TokenSource;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountTokenStore.StoreAuthenticationResult;
|
||||
import org.elasticsearch.xpack.core.security.support.ValidationTests;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccountTokenStore.StoreAuthenticationResult;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
||||
|
@ -34,6 +35,7 @@ import static org.hamcrest.Matchers.equalTo;
|
|||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class CachingServiceAccountTokenStoreTests extends ESTestCase {
|
||||
|
||||
|
@ -53,14 +55,22 @@ public class CachingServiceAccountTokenStoreTests extends ESTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
private ServiceAccountToken newMockServiceAccountToken(ServiceAccountId accountId, String tokenName, SecureString secret) {
|
||||
ServiceAccountToken serviceAccountToken = mock(ServiceAccountToken.class);
|
||||
var serviceAccountTokenId = new ServiceAccountToken.ServiceAccountTokenId(accountId, tokenName);
|
||||
when(serviceAccountToken.getQualifiedName()).thenReturn(serviceAccountTokenId.getQualifiedName());
|
||||
when(serviceAccountToken.getSecret()).thenReturn(secret);
|
||||
return serviceAccountToken;
|
||||
}
|
||||
|
||||
public void testCache() throws ExecutionException, InterruptedException {
|
||||
final ServiceAccountId accountId = new ServiceAccountId(randomAlphaOfLengthBetween(3, 8), randomAlphaOfLengthBetween(3, 8));
|
||||
final SecureString validSecret = new SecureString("super-secret-value".toCharArray());
|
||||
final SecureString invalidSecret = new SecureString("some-fishy-value".toCharArray());
|
||||
final ServiceAccountToken token1Valid = new ServiceAccountToken(accountId, "token1", validSecret);
|
||||
final ServiceAccountToken token1Invalid = new ServiceAccountToken(accountId, "token1", invalidSecret);
|
||||
final ServiceAccountToken token2Valid = new ServiceAccountToken(accountId, "token2", validSecret);
|
||||
final ServiceAccountToken token2Invalid = new ServiceAccountToken(accountId, "token2", invalidSecret);
|
||||
final ServiceAccountToken token1Valid = newMockServiceAccountToken(accountId, "token1", validSecret);
|
||||
final ServiceAccountToken token1Invalid = newMockServiceAccountToken(accountId, "token1", invalidSecret);
|
||||
final ServiceAccountToken token2Valid = newMockServiceAccountToken(accountId, "token2", validSecret);
|
||||
final ServiceAccountToken token2Invalid = newMockServiceAccountToken(accountId, "token2", invalidSecret);
|
||||
final AtomicBoolean doAuthenticateInvoked = new AtomicBoolean(false);
|
||||
final TokenSource tokenSource = randomFrom(TokenSource.values());
|
||||
|
||||
|
@ -68,7 +78,7 @@ public class CachingServiceAccountTokenStoreTests extends ESTestCase {
|
|||
@Override
|
||||
void doAuthenticate(ServiceAccountToken token, ActionListener<StoreAuthenticationResult> listener) {
|
||||
doAuthenticateInvoked.set(true);
|
||||
listener.onResponse(new StoreAuthenticationResult(validSecret.equals(token.getSecret()), getTokenSource()));
|
||||
listener.onResponse(StoreAuthenticationResult.fromBooleanResult(getTokenSource(), validSecret.equals(token.getSecret())));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -160,7 +170,7 @@ public class CachingServiceAccountTokenStoreTests extends ESTestCase {
|
|||
final CachingServiceAccountTokenStore store = new CachingServiceAccountTokenStore(settings, threadPool) {
|
||||
@Override
|
||||
void doAuthenticate(ServiceAccountToken token, ActionListener<StoreAuthenticationResult> listener) {
|
||||
listener.onResponse(new StoreAuthenticationResult(success, getTokenSource()));
|
||||
listener.onResponse(StoreAuthenticationResult.fromBooleanResult(getTokenSource(), success));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -181,7 +191,7 @@ public class CachingServiceAccountTokenStoreTests extends ESTestCase {
|
|||
final CachingServiceAccountTokenStore store = new CachingServiceAccountTokenStore(globalSettings, threadPool) {
|
||||
@Override
|
||||
void doAuthenticate(ServiceAccountToken token, ActionListener<StoreAuthenticationResult> listener) {
|
||||
listener.onResponse(new StoreAuthenticationResult(true, getTokenSource()));
|
||||
listener.onResponse(StoreAuthenticationResult.successful(getTokenSource()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -13,7 +13,9 @@ import org.elasticsearch.common.settings.Settings;
|
|||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.xpack.core.security.action.service.TokenInfo.TokenSource;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccountTokenStore.StoreAuthenticationResult;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountTokenStore;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountTokenStore.StoreAuthenticationResult;
|
||||
import org.junit.Before;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
|
@ -58,7 +60,7 @@ public class CompositeServiceAccountTokenStoreTests extends ESTestCase {
|
|||
@SuppressWarnings("unchecked")
|
||||
final ActionListener<StoreAuthenticationResult> listener = (ActionListener<StoreAuthenticationResult>) invocationOnMock
|
||||
.getArguments()[1];
|
||||
listener.onResponse(new StoreAuthenticationResult(store1Success, tokenSource));
|
||||
listener.onResponse(StoreAuthenticationResult.fromBooleanResult(tokenSource, store1Success));
|
||||
return null;
|
||||
}).when(store1).authenticate(eq(token), any());
|
||||
|
||||
|
@ -66,7 +68,7 @@ public class CompositeServiceAccountTokenStoreTests extends ESTestCase {
|
|||
@SuppressWarnings("unchecked")
|
||||
final ActionListener<StoreAuthenticationResult> listener = (ActionListener<StoreAuthenticationResult>) invocationOnMock
|
||||
.getArguments()[1];
|
||||
listener.onResponse(new StoreAuthenticationResult(store2Success, tokenSource));
|
||||
listener.onResponse(StoreAuthenticationResult.fromBooleanResult(tokenSource, store2Success));
|
||||
return null;
|
||||
}).when(store2).authenticate(eq(token), any());
|
||||
|
||||
|
@ -74,7 +76,7 @@ public class CompositeServiceAccountTokenStoreTests extends ESTestCase {
|
|||
@SuppressWarnings("unchecked")
|
||||
final ActionListener<StoreAuthenticationResult> listener = (ActionListener<StoreAuthenticationResult>) invocationOnMock
|
||||
.getArguments()[1];
|
||||
listener.onResponse(new StoreAuthenticationResult(store3Success, tokenSource));
|
||||
listener.onResponse(StoreAuthenticationResult.fromBooleanResult(tokenSource, store3Success));
|
||||
return null;
|
||||
}).when(store3).authenticate(eq(token), any());
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@ import org.elasticsearch.xpack.core.security.action.role.PutRoleAction;
|
|||
import org.elasticsearch.xpack.core.security.action.user.PutUserAction;
|
||||
import org.elasticsearch.xpack.core.security.authc.Authentication;
|
||||
import org.elasticsearch.xpack.core.security.authc.AuthenticationTestHelper;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount;
|
||||
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
|
||||
import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsCache;
|
||||
import org.elasticsearch.xpack.core.security.authz.permission.Role;
|
||||
|
|
|
@ -21,8 +21,8 @@ import org.elasticsearch.threadpool.ThreadPool;
|
|||
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||
import org.elasticsearch.xpack.core.security.action.service.TokenInfo;
|
||||
import org.elasticsearch.xpack.core.security.audit.logfile.CapturingLogger;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
import org.elasticsearch.xpack.security.support.CacheInvalidatorRegistry;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
@ -238,7 +238,7 @@ public class FileServiceAccountTokenStoreTests extends ESTestCase {
|
|||
);
|
||||
|
||||
final ServiceAccountId accountId = new ServiceAccountId("elastic", "fleet-server");
|
||||
final List<TokenInfo> tokenInfos = store.findTokensFor(accountId);
|
||||
final List<TokenInfo> tokenInfos = store.findNodeLocalTokensFor(accountId);
|
||||
assertThat(tokenInfos, hasSize(5));
|
||||
assertThat(
|
||||
tokenInfos.stream().map(TokenInfo::getName).collect(Collectors.toUnmodifiableSet()),
|
||||
|
|
|
@ -55,10 +55,11 @@ import org.elasticsearch.xpack.core.security.action.service.TokenInfo;
|
|||
import org.elasticsearch.xpack.core.security.action.service.TokenInfo.TokenSource;
|
||||
import org.elasticsearch.xpack.core.security.authc.Authentication;
|
||||
import org.elasticsearch.xpack.core.security.authc.AuthenticationTestHelper;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountTokenStore.StoreAuthenticationResult;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
|
||||
import org.elasticsearch.xpack.core.security.support.ValidationTests;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccountTokenStore.StoreAuthenticationResult;
|
||||
import org.elasticsearch.xpack.security.support.CacheInvalidatorRegistry;
|
||||
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
|
||||
import org.junit.Before;
|
||||
|
|
|
@ -10,6 +10,7 @@ package org.elasticsearch.xpack.security.authc.service;
|
|||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
|
|
@ -33,10 +33,12 @@ import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountNod
|
|||
import org.elasticsearch.xpack.core.security.action.service.TokenInfo;
|
||||
import org.elasticsearch.xpack.core.security.authc.Authentication;
|
||||
import org.elasticsearch.xpack.core.security.authc.AuthenticationTestHelper;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountTokenStore;
|
||||
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
|
||||
import org.elasticsearch.xpack.core.security.support.ValidationTests;
|
||||
import org.elasticsearch.xpack.core.security.user.User;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccount.ServiceAccountId;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
||||
|
@ -83,7 +85,14 @@ public class ServiceAccountServiceTests extends ESTestCase {
|
|||
when(indexServiceAccountTokenStore.getTokenSource()).thenReturn(TokenInfo.TokenSource.INDEX);
|
||||
client = mock(Client.class);
|
||||
when(client.threadPool()).thenReturn(threadPool);
|
||||
serviceAccountService = new ServiceAccountService(client, fileServiceAccountTokenStore, indexServiceAccountTokenStore);
|
||||
serviceAccountService = new ServiceAccountService(
|
||||
client,
|
||||
new CompositeServiceAccountTokenStore(
|
||||
List.of(fileServiceAccountTokenStore, indexServiceAccountTokenStore),
|
||||
threadPool.getThreadContext()
|
||||
),
|
||||
indexServiceAccountTokenStore
|
||||
);
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -228,16 +237,15 @@ public class ServiceAccountServiceTests extends ESTestCase {
|
|||
List.of(magicBytes, (namespace + "/" + serviceName + "/" + tokenName + ":" + secret).getBytes(StandardCharsets.UTF_8))
|
||||
);
|
||||
final ServiceAccountToken serviceAccountToken1 = ServiceAccountService.tryParseToken(bearerString5);
|
||||
final ServiceAccountToken serviceAccountToken2 = new ServiceAccountToken(
|
||||
accountId,
|
||||
tokenName,
|
||||
new SecureString(secret.toCharArray())
|
||||
);
|
||||
assertThat(serviceAccountToken1, equalTo(serviceAccountToken2));
|
||||
|
||||
assertNotNull(serviceAccountToken1);
|
||||
assertThat(serviceAccountToken1.getAccountId(), equalTo(accountId));
|
||||
assertThat(serviceAccountToken1.getTokenName(), equalTo(tokenName));
|
||||
assertThat(serviceAccountToken1.getSecret(), equalTo(new SecureString(secret.toCharArray())));
|
||||
|
||||
// Serialise and de-serialise service account token
|
||||
final ServiceAccountToken parsedToken = ServiceAccountService.tryParseToken(serviceAccountToken2.asBearerString());
|
||||
assertThat(parsedToken, equalTo(serviceAccountToken2));
|
||||
final ServiceAccountToken parsedToken = ServiceAccountService.tryParseToken(serviceAccountToken1.asBearerString());
|
||||
assertThat(parsedToken, equalTo(serviceAccountToken1));
|
||||
|
||||
// Invalid magic byte
|
||||
satMockLog.addExpectation(
|
||||
|
@ -295,25 +303,31 @@ public class ServiceAccountServiceTests extends ESTestCase {
|
|||
);
|
||||
sasMockLog.assertAllExpectationsMatched();
|
||||
|
||||
// everything is fine
|
||||
assertThat(
|
||||
ServiceAccountService.tryParseToken(
|
||||
new SecureString("AAEAAWVsYXN0aWMvZmxlZXQtc2VydmVyL3Rva2VuMTpzdXBlcnNlY3JldA".toCharArray())
|
||||
),
|
||||
equalTo(
|
||||
new ServiceAccountToken(
|
||||
new ServiceAccountId("elastic", "fleet-server"),
|
||||
"token1",
|
||||
new SecureString("supersecret".toCharArray())
|
||||
)
|
||||
)
|
||||
ServiceAccountToken parsedServiceAccountToken = ServiceAccountService.tryParseToken(
|
||||
new SecureString("AAEAAWVsYXN0aWMvZmxlZXQtc2VydmVyL3Rva2VuMTpzdXBlcnNlY3JldA".toCharArray())
|
||||
);
|
||||
|
||||
// everything is fine
|
||||
assertNotNull(parsedServiceAccountToken);
|
||||
assertThat(parsedServiceAccountToken.getAccountId(), equalTo(new ServiceAccountId("elastic", "fleet-server")));
|
||||
assertThat(parsedServiceAccountToken.getTokenName(), equalTo("token1"));
|
||||
assertThat(parsedServiceAccountToken.getSecret(), equalTo(new SecureString("supersecret".toCharArray())));
|
||||
} finally {
|
||||
Loggers.setLevel(satLogger, Level.INFO);
|
||||
Loggers.setLevel(sasLogger, Level.INFO);
|
||||
}
|
||||
}
|
||||
|
||||
private ServiceAccountToken newMockServiceAccountToken(ServiceAccountId accountId, String tokenName, SecureString secret) {
|
||||
ServiceAccountToken serviceAccountToken = mock(ServiceAccountToken.class);
|
||||
var serviceAccountTokenId = new ServiceAccountToken.ServiceAccountTokenId(accountId, tokenName);
|
||||
when(serviceAccountToken.getQualifiedName()).thenReturn(serviceAccountTokenId.getQualifiedName());
|
||||
when(serviceAccountToken.getSecret()).thenReturn(secret);
|
||||
when(serviceAccountToken.getAccountId()).thenReturn(accountId);
|
||||
when(serviceAccountToken.getTokenName()).thenReturn(tokenName);
|
||||
return serviceAccountToken;
|
||||
}
|
||||
|
||||
public void testTryAuthenticateBearerToken() throws ExecutionException, InterruptedException {
|
||||
// Valid token
|
||||
final PlainActionFuture<Authentication> future5 = new PlainActionFuture<>();
|
||||
|
@ -325,7 +339,10 @@ public class ServiceAccountServiceTests extends ESTestCase {
|
|||
final ActionListener<ServiceAccountTokenStore.StoreAuthenticationResult> listener = (ActionListener<
|
||||
ServiceAccountTokenStore.StoreAuthenticationResult>) invocationOnMock.getArguments()[1];
|
||||
listener.onResponse(
|
||||
new ServiceAccountTokenStore.StoreAuthenticationResult(store == authenticatingStore, store.getTokenSource())
|
||||
ServiceAccountTokenStore.StoreAuthenticationResult.fromBooleanResult(
|
||||
store.getTokenSource(),
|
||||
store == authenticatingStore
|
||||
)
|
||||
);
|
||||
return null;
|
||||
}).when(store).authenticate(any(), any());
|
||||
|
@ -333,7 +350,7 @@ public class ServiceAccountServiceTests extends ESTestCase {
|
|||
|
||||
final String nodeName = randomAlphaOfLengthBetween(3, 8);
|
||||
serviceAccountService.authenticateToken(
|
||||
new ServiceAccountToken(
|
||||
newMockServiceAccountToken(
|
||||
new ServiceAccountId("elastic", "fleet-server"),
|
||||
"token1",
|
||||
new SecureString("super-secret-value".toCharArray())
|
||||
|
@ -379,7 +396,7 @@ public class ServiceAccountServiceTests extends ESTestCase {
|
|||
)
|
||||
);
|
||||
final SecureString secret = new SecureString(randomAlphaOfLength(20).toCharArray());
|
||||
final ServiceAccountToken token1 = new ServiceAccountToken(accountId1, randomAlphaOfLengthBetween(3, 8), secret);
|
||||
final ServiceAccountToken token1 = newMockServiceAccountToken(accountId1, randomAlphaOfLengthBetween(3, 8), secret);
|
||||
final PlainActionFuture<Authentication> future1 = new PlainActionFuture<>();
|
||||
serviceAccountService.authenticateToken(token1, randomAlphaOfLengthBetween(3, 8), future1);
|
||||
final ExecutionException e1 = expectThrows(ExecutionException.class, future1::get);
|
||||
|
@ -409,7 +426,7 @@ public class ServiceAccountServiceTests extends ESTestCase {
|
|||
"the [" + accountId2.asPrincipal() + "] service account does not exist"
|
||||
)
|
||||
);
|
||||
final ServiceAccountToken token2 = new ServiceAccountToken(accountId2, randomAlphaOfLengthBetween(3, 8), secret);
|
||||
final ServiceAccountToken token2 = newMockServiceAccountToken(accountId2, randomAlphaOfLengthBetween(3, 8), secret);
|
||||
final PlainActionFuture<Authentication> future2 = new PlainActionFuture<>();
|
||||
serviceAccountService.authenticateToken(token2, randomAlphaOfLengthBetween(3, 8), future2);
|
||||
final ExecutionException e2 = expectThrows(ExecutionException.class, future2::get);
|
||||
|
@ -429,7 +446,7 @@ public class ServiceAccountServiceTests extends ESTestCase {
|
|||
// Length of secret value is too short
|
||||
final ServiceAccountId accountId3 = new ServiceAccountId(ElasticServiceAccounts.NAMESPACE, "fleet-server");
|
||||
final SecureString secret3 = new SecureString(randomAlphaOfLengthBetween(1, 9).toCharArray());
|
||||
final ServiceAccountToken token3 = new ServiceAccountToken(accountId3, randomAlphaOfLengthBetween(3, 8), secret3);
|
||||
final ServiceAccountToken token3 = newMockServiceAccountToken(accountId3, randomAlphaOfLengthBetween(3, 8), secret3);
|
||||
mockLog.addExpectation(
|
||||
new MockLog.SeenEventExpectation(
|
||||
"secret value too short",
|
||||
|
@ -456,7 +473,7 @@ public class ServiceAccountServiceTests extends ESTestCase {
|
|||
);
|
||||
mockLog.assertAllExpectationsMatched();
|
||||
|
||||
final TokenInfo.TokenSource tokenSource = randomFrom(TokenInfo.TokenSource.values());
|
||||
final TokenInfo.TokenSource tokenSource = randomFrom(TokenInfo.TokenSource.FILE, TokenInfo.TokenSource.INDEX);
|
||||
final CachingServiceAccountTokenStore store;
|
||||
final CachingServiceAccountTokenStore otherStore;
|
||||
if (tokenSource == TokenInfo.TokenSource.FILE) {
|
||||
|
@ -469,8 +486,8 @@ public class ServiceAccountServiceTests extends ESTestCase {
|
|||
|
||||
// Success based on credential store
|
||||
final ServiceAccountId accountId4 = new ServiceAccountId(ElasticServiceAccounts.NAMESPACE, "fleet-server");
|
||||
final ServiceAccountToken token4 = new ServiceAccountToken(accountId4, randomAlphaOfLengthBetween(3, 8), secret);
|
||||
final ServiceAccountToken token5 = new ServiceAccountToken(
|
||||
final ServiceAccountToken token4 = newMockServiceAccountToken(accountId4, randomAlphaOfLengthBetween(3, 8), secret);
|
||||
final ServiceAccountToken token5 = newMockServiceAccountToken(
|
||||
accountId4,
|
||||
randomAlphaOfLengthBetween(3, 8),
|
||||
new SecureString(randomAlphaOfLength(20).toCharArray())
|
||||
|
@ -480,7 +497,7 @@ public class ServiceAccountServiceTests extends ESTestCase {
|
|||
@SuppressWarnings("unchecked")
|
||||
final ActionListener<ServiceAccountTokenStore.StoreAuthenticationResult> listener = (ActionListener<
|
||||
ServiceAccountTokenStore.StoreAuthenticationResult>) invocationOnMock.getArguments()[1];
|
||||
listener.onResponse(new ServiceAccountTokenStore.StoreAuthenticationResult(true, store.getTokenSource()));
|
||||
listener.onResponse(ServiceAccountTokenStore.StoreAuthenticationResult.successful(store.getTokenSource()));
|
||||
return null;
|
||||
}).when(store).authenticate(eq(token4), any());
|
||||
|
||||
|
@ -488,7 +505,7 @@ public class ServiceAccountServiceTests extends ESTestCase {
|
|||
@SuppressWarnings("unchecked")
|
||||
final ActionListener<ServiceAccountTokenStore.StoreAuthenticationResult> listener = (ActionListener<
|
||||
ServiceAccountTokenStore.StoreAuthenticationResult>) invocationOnMock.getArguments()[1];
|
||||
listener.onResponse(new ServiceAccountTokenStore.StoreAuthenticationResult(false, store.getTokenSource()));
|
||||
listener.onResponse(ServiceAccountTokenStore.StoreAuthenticationResult.failed(store.getTokenSource()));
|
||||
return null;
|
||||
}).when(store).authenticate(eq(token5), any());
|
||||
|
||||
|
@ -496,7 +513,7 @@ public class ServiceAccountServiceTests extends ESTestCase {
|
|||
@SuppressWarnings("unchecked")
|
||||
final ActionListener<ServiceAccountTokenStore.StoreAuthenticationResult> listener = (ActionListener<
|
||||
ServiceAccountTokenStore.StoreAuthenticationResult>) invocationOnMock.getArguments()[1];
|
||||
listener.onResponse(new ServiceAccountTokenStore.StoreAuthenticationResult(false, otherStore.getTokenSource()));
|
||||
listener.onResponse(ServiceAccountTokenStore.StoreAuthenticationResult.failed(otherStore.getTokenSource()));
|
||||
return null;
|
||||
}).when(otherStore).authenticate(any(), any());
|
||||
|
||||
|
|
|
@ -22,10 +22,10 @@ import org.elasticsearch.common.settings.Settings;
|
|||
import org.elasticsearch.core.IOUtils;
|
||||
import org.elasticsearch.core.PathUtilsForTesting;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken.ServiceAccountTokenId;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
|
||||
import org.elasticsearch.xpack.core.security.support.Validation;
|
||||
import org.elasticsearch.xpack.core.security.support.ValidationTests;
|
||||
import org.elasticsearch.xpack.security.authc.service.ServiceAccountToken.ServiceAccountTokenId;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
|
|
Loading…
Reference in New Issue