Browse Source

Service Accounts - HLRC (#72431)

This PR adds corresponding components in High Level Rest Client for the new
APIs related to the service accounts feature.
Yang Wang 4 years ago
parent
commit
2350369782
35 changed files with 2250 additions and 67 deletions
  1. 166 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java
  2. 70 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java
  3. 43 19
      client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java
  4. 63 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/security/ClearServiceAccountTokenCacheRequest.java
  5. 76 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/security/CreateServiceAccountTokenRequest.java
  6. 92 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/security/CreateServiceAccountTokenResponse.java
  7. 70 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/security/DeleteServiceAccountTokenRequest.java
  8. 39 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/security/DeleteServiceAccountTokenResponse.java
  9. 50 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetServiceAccountCredentialsRequest.java
  10. 92 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetServiceAccountCredentialsResponse.java
  11. 74 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetServiceAccountsRequest.java
  12. 72 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetServiceAccountsResponse.java
  13. 55 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/security/support/ServiceAccountInfo.java
  14. 49 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/security/support/ServiceTokenInfo.java
  15. 39 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java
  16. 60 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityRequestConvertersTests.java
  17. 300 1
      client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java
  18. 46 42
      client/rest-high-level/src/test/java/org/elasticsearch/client/security/AuthenticateResponseTests.java
  19. 65 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/security/ClearServiceAccountTokenCacheRequestTests.java
  20. 86 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/security/CreateServiceAccountTokenRequestTests.java
  21. 45 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/security/CreateServiceAccountTokenResponseTests.java
  22. 82 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/security/DeleteServiceAccountTokenRequestTests.java
  23. 40 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/security/DeleteServiceAccountTokenResponseTests.java
  24. 47 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetServiceAccountCredentialsRequestTests.java
  25. 61 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetServiceAccountCredentialsResponseTests.java
  26. 64 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetServiceAccountsRequestTests.java
  27. 87 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetServiceAccountsResponseTests.java
  28. 4 0
      docs/java-rest/high-level/security/authenticate.asciidoc
  29. 37 0
      docs/java-rest/high-level/security/clear-service-account-token-cache.asciidoc
  30. 42 0
      docs/java-rest/high-level/security/create-service-account-token.asciidoc
  31. 35 0
      docs/java-rest/high-level/security/delete-service-account-token.asciidoc
  32. 38 0
      docs/java-rest/high-level/security/get-service-account-credentials.asciidoc
  33. 49 0
      docs/java-rest/high-level/security/get-service-accounts.asciidoc
  34. 10 0
      docs/java-rest/high-level/supported-apis.asciidoc
  35. 2 5
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/service/CreateServiceAccountTokenResponse.java

+ 166 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java

@@ -20,8 +20,11 @@ import org.elasticsearch.client.security.ClearRealmCacheResponse;
 import org.elasticsearch.client.security.ClearRolesCacheRequest;
 import org.elasticsearch.client.security.ClearRolesCacheResponse;
 import org.elasticsearch.client.security.ClearSecurityCacheResponse;
+import org.elasticsearch.client.security.ClearServiceAccountTokenCacheRequest;
 import org.elasticsearch.client.security.CreateApiKeyRequest;
 import org.elasticsearch.client.security.CreateApiKeyResponse;
+import org.elasticsearch.client.security.CreateServiceAccountTokenRequest;
+import org.elasticsearch.client.security.CreateServiceAccountTokenResponse;
 import org.elasticsearch.client.security.CreateTokenRequest;
 import org.elasticsearch.client.security.CreateTokenResponse;
 import org.elasticsearch.client.security.DelegatePkiAuthenticationRequest;
@@ -32,6 +35,8 @@ import org.elasticsearch.client.security.DeleteRoleMappingRequest;
 import org.elasticsearch.client.security.DeleteRoleMappingResponse;
 import org.elasticsearch.client.security.DeleteRoleRequest;
 import org.elasticsearch.client.security.DeleteRoleResponse;
+import org.elasticsearch.client.security.DeleteServiceAccountTokenRequest;
+import org.elasticsearch.client.security.DeleteServiceAccountTokenResponse;
 import org.elasticsearch.client.security.DeleteUserRequest;
 import org.elasticsearch.client.security.DeleteUserResponse;
 import org.elasticsearch.client.security.DisableUserRequest;
@@ -46,6 +51,10 @@ import org.elasticsearch.client.security.GetRoleMappingsRequest;
 import org.elasticsearch.client.security.GetRoleMappingsResponse;
 import org.elasticsearch.client.security.GetRolesRequest;
 import org.elasticsearch.client.security.GetRolesResponse;
+import org.elasticsearch.client.security.GetServiceAccountCredentialsRequest;
+import org.elasticsearch.client.security.GetServiceAccountCredentialsResponse;
+import org.elasticsearch.client.security.GetServiceAccountsRequest;
+import org.elasticsearch.client.security.GetServiceAccountsResponse;
 import org.elasticsearch.client.security.GetSslCertificatesRequest;
 import org.elasticsearch.client.security.GetSslCertificatesResponse;
 import org.elasticsearch.client.security.GetUserPrivilegesRequest;
@@ -569,6 +578,38 @@ public final class SecurityClient {
             ClearSecurityCacheResponse::fromXContent, listener, emptySet());
     }
 
