Token API supports the client_credentials grant (#33106)
This change adds support for the client credentials grant type to the token api. The client credentials grant allows for a client to authenticate with the authorization server and obtain a token to access as itself. Per RFC 6749, a refresh token should not be included with the access token and as such a refresh token is not issued when the client credentials grant is used. The addition of the client credentials grant will allow users authenticated with mechanisms such as kerberos or PKI to obtain a token that can be used for subsequent access.
This commit is contained in:
parent
309fb22181
commit
5d9c270608
|
@ -38,16 +38,19 @@ The following parameters can be specified in the body of a POST request and
|
||||||
pertain to creating a token:
|
pertain to creating a token:
|
||||||
|
|
||||||
`grant_type`::
|
`grant_type`::
|
||||||
(string) The type of grant. Valid grant types are: `password` and `refresh_token`.
|
(string) The type of grant. Supported grant types are: `password`,
|
||||||
|
`client_credentials` and `refresh_token`.
|
||||||
|
|
||||||
`password`::
|
`password`::
|
||||||
(string) The user's password. If you specify the `password` grant type, this
|
(string) The user's password. If you specify the `password` grant type, this
|
||||||
parameter is required.
|
parameter is required. This parameter is not valid with any other supported
|
||||||
|
grant type.
|
||||||
|
|
||||||
`refresh_token`::
|
`refresh_token`::
|
||||||
(string) If you specify the `refresh_token` grant type, this parameter is
|
(string) If you specify the `refresh_token` grant type, this parameter is
|
||||||
required. It contains the string that was returned when you created the token
|
required. It contains the string that was returned when you created the token
|
||||||
and enables you to extend its life.
|
and enables you to extend its life. This parameter is not valid with any other
|
||||||
|
supported grant type.
|
||||||
|
|
||||||
`scope`::
|
`scope`::
|
||||||
(string) The scope of the token. Currently tokens are only issued for a scope of
|
(string) The scope of the token. Currently tokens are only issued for a scope of
|
||||||
|
@ -55,11 +58,48 @@ and enables you to extend its life.
|
||||||
|
|
||||||
`username`::
|
`username`::
|
||||||
(string) The username that identifies the user. If you specify the `password`
|
(string) The username that identifies the user. If you specify the `password`
|
||||||
grant type, this parameter is required.
|
grant type, this parameter is required. This parameter is not valid with any
|
||||||
|
other supported grant type.
|
||||||
|
|
||||||
==== Examples
|
==== Examples
|
||||||
|
|
||||||
The following example obtains a token for the `test_admin` user:
|
The following example obtains a token using the `client_credentials` grant type,
|
||||||
|
which simply creates a token as the authenticated user:
|
||||||
|
|
||||||
|
[source,js]
|
||||||
|
--------------------------------------------------
|
||||||
|
POST /_xpack/security/oauth2/token
|
||||||
|
{
|
||||||
|
"grant_type" : "client_credentials"
|
||||||
|
}
|
||||||
|
--------------------------------------------------
|
||||||
|
// CONSOLE
|
||||||
|
|
||||||
|
The following example output contains the access token, the amount of time (in
|
||||||
|
seconds) that the token expires in, and the type:
|
||||||
|
|
||||||
|
[source,js]
|
||||||
|
--------------------------------------------------
|
||||||
|
{
|
||||||
|
"access_token" : "dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==",
|
||||||
|
"type" : "Bearer",
|
||||||
|
"expires_in" : 1200
|
||||||
|
}
|
||||||
|
--------------------------------------------------
|
||||||
|
// TESTRESPONSE[s/dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==/$body.access_token/]
|
||||||
|
|
||||||
|
The token returned by this API can be used by sending a request with a
|
||||||
|
`Authorization` header with a value having the prefix `Bearer ` followed
|
||||||
|
by the value of the `access_token`.
|
||||||
|
|
||||||
|
[source,shell]
|
||||||
|
--------------------------------------------------
|
||||||
|
curl -H "Authorization: Bearer dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==" http://localhost:9200/_cluster/health
|
||||||
|
--------------------------------------------------
|
||||||
|
// NOTCONSOLE
|
||||||
|
|
||||||
|
The following example obtains a token for the `test_admin` user using the
|
||||||
|
`password` grant type:
|
||||||
|
|
||||||
[source,js]
|
[source,js]
|
||||||
--------------------------------------------------
|
--------------------------------------------------
|
||||||
|
@ -73,7 +113,7 @@ POST /_xpack/security/oauth2/token
|
||||||
// CONSOLE
|
// CONSOLE
|
||||||
|
|
||||||
The following example output contains the access token, the amount of time (in
|
The following example output contains the access token, the amount of time (in
|
||||||
seconds) that the token expires in, and the type:
|
seconds) that the token expires in, the type, and the refresh token:
|
||||||
|
|
||||||
[source,js]
|
[source,js]
|
||||||
--------------------------------------------------
|
--------------------------------------------------
|
||||||
|
@ -87,19 +127,10 @@ seconds) that the token expires in, and the type:
|
||||||
// TESTRESPONSE[s/dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==/$body.access_token/]
|
// TESTRESPONSE[s/dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==/$body.access_token/]
|
||||||
// TESTRESPONSE[s/vLBPvmAB6KvwvJZr27cS/$body.refresh_token/]
|
// TESTRESPONSE[s/vLBPvmAB6KvwvJZr27cS/$body.refresh_token/]
|
||||||
|
|
||||||
The token returned by this API can be used by sending a request with a
|
|
||||||
`Authorization` header with a value having the prefix `Bearer ` followed
|
|
||||||
by the value of the `access_token`.
|
|
||||||
|
|
||||||
[source,shell]
|
|
||||||
--------------------------------------------------
|
|
||||||
curl -H "Authorization: Bearer dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==" http://localhost:9200/_cluster/health
|
|
||||||
--------------------------------------------------
|
|
||||||
// NOTCONSOLE
|
|
||||||
|
|
||||||
[[security-api-refresh-token]]
|
[[security-api-refresh-token]]
|
||||||
To extend the life of an existing token, you can call the API again with the
|
To extend the life of an existing token obtained using the `password` grant type,
|
||||||
refresh token within 24 hours of the token's creation. For example:
|
you can call the API again with the refresh token within 24 hours of the token's
|
||||||
|
creation. For example:
|
||||||
|
|
||||||
[source,js]
|
[source,js]
|
||||||
--------------------------------------------------
|
--------------------------------------------------
|
||||||
|
|
|
@ -19,6 +19,10 @@ import org.elasticsearch.common.CharArrays;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.elasticsearch.action.ValidateActions.addValidationError;
|
import static org.elasticsearch.action.ValidateActions.addValidationError;
|
||||||
|
|
||||||
|
@ -29,6 +33,37 @@ import static org.elasticsearch.action.ValidateActions.addValidationError;
|
||||||
*/
|
*/
|
||||||
public final class CreateTokenRequest extends ActionRequest {
|
public final class CreateTokenRequest extends ActionRequest {
|
||||||
|
|
||||||
|
public enum GrantType {
|
||||||
|
PASSWORD("password"),
|
||||||
|
REFRESH_TOKEN("refresh_token"),
|
||||||
|
AUTHORIZATION_CODE("authorization_code"),
|
||||||
|
CLIENT_CREDENTIALS("client_credentials");
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
GrantType(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GrantType fromString(String grantType) {
|
||||||
|
if (grantType != null) {
|
||||||
|
for (GrantType type : values()) {
|
||||||
|
if (type.getValue().equals(grantType)) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Set<GrantType> SUPPORTED_GRANT_TYPES = Collections.unmodifiableSet(
|
||||||
|
EnumSet.of(GrantType.PASSWORD, GrantType.REFRESH_TOKEN, GrantType.CLIENT_CREDENTIALS));
|
||||||
|
|
||||||
private String grantType;
|
private String grantType;
|
||||||
private String username;
|
private String username;
|
||||||
private SecureString password;
|
private SecureString password;
|
||||||
|
@ -49,33 +84,58 @@ public final class CreateTokenRequest extends ActionRequest {
|
||||||
@Override
|
@Override
|
||||||
public ActionRequestValidationException validate() {
|
public ActionRequestValidationException validate() {
|
||||||
ActionRequestValidationException validationException = null;
|
ActionRequestValidationException validationException = null;
|
||||||
if ("password".equals(grantType)) {
|
GrantType type = GrantType.fromString(grantType);
|
||||||
if (Strings.isNullOrEmpty(username)) {
|
if (type != null) {
|
||||||
validationException = addValidationError("username is missing", validationException);
|
switch (type) {
|
||||||
}
|
case PASSWORD:
|
||||||
if (password == null || password.getChars() == null || password.getChars().length == 0) {
|
if (Strings.isNullOrEmpty(username)) {
|
||||||
validationException = addValidationError("password is missing", validationException);
|
validationException = addValidationError("username is missing", validationException);
|
||||||
}
|
}
|
||||||
if (refreshToken != null) {
|
if (password == null || password.getChars() == null || password.getChars().length == 0) {
|
||||||
validationException =
|
validationException = addValidationError("password is missing", validationException);
|
||||||
addValidationError("refresh_token is not supported with the password grant_type", validationException);
|
}
|
||||||
}
|
if (refreshToken != null) {
|
||||||
} else if ("refresh_token".equals(grantType)) {
|
validationException =
|
||||||
if (username != null) {
|
addValidationError("refresh_token is not supported with the password grant_type", validationException);
|
||||||
validationException =
|
}
|
||||||
addValidationError("username is not supported with the refresh_token grant_type", validationException);
|
break;
|
||||||
}
|
case REFRESH_TOKEN:
|
||||||
if (password != null) {
|
if (username != null) {
|
||||||
validationException =
|
validationException =
|
||||||
addValidationError("password is not supported with the refresh_token grant_type", validationException);
|
addValidationError("username is not supported with the refresh_token grant_type", validationException);
|
||||||
}
|
}
|
||||||
if (refreshToken == null) {
|
if (password != null) {
|
||||||
validationException = addValidationError("refresh_token is missing", validationException);
|
validationException =
|
||||||
|
addValidationError("password is not supported with the refresh_token grant_type", validationException);
|
||||||
|
}
|
||||||
|
if (refreshToken == null) {
|
||||||
|
validationException = addValidationError("refresh_token is missing", validationException);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CLIENT_CREDENTIALS:
|
||||||
|
if (username != null) {
|
||||||
|
validationException =
|
||||||
|
addValidationError("username is not supported with the client_credentials grant_type", validationException);
|
||||||
|
}
|
||||||
|
if (password != null) {
|
||||||
|
validationException =
|
||||||
|
addValidationError("password is not supported with the client_credentials grant_type", validationException);
|
||||||
|
}
|
||||||
|
if (refreshToken != null) {
|
||||||
|
validationException = addValidationError("refresh_token is not supported with the client_credentials grant_type",
|
||||||
|
validationException);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
validationException = addValidationError("grant_type only supports the values: [" +
|
||||||
|
SUPPORTED_GRANT_TYPES.stream().map(GrantType::getValue).collect(Collectors.joining(", ")) + "]",
|
||||||
|
validationException);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
validationException = addValidationError("grant_type only supports the values: [password, refresh_token]", validationException);
|
validationException = addValidationError("grant_type only supports the values: [" +
|
||||||
|
SUPPORTED_GRANT_TYPES.stream().map(GrantType::getValue).collect(Collectors.joining(", ")) + "]",
|
||||||
|
validationException);
|
||||||
}
|
}
|
||||||
|
|
||||||
return validationException;
|
return validationException;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,6 +186,11 @@ public final class CreateTokenRequest extends ActionRequest {
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
super.writeTo(out);
|
super.writeTo(out);
|
||||||
|
if (out.getVersion().before(Version.V_7_0_0_alpha1) && GrantType.CLIENT_CREDENTIALS.getValue().equals(grantType)) {
|
||||||
|
throw new IllegalArgumentException("a request with the client_credentials grant_type cannot be sent to version [" +
|
||||||
|
out.getVersion() + "]");
|
||||||
|
}
|
||||||
|
|
||||||
out.writeString(grantType);
|
out.writeString(grantType);
|
||||||
if (out.getVersion().onOrAfter(Version.V_6_2_0)) {
|
if (out.getVersion().onOrAfter(Version.V_6_2_0)) {
|
||||||
out.writeOptionalString(username);
|
out.writeOptionalString(username);
|
||||||
|
|
|
@ -59,8 +59,14 @@ public final class CreateTokenResponse extends ActionResponse implements ToXCont
|
||||||
out.writeString(tokenString);
|
out.writeString(tokenString);
|
||||||
out.writeTimeValue(expiresIn);
|
out.writeTimeValue(expiresIn);
|
||||||
out.writeOptionalString(scope);
|
out.writeOptionalString(scope);
|
||||||
if (out.getVersion().onOrAfter(Version.V_6_2_0)) {
|
if (out.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) { // TODO change to V_6_5_0 after backport
|
||||||
out.writeString(refreshToken);
|
out.writeOptionalString(refreshToken);
|
||||||
|
} else if (out.getVersion().onOrAfter(Version.V_6_2_0)) {
|
||||||
|
if (refreshToken == null) {
|
||||||
|
out.writeString("");
|
||||||
|
} else {
|
||||||
|
out.writeString(refreshToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +76,9 @@ public final class CreateTokenResponse extends ActionResponse implements ToXCont
|
||||||
tokenString = in.readString();
|
tokenString = in.readString();
|
||||||
expiresIn = in.readTimeValue();
|
expiresIn = in.readTimeValue();
|
||||||
scope = in.readOptionalString();
|
scope = in.readOptionalString();
|
||||||
if (in.getVersion().onOrAfter(Version.V_6_2_0)) {
|
if (in.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) { // TODO change to V_6_5_0 after backport
|
||||||
|
refreshToken = in.readOptionalString();
|
||||||
|
} else if (in.getVersion().onOrAfter(Version.V_6_2_0)) {
|
||||||
refreshToken = in.readString();
|
refreshToken = in.readString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,4 +98,20 @@ public final class CreateTokenResponse extends ActionResponse implements ToXCont
|
||||||
}
|
}
|
||||||
return builder.endObject();
|
return builder.endObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
CreateTokenResponse that = (CreateTokenResponse) o;
|
||||||
|
return Objects.equals(tokenString, that.tokenString) &&
|
||||||
|
Objects.equals(expiresIn, that.expiresIn) &&
|
||||||
|
Objects.equals(scope, that.scope) &&
|
||||||
|
Objects.equals(refreshToken, that.refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(tokenString, expiresIn, scope, refreshToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.security.action.token;
|
package org.elasticsearch.xpack.core.security.action.token;
|
||||||
|
|
||||||
import org.elasticsearch.action.ActionRequestValidationException;
|
import org.elasticsearch.action.ActionRequestValidationException;
|
||||||
import org.elasticsearch.common.settings.SecureString;
|
import org.elasticsearch.common.settings.SecureString;
|
||||||
|
@ -20,7 +20,7 @@ public class CreateTokenRequestTests extends ESTestCase {
|
||||||
ActionRequestValidationException ve = request.validate();
|
ActionRequestValidationException ve = request.validate();
|
||||||
assertNotNull(ve);
|
assertNotNull(ve);
|
||||||
assertEquals(1, ve.validationErrors().size());
|
assertEquals(1, ve.validationErrors().size());
|
||||||
assertThat(ve.validationErrors().get(0), containsString("[password, refresh_token]"));
|
assertThat(ve.validationErrors().get(0), containsString("[password, refresh_token, client_credentials]"));
|
||||||
assertThat(ve.validationErrors().get(0), containsString("grant_type"));
|
assertThat(ve.validationErrors().get(0), containsString("grant_type"));
|
||||||
|
|
||||||
request.setGrantType("password");
|
request.setGrantType("password");
|
||||||
|
@ -72,5 +72,19 @@ public class CreateTokenRequestTests extends ESTestCase {
|
||||||
assertNotNull(ve);
|
assertNotNull(ve);
|
||||||
assertEquals(1, ve.validationErrors().size());
|
assertEquals(1, ve.validationErrors().size());
|
||||||
assertThat(ve.validationErrors(), hasItem("refresh_token is missing"));
|
assertThat(ve.validationErrors(), hasItem("refresh_token is missing"));
|
||||||
|
|
||||||
|
request.setGrantType("client_credentials");
|
||||||
|
ve = request.validate();
|
||||||
|
assertNull(ve);
|
||||||
|
|
||||||
|
request.setUsername(randomAlphaOfLengthBetween(1, 32));
|
||||||
|
request.setPassword(new SecureString(randomAlphaOfLengthBetween(1, 32).toCharArray()));
|
||||||
|
request.setRefreshToken(randomAlphaOfLengthBetween(1, 32));
|
||||||
|
ve = request.validate();
|
||||||
|
assertNotNull(ve);
|
||||||
|
assertEquals(3, ve.validationErrors().size());
|
||||||
|
assertThat(ve.validationErrors(), hasItem(containsString("username is not supported")));
|
||||||
|
assertThat(ve.validationErrors(), hasItem(containsString("password is not supported")));
|
||||||
|
assertThat(ve.validationErrors(), hasItem(containsString("refresh_token is not supported")));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.core.security.action.token;
|
||||||
|
|
||||||
|
import org.elasticsearch.Version;
|
||||||
|
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||||
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
|
import org.elasticsearch.common.unit.TimeValue;
|
||||||
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
import org.elasticsearch.test.VersionUtils;
|
||||||
|
|
||||||
|
public class CreateTokenResponseTests extends ESTestCase {
|
||||||
|
|
||||||
|
public void testSerialization() throws Exception {
|
||||||
|
CreateTokenResponse response = new CreateTokenResponse(randomAlphaOfLengthBetween(1, 10), TimeValue.timeValueMinutes(20L),
|
||||||
|
randomBoolean() ? null : "FULL", randomAlphaOfLengthBetween(1, 10));
|
||||||
|
try (BytesStreamOutput output = new BytesStreamOutput()) {
|
||||||
|
response.writeTo(output);
|
||||||
|
try (StreamInput input = output.bytes().streamInput()) {
|
||||||
|
CreateTokenResponse serialized = new CreateTokenResponse();
|
||||||
|
serialized.readFrom(input);
|
||||||
|
assertEquals(response, serialized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response = new CreateTokenResponse(randomAlphaOfLengthBetween(1, 10), TimeValue.timeValueMinutes(20L),
|
||||||
|
randomBoolean() ? null : "FULL", null);
|
||||||
|
try (BytesStreamOutput output = new BytesStreamOutput()) {
|
||||||
|
response.writeTo(output);
|
||||||
|
try (StreamInput input = output.bytes().streamInput()) {
|
||||||
|
CreateTokenResponse serialized = new CreateTokenResponse();
|
||||||
|
serialized.readFrom(input);
|
||||||
|
assertEquals(response, serialized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSerializationToPre62Version() throws Exception {
|
||||||
|
CreateTokenResponse response = new CreateTokenResponse(randomAlphaOfLengthBetween(1, 10), TimeValue.timeValueMinutes(20L),
|
||||||
|
randomBoolean() ? null : "FULL", randomBoolean() ? null : randomAlphaOfLengthBetween(1, 10));
|
||||||
|
final Version version = VersionUtils.randomVersionBetween(random(), Version.V_6_0_0, Version.V_6_1_4);
|
||||||
|
try (BytesStreamOutput output = new BytesStreamOutput()) {
|
||||||
|
output.setVersion(version);
|
||||||
|
response.writeTo(output);
|
||||||
|
try (StreamInput input = output.bytes().streamInput()) {
|
||||||
|
input.setVersion(version);
|
||||||
|
CreateTokenResponse serialized = new CreateTokenResponse();
|
||||||
|
serialized.readFrom(input);
|
||||||
|
assertNull(serialized.getRefreshToken());
|
||||||
|
assertEquals(response.getTokenString(), serialized.getTokenString());
|
||||||
|
assertEquals(response.getExpiresIn(), serialized.getExpiresIn());
|
||||||
|
assertEquals(response.getScope(), serialized.getScope());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSerializationToPost62Pre65Version() throws Exception {
|
||||||
|
CreateTokenResponse response = new CreateTokenResponse(randomAlphaOfLengthBetween(1, 10), TimeValue.timeValueMinutes(20L),
|
||||||
|
randomBoolean() ? null : "FULL", randomAlphaOfLengthBetween(1, 10));
|
||||||
|
final Version version = VersionUtils.randomVersionBetween(random(), Version.V_6_2_0, Version.V_6_4_0);
|
||||||
|
try (BytesStreamOutput output = new BytesStreamOutput()) {
|
||||||
|
output.setVersion(version);
|
||||||
|
response.writeTo(output);
|
||||||
|
try (StreamInput input = output.bytes().streamInput()) {
|
||||||
|
input.setVersion(version);
|
||||||
|
CreateTokenResponse serialized = new CreateTokenResponse();
|
||||||
|
serialized.readFrom(input);
|
||||||
|
assertEquals(response, serialized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no refresh token
|
||||||
|
response = new CreateTokenResponse(randomAlphaOfLengthBetween(1, 10), TimeValue.timeValueMinutes(20L),
|
||||||
|
randomBoolean() ? null : "FULL", null);
|
||||||
|
try (BytesStreamOutput output = new BytesStreamOutput()) {
|
||||||
|
output.setVersion(version);
|
||||||
|
response.writeTo(output);
|
||||||
|
try (StreamInput input = output.bytes().streamInput()) {
|
||||||
|
input.setVersion(version);
|
||||||
|
CreateTokenResponse serialized = new CreateTokenResponse();
|
||||||
|
serialized.readFrom(input);
|
||||||
|
assertEquals("", serialized.getRefreshToken());
|
||||||
|
assertEquals(response.getTokenString(), serialized.getTokenString());
|
||||||
|
assertEquals(response.getExpiresIn(), serialized.getExpiresIn());
|
||||||
|
assertEquals(response.getScope(), serialized.getScope());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -61,7 +61,7 @@ public final class TransportSamlAuthenticateAction extends HandledTransportActio
|
||||||
final TimeValue expiresIn = tokenService.getExpirationDelay();
|
final TimeValue expiresIn = tokenService.getExpirationDelay();
|
||||||
listener.onResponse(
|
listener.onResponse(
|
||||||
new SamlAuthenticateResponse(authentication.getUser().principal(), tokenString, tuple.v2(), expiresIn));
|
new SamlAuthenticateResponse(authentication.getUser().principal(), tokenString, tuple.v2(), expiresIn));
|
||||||
}, listener::onFailure), tokenMeta);
|
}, listener::onFailure), tokenMeta, true);
|
||||||
}, e -> {
|
}, e -> {
|
||||||
logger.debug(() -> new ParameterizedMessage("SamlToken [{}] could not be authenticated", saml), e);
|
logger.debug(() -> new ParameterizedMessage("SamlToken [{}] could not be authenticated", saml), e);
|
||||||
listener.onFailure(e);
|
listener.onFailure(e);
|
||||||
|
|
|
@ -22,6 +22,7 @@ import org.elasticsearch.xpack.security.authc.AuthenticationService;
|
||||||
import org.elasticsearch.xpack.security.authc.TokenService;
|
import org.elasticsearch.xpack.security.authc.TokenService;
|
||||||
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
|
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,29 +49,52 @@ public final class TransportCreateTokenAction extends HandledTransportAction<Cre
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doExecute(Task task, CreateTokenRequest request, ActionListener<CreateTokenResponse> listener) {
|
protected void doExecute(Task task, CreateTokenRequest request, ActionListener<CreateTokenResponse> listener) {
|
||||||
|
CreateTokenRequest.GrantType type = CreateTokenRequest.GrantType.fromString(request.getGrantType());
|
||||||
|
assert type != null : "type should have been validated in the action";
|
||||||
|
switch (type) {
|
||||||
|
case PASSWORD:
|
||||||
|
authenticateAndCreateToken(request, listener);
|
||||||
|
break;
|
||||||
|
case CLIENT_CREDENTIALS:
|
||||||
|
Authentication authentication = Authentication.getAuthentication(threadPool.getThreadContext());
|
||||||
|
createToken(request, authentication, authentication, false, listener);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
listener.onFailure(new IllegalStateException("grant_type [" + request.getGrantType() +
|
||||||
|
"] is not supported by the create token action"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void authenticateAndCreateToken(CreateTokenRequest request, ActionListener<CreateTokenResponse> listener) {
|
||||||
Authentication originatingAuthentication = Authentication.getAuthentication(threadPool.getThreadContext());
|
Authentication originatingAuthentication = Authentication.getAuthentication(threadPool.getThreadContext());
|
||||||
try (ThreadContext.StoredContext ignore = threadPool.getThreadContext().stashContext()) {
|
try (ThreadContext.StoredContext ignore = threadPool.getThreadContext().stashContext()) {
|
||||||
final UsernamePasswordToken authToken = new UsernamePasswordToken(request.getUsername(), request.getPassword());
|
final UsernamePasswordToken authToken = new UsernamePasswordToken(request.getUsername(), request.getPassword());
|
||||||
authenticationService.authenticate(CreateTokenAction.NAME, request, authToken,
|
authenticationService.authenticate(CreateTokenAction.NAME, request, authToken,
|
||||||
ActionListener.wrap(authentication -> {
|
ActionListener.wrap(authentication -> {
|
||||||
request.getPassword().close();
|
request.getPassword().close();
|
||||||
tokenService.createUserToken(authentication, originatingAuthentication, ActionListener.wrap(tuple -> {
|
createToken(request, authentication, originatingAuthentication, true, listener);
|
||||||
final String tokenStr = tokenService.getUserTokenString(tuple.v1());
|
}, e -> {
|
||||||
final String scope = getResponseScopeValue(request.getScope());
|
// clear the request password
|
||||||
|
request.getPassword().close();
|
||||||
|
listener.onFailure(e);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final CreateTokenResponse response =
|
private void createToken(CreateTokenRequest request, Authentication authentication, Authentication originatingAuth,
|
||||||
new CreateTokenResponse(tokenStr, tokenService.getExpirationDelay(), scope, tuple.v2());
|
boolean includeRefreshToken, ActionListener<CreateTokenResponse> listener) {
|
||||||
listener.onResponse(response);
|
try {
|
||||||
}, e -> {
|
tokenService.createUserToken(authentication, originatingAuth, ActionListener.wrap(tuple -> {
|
||||||
// clear the request password
|
final String tokenStr = tokenService.getUserTokenString(tuple.v1());
|
||||||
request.getPassword().close();
|
final String scope = getResponseScopeValue(request.getScope());
|
||||||
listener.onFailure(e);
|
|
||||||
}), Collections.emptyMap());
|
final CreateTokenResponse response =
|
||||||
}, e -> {
|
new CreateTokenResponse(tokenStr, tokenService.getExpirationDelay(), scope, tuple.v2());
|
||||||
// clear the request password
|
listener.onResponse(response);
|
||||||
request.getPassword().close();
|
}, listener::onFailure), Collections.emptyMap(), includeRefreshToken);
|
||||||
listener.onFailure(e);
|
} catch (IOException e) {
|
||||||
}));
|
listener.onFailure(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -212,7 +212,8 @@ public final class TokenService extends AbstractComponent {
|
||||||
* The created token will be stored in the security index.
|
* The created token will be stored in the security index.
|
||||||
*/
|
*/
|
||||||
public void createUserToken(Authentication authentication, Authentication originatingClientAuth,
|
public void createUserToken(Authentication authentication, Authentication originatingClientAuth,
|
||||||
ActionListener<Tuple<UserToken, String>> listener, Map<String, Object> metadata) throws IOException {
|
ActionListener<Tuple<UserToken, String>> listener, Map<String, Object> metadata,
|
||||||
|
boolean includeRefreshToken) throws IOException {
|
||||||
ensureEnabled();
|
ensureEnabled();
|
||||||
if (authentication == null) {
|
if (authentication == null) {
|
||||||
listener.onFailure(new IllegalArgumentException("authentication must be provided"));
|
listener.onFailure(new IllegalArgumentException("authentication must be provided"));
|
||||||
|
@ -226,13 +227,14 @@ public final class TokenService extends AbstractComponent {
|
||||||
new Authentication(authentication.getUser(), authentication.getAuthenticatedBy(), authentication.getLookedUpBy(),
|
new Authentication(authentication.getUser(), authentication.getAuthenticatedBy(), authentication.getLookedUpBy(),
|
||||||
version);
|
version);
|
||||||
final UserToken userToken = new UserToken(version, matchingVersionAuth, expiration, metadata);
|
final UserToken userToken = new UserToken(version, matchingVersionAuth, expiration, metadata);
|
||||||
final String refreshToken = UUIDs.randomBase64UUID();
|
final String refreshToken = includeRefreshToken ? UUIDs.randomBase64UUID() : null;
|
||||||
|
|
||||||
try (XContentBuilder builder = XContentFactory.jsonBuilder()) {
|
try (XContentBuilder builder = XContentFactory.jsonBuilder()) {
|
||||||
builder.startObject();
|
builder.startObject();
|
||||||
builder.field("doc_type", "token");
|
builder.field("doc_type", "token");
|
||||||
builder.field("creation_time", created.toEpochMilli());
|
builder.field("creation_time", created.toEpochMilli());
|
||||||
builder.startObject("refresh_token")
|
if (includeRefreshToken) {
|
||||||
|
builder.startObject("refresh_token")
|
||||||
.field("token", refreshToken)
|
.field("token", refreshToken)
|
||||||
.field("invalidated", false)
|
.field("invalidated", false)
|
||||||
.field("refreshed", false)
|
.field("refreshed", false)
|
||||||
|
@ -242,6 +244,7 @@ public final class TokenService extends AbstractComponent {
|
||||||
.field("realm", originatingClientAuth.getAuthenticatedBy().getName())
|
.field("realm", originatingClientAuth.getAuthenticatedBy().getName())
|
||||||
.endObject()
|
.endObject()
|
||||||
.endObject();
|
.endObject();
|
||||||
|
}
|
||||||
builder.startObject("access_token")
|
builder.startObject("access_token")
|
||||||
.field("invalidated", false)
|
.field("invalidated", false)
|
||||||
.field("user_token", userToken)
|
.field("user_token", userToken)
|
||||||
|
@ -734,7 +737,7 @@ public final class TokenService extends AbstractComponent {
|
||||||
.request();
|
.request();
|
||||||
executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, updateRequest,
|
executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, updateRequest,
|
||||||
ActionListener.<UpdateResponse>wrap(
|
ActionListener.<UpdateResponse>wrap(
|
||||||
updateResponse -> createUserToken(authentication, userAuth, listener, metadata),
|
updateResponse -> createUserToken(authentication, userAuth, listener, metadata, true),
|
||||||
e -> {
|
e -> {
|
||||||
Throwable cause = ExceptionsHelper.unwrapCause(e);
|
Throwable cause = ExceptionsHelper.unwrapCause(e);
|
||||||
if (cause instanceof VersionConflictEngineException ||
|
if (cause instanceof VersionConflictEngineException ||
|
||||||
|
|
|
@ -316,7 +316,7 @@ public class TransportSamlInvalidateSessionActionTests extends SamlTestCase {
|
||||||
new RealmRef("native", NativeRealmSettings.TYPE, "node01"), null);
|
new RealmRef("native", NativeRealmSettings.TYPE, "node01"), null);
|
||||||
final Map<String, Object> metadata = samlRealm.createTokenMetadata(nameId, session);
|
final Map<String, Object> metadata = samlRealm.createTokenMetadata(nameId, session);
|
||||||
final PlainActionFuture<Tuple<UserToken, String>> future = new PlainActionFuture<>();
|
final PlainActionFuture<Tuple<UserToken, String>> future = new PlainActionFuture<>();
|
||||||
tokenService.createUserToken(authentication, authentication, future, metadata);
|
tokenService.createUserToken(authentication, authentication, future, metadata, true);
|
||||||
return future.actionGet();
|
return future.actionGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -222,7 +222,7 @@ public class TransportSamlLogoutActionTests extends SamlTestCase {
|
||||||
new SamlNameId(NameID.TRANSIENT, nameId, null, null, null), session);
|
new SamlNameId(NameID.TRANSIENT, nameId, null, null, null), session);
|
||||||
|
|
||||||
final PlainActionFuture<Tuple<UserToken, String>> future = new PlainActionFuture<>();
|
final PlainActionFuture<Tuple<UserToken, String>> future = new PlainActionFuture<>();
|
||||||
tokenService.createUserToken(authentication, authentication, future, tokenMetaData);
|
tokenService.createUserToken(authentication, authentication, future, tokenMetaData, true);
|
||||||
final UserToken userToken = future.actionGet().v1();
|
final UserToken userToken = future.actionGet().v1();
|
||||||
mockGetTokenFromId(userToken, client);
|
mockGetTokenFromId(userToken, client);
|
||||||
final String tokenString = tokenService.getUserTokenString(userToken);
|
final String tokenString = tokenService.getUserTokenString(userToken);
|
||||||
|
|
|
@ -0,0 +1,195 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.xpack.security.action.token;
|
||||||
|
|
||||||
|
import org.elasticsearch.action.ActionListener;
|
||||||
|
import org.elasticsearch.action.get.GetAction;
|
||||||
|
import org.elasticsearch.action.get.GetRequestBuilder;
|
||||||
|
import org.elasticsearch.action.get.GetResponse;
|
||||||
|
import org.elasticsearch.action.get.MultiGetAction;
|
||||||
|
import org.elasticsearch.action.get.MultiGetItemResponse;
|
||||||
|
import org.elasticsearch.action.get.MultiGetRequest;
|
||||||
|
import org.elasticsearch.action.get.MultiGetRequestBuilder;
|
||||||
|
import org.elasticsearch.action.get.MultiGetResponse;
|
||||||
|
import org.elasticsearch.action.index.IndexAction;
|
||||||
|
import org.elasticsearch.action.index.IndexRequest;
|
||||||
|
import org.elasticsearch.action.index.IndexRequestBuilder;
|
||||||
|
import org.elasticsearch.action.index.IndexResponse;
|
||||||
|
import org.elasticsearch.action.support.ActionFilters;
|
||||||
|
import org.elasticsearch.action.support.PlainActionFuture;
|
||||||
|
import org.elasticsearch.action.update.UpdateAction;
|
||||||
|
import org.elasticsearch.action.update.UpdateRequestBuilder;
|
||||||
|
import org.elasticsearch.client.Client;
|
||||||
|
import org.elasticsearch.cluster.service.ClusterService;
|
||||||
|
import org.elasticsearch.common.settings.SecureString;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.node.Node;
|
||||||
|
import org.elasticsearch.protocol.xpack.security.User;
|
||||||
|
import org.elasticsearch.test.ClusterServiceUtils;
|
||||||
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
import org.elasticsearch.threadpool.TestThreadPool;
|
||||||
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
|
import org.elasticsearch.transport.TransportService;
|
||||||
|
import org.elasticsearch.xpack.core.XPackSettings;
|
||||||
|
import org.elasticsearch.xpack.core.security.action.token.CreateTokenAction;
|
||||||
|
import org.elasticsearch.xpack.core.security.action.token.CreateTokenRequest;
|
||||||
|
import org.elasticsearch.xpack.core.security.action.token.CreateTokenResponse;
|
||||||
|
import org.elasticsearch.xpack.core.security.authc.Authentication;
|
||||||
|
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
|
||||||
|
import org.elasticsearch.xpack.security.authc.AuthenticationService;
|
||||||
|
import org.elasticsearch.xpack.security.authc.TokenService;
|
||||||
|
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
|
||||||
|
import java.time.Clock;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static org.mockito.Matchers.any;
|
||||||
|
import static org.mockito.Matchers.anyString;
|
||||||
|
import static org.mockito.Matchers.eq;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class TransportCreateTokenActionTests extends ESTestCase {
|
||||||
|
|
||||||
|
private static final Settings SETTINGS = Settings.builder().put(Node.NODE_NAME_SETTING.getKey(), "TokenServiceTests")
|
||||||
|
.put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), true).build();
|
||||||
|
|
||||||
|
private ThreadPool threadPool;
|
||||||
|
private Client client;
|
||||||
|
private SecurityIndexManager securityIndex;
|
||||||
|
private ClusterService clusterService;
|
||||||
|
private AtomicReference<IndexRequest> idxReqReference;
|
||||||
|
private AuthenticationService authenticationService;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setupClient() {
|
||||||
|
threadPool = new TestThreadPool(getTestName());
|
||||||
|
client = mock(Client.class);
|
||||||
|
idxReqReference = new AtomicReference<>();
|
||||||
|
authenticationService = mock(AuthenticationService.class);
|
||||||
|
when(client.threadPool()).thenReturn(threadPool);
|
||||||
|
when(client.settings()).thenReturn(SETTINGS);
|
||||||
|
doAnswer(invocationOnMock -> {
|
||||||
|
GetRequestBuilder builder = new GetRequestBuilder(client, GetAction.INSTANCE);
|
||||||
|
builder.setIndex((String) invocationOnMock.getArguments()[0])
|
||||||
|
.setType((String) invocationOnMock.getArguments()[1])
|
||||||
|
.setId((String) invocationOnMock.getArguments()[2]);
|
||||||
|
return builder;
|
||||||
|
}).when(client).prepareGet(anyString(), anyString(), anyString());
|
||||||
|
when(client.prepareMultiGet()).thenReturn(new MultiGetRequestBuilder(client, MultiGetAction.INSTANCE));
|
||||||
|
doAnswer(invocationOnMock -> {
|
||||||
|
ActionListener<MultiGetResponse> listener = (ActionListener<MultiGetResponse>) invocationOnMock.getArguments()[1];
|
||||||
|
MultiGetResponse response = mock(MultiGetResponse.class);
|
||||||
|
MultiGetItemResponse[] responses = new MultiGetItemResponse[2];
|
||||||
|
when(response.getResponses()).thenReturn(responses);
|
||||||
|
|
||||||
|
GetResponse oldGetResponse = mock(GetResponse.class);
|
||||||
|
when(oldGetResponse.isExists()).thenReturn(false);
|
||||||
|
responses[0] = new MultiGetItemResponse(oldGetResponse, null);
|
||||||
|
|
||||||
|
GetResponse getResponse = mock(GetResponse.class);
|
||||||
|
responses[1] = new MultiGetItemResponse(getResponse, null);
|
||||||
|
when(getResponse.isExists()).thenReturn(false);
|
||||||
|
listener.onResponse(response);
|
||||||
|
return Void.TYPE;
|
||||||
|
}).when(client).multiGet(any(MultiGetRequest.class), any(ActionListener.class));
|
||||||
|
when(client.prepareIndex(any(String.class), any(String.class), any(String.class)))
|
||||||
|
.thenReturn(new IndexRequestBuilder(client, IndexAction.INSTANCE));
|
||||||
|
when(client.prepareUpdate(any(String.class), any(String.class), any(String.class)))
|
||||||
|
.thenReturn(new UpdateRequestBuilder(client, UpdateAction.INSTANCE));
|
||||||
|
doAnswer(invocationOnMock -> {
|
||||||
|
idxReqReference.set((IndexRequest) invocationOnMock.getArguments()[1]);
|
||||||
|
ActionListener<IndexResponse> responseActionListener = (ActionListener<IndexResponse>) invocationOnMock.getArguments()[2];
|
||||||
|
responseActionListener.onResponse(new IndexResponse());
|
||||||
|
return null;
|
||||||
|
}).when(client).execute(eq(IndexAction.INSTANCE), any(IndexRequest.class), any(ActionListener.class));
|
||||||
|
|
||||||
|
// setup lifecycle service
|
||||||
|
securityIndex = mock(SecurityIndexManager.class);
|
||||||
|
doAnswer(invocationOnMock -> {
|
||||||
|
Runnable runnable = (Runnable) invocationOnMock.getArguments()[1];
|
||||||
|
runnable.run();
|
||||||
|
return null;
|
||||||
|
}).when(securityIndex).prepareIndexIfNeededThenExecute(any(Consumer.class), any(Runnable.class));
|
||||||
|
|
||||||
|
doAnswer(invocationOnMock -> {
|
||||||
|
UsernamePasswordToken token = (UsernamePasswordToken) invocationOnMock.getArguments()[2];
|
||||||
|
User user = new User(token.principal());
|
||||||
|
Authentication authentication = new Authentication(user, new Authentication.RealmRef("fake", "mock", "n1"), null);
|
||||||
|
authentication.writeToContext(threadPool.getThreadContext());
|
||||||
|
ActionListener<Authentication> authListener = (ActionListener<Authentication>) invocationOnMock.getArguments()[3];
|
||||||
|
authListener.onResponse(authentication);
|
||||||
|
return Void.TYPE;
|
||||||
|
}).when(authenticationService).authenticate(eq(CreateTokenAction.NAME), any(CreateTokenRequest.class),
|
||||||
|
any(UsernamePasswordToken.class), any(ActionListener.class));
|
||||||
|
|
||||||
|
this.clusterService = ClusterServiceUtils.createClusterService(threadPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void stopThreadPool() throws Exception {
|
||||||
|
if (threadPool != null) {
|
||||||
|
terminate(threadPool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testClientCredentialsCreatesWithoutRefreshToken() throws Exception {
|
||||||
|
final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, securityIndex, clusterService);
|
||||||
|
Authentication authentication = new Authentication(new User("joe"), new Authentication.RealmRef("realm", "type", "node"), null);
|
||||||
|
authentication.writeToContext(threadPool.getThreadContext());
|
||||||
|
|
||||||
|
final TransportCreateTokenAction action = new TransportCreateTokenAction(SETTINGS, threadPool,
|
||||||
|
mock(TransportService.class), new ActionFilters(Collections.emptySet()), tokenService,
|
||||||
|
authenticationService);
|
||||||
|
final CreateTokenRequest createTokenRequest = new CreateTokenRequest();
|
||||||
|
createTokenRequest.setGrantType("client_credentials");
|
||||||
|
|
||||||
|
PlainActionFuture<CreateTokenResponse> tokenResponseFuture = new PlainActionFuture<>();
|
||||||
|
action.doExecute(null, createTokenRequest, tokenResponseFuture);
|
||||||
|
CreateTokenResponse createTokenResponse = tokenResponseFuture.get();
|
||||||
|
assertNull(createTokenResponse.getRefreshToken());
|
||||||
|
assertNotNull(createTokenResponse.getTokenString());
|
||||||
|
|
||||||
|
assertNotNull(idxReqReference.get());
|
||||||
|
Map<String, Object> sourceMap = idxReqReference.get().sourceAsMap();
|
||||||
|
assertNotNull(sourceMap);
|
||||||
|
assertNotNull(sourceMap.get("access_token"));
|
||||||
|
assertNull(sourceMap.get("refresh_token"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPasswordGrantTypeCreatesWithRefreshToken() throws Exception {
|
||||||
|
final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, securityIndex, clusterService);
|
||||||
|
Authentication authentication = new Authentication(new User("joe"), new Authentication.RealmRef("realm", "type", "node"), null);
|
||||||
|
authentication.writeToContext(threadPool.getThreadContext());
|
||||||
|
|
||||||
|
final TransportCreateTokenAction action = new TransportCreateTokenAction(SETTINGS, threadPool,
|
||||||
|
mock(TransportService.class), new ActionFilters(Collections.emptySet()), tokenService,
|
||||||
|
authenticationService);
|
||||||
|
final CreateTokenRequest createTokenRequest = new CreateTokenRequest();
|
||||||
|
createTokenRequest.setGrantType("password");
|
||||||
|
createTokenRequest.setUsername("user");
|
||||||
|
createTokenRequest.setPassword(new SecureString("password".toCharArray()));
|
||||||
|
|
||||||
|
PlainActionFuture<CreateTokenResponse> tokenResponseFuture = new PlainActionFuture<>();
|
||||||
|
action.doExecute(null, createTokenRequest, tokenResponseFuture);
|
||||||
|
CreateTokenResponse createTokenResponse = tokenResponseFuture.get();
|
||||||
|
assertNotNull(createTokenResponse.getRefreshToken());
|
||||||
|
assertNotNull(createTokenResponse.getTokenString());
|
||||||
|
|
||||||
|
assertNotNull(idxReqReference.get());
|
||||||
|
Map<String, Object> sourceMap = idxReqReference.get().sourceAsMap();
|
||||||
|
assertNotNull(sourceMap);
|
||||||
|
assertNotNull(sourceMap.get("access_token"));
|
||||||
|
assertNotNull(sourceMap.get("refresh_token"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -896,7 +896,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
||||||
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
||||||
try (ThreadContext.StoredContext ctx = threadContext.stashContext()) {
|
try (ThreadContext.StoredContext ctx = threadContext.stashContext()) {
|
||||||
Authentication originatingAuth = new Authentication(new User("creator"), new RealmRef("test", "test", "test"), null);
|
Authentication originatingAuth = new Authentication(new User("creator"), new RealmRef("test", "test", "test"), null);
|
||||||
tokenService.createUserToken(expected, originatingAuth, tokenFuture, Collections.emptyMap());
|
tokenService.createUserToken(expected, originatingAuth, tokenFuture, Collections.emptyMap(), true);
|
||||||
}
|
}
|
||||||
String token = tokenService.getUserTokenString(tokenFuture.get().v1());
|
String token = tokenService.getUserTokenString(tokenFuture.get().v1());
|
||||||
mockGetTokenFromId(tokenFuture.get().v1(), client);
|
mockGetTokenFromId(tokenFuture.get().v1(), client);
|
||||||
|
@ -975,7 +975,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
||||||
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
||||||
try (ThreadContext.StoredContext ctx = threadContext.stashContext()) {
|
try (ThreadContext.StoredContext ctx = threadContext.stashContext()) {
|
||||||
Authentication originatingAuth = new Authentication(new User("creator"), new RealmRef("test", "test", "test"), null);
|
Authentication originatingAuth = new Authentication(new User("creator"), new RealmRef("test", "test", "test"), null);
|
||||||
tokenService.createUserToken(expected, originatingAuth, tokenFuture, Collections.emptyMap());
|
tokenService.createUserToken(expected, originatingAuth, tokenFuture, Collections.emptyMap(), true);
|
||||||
}
|
}
|
||||||
String token = tokenService.getUserTokenString(tokenFuture.get().v1());
|
String token = tokenService.getUserTokenString(tokenFuture.get().v1());
|
||||||
mockGetTokenFromId(tokenFuture.get().v1(), client);
|
mockGetTokenFromId(tokenFuture.get().v1(), client);
|
||||||
|
|
|
@ -341,6 +341,39 @@ public class TokenAuthIntegTests extends SecurityIntegTestCase {
|
||||||
assertEquals(SecuritySettingsSource.TEST_USER_NAME, response.user().principal());
|
assertEquals(SecuritySettingsSource.TEST_USER_NAME, response.user().principal());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testClientCredentialsGrant() throws Exception {
|
||||||
|
Client client = client().filterWithHeader(Collections.singletonMap("Authorization",
|
||||||
|
UsernamePasswordToken.basicAuthHeaderValue(SecuritySettingsSource.TEST_SUPERUSER,
|
||||||
|
SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)));
|
||||||
|
SecurityClient securityClient = new SecurityClient(client);
|
||||||
|
CreateTokenResponse createTokenResponse = securityClient.prepareCreateToken()
|
||||||
|
.setGrantType("client_credentials")
|
||||||
|
.get();
|
||||||
|
assertNull(createTokenResponse.getRefreshToken());
|
||||||
|
|
||||||
|
AuthenticateRequest request = new AuthenticateRequest();
|
||||||
|
request.username(SecuritySettingsSource.TEST_SUPERUSER);
|
||||||
|
PlainActionFuture<AuthenticateResponse> authFuture = new PlainActionFuture<>();
|
||||||
|
client.filterWithHeader(Collections.singletonMap("Authorization", "Bearer " + createTokenResponse.getTokenString()))
|
||||||
|
.execute(AuthenticateAction.INSTANCE, request, authFuture);
|
||||||
|
AuthenticateResponse response = authFuture.get();
|
||||||
|
assertEquals(SecuritySettingsSource.TEST_SUPERUSER, response.user().principal());
|
||||||
|
|
||||||
|
// invalidate
|
||||||
|
PlainActionFuture<InvalidateTokenResponse> invalidateResponseFuture = new PlainActionFuture<>();
|
||||||
|
InvalidateTokenRequest invalidateTokenRequest =
|
||||||
|
new InvalidateTokenRequest(createTokenResponse.getTokenString(), InvalidateTokenRequest.Type.ACCESS_TOKEN);
|
||||||
|
securityClient.invalidateToken(invalidateTokenRequest, invalidateResponseFuture);
|
||||||
|
assertTrue(invalidateResponseFuture.get().isCreated());
|
||||||
|
|
||||||
|
ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, () -> {
|
||||||
|
PlainActionFuture<AuthenticateResponse> responseFuture = new PlainActionFuture<>();
|
||||||
|
client.filterWithHeader(Collections.singletonMap("Authorization", "Bearer " + createTokenResponse.getTokenString()))
|
||||||
|
.execute(AuthenticateAction.INSTANCE, request, responseFuture);
|
||||||
|
responseFuture.actionGet();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void waitForSecurityIndexWritable() throws Exception {
|
public void waitForSecurityIndexWritable() throws Exception {
|
||||||
assertSecurityIndexActive();
|
assertSecurityIndexActive();
|
||||||
|
|
|
@ -157,7 +157,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService);
|
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService);
|
||||||
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
||||||
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
||||||
tokenService.createUserToken(authentication, authentication, tokenFuture, Collections.emptyMap());
|
tokenService.createUserToken(authentication, authentication, tokenFuture, Collections.emptyMap(), true);
|
||||||
final UserToken token = tokenFuture.get().v1();
|
final UserToken token = tokenFuture.get().v1();
|
||||||
assertNotNull(token);
|
assertNotNull(token);
|
||||||
mockGetTokenFromId(token);
|
mockGetTokenFromId(token);
|
||||||
|
@ -203,7 +203,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService);
|
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService);
|
||||||
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
||||||
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
||||||
tokenService.createUserToken(authentication, authentication, tokenFuture, Collections.emptyMap());
|
tokenService.createUserToken(authentication, authentication, tokenFuture, Collections.emptyMap(), true);
|
||||||
final UserToken token = tokenFuture.get().v1();
|
final UserToken token = tokenFuture.get().v1();
|
||||||
assertNotNull(token);
|
assertNotNull(token);
|
||||||
mockGetTokenFromId(token);
|
mockGetTokenFromId(token);
|
||||||
|
@ -227,7 +227,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
PlainActionFuture<Tuple<UserToken, String>> newTokenFuture = new PlainActionFuture<>();
|
PlainActionFuture<Tuple<UserToken, String>> newTokenFuture = new PlainActionFuture<>();
|
||||||
tokenService.createUserToken(authentication, authentication, newTokenFuture, Collections.emptyMap());
|
tokenService.createUserToken(authentication, authentication, newTokenFuture, Collections.emptyMap(), true);
|
||||||
final UserToken newToken = newTokenFuture.get().v1();
|
final UserToken newToken = newTokenFuture.get().v1();
|
||||||
assertNotNull(newToken);
|
assertNotNull(newToken);
|
||||||
assertNotEquals(tokenService.getUserTokenString(newToken), tokenService.getUserTokenString(token));
|
assertNotEquals(tokenService.getUserTokenString(newToken), tokenService.getUserTokenString(token));
|
||||||
|
@ -262,7 +262,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
otherTokenService.refreshMetaData(tokenService.getTokenMetaData());
|
otherTokenService.refreshMetaData(tokenService.getTokenMetaData());
|
||||||
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
||||||
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
||||||
tokenService.createUserToken(authentication, authentication, tokenFuture, Collections.emptyMap());
|
tokenService.createUserToken(authentication, authentication, tokenFuture, Collections.emptyMap(), true);
|
||||||
final UserToken token = tokenFuture.get().v1();
|
final UserToken token = tokenFuture.get().v1();
|
||||||
assertNotNull(token);
|
assertNotNull(token);
|
||||||
mockGetTokenFromId(token);
|
mockGetTokenFromId(token);
|
||||||
|
@ -292,7 +292,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService);
|
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService);
|
||||||
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
||||||
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
||||||
tokenService.createUserToken(authentication, authentication, tokenFuture, Collections.emptyMap());
|
tokenService.createUserToken(authentication, authentication, tokenFuture, Collections.emptyMap(), true);
|
||||||
final UserToken token = tokenFuture.get().v1();
|
final UserToken token = tokenFuture.get().v1();
|
||||||
assertNotNull(token);
|
assertNotNull(token);
|
||||||
mockGetTokenFromId(token);
|
mockGetTokenFromId(token);
|
||||||
|
@ -322,7 +322,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
PlainActionFuture<Tuple<UserToken, String>> newTokenFuture = new PlainActionFuture<>();
|
PlainActionFuture<Tuple<UserToken, String>> newTokenFuture = new PlainActionFuture<>();
|
||||||
tokenService.createUserToken(authentication, authentication, newTokenFuture, Collections.emptyMap());
|
tokenService.createUserToken(authentication, authentication, newTokenFuture, Collections.emptyMap(), true);
|
||||||
final UserToken newToken = newTokenFuture.get().v1();
|
final UserToken newToken = newTokenFuture.get().v1();
|
||||||
assertNotNull(newToken);
|
assertNotNull(newToken);
|
||||||
assertNotEquals(tokenService.getUserTokenString(newToken), tokenService.getUserTokenString(token));
|
assertNotEquals(tokenService.getUserTokenString(newToken), tokenService.getUserTokenString(token));
|
||||||
|
@ -353,7 +353,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService);
|
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService);
|
||||||
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
||||||
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
||||||
tokenService.createUserToken(authentication, authentication, tokenFuture, Collections.emptyMap());
|
tokenService.createUserToken(authentication, authentication, tokenFuture, Collections.emptyMap(), true);
|
||||||
final UserToken token = tokenFuture.get().v1();
|
final UserToken token = tokenFuture.get().v1();
|
||||||
assertNotNull(token);
|
assertNotNull(token);
|
||||||
mockGetTokenFromId(token);
|
mockGetTokenFromId(token);
|
||||||
|
@ -383,7 +383,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
||||||
|
|
||||||
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
||||||
tokenService.createUserToken(authentication, authentication, tokenFuture, Collections.emptyMap());
|
tokenService.createUserToken(authentication, authentication, tokenFuture, Collections.emptyMap(), true);
|
||||||
UserToken token = tokenFuture.get().v1();
|
UserToken token = tokenFuture.get().v1();
|
||||||
assertThat(tokenService.getUserTokenString(token), notNullValue());
|
assertThat(tokenService.getUserTokenString(token), notNullValue());
|
||||||
|
|
||||||
|
@ -397,7 +397,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService);
|
new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService);
|
||||||
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
||||||
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
||||||
tokenService.createUserToken(authentication, authentication, tokenFuture, Collections.emptyMap());
|
tokenService.createUserToken(authentication, authentication, tokenFuture, Collections.emptyMap(), true);
|
||||||
final UserToken token = tokenFuture.get().v1();
|
final UserToken token = tokenFuture.get().v1();
|
||||||
assertNotNull(token);
|
assertNotNull(token);
|
||||||
doAnswer(invocationOnMock -> {
|
doAnswer(invocationOnMock -> {
|
||||||
|
@ -451,7 +451,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, clock, client, securityIndex, clusterService);
|
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, clock, client, securityIndex, clusterService);
|
||||||
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
||||||
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
||||||
tokenService.createUserToken(authentication, authentication, tokenFuture, Collections.emptyMap());
|
tokenService.createUserToken(authentication, authentication, tokenFuture, Collections.emptyMap(), true);
|
||||||
final UserToken token = tokenFuture.get().v1();
|
final UserToken token = tokenFuture.get().v1();
|
||||||
mockGetTokenFromId(token);
|
mockGetTokenFromId(token);
|
||||||
|
|
||||||
|
@ -501,7 +501,8 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
.put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), false)
|
.put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), false)
|
||||||
.build(),
|
.build(),
|
||||||
Clock.systemUTC(), client, securityIndex, clusterService);
|
Clock.systemUTC(), client, securityIndex, clusterService);
|
||||||
IllegalStateException e = expectThrows(IllegalStateException.class, () -> tokenService.createUserToken(null, null, null, null));
|
IllegalStateException e = expectThrows(IllegalStateException.class,
|
||||||
|
() -> tokenService.createUserToken(null, null, null, null, true));
|
||||||
assertEquals("tokens are not enabled", e.getMessage());
|
assertEquals("tokens are not enabled", e.getMessage());
|
||||||
|
|
||||||
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
|
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
|
||||||
|
@ -559,7 +560,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService);
|
new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService);
|
||||||
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
||||||
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
||||||
tokenService.createUserToken(authentication, authentication, tokenFuture, Collections.emptyMap());
|
tokenService.createUserToken(authentication, authentication, tokenFuture, Collections.emptyMap(), true);
|
||||||
final UserToken token = tokenFuture.get().v1();
|
final UserToken token = tokenFuture.get().v1();
|
||||||
assertNotNull(token);
|
assertNotNull(token);
|
||||||
mockGetTokenFromId(token);
|
mockGetTokenFromId(token);
|
||||||
|
|
|
@ -158,6 +158,7 @@ subprojects {
|
||||||
} else {
|
} else {
|
||||||
String systemKeyFile = version.before('6.3.0') ? 'x-pack/system_key' : 'system_key'
|
String systemKeyFile = version.before('6.3.0') ? 'x-pack/system_key' : 'system_key'
|
||||||
extraConfigFile systemKeyFile, "${mainProject.projectDir}/src/test/resources/system_key"
|
extraConfigFile systemKeyFile, "${mainProject.projectDir}/src/test/resources/system_key"
|
||||||
|
keystoreSetting 'xpack.security.authc.token.passphrase', 'token passphrase'
|
||||||
}
|
}
|
||||||
setting 'xpack.watcher.encrypt_sensitive_data', 'true'
|
setting 'xpack.watcher.encrypt_sensitive_data', 'true'
|
||||||
}
|
}
|
||||||
|
@ -199,6 +200,9 @@ subprojects {
|
||||||
setting 'xpack.watcher.encrypt_sensitive_data', 'true'
|
setting 'xpack.watcher.encrypt_sensitive_data', 'true'
|
||||||
keystoreFile 'xpack.watcher.encryption_key', "${mainProject.projectDir}/src/test/resources/system_key"
|
keystoreFile 'xpack.watcher.encryption_key', "${mainProject.projectDir}/src/test/resources/system_key"
|
||||||
}
|
}
|
||||||
|
if (version.before('6.0.0')) {
|
||||||
|
keystoreSetting 'xpack.security.authc.token.passphrase', 'token passphrase'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue