Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add kerberos grant_type to get token in exchange for Kerberos ticket #42847

Merged
merged 24 commits into from
Jun 18, 2019
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,16 @@ public final class CreateTokenResponse {
private final TimeValue expiresIn;
private final String scope;
private final String refreshToken;
private final String kerberosAuthenticationResponseToken;

public CreateTokenResponse(String accessToken, String type, TimeValue expiresIn, String scope, String refreshToken) {
public CreateTokenResponse(String accessToken, String type, TimeValue expiresIn, String scope, String refreshToken,
String kerberosAuthenticationResponseToken) {
this.accessToken = accessToken;
this.type = type;
this.expiresIn = expiresIn;
this.scope = scope;
this.refreshToken = refreshToken;
this.kerberosAuthenticationResponseToken = kerberosAuthenticationResponseToken;
}

public String getAccessToken() {
Expand All @@ -70,6 +73,10 @@ public String getRefreshToken() {
return refreshToken;
}

public String getKerberosAuthenticationResponseToken() {
return kerberosAuthenticationResponseToken;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand All @@ -83,24 +90,26 @@ public boolean equals(Object o) {
Objects.equals(type, that.type) &&
Objects.equals(expiresIn, that.expiresIn) &&
Objects.equals(scope, that.scope) &&
Objects.equals(refreshToken, that.refreshToken);
Objects.equals(refreshToken, that.refreshToken) &&
Objects.equals(kerberosAuthenticationResponseToken, that.kerberosAuthenticationResponseToken);
}

@Override
public int hashCode() {
return Objects.hash(accessToken, type, expiresIn, scope, refreshToken);
return Objects.hash(accessToken, type, expiresIn, scope, refreshToken, kerberosAuthenticationResponseToken);
}

private static final ConstructingObjectParser<CreateTokenResponse, Void> PARSER = new ConstructingObjectParser<>(
"create_token_response", true, args -> new CreateTokenResponse(
(String) args[0], (String) args[1], TimeValue.timeValueSeconds((Long) args[2]), (String) args[3], (String) args[4]));
"create_token_response", true, args -> new CreateTokenResponse((String) args[0], (String) args[1],
TimeValue.timeValueSeconds((Long) args[2]), (String) args[3], (String) args[4], (String) args[5]));

static {
PARSER.declareString(constructorArg(), new ParseField("access_token"));
PARSER.declareString(constructorArg(), new ParseField("type"));
PARSER.declareLong(constructorArg(), new ParseField("expires_in"));
PARSER.declareStringOrNull(optionalConstructorArg(), new ParseField("scope"));
PARSER.declareStringOrNull(optionalConstructorArg(), new ParseField("refresh_token"));
PARSER.declareStringOrNull(optionalConstructorArg(), new ParseField("kerberos_authentication_response_token"));
}

public static CreateTokenResponse fromXContent(XContentParser parser) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public void testFromXContent() throws IOException {
final String refreshToken = randomBoolean() ? null : randomAlphaOfLengthBetween(12, 24);
final String scope = randomBoolean() ? null : randomAlphaOfLength(4);
final String type = randomAlphaOfLength(6);
final String kerberosAuthenticationResponseToken = randomBoolean() ? null : randomAlphaOfLength(7);

final XContentType xContentType = randomFrom(XContentType.values());
final XContentBuilder builder = XContentFactory.contentBuilder(xContentType);
Expand All @@ -50,6 +51,9 @@ public void testFromXContent() throws IOException {
if (scope != null || randomBoolean()) {
builder.field("scope", scope);
}
if (kerberosAuthenticationResponseToken != null) {
builder.field("kerberos_authentication_response_token", kerberosAuthenticationResponseToken);
}
builder.endObject();
BytesReference xContent = BytesReference.bytes(builder);

Expand All @@ -59,5 +63,6 @@ public void testFromXContent() throws IOException {
assertThat(response.getScope(), equalTo(scope));
assertThat(response.getType(), equalTo(type));
assertThat(response.getExpiresIn(), equalTo(expiresIn));
assertThat(response.getKerberosAuthenticationResponseToken(), equalTo(kerberosAuthenticationResponseToken));
}
}
40 changes: 38 additions & 2 deletions x-pack/docs/en/rest-api/security/get-tokens.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,20 @@ The following parameters can be specified in the body of a POST request and
pertain to creating a token:

`grant_type`::
(string) The type of grant. Supported grant types are: `password`,
(string) The type of grant. Supported grant types are: `password`, `_kerberos` (beta),
bizybot marked this conversation as resolved.
Show resolved Hide resolved
`client_credentials` and `refresh_token`.
`_kerberos` implements SPNEGO based Kerberos support.

`password`::
(string) The user's password. If you specify the `password` grant type, this
parameter is required. This parameter is not valid with any other supported
grant type.

`kerberos_ticket`::
(string) base64 encoded kerberos ticket. If you specify the `_kerberos` grant type,
this parameter is required. This parameter is not valid with any other supported
grant type.

`refresh_token`::
(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
Expand Down Expand Up @@ -160,4 +166,34 @@ be used one time.
}
--------------------------------------------------
// TESTRESPONSE[s/dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==/$body.access_token/]
// TESTRESPONSE[s/vLBPvmAB6KvwvJZr27cS/$body.refresh_token/]
// TESTRESPONSE[s/vLBPvmAB6KvwvJZr27cS/$body.refresh_token/]

The following example obtains a access token and refresh token using the `kerberos` grant type,
which simply creates a token in exchange for the base64 encoded kerberos ticket:

[source,js]
--------------------------------------------------
POST /_security/oauth2/token
{
"grant_type" : "_kerberos",
"kerberos_ticket" : "YIIB6wYJKoZIhvcSAQICAQBuggHaMIIB1qADAgEFoQMCAQ6iBtaDcp4cdMODwOsIvmvdX//sye8NDJZ8Gstabor3MOGryBWyaJ1VxI4WBVZaSn1WnzE06Xy2"
}
--------------------------------------------------
// NOTCONSOLE

The API will return a new token and refresh token if kerberos authentication is successful. Each refresh token may only
bizybot marked this conversation as resolved.
Show resolved Hide resolved
be used one time. Along with this there might be an base64 encoded token for clients to consume and finalize the authentication.
jkakavas marked this conversation as resolved.
Show resolved Hide resolved
When the mutual authentication is requested on Spnego GSS context, this token will be presented by the server.
`kerberos_authentication_response_token` is the field in the response containing the token.

[source,js]
--------------------------------------------------
{
"access_token" : "dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==",
"type" : "Bearer",
"expires_in" : 1200,
"refresh_token": "vLBPvmAB6KvwvJZr27cS"
"kerberos_authentication_response_token": "YIIB6wYJKoZIhvcSAQICAQBuggHaMIIB1qADAg"
}
--------------------------------------------------
// NOTCONSOLE
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,20 @@

import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.CharArrays;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.CharArrays;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;

Expand All @@ -34,6 +35,7 @@ public final class CreateTokenRequest extends ActionRequest {

public enum GrantType {
PASSWORD("password"),
KERBEROS("_kerberos"),
REFRESH_TOKEN("refresh_token"),
AUTHORIZATION_CODE("authorization_code"),
CLIENT_CREDENTIALS("client_credentials");
Expand Down Expand Up @@ -61,21 +63,23 @@ public static GrantType fromString(String grantType) {
}

private static final Set<GrantType> SUPPORTED_GRANT_TYPES = Collections.unmodifiableSet(
EnumSet.of(GrantType.PASSWORD, GrantType.REFRESH_TOKEN, GrantType.CLIENT_CREDENTIALS));
EnumSet.of(GrantType.PASSWORD, GrantType.KERBEROS, GrantType.REFRESH_TOKEN, GrantType.CLIENT_CREDENTIALS));

private String grantType;
private String username;
private SecureString password;
private SecureString kerberosTicket;
private String scope;
private String refreshToken;

public CreateTokenRequest() {}

public CreateTokenRequest(String grantType, @Nullable String username, @Nullable SecureString password, @Nullable String scope,
@Nullable String refreshToken) {
public CreateTokenRequest(String grantType, @Nullable String username, @Nullable SecureString password,
@Nullable SecureString kerberosTicket, @Nullable String scope, @Nullable String refreshToken) {
this.grantType = grantType;
this.username = username;
this.password = password;
this.kerberosTicket = kerberosTicket;
this.scope = scope;
this.refreshToken = refreshToken;
}
Expand All @@ -87,43 +91,28 @@ public ActionRequestValidationException validate() {
if (type != null) {
switch (type) {
case PASSWORD:
if (Strings.isNullOrEmpty(username)) {
validationException = addValidationError("username is missing", validationException);
}
if (password == null || password.getChars() == null || password.getChars().length == 0) {
validationException = addValidationError("password is missing", validationException);
}
if (refreshToken != null) {
validationException =
addValidationError("refresh_token is not supported with the password grant_type", validationException);
}
validationException = validateUnsupportedField(type, "kerberos_ticket", kerberosTicket, validationException);
validationException = validateUnsupportedField(type, "refresh_token", refreshToken, validationException);
validationException = validateRequiredField("username", username, validationException);
validationException = validateRequiredField("password", password, validationException);
break;
case KERBEROS:
validationException = validateUnsupportedField(type, "username", username, validationException);
validationException = validateUnsupportedField(type, "password", password, validationException);
validationException = validateUnsupportedField(type, "refresh_token", refreshToken, validationException);
validationException = validateRequiredField("kerberos_ticket", kerberosTicket, validationException);
break;
case REFRESH_TOKEN:
if (username != null) {
validationException =
addValidationError("username is not supported with the refresh_token grant_type", validationException);
}
if (password != null) {
validationException =
addValidationError("password is not supported with the refresh_token grant_type", validationException);
}
if (refreshToken == null) {
validationException = addValidationError("refresh_token is missing", validationException);
}
validationException = validateUnsupportedField(type, "username", username, validationException);
validationException = validateUnsupportedField(type, "password", password, validationException);
validationException = validateUnsupportedField(type, "kerberos_ticket", kerberosTicket, validationException);
validationException = validateRequiredField("refresh_token", refreshToken, 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);
}
validationException = validateUnsupportedField(type, "username", username, validationException);
validationException = validateUnsupportedField(type, "password", password, validationException);
validationException = validateUnsupportedField(type, "kerberos_ticket", kerberosTicket, validationException);
validationException = validateUnsupportedField(type, "refresh_token", refreshToken, validationException);
break;
default:
validationException = addValidationError("grant_type only supports the values: [" +
Expand All @@ -138,6 +127,32 @@ public ActionRequestValidationException validate() {
return validationException;
}

private static ActionRequestValidationException validateRequiredField(String field, String fieldValue,
ActionRequestValidationException validationException) {
if (Strings.isNullOrEmpty(fieldValue)) {
validationException = addValidationError(String.format(Locale.ROOT, "%s is missing", field), validationException);
}
return validationException;
}

private static ActionRequestValidationException validateRequiredField(String field, SecureString fieldValue,
ActionRequestValidationException validationException) {
if (fieldValue == null || fieldValue.getChars() == null || fieldValue.length() == 0) {
validationException = addValidationError(String.format(Locale.ROOT, "%s is missing", field), validationException);
}
return validationException;
}

private static ActionRequestValidationException validateUnsupportedField(GrantType grantType, String field, Object fieldValue,
ActionRequestValidationException validationException) {
if (fieldValue != null) {
validationException = addValidationError(
String.format(Locale.ROOT, "%s is not supported with the %s grant_type", field, grantType.getValue()),
validationException);
}
return validationException;
}

public void setGrantType(String grantType) {
this.grantType = grantType;
}
Expand All @@ -150,6 +165,10 @@ public void setPassword(@Nullable SecureString password) {
this.password = password;
}

public void setKerberosTicket(@Nullable SecureString kerberosTicket) {
this.kerberosTicket = kerberosTicket;
}

public void setScope(@Nullable String scope) {
this.scope = scope;
}
Expand All @@ -172,6 +191,11 @@ public SecureString getPassword() {
return password;
}

@Nullable
public SecureString getKerberosTicket() {
return kerberosTicket;
}

@Nullable
public String getScope() {
return scope;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,17 @@ public final class CreateTokenResponse extends ActionResponse implements ToXCont
private TimeValue expiresIn;
private String scope;
private String refreshToken;
private String kerberosAuthenticationResponseToken;

CreateTokenResponse() {}

public CreateTokenResponse(String tokenString, TimeValue expiresIn, String scope, String refreshToken) {
public CreateTokenResponse(String tokenString, TimeValue expiresIn, String scope, String refreshToken,
String kerberosAuthenticationResponseToken) {
this.tokenString = Objects.requireNonNull(tokenString);
this.expiresIn = Objects.requireNonNull(expiresIn);
this.scope = scope;
this.refreshToken = refreshToken;
this.kerberosAuthenticationResponseToken = kerberosAuthenticationResponseToken;
}

public String getTokenString() {
Expand All @@ -52,13 +55,18 @@ public String getRefreshToken() {
return refreshToken;
}

public String getKerberosAuthenticationResponseToken() {
return kerberosAuthenticationResponseToken;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(tokenString);
out.writeTimeValue(expiresIn);
out.writeOptionalString(scope);
out.writeOptionalString(refreshToken);
out.writeOptionalString(kerberosAuthenticationResponseToken);
}

@Override
Expand All @@ -68,6 +76,7 @@ public void readFrom(StreamInput in) throws IOException {
expiresIn = in.readTimeValue();
scope = in.readOptionalString();
refreshToken = in.readOptionalString();
kerberosAuthenticationResponseToken = in.readOptionalString();
}

@Override
Expand All @@ -83,6 +92,9 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
if (scope != null) {
builder.field("scope", scope);
}
if (kerberosAuthenticationResponseToken != null) {
builder.field("kerberos_authentication_response_token", kerberosAuthenticationResponseToken);
}
return builder.endObject();
}

Expand All @@ -94,11 +106,12 @@ public boolean equals(Object o) {
return Objects.equals(tokenString, that.tokenString) &&
Objects.equals(expiresIn, that.expiresIn) &&
Objects.equals(scope, that.scope) &&
Objects.equals(refreshToken, that.refreshToken);
Objects.equals(refreshToken, that.refreshToken) &&
Objects.equals(kerberosAuthenticationResponseToken, that.kerberosAuthenticationResponseToken);
}

@Override
public int hashCode() {
return Objects.hash(tokenString, expiresIn, scope, refreshToken);
return Objects.hash(tokenString, expiresIn, scope, refreshToken, kerberosAuthenticationResponseToken);
}
}
Loading