+    /**
+     * Clears the service account token cache for the specified namespace, service-name and list of token names.
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-clear-service-token-cache.html">
+     * the docs</a> for more.
+     *
+     * @param request the request with namespace, service-name and token names for the service account tokens
+     *                that should be cleared from the cache.
+     * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @return the response from the clear security cache call
+     * @throws IOException in case there is a problem sending the request or parsing back the response
+     */public ClearSecurityCacheResponse clearServiceAccountTokenCache(ClearServiceAccountTokenCacheRequest request,
+                                                                       RequestOptions options) throws IOException {
+        return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::clearServiceAccountTokenCache,
+            options, ClearSecurityCacheResponse::fromXContent, emptySet());
+    }
+
+    /**
+     * Clears the service account token cache for the specified namespace, service-name and list of token names asynchronously.
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-clear-service-token-cache.html">
+     * the docs</a> for more.
+     *
+     * @param request the request with namespace, service-name and token names for the service account tokens
+     *                that should be cleared from the cache.
+     * @param options  the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @param listener the listener to be notified upon request completion
+     * @return cancellable that may be used to cancel the request
+     */public Cancellable clearServiceAccountTokenCacheAsync(ClearServiceAccountTokenCacheRequest request, RequestOptions options,
+                                                             ActionListener<ClearSecurityCacheResponse> listener) {
+        return restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::clearServiceAccountTokenCache,
+            options, ClearSecurityCacheResponse::fromXContent, listener, emptySet());
+    }
+
     /**
      * Synchronously retrieve the X.509 certificates that are used to encrypt communications in an Elasticsearch cluster.
      * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-ssl.html">
@@ -1098,6 +1139,131 @@ public final class SecurityClient {
             CreateApiKeyResponse::fromXContent, listener, emptySet());
     }
 
+    /**
+     * Get a service account, or list of service accounts synchronously.
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-service-accounts.html">
+     * the docs</a> for more information.
+     * @param request the request with namespace and service-name
+     * @param options the request options (e.g., headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @return the response from the get service accounts call
+     * @throws IOException in case there is a problem sending the request or parsing back the response
+     */
+    public GetServiceAccountsResponse getServiceAccounts(GetServiceAccountsRequest request, RequestOptions options) throws IOException {
+        return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::getServiceAccounts, options,
+            GetServiceAccountsResponse::fromXContent, emptySet());
+    }
+
+    /**
+     * Get a service account, or list of service accounts asynchronously.
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-service-accounts.html">
+     * the docs</a> for more information.
+     * @param request the request with namespace and service-name
+     * @param options the request options (e.g., headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @param listener the listener to be notified upon request completion
+     * @return cancellable that may be used to cancel the request
+     */
+    public Cancellable getServiceAccountsAsync(GetServiceAccountsRequest request, RequestOptions options,
+                                               ActionListener<GetServiceAccountsResponse> listener) {
+        return restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::getServiceAccounts, options,
+            GetServiceAccountsResponse::fromXContent, listener, emptySet());
+    }
+
+    /**
+     * Create a service account token.<br>
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-service-token.html">
+     * the docs</a> for more.
+     *
+     * @param request the request to create a service account token
+     * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @return the response from the create service account token call
+     * @throws IOException in case there is a problem sending the request or parsing back the response
+     */
+    public CreateServiceAccountTokenResponse createServiceAccountToken(final CreateServiceAccountTokenRequest request,
+                                                                       final RequestOptions options) throws IOException {
+        return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::createServiceAccountToken, options,
+            CreateServiceAccountTokenResponse::fromXContent, emptySet());
+    }
+
+    /**
+     * Asynchronously creates a service account token.<br>
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-service-token.html">
+     * the docs</a> for more.
+     *
+     * @param request the request to create a service account token
+     * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @param listener the listener to be notified upon request completion
+     * @return cancellable that may be used to cancel the request
+     */
+    public Cancellable createServiceAccountTokenAsync(final CreateServiceAccountTokenRequest request,
+                                                      final RequestOptions options,
+                                                      final ActionListener<CreateServiceAccountTokenResponse> listener) {
+        return restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::createServiceAccountToken, options,
+            CreateServiceAccountTokenResponse::fromXContent, listener, emptySet());
+    }
+
+    /**
+     * Delete a service account token.<br>
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-delete-service-token.html">
+     * the docs</a> for more.
+     *
+     * @param request the request to delete a service account token
+     * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @return the response from the create service account token call
+     * @throws IOException in case there is a problem sending the request or parsing back the response
+     */
+    public DeleteServiceAccountTokenResponse deleteServiceAccountToken(final DeleteServiceAccountTokenRequest request,
+                                                                       final RequestOptions options) throws IOException {
+        return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::deleteServiceAccountToken, options,
+            DeleteServiceAccountTokenResponse::fromXContent, emptySet());
+    }
+
+    /**
+     * Asynchronously deletes a service account token.<br>
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-delete-service-token.html">
+     * the docs</a> for more.
+     *
+     * @param request the request to delete a service account token
+     * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @param listener the listener to be notified upon request completion
+     * @return cancellable that may be used to cancel the request
+     */
+    public Cancellable deleteServiceAccountTokenAsync(final DeleteServiceAccountTokenRequest request,
+                                                      final RequestOptions options,
+                                                      final ActionListener<DeleteServiceAccountTokenResponse> listener) {
+        return restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::deleteServiceAccountToken, options,
+            DeleteServiceAccountTokenResponse::fromXContent, listener, emptySet());
+    }
+
+    /**
+     * Get credentials for a service account synchronously.
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-service-credentails.html">
+     * the docs</a> for more information.
+     * @param request the request with namespace and service-name
+     * @param options the request options (e.g., headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @return the response from the get service accounts call
+     * @throws IOException in case there is a problem sending the request or parsing back the response
+     */
+    public GetServiceAccountCredentialsResponse getServiceAccountCredentials(GetServiceAccountCredentialsRequest request,
+                                                                             RequestOptions options) throws IOException {
+        return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::getServiceAccountCredentials,
+            options, GetServiceAccountCredentialsResponse::fromXContent, emptySet());
+    }
+
+    /**
+     * Get credentials for a service account asynchronously.
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-service-credentails.html">
+     * the docs</a> for more information.
+     * @param request the request with namespace and service-name
+     * @param options the request options (e.g., headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @param listener the listener to be notified upon request completion
+     * @return cancellable that may be used to cancel the request
+     */
+    public Cancellable getServiceAccountCredentialsAsync(GetServiceAccountCredentialsRequest request, RequestOptions options,
+                                                         ActionListener<GetServiceAccountCredentialsResponse> listener) {
+        return restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::getServiceAccountCredentials,
+            options, GetServiceAccountCredentialsResponse::fromXContent, listener, emptySet());
+    }
+
     /**
      * Get an Elasticsearch access token from an {@code X509Certificate} chain. The certificate chain is that of the client from a mutually
      * authenticated TLS session, and it is validated by the PKI realms with {@code delegation.enabled} toggled to {@code true}.<br>

+ 70 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java

@@ -17,12 +17,15 @@ import org.elasticsearch.client.security.ClearApiKeyCacheRequest;
 import org.elasticsearch.client.security.ClearPrivilegesCacheRequest;
 import org.elasticsearch.client.security.ClearRealmCacheRequest;
 import org.elasticsearch.client.security.ClearRolesCacheRequest;
+import org.elasticsearch.client.security.ClearServiceAccountTokenCacheRequest;
 import org.elasticsearch.client.security.CreateApiKeyRequest;
+import org.elasticsearch.client.security.CreateServiceAccountTokenRequest;
 import org.elasticsearch.client.security.CreateTokenRequest;
 import org.elasticsearch.client.security.DelegatePkiAuthenticationRequest;
 import org.elasticsearch.client.security.DeletePrivilegesRequest;
 import org.elasticsearch.client.security.DeleteRoleMappingRequest;
 import org.elasticsearch.client.security.DeleteRoleRequest;
+import org.elasticsearch.client.security.DeleteServiceAccountTokenRequest;
 import org.elasticsearch.client.security.DeleteUserRequest;
 import org.elasticsearch.client.security.DisableUserRequest;
 import org.elasticsearch.client.security.EnableUserRequest;
@@ -30,6 +33,8 @@ import org.elasticsearch.client.security.GetApiKeyRequest;
 import org.elasticsearch.client.security.GetPrivilegesRequest;
 import org.elasticsearch.client.security.GetRoleMappingsRequest;
 import org.elasticsearch.client.security.GetRolesRequest;
+import org.elasticsearch.client.security.GetServiceAccountCredentialsRequest;
+import org.elasticsearch.client.security.GetServiceAccountsRequest;
 import org.elasticsearch.client.security.GetUsersRequest;
 import org.elasticsearch.client.security.GrantApiKeyRequest;
 import org.elasticsearch.client.security.HasPrivilegesRequest;
@@ -193,6 +198,17 @@ final class SecurityRequestConverters {
         return new Request(HttpPost.METHOD_NAME, endpoint);
     }
 
+    static Request clearServiceAccountTokenCache(ClearServiceAccountTokenCacheRequest clearServiceAccountTokenCacheRequest) {
+        String endpoint = new RequestConverters.EndpointBuilder()
+            .addPathPartAsIs("_security/service")
+            .addPathPart(clearServiceAccountTokenCacheRequest.getNamespace(), clearServiceAccountTokenCacheRequest.getServiceName())
+            .addPathPartAsIs("credential/token")
+            .addCommaSeparatedPathParts(clearServiceAccountTokenCacheRequest.getTokenNames())
+            .addPathPart("_clear_cache")
+            .build();
+        return new Request(HttpPost.METHOD_NAME, endpoint);
+    }
+
     static Request deleteRoleMapping(DeleteRoleMappingRequest deleteRoleMappingRequest) {
         final String endpoint = new RequestConverters.EndpointBuilder()
             .addPathPartAsIs("_security/role_mapping")
@@ -329,4 +345,58 @@ final class SecurityRequestConverters {
         request.setEntity(createEntity(invalidateApiKeyRequest, REQUEST_BODY_CONTENT_TYPE));
         return request;
     }
+
+    static Request getServiceAccounts(final GetServiceAccountsRequest getServiceAccountsRequest) {
+        final RequestConverters.EndpointBuilder endpointBuilder = new RequestConverters.EndpointBuilder()
+            .addPathPartAsIs("_security/service");
+        if (getServiceAccountsRequest.getNamespace() != null) {
+            endpointBuilder.addPathPart(getServiceAccountsRequest.getNamespace());
+            if (getServiceAccountsRequest.getServiceName() != null) {
+                endpointBuilder.addPathPart(getServiceAccountsRequest.getServiceName());
+            }
+        }
+        return new Request(HttpGet.METHOD_NAME, endpointBuilder.build());
+    }
+
+    static Request createServiceAccountToken(final CreateServiceAccountTokenRequest createServiceAccountTokenRequest) throws IOException {
+        final RequestConverters.EndpointBuilder endpointBuilder = new RequestConverters.EndpointBuilder()
+            .addPathPartAsIs("_security/service")
+            .addPathPart(createServiceAccountTokenRequest.getNamespace(), createServiceAccountTokenRequest.getServiceName())
+            .addPathPartAsIs("credential/token");
+        if (createServiceAccountTokenRequest.getTokenName() != null) {
+            endpointBuilder.addPathPart(createServiceAccountTokenRequest.getTokenName());
+        }
+        final Request request = new Request(HttpPost.METHOD_NAME, endpointBuilder.build());
+        final RequestConverters.Params params = new RequestConverters.Params();
+        if (createServiceAccountTokenRequest.getRefreshPolicy() != null) {
+            params.withRefreshPolicy(createServiceAccountTokenRequest.getRefreshPolicy());
+        }
+        request.addParameters(params.asMap());
+        return request;
+    }
+
+    static Request deleteServiceAccountToken(final DeleteServiceAccountTokenRequest deleteServiceAccountTokenRequest) {
+        final RequestConverters.EndpointBuilder endpointBuilder = new RequestConverters.EndpointBuilder()
+            .addPathPartAsIs("_security/service")
+            .addPathPart(deleteServiceAccountTokenRequest.getNamespace(), deleteServiceAccountTokenRequest.getServiceName())
+            .addPathPartAsIs("credential/token")
+            .addPathPart(deleteServiceAccountTokenRequest.getTokenName());
+
+        final Request request = new Request(HttpDelete.METHOD_NAME, endpointBuilder.build());
+        final RequestConverters.Params params = new RequestConverters.Params();
+        if (deleteServiceAccountTokenRequest.getRefreshPolicy() != null) {
+            params.withRefreshPolicy(deleteServiceAccountTokenRequest.getRefreshPolicy());
+        }
+        request.addParameters(params.asMap());
+        return request;
+    }
+
+    static Request getServiceAccountCredentials(final GetServiceAccountCredentialsRequest getServiceAccountCredentialsRequest) {
+        final RequestConverters.EndpointBuilder endpointBuilder = new RequestConverters.EndpointBuilder()
+            .addPathPartAsIs("_security/service")
+            .addPathPart(getServiceAccountCredentialsRequest.getNamespace(), getServiceAccountCredentialsRequest.getServiceName())
+            .addPathPartAsIs("credential");
+
+        return new Request(HttpGet.METHOD_NAME, endpointBuilder.build());
+    }
 }

+ 43 - 19
client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java

@@ -14,6 +14,7 @@ import org.elasticsearch.common.xcontent.ConstructingObjectParser;
 import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.core.Nullable;
 
 import java.io.IOException;
 import java.util.List;
@@ -42,12 +43,15 @@ public final class AuthenticateResponse implements ToXContentObject {
     static final ParseField REALM_NAME = new ParseField("name");
     static final ParseField REALM_TYPE = new ParseField("type");
     static final ParseField AUTHENTICATION_TYPE = new ParseField("authentication_type");
+    static final ParseField TOKEN = new ParseField("token");
 
     @SuppressWarnings("unchecked")
     private static final ConstructingObjectParser<AuthenticateResponse, Void> PARSER = new ConstructingObjectParser<>(
             "client_security_authenticate_response", true,
-            a -> new AuthenticateResponse(new User((String) a[0], ((List<String>) a[1]), (Map<String, Object>) a[2],
-                (String) a[3], (String) a[4]), (Boolean) a[5], (RealmInfo) a[6], (RealmInfo) a[7], (String) a[8]));
+            a -> new AuthenticateResponse(
+                new User((String) a[0], ((List<String>) a[1]), (Map<String, Object>) a[2],
+                (String) a[3], (String) a[4]), (Boolean) a[5], (RealmInfo) a[6], (RealmInfo) a[7], (String) a[8],
+                (Map<String, Object>) a[9]));
     static {
         final ConstructingObjectParser<RealmInfo, Void> realmInfoParser = new ConstructingObjectParser<>("realm_info", true,
             a -> new RealmInfo((String) a[0], (String) a[1]));
@@ -62,6 +66,7 @@ public final class AuthenticateResponse implements ToXContentObject {
         PARSER.declareObject(constructorArg(), realmInfoParser, AUTHENTICATION_REALM);
         PARSER.declareObject(constructorArg(), realmInfoParser, LOOKUP_REALM);
         PARSER.declareString(constructorArg(), AUTHENTICATION_TYPE);
+        PARSER.declareObjectOrNull(optionalConstructorArg(), (p, c) -> p.map(), null, TOKEN);
     }
 
     private final User user;
@@ -69,15 +74,22 @@ public final class AuthenticateResponse implements ToXContentObject {
     private final RealmInfo authenticationRealm;
     private final RealmInfo lookupRealm;
     private final String authenticationType;
-
+    @Nullable
+    private final Map<String, Object> token;
 
     public AuthenticateResponse(User user, boolean enabled, RealmInfo authenticationRealm,
                                 RealmInfo lookupRealm, String authenticationType) {
+        this(user, enabled, authenticationRealm, lookupRealm, authenticationType, null);
+    }
+
+    public AuthenticateResponse(User user, boolean enabled, RealmInfo authenticationRealm,
+                                RealmInfo lookupRealm, String authenticationType, @Nullable Map<String, Object> token) {
         this.user = user;
         this.enabled = enabled;
         this.authenticationRealm = authenticationRealm;
         this.lookupRealm = lookupRealm;
         this.authenticationType = authenticationType;
+        this.token = token;
     }
 
     /**
@@ -114,24 +126,35 @@ public final class AuthenticateResponse implements ToXContentObject {
         return authenticationType;
     }
 
+    public Map<String, Object> getToken() {
+        return token;
+    }
+
     @Override
     public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
-        builder.startObject()
-            .field("username", user.getUsername())
-            .field("roles", user.getRoles())
-            .field("metadata", user.getMetadata())
-            .field("full_name", user.getFullName())
-            .field("email", user.getEmail())
-            .field("enabled", enabled);
-        builder.startObject("authentication_realm")
-            .field("name", authenticationRealm.name)
-            .field("type", authenticationRealm.type);
+        builder.startObject();
+        builder.field(AuthenticateResponse.USERNAME.getPreferredName(), user.getUsername());
+        builder.field(AuthenticateResponse.ROLES.getPreferredName(), user.getRoles());
+        builder.field(AuthenticateResponse.METADATA.getPreferredName(), user.getMetadata());
+        if (user.getFullName() != null) {
+            builder.field(AuthenticateResponse.FULL_NAME.getPreferredName(), user.getFullName());
+        }
+        if (user.getEmail() != null) {
+            builder.field(AuthenticateResponse.EMAIL.getPreferredName(), user.getEmail());
+        }
+        builder.field(AuthenticateResponse.ENABLED.getPreferredName(), enabled);
+        builder.startObject(AuthenticateResponse.AUTHENTICATION_REALM.getPreferredName());
+        builder.field(AuthenticateResponse.REALM_NAME.getPreferredName(), authenticationRealm.getName());
+        builder.field(AuthenticateResponse.REALM_TYPE.getPreferredName(), authenticationRealm.getType());
         builder.endObject();
-        builder.startObject("lookup_realm")
-            .field("name", lookupRealm == null? authenticationRealm.name: lookupRealm.name)
-            .field("type", lookupRealm == null? authenticationRealm.type: lookupRealm.type);
+        builder.startObject(AuthenticateResponse.LOOKUP_REALM.getPreferredName());
+        builder.field(AuthenticateResponse.REALM_NAME.getPreferredName(), lookupRealm.getName());
+        builder.field(AuthenticateResponse.REALM_TYPE.getPreferredName(), lookupRealm.getType());
         builder.endObject();
-            builder.field("authentication_type", authenticationType);
+        builder.field(AuthenticateResponse.AUTHENTICATION_TYPE.getPreferredName(), authenticationType);
+        if (token != null) {
+            builder.field(AuthenticateResponse.TOKEN.getPreferredName(), token);
+        }
         return builder.endObject();
     }
 
@@ -144,12 +167,13 @@ public final class AuthenticateResponse implements ToXContentObject {
             Objects.equals(user, that.user) &&
             Objects.equals(authenticationRealm, that.authenticationRealm) &&
             Objects.equals(lookupRealm, that.lookupRealm) &&
-            Objects.equals(authenticationType, that.authenticationType);
+            Objects.equals(authenticationType, that.authenticationType) &&
+            Objects.equals(token, that.token);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(user, enabled, authenticationRealm, lookupRealm, authenticationType);
+        return Objects.hash(user, enabled, authenticationRealm, lookupRealm, authenticationType, token);
     }
 
     public static AuthenticateResponse fromXContent(XContentParser parser) throws IOException {

+ 63 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/security/ClearServiceAccountTokenCacheRequest.java

@@ -0,0 +1,63 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.client.Validatable;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * The request used to clear the service account token cache.
+ */
+public final class ClearServiceAccountTokenCacheRequest implements Validatable {
+
+    private final String namespace;
+    private final String serviceName;
+    private final String[] tokenNames;
+
+    /**
+     * @param tokenNames An array of token names to be cleared from the specified cache.
+     *                   If not specified, all entries will be cleared.
+     */
+    public ClearServiceAccountTokenCacheRequest(String namespace, String serviceName, String... tokenNames) {
+        this.namespace = Objects.requireNonNull(namespace, "namespace is required");
+        this.serviceName = Objects.requireNonNull(serviceName, "service-name is required");
+        this.tokenNames = tokenNames;
+    }
+
+    public String getNamespace() {
+        return namespace;
+    }
+
+    public String getServiceName() {
+        return serviceName;
+    }
+
+    public String[] getTokenNames() {
+        return tokenNames;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+        ClearServiceAccountTokenCacheRequest that = (ClearServiceAccountTokenCacheRequest) o;
+        return namespace.equals(that.namespace) && serviceName.equals(that.serviceName) && Arrays.equals(tokenNames, that.tokenNames);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = Objects.hash(namespace, serviceName);
+        result = 31 * result + Arrays.hashCode(tokenNames);
+        return result;
+    }
+}

+ 76 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/security/CreateServiceAccountTokenRequest.java

@@ -0,0 +1,76 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.client.Validatable;
+import org.elasticsearch.core.Nullable;
+
+import java.util.Objects;
+
+/**
+ * Request to create a service account token
+ */
+public final class CreateServiceAccountTokenRequest implements Validatable {
+
+    private final String namespace;
+    private final String serviceName;
+    @Nullable
+    private final String tokenName;
+    @Nullable
+    private final RefreshPolicy refreshPolicy;
+
+    public CreateServiceAccountTokenRequest(String namespace, String serviceName,
+                                            @Nullable String tokenName,
+                                            @Nullable RefreshPolicy refreshPolicy) {
+        this.namespace = Objects.requireNonNull(namespace, "namespace is required");
+        this.serviceName = Objects.requireNonNull(serviceName, "service-name is required");
+        this.tokenName = tokenName;
+        this.refreshPolicy = refreshPolicy;
+    }
+
+    public CreateServiceAccountTokenRequest(String namespace, String serviceName, String tokenName) {
+        this(namespace, serviceName, tokenName, null);
+    }
+
+    public CreateServiceAccountTokenRequest(String namespace, String serviceName) {
+        this(namespace, serviceName, null, null);
+    }
+
+    public String getNamespace() {
+        return namespace;
+    }
+
+    public String getServiceName() {
+        return serviceName;
+    }
+
+    public String getTokenName() {
+        return tokenName;
+    }
+
+    public RefreshPolicy getRefreshPolicy() {
+        return refreshPolicy;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+        CreateServiceAccountTokenRequest that = (CreateServiceAccountTokenRequest) o;
+        return namespace.equals(that.namespace) && serviceName.equals(that.serviceName) && Objects.equals(tokenName,
+            that.tokenName) && refreshPolicy == that.refreshPolicy;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(namespace, serviceName, tokenName, refreshPolicy);
+    }
+}

