|  | @@ -18,10 +18,10 @@ import org.elasticsearch.rest.RestStatus;
 | 
	
		
			
				|  |  |  import org.elasticsearch.xpack.core.security.action.service.CreateServiceAccountTokenRequest;
 | 
	
		
			
				|  |  |  import org.elasticsearch.xpack.core.security.action.service.CreateServiceAccountTokenResponse;
 | 
	
		
			
				|  |  |  import org.elasticsearch.xpack.core.security.action.service.DeleteServiceAccountTokenRequest;
 | 
	
		
			
				|  |  | +import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountCredentialsNodesRequest;
 | 
	
		
			
				|  |  |  import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountCredentialsRequest;
 | 
	
		
			
				|  |  |  import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountCredentialsResponse;
 | 
	
		
			
				|  |  |  import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountNodesCredentialsAction;
 | 
	
		
			
				|  |  | -import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountCredentialsNodesRequest;
 | 
	
		
			
				|  |  |  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;
 | 
	
	
		
			
				|  | @@ -29,7 +29,6 @@ import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountSetting
 | 
	
		
			
				|  |  |  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 org.elasticsearch.xpack.security.authc.support.HttpTlsRuntimeCheck;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  import java.util.Collection;
 | 
	
		
			
				|  |  |  import java.util.List;
 | 
	
	
		
			
				|  | @@ -50,17 +49,14 @@ public class ServiceAccountService {
 | 
	
		
			
				|  |  |      private final Client client;
 | 
	
		
			
				|  |  |      private final IndexServiceAccountTokenStore indexServiceAccountTokenStore;
 | 
	
		
			
				|  |  |      private final CompositeServiceAccountTokenStore compositeServiceAccountTokenStore;
 | 
	
		
			
				|  |  | -    private final HttpTlsRuntimeCheck httpTlsRuntimeCheck;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      public ServiceAccountService(Client client,
 | 
	
		
			
				|  |  |                                   FileServiceAccountTokenStore fileServiceAccountTokenStore,
 | 
	
		
			
				|  |  | -                                 IndexServiceAccountTokenStore indexServiceAccountTokenStore,
 | 
	
		
			
				|  |  | -                                 HttpTlsRuntimeCheck httpTlsRuntimeCheck) {
 | 
	
		
			
				|  |  | +                                 IndexServiceAccountTokenStore indexServiceAccountTokenStore) {
 | 
	
		
			
				|  |  |          this.client = client;
 | 
	
		
			
				|  |  |          this.indexServiceAccountTokenStore = indexServiceAccountTokenStore;
 | 
	
		
			
				|  |  |          this.compositeServiceAccountTokenStore = new CompositeServiceAccountTokenStore(
 | 
	
		
			
				|  |  |              List.of(fileServiceAccountTokenStore, indexServiceAccountTokenStore), client.threadPool().getThreadContext());
 | 
	
		
			
				|  |  | -        this.httpTlsRuntimeCheck = httpTlsRuntimeCheck;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      public static boolean isServiceAccountPrincipal(String principal) {
 | 
	
	
		
			
				|  | @@ -103,79 +99,76 @@ public class ServiceAccountService {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      public void authenticateToken(ServiceAccountToken serviceAccountToken, String nodeName, ActionListener<Authentication> listener) {
 | 
	
		
			
				|  |  |          logger.trace("attempt to authenticate service account token [{}]", serviceAccountToken.getQualifiedName());
 | 
	
		
			
				|  |  | -        httpTlsRuntimeCheck.checkTlsThenExecute(listener::onFailure, "service account authentication", () -> {
 | 
	
		
			
				|  |  | -            if (ElasticServiceAccounts.NAMESPACE.equals(serviceAccountToken.getAccountId().namespace()) == false) {
 | 
	
		
			
				|  |  | -                logger.debug("only [{}] service accounts are supported, but received [{}]",
 | 
	
		
			
				|  |  | -                    ElasticServiceAccounts.NAMESPACE, serviceAccountToken.getAccountId().asPrincipal());
 | 
	
		
			
				|  |  | -                listener.onFailure(createAuthenticationException(serviceAccountToken));
 | 
	
		
			
				|  |  | -                return;
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            final ServiceAccount account = ACCOUNTS.get(serviceAccountToken.getAccountId().asPrincipal());
 | 
	
		
			
				|  |  | -            if (account == null) {
 | 
	
		
			
				|  |  | -                logger.debug("the [{}] service account does not exist", serviceAccountToken.getAccountId().asPrincipal());
 | 
	
		
			
				|  |  | -                listener.onFailure(createAuthenticationException(serviceAccountToken));
 | 
	
		
			
				|  |  | -                return;
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | +        if (ElasticServiceAccounts.NAMESPACE.equals(serviceAccountToken.getAccountId().namespace()) == false) {
 | 
	
		
			
				|  |  | +            logger.debug(
 | 
	
		
			
				|  |  | +                "only [{}] service accounts are supported, but received [{}]",
 | 
	
		
			
				|  |  | +                ElasticServiceAccounts.NAMESPACE,
 | 
	
		
			
				|  |  | +                serviceAccountToken.getAccountId().asPrincipal()
 | 
	
		
			
				|  |  | +            );
 | 
	
		
			
				|  |  | +            listener.onFailure(createAuthenticationException(serviceAccountToken));
 | 
	
		
			
				|  |  | +            return;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            if (serviceAccountToken.getSecret().length() < MIN_TOKEN_SECRET_LENGTH) {
 | 
	
		
			
				|  |  | -                logger.debug("failing authentication for service account token [{}],"
 | 
	
		
			
				|  |  | -                        + " the provided credential has length [{}]"
 | 
	
		
			
				|  |  | -                        + " but a token's secret value must be at least [{}] characters",
 | 
	
		
			
				|  |  | -                    serviceAccountToken.getQualifiedName(),
 | 
	
		
			
				|  |  | -                    serviceAccountToken.getSecret().length(),
 | 
	
		
			
				|  |  | -                    MIN_TOKEN_SECRET_LENGTH);
 | 
	
		
			
				|  |  | -                listener.onFailure(createAuthenticationException(serviceAccountToken));
 | 
	
		
			
				|  |  | -                return;
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | +        final ServiceAccount account = ACCOUNTS.get(serviceAccountToken.getAccountId().asPrincipal());
 | 
	
		
			
				|  |  | +        if (account == null) {
 | 
	
		
			
				|  |  | +            logger.debug("the [{}] service account does not exist", serviceAccountToken.getAccountId().asPrincipal());
 | 
	
		
			
				|  |  | +            listener.onFailure(createAuthenticationException(serviceAccountToken));
 | 
	
		
			
				|  |  | +            return;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            compositeServiceAccountTokenStore.authenticate(serviceAccountToken, ActionListener.wrap(storeAuthenticationResult -> {
 | 
	
		
			
				|  |  | -                if (storeAuthenticationResult.isSuccess()) {
 | 
	
		
			
				|  |  | -                    listener.onResponse(
 | 
	
		
			
				|  |  | -                        createAuthentication(account, serviceAccountToken, storeAuthenticationResult.getTokenSource() , nodeName));
 | 
	
		
			
				|  |  | -                } else {
 | 
	
		
			
				|  |  | -                    final ElasticsearchSecurityException e = createAuthenticationException(serviceAccountToken);
 | 
	
		
			
				|  |  | -                    logger.debug(e.getMessage());
 | 
	
		
			
				|  |  | -                    listener.onFailure(e);
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -            }, listener::onFailure));
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | +        if (serviceAccountToken.getSecret().length() < MIN_TOKEN_SECRET_LENGTH) {
 | 
	
		
			
				|  |  | +            logger.debug(
 | 
	
		
			
				|  |  | +                "failing authentication for service account token [{}],"
 | 
	
		
			
				|  |  | +                    + " the provided credential has length [{}]"
 | 
	
		
			
				|  |  | +                    + " but a token's secret value must be at least [{}] characters",
 | 
	
		
			
				|  |  | +                serviceAccountToken.getQualifiedName(),
 | 
	
		
			
				|  |  | +                serviceAccountToken.getSecret().length(),
 | 
	
		
			
				|  |  | +                MIN_TOKEN_SECRET_LENGTH
 | 
	
		
			
				|  |  | +            );
 | 
	
		
			
				|  |  | +            listener.onFailure(createAuthenticationException(serviceAccountToken));
 | 
	
		
			
				|  |  | +            return;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        compositeServiceAccountTokenStore.authenticate(serviceAccountToken, ActionListener.wrap(storeAuthenticationResult -> {
 | 
	
		
			
				|  |  | +            if (storeAuthenticationResult.isSuccess()) {
 | 
	
		
			
				|  |  | +                listener.onResponse(
 | 
	
		
			
				|  |  | +                    createAuthentication(account, serviceAccountToken, storeAuthenticationResult.getTokenSource(), nodeName)
 | 
	
		
			
				|  |  | +                );
 | 
	
		
			
				|  |  | +            } else {
 | 
	
		
			
				|  |  | +                final ElasticsearchSecurityException e = createAuthenticationException(serviceAccountToken);
 | 
	
		
			
				|  |  | +                logger.debug(e.getMessage());
 | 
	
		
			
				|  |  | +                listener.onFailure(e);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }, listener::onFailure));
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      public void createIndexToken(Authentication authentication, CreateServiceAccountTokenRequest request,
 | 
	
		
			
				|  |  |                                   ActionListener<CreateServiceAccountTokenResponse> listener) {
 | 
	
		
			
				|  |  | -        httpTlsRuntimeCheck.checkTlsThenExecute(listener::onFailure,
 | 
	
		
			
				|  |  | -            "create index-backed service token",
 | 
	
		
			
				|  |  | -            () -> indexServiceAccountTokenStore.createToken(authentication, request, listener));
 | 
	
		
			
				|  |  | +        indexServiceAccountTokenStore.createToken(authentication, request, listener);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      public void deleteIndexToken(DeleteServiceAccountTokenRequest request, ActionListener<Boolean> listener) {
 | 
	
		
			
				|  |  | -        httpTlsRuntimeCheck.checkTlsThenExecute(
 | 
	
		
			
				|  |  | -            listener::onFailure,
 | 
	
		
			
				|  |  | -            "delete index-backed service token",
 | 
	
		
			
				|  |  | -            () -> indexServiceAccountTokenStore.deleteToken(request, listener));
 | 
	
		
			
				|  |  | +       indexServiceAccountTokenStore.deleteToken(request, listener);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      public void findTokensFor(GetServiceAccountCredentialsRequest request,
 | 
	
		
			
				|  |  |                                ActionListener<GetServiceAccountCredentialsResponse> listener) {
 | 
	
		
			
				|  |  | -        httpTlsRuntimeCheck.checkTlsThenExecute(listener::onFailure, "find service tokens", () -> {
 | 
	
		
			
				|  |  | -            final ServiceAccountId accountId = new ServiceAccountId(request.getNamespace(), request.getServiceName());
 | 
	
		
			
				|  |  | -            findIndexTokens(accountId, listener);
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | +        final ServiceAccountId accountId = new ServiceAccountId(request.getNamespace(), request.getServiceName());
 | 
	
		
			
				|  |  | +        findIndexTokens(accountId, listener);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      public void getRoleDescriptor(Authentication authentication, ActionListener<RoleDescriptor> listener) {
 | 
	
		
			
				|  |  |          assert authentication.isServiceAccount() : "authentication is not for service account: " + authentication;
 | 
	
		
			
				|  |  | -        httpTlsRuntimeCheck.checkTlsThenExecute(listener::onFailure, "service account role descriptor resolving", () -> {
 | 
	
		
			
				|  |  | -            final String principal = authentication.getUser().principal();
 | 
	
		
			
				|  |  | -            final ServiceAccount account = ACCOUNTS.get(principal);
 | 
	
		
			
				|  |  | -            if (account == null) {
 | 
	
		
			
				|  |  | -                listener.onFailure(new ElasticsearchSecurityException(
 | 
	
		
			
				|  |  | -                    "cannot load role for service account [" + principal + "] - no such service account"));
 | 
	
		
			
				|  |  | -                return;
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            listener.onResponse(account.roleDescriptor());
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | +        final String principal = authentication.getUser().principal();
 | 
	
		
			
				|  |  | +        final ServiceAccount account = ACCOUNTS.get(principal);
 | 
	
		
			
				|  |  | +        if (account == null) {
 | 
	
		
			
				|  |  | +            listener.onFailure(
 | 
	
		
			
				|  |  | +                new ElasticsearchSecurityException("cannot load role for service account [" + principal + "] - no such service account")
 | 
	
		
			
				|  |  | +            );
 | 
	
		
			
				|  |  | +            return;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        listener.onResponse(account.roleDescriptor());
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      private Authentication createAuthentication(ServiceAccount account, ServiceAccountToken token, TokenSource tokenSource,
 |