+ 92 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/security/CreateServiceAccountTokenResponse.java

@@ -0,0 +1,92 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.common.xcontent.ParseField;
+import org.elasticsearch.common.settings.SecureString;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+import java.util.Objects;
+
+import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
+
+/**
+ * Response for creating a service account token. Contains the token's name and value for bearer authentication.
+ */
+public final class CreateServiceAccountTokenResponse {
+
+    private final String name;
+    private final SecureString value;
+
+    public CreateServiceAccountTokenResponse(String name, SecureString value) {
+        this.name = name;
+        this.value = value;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public SecureString getValue() {
+        return value;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+        CreateServiceAccountTokenResponse that = (CreateServiceAccountTokenResponse) o;
+        return Objects.equals(name, that.name) && Objects.equals(value, that.value);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(name, value);
+    }
+
+    static final ConstructingObjectParser<Token, Void> TOKEN_PARSER = new ConstructingObjectParser<>(
+        "create_service_token_response_token",
+        args -> new Token((String) args[0], (String) args[1])
+    );
+
+    static final ConstructingObjectParser<CreateServiceAccountTokenResponse, Void> PARSER = new ConstructingObjectParser<>(
+        "create_service_token_response",
+        args -> {
+            if (false == (Boolean) args[0]) {
+                throw new IllegalStateException("The create field should always be true");
+            }
+            final Token token = (Token) args[1];
+            return new CreateServiceAccountTokenResponse(token.name, new SecureString(token.value.toCharArray()));
+        });
+
+    static {
+        TOKEN_PARSER.declareString(constructorArg(), new ParseField("name"));
+        TOKEN_PARSER.declareString(constructorArg(), new ParseField("value"));
+        PARSER.declareBoolean(constructorArg(), new ParseField("created"));
+        PARSER.declareObject(constructorArg(), TOKEN_PARSER, new ParseField("token"));
+    }
+
+    public static CreateServiceAccountTokenResponse fromXContent(XContentParser parser) throws IOException {
+        return PARSER.parse(parser, null);
+    }
+
+    private static class Token {
+        private final String name;
+        private final String value;
+
+        Token(String name, String value) {
+            this.name = name;
+            this.value = value;
+        }
+    }
+}

+ 70 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/security/DeleteServiceAccountTokenRequest.java

@@ -0,0 +1,70 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.client.Validatable;
+import org.elasticsearch.core.Nullable;
+
+import java.util.Objects;
+
+/**
+ * Request to delete a service account token
+ */
+public class DeleteServiceAccountTokenRequest implements Validatable {
+
+    private final String namespace;
+    private final String serviceName;
+    private final String tokenName;
+    @Nullable
+    private final RefreshPolicy refreshPolicy;
+
+    public DeleteServiceAccountTokenRequest(String namespace, String serviceName, String tokenName,
+                                            @Nullable RefreshPolicy refreshPolicy) {
+        this.namespace = Objects.requireNonNull(namespace, "namespace is required");
+        this.serviceName = Objects.requireNonNull(serviceName, "service-name is required");
+        this.tokenName = Objects.requireNonNull(tokenName, "token name is required");
+        this.refreshPolicy = refreshPolicy;
+    }
+
+    public DeleteServiceAccountTokenRequest(String namespace, String serviceName, String tokenName) {
+        this(namespace, serviceName, tokenName, null);
+    }
+
+    public String getNamespace() {
+        return namespace;
+    }
+
+    public String getServiceName() {
+        return serviceName;
+    }
+
+    public String getTokenName() {
+        return tokenName;
+    }
+
+    public RefreshPolicy getRefreshPolicy() {
+        return refreshPolicy;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+        DeleteServiceAccountTokenRequest that = (DeleteServiceAccountTokenRequest) o;
+        return namespace.equals(that.namespace) && serviceName.equals(that.serviceName)
+            && tokenName.equals(that.tokenName) && refreshPolicy == that.refreshPolicy;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(namespace, serviceName, tokenName, refreshPolicy);
+    }
+}

+ 39 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/security/DeleteServiceAccountTokenResponse.java

@@ -0,0 +1,39 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.client.core.AcknowledgedResponse;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+
+/**
+ * Response for a service account token deletion
+ */
+public final class DeleteServiceAccountTokenResponse extends AcknowledgedResponse {
+
+    private static final String PARSE_FIELD_NAME = "found";
+
+    private static final ConstructingObjectParser<DeleteServiceAccountTokenResponse, Void> PARSER = AcknowledgedResponse
+        .generateParser("delete_service_account_token_response", DeleteServiceAccountTokenResponse::new, PARSE_FIELD_NAME);
+
+    public DeleteServiceAccountTokenResponse(boolean acknowledged) {
+        super(acknowledged);
+    }
+
+    public static DeleteServiceAccountTokenResponse fromXContent(final XContentParser parser) throws IOException {
+        return PARSER.parse(parser, null);
+    }
+
+    @Override
+    protected String getFieldName() {
+        return PARSE_FIELD_NAME;
+    }
+}

+ 50 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetServiceAccountCredentialsRequest.java

@@ -0,0 +1,50 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.client.Validatable;
+
+import java.util.Objects;
+
+/**
+ * Request to retrieve credentials of a service account
+ */
+public final class GetServiceAccountCredentialsRequest implements Validatable {
+
+    private final String namespace;
+    private final String serviceName;
+
+    public GetServiceAccountCredentialsRequest(String namespace, String serviceName) {
+        this.namespace = Objects.requireNonNull(namespace, "namespace is required");
+        this.serviceName = Objects.requireNonNull(serviceName, "service-name is required");
+    }
+
+    public String getNamespace() {
+        return namespace;
+    }
+
+    public String getServiceName() {
+        return serviceName;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+        GetServiceAccountCredentialsRequest that = (GetServiceAccountCredentialsRequest) o;
+        return namespace.equals(that.namespace) && serviceName.equals(that.serviceName);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(namespace, serviceName);
+    }
+}

+ 92 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetServiceAccountCredentialsResponse.java

@@ -0,0 +1,92 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.client.security.support.ServiceTokenInfo;
+import org.elasticsearch.common.xcontent.ParseField;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
+
+/**
+ * Response when requesting credentials of a service account.
+ */
+public final class GetServiceAccountCredentialsResponse {
+
+    private final String principal;
+    private final String nodeName;
+    private final List<ServiceTokenInfo> serviceTokenInfos;
+
+    public GetServiceAccountCredentialsResponse(
+        String principal, String nodeName, List<ServiceTokenInfo> serviceTokenInfos) {
+        this.principal = Objects.requireNonNull(principal, "principal is required");
+        this.nodeName = Objects.requireNonNull(nodeName, "nodeName is required");
+        this.serviceTokenInfos = List.copyOf(Objects.requireNonNull(serviceTokenInfos, "service token infos are required)"));
+    }
+
+    public String getPrincipal() {
+        return principal;
+    }
+
+    public String getNodeName() {
+        return nodeName;
+    }
+
+    public List<ServiceTokenInfo> getServiceTokenInfos() {
+        return serviceTokenInfos;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+        GetServiceAccountCredentialsResponse that = (GetServiceAccountCredentialsResponse) o;
+        return principal.equals(that.principal) && nodeName.equals(that.nodeName) && serviceTokenInfos.equals(that.serviceTokenInfos);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(principal, nodeName, serviceTokenInfos);
+    }
+
+    static ConstructingObjectParser<GetServiceAccountCredentialsResponse, Void> PARSER =
+        new ConstructingObjectParser<>("get_service_account_credentials_response",
+            args -> {
+                @SuppressWarnings("unchecked")
+                final List<ServiceTokenInfo> tokenInfos = Stream.concat(
+                    ((Map<String, Object>) args[3]).keySet().stream().map(name -> new ServiceTokenInfo(name, "index")),
+                    ((Map<String, Object>) args[4]).keySet().stream().map(name -> new ServiceTokenInfo(name, "file")))
+                    .collect(Collectors.toList());
+                assert tokenInfos.size() == (int) args[2] : "number of tokens do not match";
+                return new GetServiceAccountCredentialsResponse((String) args[0], (String) args[1], tokenInfos);
+            });
+
+    static {
+        PARSER.declareString(constructorArg(), new ParseField("service_account"));
+        PARSER.declareString(constructorArg(), new ParseField("node_name"));
+        PARSER.declareInt(constructorArg(), new ParseField("count"));
+        PARSER.declareObject(constructorArg(), (p, c) -> p.map(), new ParseField("tokens"));
+        PARSER.declareObject(constructorArg(), (p, c) -> p.map(), new ParseField("file_tokens"));
+    }
+
+    public static GetServiceAccountCredentialsResponse fromXContent(XContentParser parser) throws IOException {
+        return PARSER.parse(parser, null);
+    }
+
+}

+ 74 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetServiceAccountsRequest.java

@@ -0,0 +1,74 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.client.Validatable;
+import org.elasticsearch.client.ValidationException;
+import org.elasticsearch.core.Nullable;
+
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Request to retrieve information of service accounts
+ */
+public final class GetServiceAccountsRequest implements Validatable {
+
+    @Nullable
+    private final String namespace;
+    @Nullable
+    private final String serviceName;
+
+    public GetServiceAccountsRequest(@Nullable String namespace, @Nullable String serviceName) {
+        this.namespace = namespace;
+        this.serviceName = serviceName;
+    }
+
+    public GetServiceAccountsRequest(String namespace) {
+        this(namespace, null);
+    }
+
+    public GetServiceAccountsRequest() {
+        this(null, null);
+    }
+
+    public String getNamespace() {
+        return namespace;
+    }
+
+    public String getServiceName() {
+        return serviceName;
+    }
+
+    @Override
+    public Optional<ValidationException> validate() {
+        if (namespace == null && serviceName != null) {
+            final ValidationException validationException = new ValidationException();
+            validationException.addValidationError("cannot specify service-name without namespace");
+            return Optional.of(validationException);
+        } else {
+            return Optional.empty();
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+        GetServiceAccountsRequest that = (GetServiceAccountsRequest) o;
+        return Objects.equals(namespace, that.namespace) && Objects.equals(serviceName, that.serviceName);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(namespace, serviceName);
+    }
+}

+ 72 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetServiceAccountsResponse.java

@@ -0,0 +1,72 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.client.security.support.ServiceAccountInfo;
+import org.elasticsearch.client.security.user.privileges.Role;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentParserUtils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Response when requesting one or more service accounts.
+ * Returns a List of {@link ServiceAccountInfo} objects
+ */
+public final class GetServiceAccountsResponse {
+
+    private final List<ServiceAccountInfo> serviceAccountInfos;
+
+    public GetServiceAccountsResponse(List<ServiceAccountInfo> serviceAccountInfos) {
+        this.serviceAccountInfos = serviceAccountInfos;
+    }
+
+    public List<ServiceAccountInfo> getServiceAccountInfos() {
+        return serviceAccountInfos;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+        GetServiceAccountsResponse that = (GetServiceAccountsResponse) o;
+        return serviceAccountInfos.equals(that.serviceAccountInfos);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(serviceAccountInfos);
+    }
+
+    public static GetServiceAccountsResponse fromXContent(XContentParser parser) throws IOException {
+        XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser);
+        XContentParser.Token token;
+        final List<ServiceAccountInfo> serviceAccountInfos = new ArrayList<>();
+        while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+            XContentParserUtils.ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser);
+            final String principal = parser.currentName();
+            final Role role = parseRoleDescriptor(parser);
+            serviceAccountInfos.add(new ServiceAccountInfo(principal, role));
+        }
+        return new GetServiceAccountsResponse(serviceAccountInfos);
+    }
+
+    private static Role parseRoleDescriptor(XContentParser parser) throws IOException {
+        XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser);
+        XContentParserUtils.ensureFieldName(parser, parser.nextToken(), "role_descriptor");
+        final Role role = Role.PARSER.parse(parser, parser.currentName()).v1();
+        XContentParserUtils.ensureExpectedToken(XContentParser.Token.END_OBJECT, parser.nextToken(), parser);
+        return role;
+    }
+}

+ 55 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/security/support/ServiceAccountInfo.java

@@ -0,0 +1,55 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.client.security.support;
+
+import org.elasticsearch.client.security.user.privileges.Role;
+
+import java.util.Objects;
+
+/**
+ * Information about a service account.
+ */
+public final class ServiceAccountInfo {
+
+    private final String principal;
+    private final Role role;
+
+    public ServiceAccountInfo(String principal, Role role) {
+        this.principal = principal;
+        this.role = role;
+    }
+
+    public String getPrincipal() {
+        return principal;
+    }
+
+    public Role getRole() {
+        return role;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+        ServiceAccountInfo that = (ServiceAccountInfo) o;
+        return principal.equals(that.principal) && role.equals(that.role);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(principal, role);
+    }
+
+    @Override
+    public String toString() {
+        return "ServiceAccountInfo{" + "principal='" + principal + '\'' + ", role=" + role + '}';
+    }
+}

+ 49 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/security/support/ServiceTokenInfo.java

@@ -0,0 +1,49 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.client.security.support;
+
+import java.util.Objects;
+
+public class ServiceTokenInfo {
+    private final String name;
+    private final String source;
+
+    public ServiceTokenInfo(String name, String source) {
+        this.name = Objects.requireNonNull(name, "token name is required");
+        this.source = Objects.requireNonNull(source, "token source is required");
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getSource() {
+        return source;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+        ServiceTokenInfo that = (ServiceTokenInfo) o;
+        return name.equals(that.name) && source.equals(that.source);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(name, source);
+    }
+
+    @Override
+    public String toString() {
+        return "ServiceTokenInfo{" + "name='" + name + '\'' + ", source='" + source + '\'' + '}';
+    }
+}

+ 39 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java

@@ -11,6 +11,8 @@ package org.elasticsearch.client;
 import org.apache.http.client.methods.HttpDelete;
 import org.elasticsearch.ElasticsearchStatusException;
 import org.elasticsearch.client.security.AuthenticateResponse;
+import org.elasticsearch.client.security.CreateServiceAccountTokenRequest;
+import org.elasticsearch.client.security.CreateServiceAccountTokenResponse;
 import org.elasticsearch.client.security.DeleteRoleRequest;
 import org.elasticsearch.client.security.DeleteRoleResponse;
 import org.elasticsearch.client.security.DeleteUserRequest;
@@ -43,12 +45,14 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 
+import static org.hamcrest.Matchers.empty;
 import static org.hamcrest.Matchers.endsWith;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
 
 public class SecurityIT extends ESRestHighLevelClientTestCase {
 
@@ -129,6 +133,41 @@ public class SecurityIT extends ESRestHighLevelClientTestCase {
         final DeleteUserResponse deleteUserResponse2 =
             execute(deleteUserRequest, securityClient::deleteUser, securityClient::deleteUserAsync);
         assertThat(deleteUserResponse2.isAcknowledged(), is(false));
+
+        // Test the authenticate response for a service token
+        {
+            RestHighLevelClient client = highLevelClient();
+            CreateServiceAccountTokenRequest createServiceAccountTokenRequest =
+                new CreateServiceAccountTokenRequest("elastic", "fleet-server", "token1");
+            CreateServiceAccountTokenResponse createServiceAccountTokenResponse =
+                client.security().createServiceAccountToken(createServiceAccountTokenRequest, RequestOptions.DEFAULT);
+
+            AuthenticateResponse response = client.security().authenticate(
+                RequestOptions.DEFAULT.toBuilder().addHeader(
+                    "Authorization", "Bearer " + createServiceAccountTokenResponse.getValue().toString()).build());
+
+            User user = response.getUser();
+            boolean enabled = response.enabled();
+            final String authenticationRealmName = response.getAuthenticationRealm().getName();
+            final String authenticationRealmType = response.getAuthenticationRealm().getType();
+            final String lookupRealmName = response.getLookupRealm().getName();
+            final String lookupRealmType = response.getLookupRealm().getType();
+            final String authenticationType = response.getAuthenticationType();
+            final Map<String, Object> token = response.getToken();
+
+            assertThat(user.getUsername(), is("elastic/fleet-server"));
+            assertThat(user.getRoles(), empty());
+            assertThat(user.getFullName(), equalTo("Service account - elastic/fleet-server"));
+            assertThat(user.getEmail(), nullValue());
+            assertThat(user.getMetadata(), equalTo(Map.of("_elastic_service_account", true)));
+            assertThat(enabled, is(true));
+            assertThat(authenticationRealmName, is("_service_account"));
+            assertThat(authenticationRealmType, is("_service_account"));
+            assertThat(lookupRealmName, is("_service_account"));
+            assertThat(lookupRealmType, is("_service_account"));
+            assertThat(authenticationType, is("token"));
+            assertThat(token, equalTo(Map.of("name", "token1", "type", "_service_account_index")));
+        }
     }
 
     public void testPutRole() throws Exception {

+ 60 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityRequestConvertersTests.java

@@ -15,11 +15,13 @@ import org.apache.http.client.methods.HttpPut;
 import org.elasticsearch.client.security.ChangePasswordRequest;
 import org.elasticsearch.client.security.CreateApiKeyRequest;
 import org.elasticsearch.client.security.CreateApiKeyRequestTests;
+import org.elasticsearch.client.security.CreateServiceAccountTokenRequest;
 import org.elasticsearch.client.security.CreateTokenRequest;
 import org.elasticsearch.client.security.DelegatePkiAuthenticationRequest;
 import org.elasticsearch.client.security.DeletePrivilegesRequest;
 import org.elasticsearch.client.security.DeleteRoleMappingRequest;
 import org.elasticsearch.client.security.DeleteRoleRequest;
+import org.elasticsearch.client.security.DeleteServiceAccountTokenRequest;
 import org.elasticsearch.client.security.DeleteUserRequest;
 import org.elasticsearch.client.security.DisableUserRequest;
 import org.elasticsearch.client.security.EnableUserRequest;
@@ -27,6 +29,8 @@ import org.elasticsearch.client.security.GetApiKeyRequest;
 import org.elasticsearch.client.security.GetPrivilegesRequest;
 import org.elasticsearch.client.security.GetRoleMappingsRequest;
 import org.elasticsearch.client.security.GetRolesRequest;
+import org.elasticsearch.client.security.GetServiceAccountCredentialsRequest;
+import org.elasticsearch.client.security.GetServiceAccountsRequest;
 import org.elasticsearch.client.security.GetUsersRequest;
 import org.elasticsearch.client.security.GrantApiKeyRequest;
 import org.elasticsearch.client.security.InvalidateApiKeyRequest;
@@ -499,4 +503,60 @@ public class SecurityRequestConvertersTests extends ESTestCase {
         assertEquals("/_security/api_key", request.getEndpoint());
         assertToXContentBody(invalidateApiKeyRequest, request.getEntity());
     }
+
+    public void testGetServiceAccounts() throws IOException {
+        final String namespace = randomBoolean() ? randomAlphaOfLengthBetween(3, 8) : null;
+        final String serviceName = namespace == null ? null : randomAlphaOfLengthBetween(3, 8);
+        final GetServiceAccountsRequest getServiceAccountsRequest = new GetServiceAccountsRequest(namespace, serviceName);
+        final Request request = SecurityRequestConverters.getServiceAccounts(getServiceAccountsRequest);
+        assertEquals(HttpGet.METHOD_NAME, request.getMethod());
+        if (namespace == null) {
+            assertEquals("/_security/service", request.getEndpoint());
+        } else if (serviceName == null) {
+            assertEquals("/_security/service/" + namespace, request.getEndpoint());
+        } else {
+            assertEquals("/_security/service/" + namespace + "/" + serviceName, request.getEndpoint());
+        }
+    }
+
+    public void testCreateServiceAccountToken() throws IOException {
+        final String namespace = randomAlphaOfLengthBetween(3, 8);
+        final String serviceName = randomAlphaOfLengthBetween(3, 8);
+        final String tokenName = randomBoolean() ? randomAlphaOfLengthBetween(3, 8) : null;
+        final RefreshPolicy refreshPolicy = randomBoolean() ? randomFrom(RefreshPolicy.values()) : null;
+        final CreateServiceAccountTokenRequest createServiceAccountTokenRequest =
+            new CreateServiceAccountTokenRequest(namespace, serviceName, tokenName, refreshPolicy);
+        final Request request = SecurityRequestConverters.createServiceAccountToken(createServiceAccountTokenRequest);
+        assertEquals(HttpPost.METHOD_NAME, request.getMethod());
+        final String url =
+            "/_security/service/" + namespace + "/" + serviceName + "/credential/token" + (tokenName == null ? "" : "/" + tokenName);
+        assertEquals(url, request.getEndpoint());
+        if (refreshPolicy != null && refreshPolicy != RefreshPolicy.NONE) {
+            assertEquals(refreshPolicy.getValue(), request.getParameters().get("refresh"));
+        }
+    }
+
+    public void testDeleteServiceAccountToken() throws IOException {
+        final String namespace = randomAlphaOfLengthBetween(3, 8);
+        final String serviceName = randomAlphaOfLengthBetween(3, 8);
+        final String tokenName = randomAlphaOfLengthBetween(3, 8);
+        final RefreshPolicy refreshPolicy = randomBoolean() ? randomFrom(RefreshPolicy.values()) : null;
+        final DeleteServiceAccountTokenRequest deleteServiceAccountTokenRequest =
+            new DeleteServiceAccountTokenRequest(namespace, serviceName, tokenName, refreshPolicy);
+        final Request request = SecurityRequestConverters.deleteServiceAccountToken(deleteServiceAccountTokenRequest);
+        assertEquals("/_security/service/" + namespace + "/" + serviceName + "/credential/token/" + tokenName, request.getEndpoint());
+        if (refreshPolicy != null && refreshPolicy != RefreshPolicy.NONE) {
+            assertEquals(refreshPolicy.getValue(), request.getParameters().get("refresh"));
+        }
+    }
+
+    public void testGetServiceAccountCredentials() {
+        final String namespace = randomAlphaOfLengthBetween(3, 8);
+        final String serviceName = randomAlphaOfLengthBetween(3, 8);
+        final GetServiceAccountCredentialsRequest getServiceAccountCredentialsRequest =
+            new GetServiceAccountCredentialsRequest(namespace, serviceName);
+        final Request request = SecurityRequestConverters.getServiceAccountCredentials(getServiceAccountCredentialsRequest);
+        assertEquals(HttpGet.METHOD_NAME, request.getMethod());
+        assertEquals("/_security/service/" + namespace + "/" + serviceName + "/credential", request.getEndpoint());
+    }
  }

+ 300 - 1
client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java

@@ -27,9 +27,12 @@ import org.elasticsearch.client.security.ClearRealmCacheResponse;
 import org.elasticsearch.client.security.ClearRolesCacheRequest;
 import org.elasticsearch.client.security.ClearRolesCacheResponse;
 import org.elasticsearch.client.security.ClearSecurityCacheResponse;
+import org.elasticsearch.client.security.ClearServiceAccountTokenCacheRequest;
 import org.elasticsearch.client.security.CreateApiKeyRequest;
 import org.elasticsearch.client.security.CreateApiKeyRequestTests;
 import org.elasticsearch.client.security.CreateApiKeyResponse;
+import org.elasticsearch.client.security.CreateServiceAccountTokenRequest;
+import org.elasticsearch.client.security.CreateServiceAccountTokenResponse;
 import org.elasticsearch.client.security.CreateTokenRequest;
 import org.elasticsearch.client.security.CreateTokenResponse;
 import org.elasticsearch.client.security.DelegatePkiAuthenticationRequest;
@@ -40,6 +43,8 @@ import org.elasticsearch.client.security.DeleteRoleMappingRequest;
 import org.elasticsearch.client.security.DeleteRoleMappingResponse;
 import org.elasticsearch.client.security.DeleteRoleRequest;
 import org.elasticsearch.client.security.DeleteRoleResponse;
+import org.elasticsearch.client.security.DeleteServiceAccountTokenRequest;
+import org.elasticsearch.client.security.DeleteServiceAccountTokenResponse;
 import org.elasticsearch.client.security.DeleteUserRequest;
 import org.elasticsearch.client.security.DeleteUserResponse;
 import org.elasticsearch.client.security.DisableUserRequest;
@@ -54,6 +59,10 @@ import org.elasticsearch.client.security.GetRoleMappingsRequest;
 import org.elasticsearch.client.security.GetRoleMappingsResponse;
 import org.elasticsearch.client.security.GetRolesRequest;
 import org.elasticsearch.client.security.GetRolesResponse;
+import org.elasticsearch.client.security.GetServiceAccountCredentialsRequest;
+import org.elasticsearch.client.security.GetServiceAccountCredentialsResponse;
+import org.elasticsearch.client.security.GetServiceAccountsRequest;
+import org.elasticsearch.client.security.GetServiceAccountsResponse;
 import org.elasticsearch.client.security.GetSslCertificatesResponse;
 import org.elasticsearch.client.security.GetUserPrivilegesResponse;
 import org.elasticsearch.client.security.GetUsersRequest;
@@ -78,6 +87,8 @@ import org.elasticsearch.client.security.RefreshPolicy;
 import org.elasticsearch.client.security.TemplateRoleName;
 import org.elasticsearch.client.security.support.ApiKey;
 import org.elasticsearch.client.security.support.CertificateInfo;
+import org.elasticsearch.client.security.support.ServiceAccountInfo;
+import org.elasticsearch.client.security.support.ServiceTokenInfo;
 import org.elasticsearch.client.security.support.expressiondsl.RoleMapperExpression;
 import org.elasticsearch.client.security.support.expressiondsl.expressions.AnyRoleMapperExpression;
 import org.elasticsearch.client.security.support.expressiondsl.fields.FieldRoleMapperExpression;
@@ -745,6 +756,7 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
             final String lookupRealmName = response.getLookupRealm().getName(); // <5>
             final String lookupRealmType = response.getLookupRealm().getType(); // <6>
             final String authenticationType = response.getAuthenticationType(); // <7>
+            final Map<String, Object> tokenInfo = response.getToken(); // <8>
             //end::authenticate-response
 
             assertThat(user.getUsername(), is("test_user"));
@@ -758,6 +770,7 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
             assertThat(lookupRealmName, is("default_file"));
             assertThat(lookupRealmType, is("file"));
             assertThat(authenticationType, is("realm"));
+            assertThat(tokenInfo, nullValue());
         }
 
         {
@@ -1068,8 +1081,8 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
         }
 
         {
-            //tag::clear-api-key-cache-execute-listener
             ClearApiKeyCacheRequest request = ClearApiKeyCacheRequest.clearById("yVGMr3QByxdh1MSaicYx");
+            //tag::clear-api-key-cache-execute-listener
             ActionListener<ClearSecurityCacheResponse> listener = new ActionListener<>() {
                 @Override
                 public void onResponse(ClearSecurityCacheResponse clearSecurityCacheResponse) {
@@ -1095,6 +1108,57 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
         }
     }
 
+    public void testClearServiceAccountTokenCache() throws Exception {
+        RestHighLevelClient client = highLevelClient();
+        {
+            //tag::clear-service-account-token-cache-request
+            ClearServiceAccountTokenCacheRequest request = new ClearServiceAccountTokenCacheRequest(
+                "elastic", // <1>
+                "fleet-server", // <2>
+                "token1" // <3>
+            );
+            //end::clear-service-account-token-cache-request
+            //tag::clear-service-account-token-cache-execute
+            ClearSecurityCacheResponse response = client.security().clearServiceAccountTokenCache(request, RequestOptions.DEFAULT);
+            //end::clear-service-account-token-cache-execute
+
+            assertNotNull(response);
+            assertThat(response.getNodes(), not(empty()));
+
+            //tag::clear-service-account-token-cache-response
+            List<ClearSecurityCacheResponse.Node> nodes = response.getNodes(); // <1>
+            //end::clear-service-account-token-cache-response
+        }
+
+        {
+            ClearServiceAccountTokenCacheRequest request = new ClearServiceAccountTokenCacheRequest("elastic", "fleet-server",
+                "token1", "token2");
+            //tag::clear-service-account-token-cache-execute-listener
+            ActionListener<ClearSecurityCacheResponse> listener = new ActionListener<>() {
+                @Override
+                public void onResponse(ClearSecurityCacheResponse clearSecurityCacheResponse) {
+                    // <1>
+                }
+
+                @Override
+                public void onFailure(Exception e) {
+                    // <2>
+                }
+            };
+            //end::clear-service-account-token-cache-execute-listener
+
+            // Replace the empty listener by a blocking listener in test
+            final CountDownLatch latch = new CountDownLatch(1);
+            listener = new LatchedActionListener<>(listener, latch);
+
+            // tag::clear-service-account-token-cache-execute-async
+            client.security().clearServiceAccountTokenCacheAsync(request, RequestOptions.DEFAULT, listener); // <1>
+            // end::clear-service-account-token-cache-execute-async
+
+            assertTrue(latch.await(30L, TimeUnit.SECONDS));
+        }
+    }
+
     public void testGetSslCertificates() throws Exception {
         RestHighLevelClient client = highLevelClient();
         {
@@ -2489,6 +2553,241 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
 
     }
 
+    public void testGetServiceAccounts() throws IOException {
+        RestHighLevelClient client = highLevelClient();
+        {
+            // tag::get-service-accounts-request
+            final GetServiceAccountsRequest getServiceAccountsRequest = new GetServiceAccountsRequest("elastic", "fleet-server");
+            // end::get-service-accounts-request
+
+            // tag::get-service-accounts-execute
+            final GetServiceAccountsResponse getServiceAccountsResponse =
+                client.security().getServiceAccounts(getServiceAccountsRequest, RequestOptions.DEFAULT);
+            // end::get-service-accounts-execute
+
+            // tag::get-service-accounts-response
+            final ServiceAccountInfo serviceAccountInfo = getServiceAccountsResponse.getServiceAccountInfos().get(0); // <1>
+            // end::get-service-accounts-response
+            assertThat(serviceAccountInfo.getPrincipal(), equalTo("elastic/fleet-server"));
+        }
+
+        {
+            // tag::get-service-accounts-request-namespace
+            final GetServiceAccountsRequest getServiceAccountsRequest = new GetServiceAccountsRequest("elastic");
+            // end::get-service-accounts-request-namespace
+        }
+
+        {
+            // tag::get-service-accounts-request-all
+            final GetServiceAccountsRequest getServiceAccountsRequest = new GetServiceAccountsRequest();
+            // end::get-service-accounts-request-all
+        }
+
+        {
+            final GetServiceAccountsRequest getServiceAccountsRequest = new GetServiceAccountsRequest("elastic", "fleet-server");
+
+            ActionListener<GetServiceAccountsResponse> listener;
+            // tag::get-service-accounts-execute-listener
+            listener = new ActionListener<GetServiceAccountsResponse>() {
+                @Override
+                public void onResponse(GetServiceAccountsResponse getServiceAccountsResponse) {
+                    // <1>
+                }
+
+                @Override
+                public void onFailure(Exception e) {
+                    // <2>
+                }
+            };
+            // end::get-service-accounts-execute-listener
+
+            final PlainActionFuture<GetServiceAccountsResponse> future = new PlainActionFuture<>();
+            listener = future;
+
+            // tag::get-service-accounts-execute-async
+            client.security().getServiceAccountsAsync(getServiceAccountsRequest, RequestOptions.DEFAULT, listener); // <1>
+            // end::get-service-accounts-execute-async
+
+            assertNotNull(future.actionGet());
+            assertThat(future.actionGet().getServiceAccountInfos().size(), equalTo(1));
+            assertThat(future.actionGet().getServiceAccountInfos().get(0).getPrincipal(), equalTo("elastic/fleet-server"));
+        }
+    }
+
+    public void testCreateServiceAccountToken() throws IOException {
+        RestHighLevelClient client = highLevelClient();
+        {
+            // tag::create-service-account-token-request
+            CreateServiceAccountTokenRequest createServiceAccountTokenRequest =
+                new CreateServiceAccountTokenRequest("elastic", "fleet-server", "token1");
+            // end::create-service-account-token-request
+
+            // tag::create-service-account-token-execute
+            CreateServiceAccountTokenResponse createServiceAccountTokenResponse =
+                client.security().createServiceAccountToken(createServiceAccountTokenRequest, RequestOptions.DEFAULT);
+            // end::create-service-account-token-execute
+
+            // tag::create-service-account-token-response
+            final String tokenName = createServiceAccountTokenResponse.getName(); // <1>
+            final SecureString tokenValue = createServiceAccountTokenResponse.getValue(); // <2>
+            // end::create-service-account-token-response
+            assertThat(createServiceAccountTokenResponse.getName(), equalTo("token1"));
+            assertNotNull(tokenValue);
+        }
+
+        {
+            // tag::create-service-account-token-request-auto-name
+            CreateServiceAccountTokenRequest createServiceAccountTokenRequest =
+                new CreateServiceAccountTokenRequest("elastic", "fleet-server");
+            // end::create-service-account-token-request-auto-name
+
+            ActionListener<CreateServiceAccountTokenResponse> listener;
+            // tag::create-service-account-token-execute-listener
+            listener = new ActionListener<CreateServiceAccountTokenResponse>() {
+                @Override
+                public void onResponse(CreateServiceAccountTokenResponse createServiceAccountTokenResponse) {
+                    // <1>
+                }
+
+                @Override
+                public void onFailure(Exception e) {
+                    // <2>
+                }
+            };
+            // end::create-service-account-token-execute-listener
+
+            final PlainActionFuture<CreateServiceAccountTokenResponse> future = new PlainActionFuture<>();
+            listener = future;
+
+            // tag::create-service-account-token-execute-async
+            client.security().createServiceAccountTokenAsync(createServiceAccountTokenRequest, RequestOptions.DEFAULT, listener); // <1>
+            // end::create-service-account-token-execute-async
+
+            assertNotNull(future.actionGet());
+            assertNotNull(future.actionGet().getName());
+            assertNotNull(future.actionGet().getValue());
+        }
+    }
+
+    public void testDeleteServiceAccountToken() throws IOException {
+        RestHighLevelClient client = highLevelClient();
+        final CreateServiceAccountTokenRequest createServiceAccountTokenRequest =
+            new CreateServiceAccountTokenRequest("elastic", "fleet-server", "test-token");
+        client.security().createServiceAccountToken(createServiceAccountTokenRequest, RequestOptions.DEFAULT);
+        {
+            // tag::delete-service-account-token-request
+            DeleteServiceAccountTokenRequest deleteServiceAccountTokenRequest =
+                new DeleteServiceAccountTokenRequest("elastic", "fleet-server", "test-token");
+            // end::delete-service-account-token-request
+
+            // tag::delete-service-account-token-execute
+            DeleteServiceAccountTokenResponse deleteServiceAccountTokenResponse =
+                client.security().deleteServiceAccountToken(deleteServiceAccountTokenRequest, RequestOptions.DEFAULT);
+            // end::delete-service-account-token-execute
+
+            // tag::delete-service-account-token-response
+            final boolean found = deleteServiceAccountTokenResponse.isAcknowledged(); // <1>
+            // end::delete-service-account-token-response
+            assertTrue(deleteServiceAccountTokenResponse.isAcknowledged());
+        }
+
+        client.security().createServiceAccountToken(createServiceAccountTokenRequest, RequestOptions.DEFAULT);
+        {
+            DeleteServiceAccountTokenRequest deleteServiceAccountTokenRequest =
+                new DeleteServiceAccountTokenRequest("elastic", "fleet-server", "test-token");
+            ActionListener<DeleteServiceAccountTokenResponse> listener;
+            // tag::delete-service-account-token-execute-listener
+            listener = new ActionListener<DeleteServiceAccountTokenResponse>() {
+                @Override
+                public void onResponse(DeleteServiceAccountTokenResponse deleteServiceAccountTokenResponse) {
+                    // <1>
+                }
+
+                @Override
+                public void onFailure(Exception e) {
+                    // <2>
+                }
+            };
+            // end::delete-service-account-token-execute-listener
+
+            final PlainActionFuture<DeleteServiceAccountTokenResponse> future = new PlainActionFuture<>();
+            listener = future;
+
+            // tag::delete-service-account-token-execute-async
+            client.security().deleteServiceAccountTokenAsync(deleteServiceAccountTokenRequest, RequestOptions.DEFAULT, listener); // <1>
+            // end::delete-service-account-token-execute-async
+
+            assertNotNull(future.actionGet());
+            assertTrue(future.actionGet().isAcknowledged());
+        }
+    }
+
+    public void testGetServiceAccountCredentials() throws IOException {
+        RestHighLevelClient client = highLevelClient();
+        final CreateServiceAccountTokenRequest createServiceAccountTokenRequest =
+            new CreateServiceAccountTokenRequest("elastic", "fleet-server", "token1");
+        final CreateServiceAccountTokenResponse createServiceAccountTokenResponse =
+            client.security().createServiceAccountToken(createServiceAccountTokenRequest, RequestOptions.DEFAULT);
+        assertThat(createServiceAccountTokenResponse.getName(), equalTo("token1"));
+
+        {
+            // tag::get-service-account-credentials-request
+            final GetServiceAccountCredentialsRequest getServiceAccountCredentialsRequest =
+                new GetServiceAccountCredentialsRequest("elastic", "fleet-server");
+            // end::get-service-account-credentials-request
+
+            // tag::get-service-account-credentials-execute
+            final GetServiceAccountCredentialsResponse getServiceAccountCredentialsResponse =
+                client.security().getServiceAccountCredentials(getServiceAccountCredentialsRequest, RequestOptions.DEFAULT);
+            // end::get-service-account-credentials-execute
+
+            // tag::get-service-account-credentials-response
+            final String principal = getServiceAccountCredentialsResponse.getPrincipal(); // <1>
+            final String nodeName = getServiceAccountCredentialsResponse.getNodeName(); // <2>
+            final List<ServiceTokenInfo> serviceTokenInfos = getServiceAccountCredentialsResponse.getServiceTokenInfos(); // <3>
+            final String tokenName = serviceTokenInfos.get(0).getName(); // <4>
+            final String tokenSource = serviceTokenInfos.get(0).getSource(); // <5>
+            // end::get-service-account-credentials-response
+            assertThat(principal, equalTo("elastic/fleet-server"));
+            assertThat(serviceTokenInfos.size(), equalTo(1));
+            assertThat(tokenName, equalTo("token1"));
+            assertThat(tokenSource, equalTo("index"));
+        }
+
+        {
+            final GetServiceAccountCredentialsRequest getServiceAccountCredentialsRequest =
+                new GetServiceAccountCredentialsRequest("elastic", "fleet-server");
+
+            ActionListener<GetServiceAccountCredentialsResponse> listener;
+            // tag::get-service-account-credentials-execute-listener
+            listener = new ActionListener<GetServiceAccountCredentialsResponse>() {
+                @Override
+                public void onResponse(GetServiceAccountCredentialsResponse getServiceAccountCredentialsResponse) {
+                    // <1>
+                }
+
+                @Override
+                public void onFailure(Exception e) {
+                    // <2>
+                }
+            };
+            // end::get-service-account-credentials-execute-listener
+
+            final PlainActionFuture<GetServiceAccountCredentialsResponse> future = new PlainActionFuture<>();
+            listener = future;
+
+            // tag::get-service-account-credentials-execute-async
+            client.security().getServiceAccountCredentialsAsync(
+                getServiceAccountCredentialsRequest, RequestOptions.DEFAULT, listener); // <1>
+            // end::get-service-account-credentials-execute-async
+
+            assertNotNull(future.actionGet());
+            assertThat(future.actionGet().getPrincipal(), equalTo("elastic/fleet-server"));
+            assertThat(future.actionGet().getServiceTokenInfos().size(), equalTo(1));
+            assertThat(future.actionGet().getServiceTokenInfos().get(0), equalTo(new ServiceTokenInfo("token1", "index")));
+        }
+    }
+
     public void testDelegatePkiAuthentication() throws Exception {
         final RestHighLevelClient client = highLevelClient();
         X509Certificate clientCertificate = readCertForPkiDelegation("testClient.crt");

+ 46 - 42
client/rest-high-level/src/test/java/org/elasticsearch/client/security/AuthenticateResponseTests.java

@@ -9,6 +9,7 @@
 package org.elasticsearch.client.security;
 
 import org.elasticsearch.client.security.user.User;
+import org.elasticsearch.common.xcontent.ToXContent;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.test.ESTestCase;
 import org.elasticsearch.test.EqualsHashCodeTestUtils;
@@ -31,8 +32,8 @@ public class AuthenticateResponseTests extends ESTestCase {
             this::toXContent,
             AuthenticateResponse::fromXContent)
             .supportsUnknownFields(true)
-            //metadata is a series of kv pairs, so we dont want to add random fields here for test equality
-            .randomFieldsExcludeFilter(f -> f.startsWith("metadata"))
+            //metadata  and token are a series of kv pairs, so we dont want to add random fields here for test equality
+            .randomFieldsExcludeFilter(f -> f.startsWith("metadata") || f.equals("token"))
             .test();
     }
 
@@ -61,40 +62,32 @@ public class AuthenticateResponseTests extends ESTestCase {
         final String email = randomFrom(random(), null, randomAlphaOfLengthBetween(0, 4));
         final boolean enabled = randomBoolean();
         final String authenticationRealmName = randomAlphaOfLength(5);
-        final String authenticationRealmType = randomFrom("file", "native", "ldap", "active_directory", "saml", "kerberos");
-        final String lookupRealmName = randomAlphaOfLength(5);
-        final String lookupRealmType = randomFrom("file", "native", "ldap", "active_directory", "saml", "kerberos");
+        final String authenticationRealmType = randomFrom(
+            "service_account");
+        final AuthenticateResponse.RealmInfo authenticationRealm =
+            new AuthenticateResponse.RealmInfo(authenticationRealmName, authenticationRealmType);
+
+        final AuthenticateResponse.RealmInfo lookupRealm;
+        final Map<String, Object> tokenInfo;
+        if ("service_account".equals(authenticationRealmType)) {
+            lookupRealm = authenticationRealm;
+            tokenInfo = Map.of("name", randomAlphaOfLengthBetween(3, 8), "type", randomAlphaOfLengthBetween(3, 8));
+        } else {
+            final String lookupRealmName = randomAlphaOfLength(5);
+            final String lookupRealmType = randomFrom("file", "native", "ldap", "active_directory", "saml", "kerberos");
+            lookupRealm = new AuthenticateResponse.RealmInfo(lookupRealmName, lookupRealmType);
+            tokenInfo = null;
+        }
+
         final String authenticationType = randomFrom("realm", "api_key", "token", "anonymous", "internal");
+
         return new AuthenticateResponse(
-            new User(username, roles, metadata, fullName, email), enabled,
-            new AuthenticateResponse.RealmInfo(authenticationRealmName, authenticationRealmType),
-            new AuthenticateResponse.RealmInfo(lookupRealmName, lookupRealmType), authenticationType);
+            new User(username, roles, metadata, fullName, email), enabled, authenticationRealm,
+            lookupRealm, authenticationType, tokenInfo);
     }
 
     private void toXContent(AuthenticateResponse response, XContentBuilder builder) throws IOException {
-        final User user = response.getUser();
-        final boolean enabled = response.enabled();
-        builder.startObject();
-        builder.field(AuthenticateResponse.USERNAME.getPreferredName(), user.getUsername());
-        builder.field(AuthenticateResponse.ROLES.getPreferredName(), user.getRoles());
-        builder.field(AuthenticateResponse.METADATA.getPreferredName(), user.getMetadata());
-        if (user.getFullName() != null) {
-            builder.field(AuthenticateResponse.FULL_NAME.getPreferredName(), user.getFullName());
-        }
-        if (user.getEmail() != null) {
-            builder.field(AuthenticateResponse.EMAIL.getPreferredName(), user.getEmail());
-        }
-        builder.field(AuthenticateResponse.ENABLED.getPreferredName(), enabled);
-        builder.startObject(AuthenticateResponse.AUTHENTICATION_REALM.getPreferredName());
-        builder.field(AuthenticateResponse.REALM_NAME.getPreferredName(), response.getAuthenticationRealm().getName());
-        builder.field(AuthenticateResponse.REALM_TYPE.getPreferredName(), response.getAuthenticationRealm().getType());
-        builder.endObject();
-        builder.startObject(AuthenticateResponse.LOOKUP_REALM.getPreferredName());
-        builder.field(AuthenticateResponse.REALM_NAME.getPreferredName(), response.getLookupRealm().getName());
-        builder.field(AuthenticateResponse.REALM_TYPE.getPreferredName(), response.getLookupRealm().getType());
-        builder.endObject();
-        builder.field(AuthenticateResponse.AUTHENTICATION_TYPE.getPreferredName(), response.getAuthenticationType());
-        builder.endObject();
+        response.toXContent(builder, ToXContent.EMPTY_PARAMS);
     }
 
     private AuthenticateResponse copy(AuthenticateResponse response) {
@@ -102,28 +95,28 @@ public class AuthenticateResponseTests extends ESTestCase {
         final User copyUser = new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
             originalUser.getFullName(), originalUser.getEmail());
         return new AuthenticateResponse(copyUser, response.enabled(), response.getAuthenticationRealm(),
-            response.getLookupRealm(), response.getAuthenticationType());
+            response.getLookupRealm(), response.getAuthenticationType(), Map.copyOf(response.getToken()));
     }
 
     private AuthenticateResponse mutate(AuthenticateResponse response) {
         final User originalUser = response.getUser();
-        switch (randomIntBetween(1, 9)) {
+        switch (randomIntBetween(1, 10)) {
             case 1:
                 return new AuthenticateResponse(new User(originalUser.getUsername() + "wrong", originalUser.getRoles(),
                     originalUser.getMetadata(), originalUser.getFullName(), originalUser.getEmail()), response.enabled(),
-                    response.getAuthenticationRealm(), response.getLookupRealm(), response.getAuthenticationType());
+                    response.getAuthenticationRealm(), response.getLookupRealm(), response.getAuthenticationType(), response.getToken());
             case 2:
                 final List<String> wrongRoles = new ArrayList<>(originalUser.getRoles());
                 wrongRoles.add(randomAlphaOfLengthBetween(1, 4));
                 return new AuthenticateResponse(new User(originalUser.getUsername(), wrongRoles, originalUser.getMetadata(),
                     originalUser.getFullName(), originalUser.getEmail()), response.enabled(), response.getAuthenticationRealm(),
-                    response.getLookupRealm(), response.getAuthenticationType());
+                    response.getLookupRealm(), response.getAuthenticationType(), response.getToken());
             case 3:
                 final Map<String, Object> wrongMetadata = new HashMap<>(originalUser.getMetadata());
                 wrongMetadata.put("wrong_string", randomAlphaOfLengthBetween(0, 4));
                 return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), wrongMetadata,
                     originalUser.getFullName(), originalUser.getEmail()), response.enabled(), response.getAuthenticationRealm(),
-                    response.getLookupRealm(), response.getAuthenticationType());
+                    response.getLookupRealm(), response.getAuthenticationType(), response.getToken());
             case 4:
                 return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
                     originalUser.getFullName() + "wrong", originalUser.getEmail()), response.enabled(),
@@ -131,28 +124,39 @@ public class AuthenticateResponseTests extends ESTestCase {
             case 5:
                 return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
                     originalUser.getFullName(), originalUser.getEmail() + "wrong"), response.enabled(),
-                    response.getAuthenticationRealm(), response.getLookupRealm(), response.getAuthenticationType());
+                    response.getAuthenticationRealm(), response.getLookupRealm(), response.getAuthenticationType(), response.getToken());
             case 6:
                 return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
                     originalUser.getFullName(), originalUser.getEmail()), response.enabled() == false, response.getAuthenticationRealm(),
-                    response.getLookupRealm(), response.getAuthenticationType());
+                    response.getLookupRealm(), response.getAuthenticationType(), response.getToken());
             case 7:
                 return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
                     originalUser.getFullName(), originalUser.getEmail()), response.enabled(), response.getAuthenticationRealm(),
                     new AuthenticateResponse.RealmInfo(randomAlphaOfLength(5), randomAlphaOfLength(5)),
-                    response.getAuthenticationType());
+                    response.getAuthenticationType(), response.getToken());
             case 8:
                 return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
                     originalUser.getFullName(), originalUser.getEmail()), response.enabled(),
                     new AuthenticateResponse.RealmInfo(randomAlphaOfLength(5), randomAlphaOfLength(5)), response.getLookupRealm(),
-                    response.getAuthenticationType());
+                    response.getAuthenticationType(), response.getToken());
             case 9:
                 return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
                     originalUser.getFullName(), originalUser.getEmail()), response.enabled(), response.getAuthenticationRealm(),
                     response.getLookupRealm(),
                     randomValueOtherThan(response.getAuthenticationType(),
-                                         () -> randomFrom("realm", "api_key", "token", "anonymous", "internal")));
+                                         () -> randomFrom("realm", "api_key", "token", "anonymous", "internal")), response.getToken());
+            default:
+                return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
+                    originalUser.getFullName(), originalUser.getEmail()), response.enabled(), response.getAuthenticationRealm(),
+                    response.getLookupRealm(),
+                    response.getAuthenticationType(),
+                    response.getToken() == null ?
+                        Map.of("foo", "bar") :
+                        randomFrom(Map.of(
+                            "name", randomValueOtherThan(response.getToken().get("name"), () -> randomAlphaOfLengthBetween(3, 8)),
+                            "type", randomValueOtherThan(response.getToken().get("type"), () -> randomAlphaOfLengthBetween(3, 8))
+                        ), null));
+
         }
-        throw new IllegalStateException("Bad random number");
     }
 }

+ 65 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/security/ClearServiceAccountTokenCacheRequestTests.java

@@ -0,0 +1,65 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.test.EqualsHashCodeTestUtils;
+
+import java.util.Arrays;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class ClearServiceAccountTokenCacheRequestTests extends ESTestCase {
+
+    public void testNewInstance() {
+        final String namespace = randomAlphaOfLengthBetween(3, 8);
+        final String serviceName = randomAlphaOfLengthBetween(3, 8);
+        final String[] tokenNames = randomArray(0, 3, String[]::new, () -> randomAlphaOfLengthBetween(3, 8));
+
+        final ClearServiceAccountTokenCacheRequest clearServiceAccountTokenCacheRequest =
+            new ClearServiceAccountTokenCacheRequest(namespace, serviceName, tokenNames);
+
+        assertThat(clearServiceAccountTokenCacheRequest.getNamespace(), equalTo(namespace));
+        assertThat(clearServiceAccountTokenCacheRequest.getServiceName(), equalTo(serviceName));
+        assertThat(clearServiceAccountTokenCacheRequest.getTokenNames(), equalTo(tokenNames));
+    }
+
+    public void testEqualsHashCode() {
+        final String namespace = randomAlphaOfLengthBetween(3, 8);
+        final String serviceName = randomAlphaOfLengthBetween(3, 8);
+        final String[] tokenNames = randomArray(0, 3, String[]::new, () -> randomAlphaOfLengthBetween(3, 8));
+
+        final ClearServiceAccountTokenCacheRequest request = new ClearServiceAccountTokenCacheRequest(namespace, serviceName, tokenNames);
+
+        EqualsHashCodeTestUtils.checkEqualsAndHashCode(request,
+            original -> new ClearServiceAccountTokenCacheRequest(request.getNamespace(), request.getServiceName(), request.getTokenNames()),
+            this::mutateInstance);
+    }
+
+    private ClearServiceAccountTokenCacheRequest mutateInstance(ClearServiceAccountTokenCacheRequest request) {
+        switch (randomIntBetween(0, 2)) {
+            case 0:
+                return new ClearServiceAccountTokenCacheRequest(
+                    randomValueOtherThan(request.getNamespace(), () -> randomAlphaOfLengthBetween(3, 8)),
+                    request.getServiceName(),
+                    request.getTokenNames());
+            case 1:
+                return new ClearServiceAccountTokenCacheRequest(
+                    request.getNamespace(),
+                    randomValueOtherThan(request.getServiceName(), () -> randomAlphaOfLengthBetween(3, 8)),
+                    request.getTokenNames());
+            default:
+                return new ClearServiceAccountTokenCacheRequest(
+                    request.getNamespace(),
+                    request.getServiceName(),
+                    randomValueOtherThanMany(a -> Arrays.equals(a, request.getTokenNames()),
+                        () -> randomArray(0, 3, String[]::new, () -> randomAlphaOfLengthBetween(3, 8))));
+        }
+    }
+}

+ 86 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/security/CreateServiceAccountTokenRequestTests.java

@@ -0,0 +1,86 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.test.EqualsHashCodeTestUtils;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class CreateServiceAccountTokenRequestTests extends ESTestCase {
+
+    public void testNewInstance() {
+        final String namespace = randomAlphaOfLengthBetween(3, 8);
+        final String serviceName = randomAlphaOfLengthBetween(3, 8);
+
+        final CreateServiceAccountTokenRequest request1 = new CreateServiceAccountTokenRequest(namespace, serviceName);
+        assertThat(request1.getNamespace(), equalTo(namespace));
+        assertThat(request1.getServiceName(), equalTo(serviceName));
+        assertNull(request1.getTokenName());
+        assertNull(request1.getRefreshPolicy());
+
+        final String tokenName = randomAlphaOfLengthBetween(3, 8);
+        final CreateServiceAccountTokenRequest request2 = new CreateServiceAccountTokenRequest(namespace, serviceName, tokenName);
+        assertThat(request2.getNamespace(), equalTo(namespace));
+        assertThat(request2.getServiceName(), equalTo(serviceName));
+        assertThat(request2.getTokenName(), equalTo(tokenName));
+        assertNull(request2.getRefreshPolicy());
+
+        final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());
+        final CreateServiceAccountTokenRequest request3 =
+            new CreateServiceAccountTokenRequest(namespace, serviceName, tokenName, refreshPolicy);
+        assertThat(request3.getNamespace(), equalTo(namespace));
+        assertThat(request3.getServiceName(), equalTo(serviceName));
+        assertThat(request3.getTokenName(), equalTo(tokenName));
+        assertThat(request3.getRefreshPolicy(), equalTo(refreshPolicy));
+    }
+
+    public void testEqualsHashCode() {
+        final String namespace = randomAlphaOfLengthBetween(3, 8);
+        final String service = randomAlphaOfLengthBetween(3, 8);
+        final String tokenName = randomBoolean() ? randomAlphaOfLengthBetween(3, 8) : null;
+        final RefreshPolicy refreshPolicy = randomBoolean() ? randomFrom(RefreshPolicy.values()) : null;
+
+        final CreateServiceAccountTokenRequest request = new CreateServiceAccountTokenRequest(namespace, service, tokenName, refreshPolicy);
+
+        EqualsHashCodeTestUtils.checkEqualsAndHashCode(request,
+            original -> new CreateServiceAccountTokenRequest(
+                request.getNamespace(), request.getServiceName(), request.getTokenName(), request.getRefreshPolicy()),
+            this::mutateInstance);
+    }
+
+    private CreateServiceAccountTokenRequest mutateInstance(CreateServiceAccountTokenRequest request) {
+        switch (randomIntBetween(0, 3)) {
+            case 0:
+                return new CreateServiceAccountTokenRequest(
+                    randomValueOtherThan(request.getNamespace(), () -> randomAlphaOfLengthBetween(3, 8)),
+                    request.getServiceName(),
+                    request.getTokenName(),
+                    request.getRefreshPolicy());
+            case 1:
+                return new CreateServiceAccountTokenRequest(
+                    request.getNamespace(),
+                    randomValueOtherThan(request.getServiceName(), () -> randomAlphaOfLengthBetween(3, 8)),
+                    request.getTokenName(),
+                    request.getRefreshPolicy());
+            case 2:
+                return new CreateServiceAccountTokenRequest(
+                    request.getNamespace(),
+                    request.getServiceName(),
+                    randomValueOtherThan(request.getTokenName(), () -> randomAlphaOfLengthBetween(3, 8)),
+                    request.getRefreshPolicy());
+            default:
+                return new CreateServiceAccountTokenRequest(
+                    request.getNamespace(),
+                    request.getServiceName(),
+                    request.getTokenName(),
+                    randomValueOtherThan(request.getRefreshPolicy(), () -> randomFrom(RefreshPolicy.values())));
+        }
+    }
+}

+ 45 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/security/CreateServiceAccountTokenResponseTests.java

@@ -0,0 +1,45 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.client.AbstractResponseTestCase;
+import org.elasticsearch.common.settings.SecureString;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentType;
+
+import java.io.IOException;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class CreateServiceAccountTokenResponseTests
+    extends AbstractResponseTestCase<org.elasticsearch.xpack.core.security.action.service.CreateServiceAccountTokenResponse,
+    CreateServiceAccountTokenResponse> {
+
+    @Override
+    protected org.elasticsearch.xpack.core.security.action.service.CreateServiceAccountTokenResponse createServerTestInstance(
+        XContentType xContentType) {
+        final String tokenName = randomAlphaOfLengthBetween(3, 8);
+        final String value = randomAlphaOfLength(22);
+        return org.elasticsearch.xpack.core.security.action.service.CreateServiceAccountTokenResponse.created(
+            tokenName, new SecureString(value.toCharArray()));
+    }
+
+    @Override
+    protected CreateServiceAccountTokenResponse doParseToClientInstance(XContentParser parser) throws IOException {
+        return CreateServiceAccountTokenResponse.fromXContent(parser);
+    }
+
+    @Override
+    protected void assertInstances(
+        org.elasticsearch.xpack.core.security.action.service.CreateServiceAccountTokenResponse serverTestInstance,
+        CreateServiceAccountTokenResponse clientInstance) {
+        assertThat(serverTestInstance.getName(), equalTo(clientInstance.getName()));
+        assertThat(serverTestInstance.getValue(), equalTo(clientInstance.getValue()));
+    }
+}

+ 82 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/security/DeleteServiceAccountTokenRequestTests.java

@@ -0,0 +1,82 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.test.EqualsHashCodeTestUtils;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class DeleteServiceAccountTokenRequestTests extends ESTestCase {
+
+    public void testNewInstance() {
+        final String namespace = randomAlphaOfLengthBetween(3, 8);
+        final String serviceName = randomAlphaOfLengthBetween(3, 8);
+        final String tokenName = randomAlphaOfLengthBetween(3, 8);
+
+        final DeleteServiceAccountTokenRequest request1 = new DeleteServiceAccountTokenRequest(namespace, serviceName, tokenName);
+        assertThat(request1.getNamespace(), equalTo(namespace));
+        assertThat(request1.getServiceName(), equalTo(serviceName));
+        assertThat(request1.getTokenName(), equalTo(tokenName));
+        assertNull(request1.getRefreshPolicy());
+
+        final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());
+        final DeleteServiceAccountTokenRequest request2 =
+            new DeleteServiceAccountTokenRequest(namespace, serviceName, tokenName, refreshPolicy);
+        assertThat(request2.getNamespace(), equalTo(namespace));
+        assertThat(request2.getServiceName(), equalTo(serviceName));
+        assertThat(request2.getTokenName(), equalTo(tokenName));
+        assertThat(request2.getRefreshPolicy(), equalTo(refreshPolicy));
+    }
+
+    public void testEqualsHashCode() {
+        final String namespace = randomAlphaOfLengthBetween(3, 8);
+        final String serviceName = randomAlphaOfLengthBetween(3, 8);
+        final String tokenName = randomAlphaOfLengthBetween(3, 8);
+        final RefreshPolicy refreshPolicy = randomBoolean() ? randomFrom(RefreshPolicy.values()) : null;
+
+        final DeleteServiceAccountTokenRequest request =
+            new DeleteServiceAccountTokenRequest(namespace, serviceName, tokenName, refreshPolicy);
+
+        EqualsHashCodeTestUtils.checkEqualsAndHashCode(request,
+            original -> new DeleteServiceAccountTokenRequest(
+                request.getNamespace(), request.getServiceName(), request.getTokenName(), request.getRefreshPolicy()),
+            this::mutateInstance);
+    }
+
+    private DeleteServiceAccountTokenRequest mutateInstance(DeleteServiceAccountTokenRequest request) {
+        switch (randomIntBetween(0, 3)) {
+            case 0:
+                return new DeleteServiceAccountTokenRequest(
+                    randomValueOtherThan(request.getNamespace(), () -> randomAlphaOfLengthBetween(3, 8)),
+                    request.getServiceName(),
+                    request.getTokenName(),
+                    request.getRefreshPolicy());
+            case 1:
+                return new DeleteServiceAccountTokenRequest(
+                    request.getNamespace(),
+                    randomValueOtherThan(request.getServiceName(), () -> randomAlphaOfLengthBetween(3, 8)),
+                    request.getTokenName(),
+                    request.getRefreshPolicy());
+            case 2:
+                return new DeleteServiceAccountTokenRequest(
+                    request.getNamespace(),
+                    request.getServiceName(),
+                    randomValueOtherThan(request.getTokenName(), () -> randomAlphaOfLengthBetween(3, 8)),
+                    request.getRefreshPolicy());
+            default:
+                return new DeleteServiceAccountTokenRequest(
+                    request.getNamespace(),
+                    request.getServiceName(),
+                    request.getTokenName(),
+                    randomValueOtherThan(request.getRefreshPolicy(), () -> randomFrom(RefreshPolicy.values())));
+        }
+    }
+
+}

+ 40 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/security/DeleteServiceAccountTokenResponseTests.java

@@ -0,0 +1,40 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.client.AbstractResponseTestCase;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentType;
+
+import java.io.IOException;
+
+import static org.hamcrest.Matchers.is;
+
+public class DeleteServiceAccountTokenResponseTests
+    extends AbstractResponseTestCase<org.elasticsearch.xpack.core.security.action.service.DeleteServiceAccountTokenResponse,
+    DeleteServiceAccountTokenResponse> {
+
+    @Override
+    protected org.elasticsearch.xpack.core.security.action.service.DeleteServiceAccountTokenResponse createServerTestInstance(
+        XContentType xContentType) {
+        return new org.elasticsearch.xpack.core.security.action.service.DeleteServiceAccountTokenResponse(randomBoolean());
+    }
+
+    @Override
+    protected DeleteServiceAccountTokenResponse doParseToClientInstance(XContentParser parser) throws IOException {
+        return DeleteServiceAccountTokenResponse.fromXContent(parser);
+    }
+
+    @Override
+    protected void assertInstances(
+        org.elasticsearch.xpack.core.security.action.service.DeleteServiceAccountTokenResponse serverTestInstance,
+        DeleteServiceAccountTokenResponse clientInstance) {
+        assertThat(serverTestInstance.found(), is(clientInstance.isAcknowledged()));
+    }
+}

+ 47 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetServiceAccountCredentialsRequestTests.java

@@ -0,0 +1,47 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.test.EqualsHashCodeTestUtils;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class GetServiceAccountCredentialsRequestTests extends ESTestCase {
+
+    public void testNewInstance() {
+        final String namespace = randomAlphaOfLengthBetween(3, 8);
+        final String serviceName = randomAlphaOfLengthBetween(3, 8);
+
+        final GetServiceAccountCredentialsRequest request = new GetServiceAccountCredentialsRequest(namespace, serviceName);
+        assertThat(request.getNamespace(), equalTo(namespace));
+        assertThat(request.getServiceName(), equalTo(serviceName));
+    }
+
+    public void testEqualsHashCode() {
+        final String namespace = randomAlphaOfLengthBetween(3, 8);
+        final String serviceName = randomAlphaOfLengthBetween(3, 8);
+        final GetServiceAccountCredentialsRequest request = new GetServiceAccountCredentialsRequest(namespace, serviceName);
+
+        EqualsHashCodeTestUtils.checkEqualsAndHashCode(request,
+            original -> new GetServiceAccountCredentialsRequest(request.getNamespace(), request.getServiceName()),
+            this::mutateInstance);
+    }
+
+    private GetServiceAccountCredentialsRequest mutateInstance(GetServiceAccountCredentialsRequest request) {
+        switch (randomIntBetween(0, 1)) {
+            case 0:
+                return new GetServiceAccountCredentialsRequest(randomValueOtherThan(request.getNamespace(),
+                    () -> randomAlphaOfLengthBetween(3, 8)), request.getServiceName());
+            default:
+                return new GetServiceAccountCredentialsRequest(request.getNamespace(),
+                    randomValueOtherThan(request.getServiceName(), () -> randomAlphaOfLengthBetween(3, 8)));
+        }
+    }
+}

+ 61 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetServiceAccountCredentialsResponseTests.java

@@ -0,0 +1,61 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.client.AbstractResponseTestCase;
+import org.elasticsearch.core.Tuple;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentType;
+import org.elasticsearch.xpack.core.security.action.service.TokenInfo;
+
+import java.io.IOException;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class GetServiceAccountCredentialsResponseTests
+    extends AbstractResponseTestCase<org.elasticsearch.xpack.core.security.action.service.GetServiceAccountCredentialsResponse,
+    GetServiceAccountCredentialsResponse> {
+
+    @Override
+    protected org.elasticsearch.xpack.core.security.action.service.GetServiceAccountCredentialsResponse createServerTestInstance(
+        XContentType xContentType) {
+        return new org.elasticsearch.xpack.core.security.action.service.GetServiceAccountCredentialsResponse(
+            randomAlphaOfLengthBetween(3, 8) + "/" + randomAlphaOfLengthBetween(3, 8),
+            randomAlphaOfLengthBetween(3, 8), randomList(
+            1,
+            5,
+            () -> randomBoolean() ?
+                TokenInfo.fileToken(randomAlphaOfLengthBetween(3, 8)) :
+                TokenInfo.indexToken(randomAlphaOfLengthBetween(3, 8)))
+        );
+    }
+
+    @Override
+    protected GetServiceAccountCredentialsResponse doParseToClientInstance(XContentParser parser) throws IOException {
+        return GetServiceAccountCredentialsResponse.fromXContent(parser);
+    }
+
+    @Override
+    protected void assertInstances(
+        org.elasticsearch.xpack.core.security.action.service.GetServiceAccountCredentialsResponse serverTestInstance,
+        GetServiceAccountCredentialsResponse clientInstance) {
+        assertThat(serverTestInstance.getPrincipal(), equalTo(clientInstance.getPrincipal()));
+        assertThat(serverTestInstance.getNodeName(), equalTo(clientInstance.getNodeName()));
+
+        assertThat(
+            serverTestInstance.getTokenInfos().stream()
+                .map(tokenInfo -> new Tuple<>(tokenInfo.getName(), tokenInfo.getSource().name().toLowerCase(Locale.ROOT)))
+                .collect(Collectors.toSet()),
+            equalTo(clientInstance.getServiceTokenInfos().stream()
+                .map(info -> new Tuple<>(info.getName(), info.getSource()))
+                .collect(Collectors.toSet())));
+    }
+}

+ 64 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetServiceAccountsRequestTests.java

@@ -0,0 +1,64 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.client.ValidationException;
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.test.EqualsHashCodeTestUtils;
+
+import java.util.Optional;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
+
+public class GetServiceAccountsRequestTests extends ESTestCase {
+
+    public void testNewInstance() {
+        final String namespace = randomAlphaOfLengthBetween(3, 8);
+        final String serviceName = randomAlphaOfLengthBetween(3, 8);
+
+        final GetServiceAccountsRequest request1 = new GetServiceAccountsRequest(namespace, serviceName);
+        assertThat(request1.getNamespace(), equalTo(namespace));
+        assertThat(request1.getServiceName(), equalTo(serviceName));
+
+        final GetServiceAccountsRequest request2 = new GetServiceAccountsRequest(namespace);
+        assertThat(request2.getNamespace(), equalTo(namespace));
+        assertNull(request2.getServiceName());
+
+        final GetServiceAccountsRequest request3 = new GetServiceAccountsRequest();
+        assertNull(request3.getNamespace());
+        assertNull(request3.getServiceName());
+
+        final GetServiceAccountsRequest request4 = new GetServiceAccountsRequest(null, namespace);
+        final Optional<ValidationException> validationException = request4.validate();
+        assertTrue(validationException.isPresent());
+        assertThat(validationException.get().getMessage(), containsString("cannot specify service-name without namespace"));
+    }
+
+    public void testEqualsHashCode() {
+        final String namespace = randomBoolean() ? randomAlphaOfLengthBetween(3, 8) : null;
+        final String serviceName = namespace == null ? null : (randomBoolean() ? randomAlphaOfLengthBetween(3, 8) : null);
+
+        final GetServiceAccountsRequest request = new GetServiceAccountsRequest(namespace, serviceName);
+        EqualsHashCodeTestUtils.checkEqualsAndHashCode(request,
+            original -> new GetServiceAccountsRequest(request.getNamespace(), request.getServiceName()),
+            this::mutateInstance);
+    }
+
+    private GetServiceAccountsRequest mutateInstance(GetServiceAccountsRequest request) {
+        switch (randomIntBetween(0, 1)) {
+            case 0:
+                return new GetServiceAccountsRequest(randomValueOtherThan(request.getNamespace(),
+                    () -> randomAlphaOfLengthBetween(3, 8)), request.getServiceName());
+            default:
+                return new GetServiceAccountsRequest(request.getNamespace(),
+                    randomValueOtherThan(request.getServiceName(), () -> randomAlphaOfLengthBetween(3, 8)));
+        }
+    }
+}

+ 87 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetServiceAccountsResponseTests.java

@@ -0,0 +1,87 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.client.AbstractResponseTestCase;
+import org.elasticsearch.client.security.support.ServiceAccountInfo;
+import org.elasticsearch.client.security.user.privileges.IndicesPrivileges;
+import org.elasticsearch.client.security.user.privileges.Role;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentType;
+import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
+
+import java.io.IOException;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class GetServiceAccountsResponseTests
+    extends AbstractResponseTestCase<org.elasticsearch.xpack.core.security.action.service.GetServiceAccountResponse,
+    GetServiceAccountsResponse> {
+
+    @Override
+    protected org.elasticsearch.xpack.core.security.action.service.GetServiceAccountResponse createServerTestInstance(
+        XContentType xContentType) {
+        final String principal = randomAlphaOfLengthBetween(3, 8) + "/" + randomAlphaOfLengthBetween(3, 8);
+        return new org.elasticsearch.xpack.core.security.action.service.GetServiceAccountResponse(
+            new org.elasticsearch.xpack.core.security.action.service.ServiceAccountInfo[]{
+                new org.elasticsearch.xpack.core.security.action.service.ServiceAccountInfo(principal,
+                    new RoleDescriptor(principal,
+                        randomArray(1, 3, String[]::new, () -> randomAlphaOfLengthBetween(3, 8)),
+                        new RoleDescriptor.IndicesPrivileges[]{
+                            RoleDescriptor.IndicesPrivileges.builder()
+                                .indices(randomArray(1, 5, String[]::new, () -> randomAlphaOfLengthBetween(3, 8)))
+                                .privileges(randomArray(1, 3, String[]::new, () -> randomAlphaOfLengthBetween(3, 8)))
+                                .build(),
+                            RoleDescriptor.IndicesPrivileges.builder()
+                                .indices(randomArray(1, 5, String[]::new, () -> randomAlphaOfLengthBetween(3, 8)))
+                                .privileges(randomArray(1, 3, String[]::new, () -> randomAlphaOfLengthBetween(3, 8)))
+                                .build()
+                        },
+                        Strings.EMPTY_ARRAY
+                    )
+                )
+            });
+    }
+
+    @Override
+    protected GetServiceAccountsResponse doParseToClientInstance(XContentParser parser) throws IOException {
+        return GetServiceAccountsResponse.fromXContent(parser);
+    }
+
+    @Override
+    protected void assertInstances(
+        org.elasticsearch.xpack.core.security.action.service.GetServiceAccountResponse serverTestInstance,
+        GetServiceAccountsResponse clientInstance) {
+        final org.elasticsearch.xpack.core.security.action.service.ServiceAccountInfo serverTestInstanceServiceAccountInfo =
+            serverTestInstance.getServiceAccountInfos()[0];
+        final String principal = serverTestInstanceServiceAccountInfo.getPrincipal();
+        final RoleDescriptor roleDescriptor = serverTestInstanceServiceAccountInfo.getRoleDescriptor();
+
+        assertThat(clientInstance.getServiceAccountInfos().size(), equalTo(1));
+        final ServiceAccountInfo serviceAccountInfo = clientInstance.getServiceAccountInfos().get(0);
+        assertThat(serviceAccountInfo.getPrincipal(), equalTo(principal));
+        assertThat(serviceAccountInfo.getRole(), equalTo(
+            Role.builder()
+                .name("role_descriptor")
+                .clusterPrivileges(roleDescriptor.getClusterPrivileges())
+                .indicesPrivileges(
+                    IndicesPrivileges.builder()
+                        .indices(roleDescriptor.getIndicesPrivileges()[0].getIndices())
+                        .privileges(roleDescriptor.getIndicesPrivileges()[0].getPrivileges())
+                        .build(),
+                    IndicesPrivileges.builder()
+                        .indices(roleDescriptor.getIndicesPrivileges()[1].getIndices())
+                        .privileges(roleDescriptor.getIndicesPrivileges()[1].getPrivileges())
+                        .build()
+                )
+                .build()
+        ));
+    }
+}

+ 4 - 0
docs/java-rest/high-level/security/authenticate.asciidoc

@@ -45,6 +45,10 @@ see {javadoc-client}/security/user/User.html.
 <5> `getLookupRealm().getName()` retrieves the name of the realm from where the user information is looked up.
 <6> `getLookupRealm().getType()` retrieves the type of the realm from where the user information is looked up.
 <7> `getAuthenticationType()` retrieves the authentication type of the authenticated user.
+<8> `getMetadata()` retrieves metadata relevant to this authentication.
+Note this is different from `user.getMetadata()`.
+For <<{upid}-create-service-account-token,service account token>> authentication, it contains
+a key of `_token_name` with the value being the token name.
 
 [id="{upid}-{api}-async"]
 ==== Asynchronous Execution

+ 37 - 0
docs/java-rest/high-level/security/clear-service-account-token-cache.asciidoc

@@ -0,0 +1,37 @@
+
+--
+:api: clear-service-account-token-cache
+:request: ClearServiceAccountTokenCacheRequest
+:response: ClearSecurityCacheResponse
+--
+[role="xpack"]
+[id="{upid}-{api}"]
+=== Clear Service Account Token Cache API
+
+[id="{upid}-{api}-request"]
+==== Clear Service Account Token Cache Request
+
+A +{request}+ supports clearing service account token cache for the given
+namespace, service name and token names.
+It can also clear the entire cache if a `*` is specified for the token name.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request]
+--------------------------------------------------
+<1> Namespace of the service account
+<2> Service name of the service account
+<3> Name(s) for the service account token to be evicted from the cache
+
+include::../execution.asciidoc[]
+
+[id="{upid}-{api}-response"]
+==== Clear Service Account Token Cache Response
+
+The returned +{response}+ allows to retrieve information about where the cache was cleared.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-response]
+--------------------------------------------------
+<1> the list of nodes that the cache was cleared on

+ 42 - 0
docs/java-rest/high-level/security/create-service-account-token.asciidoc

@@ -0,0 +1,42 @@
+--
+:api: create-service-account-token
+:request: CreateServiceAccountTokenRequest
+:response: CreateServiceAccountTokenResponse
+--
+[role="xpack"]
+[id="{upid}-{api}"]
+=== Create Service Account Token API
+
+Index-based service account token can be created using this API.
+
+[id="{upid}-{api}-request"]
+==== Create Service Account Token Request
+
+A +{request}+ contains the namespace and service-name of a
+service account and an optional name for the service account token.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request]
+--------------------------------------------------
+
+A token name will be auto generated if the +{request}+ does not specify it:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request-auto-name]
+--------------------------------------------------
+
+include::../execution.asciidoc[]
+
+[id="{upid}-{api}-response"]
+==== Create Service Account Token Response
+
+The returned +{response}+ contains the name and value of the service account token.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-response]
+--------------------------------------------------
+<1> Name of the service account token
+<2> Value of the service account token to be used as the bearer authentication header

+ 35 - 0
docs/java-rest/high-level/security/delete-service-account-token.asciidoc

@@ -0,0 +1,35 @@
+--
+:api: delete-service-account-token
+:request: DeleteServiceAccountTokenRequest
+:response: DeleteServiceAccountTokenResponse
+--
+[role="xpack"]
+[id="{upid}-{api}"]
+=== Delete Service Account Token API
+
+Index-based service account token can be deleted using this API.
+
+[id="{upid}-{api}-request"]
+==== Delete Service Account Token Request
+
+A +{request}+ contains the namespace, service-name and token name of a
+service account token.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request]
+--------------------------------------------------
+
+include::../execution.asciidoc[]
+
+[id="{upid}-{api}-response"]
+==== Delete Service Account Token Response
+
+The returned +{response}+ allows to retrieve information about the executed
+operation as follows:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-response]
+--------------------------------------------------
+<1> whether the given service account token was found

+ 38 - 0
docs/java-rest/high-level/security/get-service-account-credentials.asciidoc

@@ -0,0 +1,38 @@
+
+--
+:api: get-service-account-credentials
+:request: GetServiceAccountCredentialsRequest
+:response: GetServiceAccountCredentialsResponse
+--
+[role="xpack"]
+[id="{upid}-{api}"]
+=== Get Service Account Credentials API
+
+[id="{upid}-{api}-request"]
+==== Get Service Account Credentials Request
+
+Retrieving all credentials for a service account can be performed by setting the namespace
+and service-name on +{request}+:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request]
+--------------------------------------------------
+
+include::../execution.asciidoc[]
+
+[id="{upid}-{api}-response"]
+==== Get Service Account Credentials Response
+
+The returned +{response}+ contains a list of service account tokens for the requested service account.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-response]
+--------------------------------------------------
+<1> Principal of the service account
+<2> Name of the node that processed the request. Information of file service tokens is only collected from this node.
+<3> List of service token information
+<4> Name of the first service account token
+<5> Source of the first service account token. The value is either `file` or `index`.
+

+ 49 - 0
docs/java-rest/high-level/security/get-service-accounts.asciidoc

@@ -0,0 +1,49 @@
+
+--
+:api: get-service-accounts
+:request: GetServiceAccountsRequest
+:response: GetServiceAccountsResponse
+--
+[role="xpack"]
+[id="{upid}-{api}"]
+=== Get Service Accounts API
+
+[id="{upid}-{api}-request"]
+==== Get Service Accounts Request
+
+Retrieving a service account can be performed by setting the namespace
+and service-name on +{request}+:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request]
+--------------------------------------------------
+
+Retrieving service accounts belong to a namespace can be performed
+by just setting the namespace on +{request}+:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request-namespace]
+--------------------------------------------------
+
+Retrieving all service accounts can be performed without specifying
+either namespace or service-name on +{request}+:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request-all]
+--------------------------------------------------
+
+include::../execution.asciidoc[]
+
+[id="{upid}-{api}-response"]
+==== Get Service Accounts Response
+
+The returned +{response}+ allows getting information about the retrieved service accounts as follows.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-response]
+--------------------------------------------------
+<1> Information for the requested service account

+ 10 - 0
docs/java-rest/high-level/supported-apis.asciidoc

@@ -487,6 +487,7 @@ The Java High Level REST Client supports the following Security APIs:
 * <<{upid}-clear-privileges-cache>>
 * <<{upid}-clear-realm-cache>>
 * <<{upid}-clear-api-key-cache>>
+* <<{upid}-clear-service-account-token-cache>>
 * <<{upid}-authenticate>>
 * <<{upid}-has-privileges>>
 * <<{upid}-get-user-privileges>>
@@ -503,6 +504,10 @@ The Java High Level REST Client supports the following Security APIs:
 * <<{upid}-create-api-key>>
 * <<{upid}-get-api-key>>
 * <<{upid}-invalidate-api-key>>
+* <<{upid}-get-service-accounts>>
+* <<{upid}-create-service-account-token>>
+* <<{upid}-delete-service-account-token>>
+* <<{upid}-get-service-account-credentials>>
 
 include::security/put-user.asciidoc[]
 include::security/get-users.asciidoc[]
@@ -520,6 +525,7 @@ include::security/clear-roles-cache.asciidoc[]
 include::security/clear-privileges-cache.asciidoc[]
 include::security/clear-realm-cache.asciidoc[]
 include::security/clear-api-key-cache.asciidoc[]
+include::security/clear-service-account-token-cache.asciidoc[]
 include::security/authenticate.asciidoc[]
 include::security/has-privileges.asciidoc[]
 include::security/get-user-privileges.asciidoc[]
@@ -534,6 +540,10 @@ include::security/create-api-key.asciidoc[]
 include::security/grant-api-key.asciidoc[]
 include::security/get-api-key.asciidoc[]
 include::security/invalidate-api-key.asciidoc[]
+include::security/get-service-accounts.asciidoc[]
+include::security/create-service-account-token.asciidoc[]
+include::security/delete-service-account-token.asciidoc[]
+include::security/get-service-account-credentials.asciidoc[]
 
 [role="xpack"]
 == Text Structure APIs

+ 2 - 5
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/service/CreateServiceAccountTokenResponse.java

@@ -8,7 +8,6 @@
 package org.elasticsearch.xpack.core.security.action.service;
 
 import org.elasticsearch.action.ActionResponse;
-import org.elasticsearch.core.Nullable;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.settings.SecureString;
@@ -20,12 +19,10 @@ import java.util.Objects;
 
 public class CreateServiceAccountTokenResponse extends ActionResponse implements ToXContentObject {
 
-    @Nullable
     private final String name;
-    @Nullable
     private final SecureString value;
 
-    private CreateServiceAccountTokenResponse(boolean created, String name, SecureString value) {
+    private CreateServiceAccountTokenResponse(String name, SecureString value) {
         this.name = name;
         this.value = value;
     }
@@ -79,6 +76,6 @@ public class CreateServiceAccountTokenResponse extends ActionResponse implements
     }
 
     public static CreateServiceAccountTokenResponse created(String name, SecureString value) {
-        return new CreateServiceAccountTokenResponse(true, name, value);
+        return new CreateServiceAccountTokenResponse(name, value);
     }
 }