From b5d0010481c6fd79e27d12c78b6d5cc316e0d987 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Tue, 4 Jun 2019 14:35:31 +1000 Subject: [PATCH 01/17] Add kerberos grant_type to exchange Kerberos credentials for token Kibana wants to create access_token/refresh_token pair using Token management APIs in exchange for kerberos tickets. `client_credentials` grant_type requires every user to have `cluster:admin/xpack/security/token/create` cluster privilege. This commit introduces `kerberos` grant_type for generating `access_token` and `refresh_token` in exchange for a valid base64 encoded kerberos ticket. In addition, `kibana_user` role now has cluster privilege to create tokens. This allows Kibana to create access_token/refresh_token pair in exchange for kerberos tickets. Note: The lifetime from the kerberos ticket is not used in ES and so even after it expires the access_token/refresh_token pair will be valid. Care must be taken to invalidate such tokens using token management APIs if required. TODO: - The `KerberosAuthenticationIT` requires krb5-kdc fixture to be enabled, currently it is disabled. This will be fixed in another commit. - Documentation --- .../action/token/CreateTokenRequest.java | 98 ++++++++++++------- .../authz/store/ReservedRolesStore.java | 9 +- .../action/token/CreateTokenRequestTests.java | 28 +++++- .../token/TransportCreateTokenAction.java | 51 ++++++++-- .../action/oauth2/RestGetTokenAction.java | 6 +- .../TransportCreateTokenActionTests.java | 41 +++++++- .../oauth2/RestGetTokenActionTests.java | 2 +- x-pack/qa/kerberos-tests/build.gradle | 3 + .../kerberos/KerberosAuthenticationIT.java | 40 +++++++- ...SpnegoHttpClientConfigCallbackHandler.java | 46 +++++++++ 10 files changed, 268 insertions(+), 56 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java index edbfce3d17aa6..19ce6867136de 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java @@ -7,6 +7,7 @@ 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; @@ -14,12 +15,12 @@ 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; @@ -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"); @@ -61,21 +63,23 @@ public static GrantType fromString(String grantType) { } private static final Set 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; } @@ -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 = unsupportedFieldValidation(type, "kerberos_ticket", kerberosTicket, validationException); + validationException = unsupportedFieldValidation(type, "refresh_token", refreshToken, validationException); + validationException = missingFieldValidation("username", username, validationException); + validationException = missingFieldValidation("password", password, validationException); + break; + case KERBEROS: + validationException = unsupportedFieldValidation(type, "username", username, validationException); + validationException = unsupportedFieldValidation(type, "password", password, validationException); + validationException = unsupportedFieldValidation(type, "refresh_token", refreshToken, validationException); + validationException = missingFieldValidation("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 = unsupportedFieldValidation(type, "username", username, validationException); + validationException = unsupportedFieldValidation(type, "password", password, validationException); + validationException = unsupportedFieldValidation(type, "kerberos_ticket", kerberosTicket, validationException); + validationException = missingFieldValidation("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 = unsupportedFieldValidation(type, "username", username, validationException); + validationException = unsupportedFieldValidation(type, "password", password, validationException); + validationException = unsupportedFieldValidation(type, "kerberos_ticket", kerberosTicket, validationException); + validationException = unsupportedFieldValidation(type, "refresh_token", refreshToken, validationException); break; default: validationException = addValidationError("grant_type only supports the values: [" + @@ -138,6 +127,32 @@ public ActionRequestValidationException validate() { return validationException; } + private static ActionRequestValidationException missingFieldValidation(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 missingFieldValidation(String field, SecureString fieldValue, + ActionRequestValidationException validationException) { + if (fieldValue == null || fieldValue.getChars() == null || fieldValue.getChars().length == 0) { + validationException = addValidationError(String.format(Locale.ROOT, "%s is missing", field), validationException); + } + return validationException; + } + + private static ActionRequestValidationException unsupportedFieldValidation(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; } @@ -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; } @@ -172,6 +191,11 @@ public SecureString getPassword() { return password; } + @Nullable + public SecureString getKerberosTicket() { + return kerberosTicket; + } + @Nullable public String getScope() { return scope; diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java index ab06fc32e288f..0601e67184bf0 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java @@ -47,9 +47,12 @@ private static Map initializeReservedRoles() { .put("superuser", SUPERUSER_ROLE_DESCRIPTOR) .put("transport_client", new RoleDescriptor("transport_client", new String[] { "transport_client" }, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA)) - .put("kibana_user", new RoleDescriptor("kibana_user", null, null, new RoleDescriptor.ApplicationResourcePrivileges[] { - RoleDescriptor.ApplicationResourcePrivileges.builder() - .application("kibana-.kibana").resources("*").privileges("all").build() }, + .put("kibana_user", new RoleDescriptor("kibana_user", new String[] { "cluster:admin/xpack/security/token/create" }, + null, + new RoleDescriptor.ApplicationResourcePrivileges[] { + RoleDescriptor.ApplicationResourcePrivileges.builder() + .application("kibana-.kibana").resources("*").privileges("all").build() + }, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA, null)) .put("monitoring_user", new RoleDescriptor("monitoring_user", diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequestTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequestTests.java index 2d8782f0111e6..dae9b95277d9c 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequestTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequestTests.java @@ -19,7 +19,7 @@ public void testRequestValidation() { ActionRequestValidationException ve = request.validate(); assertNotNull(ve); assertEquals(1, ve.validationErrors().size()); - assertThat(ve.validationErrors().get(0), containsString("[password, refresh_token, client_credentials]")); + assertThat(ve.validationErrors().get(0), containsString("[password, kerberos, refresh_token, client_credentials]")); assertThat(ve.validationErrors().get(0), containsString("grant_type")); request.setGrantType("password"); @@ -49,20 +49,24 @@ public void testRequestValidation() { assertNull(ve); request.setRefreshToken(randomAlphaOfLengthBetween(1, 10)); + request.setKerberosTicket(new SecureString(randomAlphaOfLengthBetween(1, 256).toCharArray())); ve = request.validate(); assertNotNull(ve); - assertEquals(1, ve.validationErrors().size()); - assertThat(ve.validationErrors().get(0), containsString("refresh_token is not supported")); + assertEquals(2, ve.validationErrors().size()); + assertThat(ve.validationErrors(), hasItem(containsString("kerberos_ticket is not supported"))); + assertThat(ve.validationErrors(), hasItem(containsString("refresh_token is not supported"))); request.setGrantType("refresh_token"); ve = request.validate(); assertNotNull(ve); - assertEquals(2, ve.validationErrors().size()); + 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("kerberos_ticket is not supported"))); request.setUsername(null); request.setPassword(null); + request.setKerberosTicket(null); ve = request.validate(); assertNull(ve); @@ -78,12 +82,28 @@ public void testRequestValidation() { request.setUsername(randomAlphaOfLengthBetween(1, 32)); request.setPassword(new SecureString(randomAlphaOfLengthBetween(1, 32).toCharArray())); + request.setKerberosTicket(new SecureString(randomAlphaOfLengthBetween(1, 256).toCharArray())); request.setRefreshToken(randomAlphaOfLengthBetween(1, 32)); ve = request.validate(); assertNotNull(ve); + assertEquals(4, 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"))); + assertThat(ve.validationErrors(), hasItem(containsString("kerberos_ticket is not supported"))); + + request.setGrantType("kerberos"); + 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"))); + + request.setUsername(null); + request.setPassword(null); + request.setRefreshToken(null); + ve = request.validate(); + assertNull(ve); } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenAction.java index 65456ccd2af51..df32ea9af6897 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenAction.java @@ -9,18 +9,23 @@ import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.HandledTransportAction; import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.tasks.Task; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; 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.CreateTokenRequest.GrantType; import org.elasticsearch.xpack.core.security.action.token.CreateTokenResponse; import org.elasticsearch.xpack.core.security.authc.Authentication; +import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; 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.authc.kerberos.KerberosAuthenticationToken; +import java.util.Base64; import java.util.Collections; /** @@ -51,7 +56,8 @@ protected void doExecute(Task task, CreateTokenRequest request, ActionListener listener) { + private void authenticateAndCreateToken(GrantType grantType, CreateTokenRequest request, ActionListener listener) { Authentication originatingAuthentication = Authentication.getAuthentication(threadPool.getThreadContext()); try (ThreadContext.StoredContext ignore = threadPool.getThreadContext().stashContext()) { - final UsernamePasswordToken authToken = new UsernamePasswordToken(request.getUsername(), request.getPassword()); + final AuthenticationToken authToken = extractAuthenticationToken(grantType, request, listener); + if (authToken == null) { + listener.onFailure(new IllegalStateException( + "grant_type [" + request.getGrantType() + "] is not supported by the create token action")); + return; + } + authenticationService.authenticate(CreateTokenAction.NAME, request, authToken, ActionListener.wrap(authentication -> { - request.getPassword().close(); + clearCredentialsFromRequest(grantType, request); + if (authentication != null) { createToken(request, authentication, originatingAuthentication, true, listener); } else { listener.onFailure(new UnsupportedOperationException("cannot create token if authentication is not allowed")); } }, e -> { - // clear the request password - request.getPassword().close(); + clearCredentialsFromRequest(grantType, request); listener.onFailure(e); })); } } + private AuthenticationToken extractAuthenticationToken(GrantType grantType, CreateTokenRequest request, + ActionListener listener) { + AuthenticationToken authToken = null; + if (grantType == GrantType.PASSWORD) { + authToken = new UsernamePasswordToken(request.getUsername(), request.getPassword()); + } else if (grantType == GrantType.KERBEROS) { + SecureString kerberosTicket = request.getKerberosTicket(); + String base64EncodedToken = kerberosTicket.toString(); + byte[] decodedKerberosTicket = null; + try { + decodedKerberosTicket = Base64.getDecoder().decode(base64EncodedToken); + } catch (IllegalArgumentException iae) { + listener.onFailure(new UnsupportedOperationException("could not decode base64 kerberos ticket " + base64EncodedToken)); + } + authToken = new KerberosAuthenticationToken(decodedKerberosTicket); + } + return authToken; + } + + private void clearCredentialsFromRequest(GrantType grantType, CreateTokenRequest request) { + if (grantType == GrantType.PASSWORD) { + request.getPassword().close(); + } else if (grantType == GrantType.KERBEROS) { + request.getKerberosTicket().close(); + } + } + private void createToken(CreateTokenRequest request, Authentication authentication, Authentication originatingAuth, boolean includeRefreshToken, ActionListener listener) { tokenService.createOAuth2Tokens(authentication, originatingAuth, Collections.emptyMap(), includeRefreshToken, diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenAction.java index 4734c39bc5af5..b094131bb178e 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenAction.java @@ -48,13 +48,17 @@ public final class RestGetTokenAction extends TokenBaseRestHandler { private static final DeprecationLogger deprecationLogger = new DeprecationLogger(LogManager.getLogger(RestGetTokenAction.class)); static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("token_request", - a -> new CreateTokenRequest((String) a[0], (String) a[1], (SecureString) a[2], (String) a[3], (String) a[4])); + a -> new CreateTokenRequest((String) a[0], (String) a[1], (SecureString) a[2], (SecureString) a[3], (String) a[4], + (String) a[5])); static { PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), new ParseField("grant_type")); PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), new ParseField("username")); PARSER.declareField(ConstructingObjectParser.optionalConstructorArg(), parser -> new SecureString( Arrays.copyOfRange(parser.textCharacters(), parser.textOffset(), parser.textOffset() + parser.textLength())), new ParseField("password"), ValueType.STRING); + PARSER.declareField(ConstructingObjectParser.optionalConstructorArg(), parser -> new SecureString( + Arrays.copyOfRange(parser.textCharacters(), parser.textOffset(), parser.textOffset() + parser.textLength())), + new ParseField("kerberos_ticket"), ValueType.STRING); PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), new ParseField("scope")); PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), new ParseField("refresh_token")); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java index c2050c95fc354..b019be317123f 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java @@ -41,10 +41,12 @@ 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.AuthenticationToken; import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authc.TokenService; +import org.elasticsearch.xpack.security.authc.kerberos.KerberosAuthenticationToken; import org.elasticsearch.xpack.security.support.SecurityIndexManager; import org.junit.After; import org.junit.Before; @@ -128,15 +130,22 @@ public void setupClient() { }).when(securityIndex).prepareIndexIfNeededThenExecute(any(Consumer.class), any(Runnable.class)); doAnswer(invocationOnMock -> { - UsernamePasswordToken token = (UsernamePasswordToken) invocationOnMock.getArguments()[2]; - User user = new User(token.principal()); + AuthenticationToken authToken = (AuthenticationToken) invocationOnMock.getArguments()[2]; + User user = null; + if (authToken instanceof UsernamePasswordToken) { + UsernamePasswordToken token = (UsernamePasswordToken) invocationOnMock.getArguments()[2]; + user = new User(token.principal()); + } else if (authToken instanceof KerberosAuthenticationToken) { + KerberosAuthenticationToken token = (KerberosAuthenticationToken) invocationOnMock.getArguments()[2]; + user = new User(token.principal()); + } Authentication authentication = new Authentication(user, new Authentication.RealmRef("fake", "mock", "n1"), null); authentication.writeToContext(threadPool.getThreadContext()); ActionListener authListener = (ActionListener) invocationOnMock.getArguments()[3]; authListener.onResponse(authentication); return Void.TYPE; }).when(authenticationService).authenticate(eq(CreateTokenAction.NAME), any(CreateTokenRequest.class), - any(UsernamePasswordToken.class), any(ActionListener.class)); + any(AuthenticationToken.class), any(ActionListener.class)); this.clusterService = ClusterServiceUtils.createClusterService(threadPool); @@ -202,4 +211,30 @@ public void testPasswordGrantTypeCreatesWithRefreshToken() throws Exception { assertNotNull(sourceMap.get("access_token")); assertNotNull(sourceMap.get("refresh_token")); } + + public void testKerberosGrantTypeCreatesWithRefreshToken() throws Exception { + final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, license, + securityIndex, 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(threadPool, + mock(TransportService.class), new ActionFilters(Collections.emptySet()), tokenService, + authenticationService); + final CreateTokenRequest createTokenRequest = new CreateTokenRequest(); + createTokenRequest.setGrantType("kerberos"); + createTokenRequest.setKerberosTicket(new SecureString(randomAlphaOfLengthBetween(3, 7).toCharArray())); + + PlainActionFuture tokenResponseFuture = new PlainActionFuture<>(); + action.doExecute(null, createTokenRequest, tokenResponseFuture); + CreateTokenResponse createTokenResponse = tokenResponseFuture.get(); + assertNotNull(createTokenResponse.getRefreshToken()); + assertNotNull(createTokenResponse.getTokenString()); + + assertNotNull(idxReqReference.get()); + Map sourceMap = idxReqReference.get().sourceAsMap(); + assertNotNull(sourceMap); + assertNotNull(sourceMap.get("access_token")); + assertNotNull(sourceMap.get("refresh_token")); + } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java index 9e99d0cf1563c..3fec0f65db2a8 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java @@ -43,7 +43,7 @@ public void sendResponse(RestResponse restResponse) { }; CreateTokenResponseActionListener listener = new CreateTokenResponseActionListener(restChannel, restRequest, NoOpLogger.INSTANCE); - ActionRequestValidationException ve = new CreateTokenRequest(null, null, null, null, null).validate(); + ActionRequestValidationException ve = new CreateTokenRequest(null, null, null, null, null, null).validate(); listener.onFailure(ve); RestResponse response = responseSetOnce.get(); assertNotNull(response); diff --git a/x-pack/qa/kerberos-tests/build.gradle b/x-pack/qa/kerberos-tests/build.gradle index 4313aad9e4295..f34112bcc5f64 100644 --- a/x-pack/qa/kerberos-tests/build.gradle +++ b/x-pack/qa/kerberos-tests/build.gradle @@ -29,6 +29,7 @@ integTestCluster { setting 'xpack.security.authc.realms.file.file1.order', '0' setting 'xpack.ml.enabled', 'false' setting 'xpack.security.audit.enabled', 'true' + setting 'xpack.security.authc.token.enabled', 'true' // Kerberos realm setting 'xpack.security.authc.realms.kerberos.kerberos.order', '1' setting 'xpack.security.authc.realms.kerberos.kerberos.keytab.path', 'es.keytab' @@ -42,6 +43,8 @@ integTestCluster { setupCommand 'setupTestAdmin', 'bin/elasticsearch-users', 'useradd', "test_admin", '-p', 'x-pack-test-password', '-r', "superuser" + setupCommand 'setupTestKibanaUser', + 'bin/elasticsearch-users', 'useradd', "test_kibana_user", '-p', 'x-pack-test-password', '-r', "kibana_user" waitCondition = { node, ant -> File tmpFile = new File(node.cwd, 'wait.success') diff --git a/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosAuthenticationIT.java b/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosAuthenticationIT.java index 0281f38933b03..8cb358105a067 100644 --- a/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosAuthenticationIT.java +++ b/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosAuthenticationIT.java @@ -21,9 +21,9 @@ import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.test.rest.ESRestTestCase; +import org.ietf.jgss.GSSException; import org.junit.Before; -import javax.security.auth.login.LoginContext; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; @@ -34,11 +34,15 @@ import java.util.List; import java.util.Map; +import javax.security.auth.login.LoginContext; + import static org.elasticsearch.common.xcontent.XContentHelper.convertToMap; import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; /** * Integration test to demonstrate authentication against a real MIT Kerberos @@ -102,6 +106,33 @@ public void testLoginByUsernamePassword() throws IOException, PrivilegedActionEx executeRequestAndVerifyResponse(userPrincipalName, callbackHandler); } + public void testExchangeOauth2TokenForKerberosTickets() throws PrivilegedActionException, GSSException, IOException { + final String userPrincipalName = System.getProperty(TEST_USER_WITH_PWD_KEY); + final String password = System.getProperty(TEST_USER_WITH_PWD_PASSWD_KEY); + final boolean enabledDebugLogs = Boolean.parseBoolean(System.getProperty(ENABLE_KERBEROS_DEBUG_LOGS_KEY)); + final SpnegoHttpClientConfigCallbackHandler callbackHandler = new SpnegoHttpClientConfigCallbackHandler(userPrincipalName, + new SecureString(password.toCharArray()), enabledDebugLogs); + final String host = getClusterHosts().get(0).getHostName(); + final String kerberosTicket = callbackHandler.getBase64EncodedTokenForSpnegoHeader(host); + + final Request request = new Request("POST", "/_security/oauth2/token"); + String json = "{" + + " \"grant_type\" : \"kerberos\", " + + " \"kerberos_ticket\" : \"" + kerberosTicket + "\"" + + "}"; + request.setJsonEntity(json); + + try (RestClient client = buildClientForUser("test_kibana_user")) { + final Response response = client.performRequest(request); + // final Response response = adminClient().performRequest(request); + assertOK(response); + final Map map = parseResponseAsMap(response.getEntity()); + assertThat(map.get("access_token"), notNullValue()); + assertThat(map.get("type"), is("Bearer")); + assertThat(map.get("refresh_token"), notNullValue()); + } + } + @Override @SuppressForbidden(reason = "SPNEGO relies on hostnames and we need to ensure host isn't a IP address") protected HttpHost buildHttpHost(String host, int port) { @@ -137,6 +168,13 @@ private Map parseResponseAsMap(final HttpEntity entity) throws I return convertToMap(XContentType.JSON.xContent(), entity.getContent(), false); } + private RestClient buildClientForUser(String user) throws IOException { + final String token = basicAuthHeaderValue(user, new SecureString("x-pack-test-password".toCharArray())); + Settings settings = Settings.builder().put(ThreadContext.PREFIX + ".Authorization", token).build(); + final HttpHost[] hosts = getClusterHosts().toArray(new HttpHost[getClusterHosts().size()]); + return buildClient(settings, hosts); + } + private RestClient buildRestClientForKerberos(final SpnegoHttpClientConfigCallbackHandler callbackHandler) throws IOException { final Settings settings = restAdminSettings(); final HttpHost[] hosts = getClusterHosts().toArray(new HttpHost[getClusterHosts().size()]); diff --git a/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/SpnegoHttpClientConfigCallbackHandler.java b/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/SpnegoHttpClientConfigCallbackHandler.java index e5768d8f2e944..e98b06f51c652 100644 --- a/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/SpnegoHttpClientConfigCallbackHandler.java +++ b/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/SpnegoHttpClientConfigCallbackHandler.java @@ -19,6 +19,7 @@ import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback; import org.elasticsearch.common.settings.SecureString; +import org.ietf.jgss.GSSContext; import org.ietf.jgss.GSSCredential; import org.ietf.jgss.GSSException; import org.ietf.jgss.GSSManager; @@ -28,11 +29,14 @@ import java.io.IOException; import java.security.AccessControlContext; import java.security.AccessController; +import java.security.Principal; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; +import java.util.Base64; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Set; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; @@ -43,6 +47,7 @@ import javax.security.auth.login.AppConfigurationEntry; import javax.security.auth.login.Configuration; import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; /** * This class implements {@link HttpClientConfigCallback} which allows for @@ -314,4 +319,45 @@ public AppConfigurationEntry[] getAppConfigurationEntry(final String name) { abstract void addOptions(Map options); } + + /** + * Initiates GSS context establishment and returns the + * base64 encoded token to be sent to server. + * + * @return Base64 encoded token + * @throws PrivilegedActionException when privileged action threw exception + * @throws GSSException when GSS context creation fails + */ + String getBase64EncodedTokenForSpnegoHeader(final String serviceHost) throws PrivilegedActionException, GSSException { + final GSSManager gssManager = GSSManager.getInstance(); + final GSSName gssServicePrincipalName = AccessController + .doPrivileged((PrivilegedExceptionAction) () -> gssManager.createName("HTTP/" + serviceHost, null)); + final GSSName gssUserPrincipalName = gssManager.createName(userPrincipalName, GSSName.NT_USER_NAME); + loginContext = AccessController + .doPrivileged((PrivilegedExceptionAction) () -> loginUsingPassword(userPrincipalName, password)); + final GSSCredential userCreds = doAsWrapper(loginContext.getSubject(), (PrivilegedExceptionAction) () -> gssManager + .createCredential(gssUserPrincipalName, GSSCredential.DEFAULT_LIFETIME, SPNEGO_OID, GSSCredential.INITIATE_ONLY)); + GSSContext gssContext = gssManager.createContext(gssServicePrincipalName, SPNEGO_OID, userCreds, GSSCredential.DEFAULT_LIFETIME); + gssContext.requestMutualAuth(true); + + final byte[] outToken = doAsWrapper(loginContext.getSubject(), + (PrivilegedExceptionAction) () -> gssContext.initSecContext(new byte[0], 0, 0)); + return Base64.getEncoder().encodeToString(outToken); + } + + private LoginContext loginUsingPassword(final String principal, final SecureString password) throws LoginException { + final Set principals = Collections.singleton(new KerberosPrincipal(principal)); + + final Subject subject = new Subject(false, principals, Collections.emptySet(), Collections.emptySet()); + + final Configuration conf = new PasswordJaasConf(principal, enableDebugLogs); + final CallbackHandler callback = new KrbCallbackHandler(principal, password); + final LoginContext loginContext = new LoginContext(CRED_CONF_NAME, subject, callback, conf); + loginContext.login(); + return loginContext; + } + + static T doAsWrapper(final Subject subject, final PrivilegedExceptionAction action) throws PrivilegedActionException { + return AccessController.doPrivileged((PrivilegedExceptionAction) () -> Subject.doAs(subject, action)); + } } From 6426100b1aa23aca959cabafd92fdf9b8f8ffe81 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Wed, 5 Jun 2019 11:01:53 +1000 Subject: [PATCH 02/17] correct the test --- .../core/security/authz/store/ReservedRolesStore.java | 9 +++------ x-pack/qa/kerberos-tests/build.gradle | 2 +- .../authc/kerberos/KerberosAuthenticationIT.java | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java index 0601e67184bf0..ab06fc32e288f 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java @@ -47,12 +47,9 @@ private static Map initializeReservedRoles() { .put("superuser", SUPERUSER_ROLE_DESCRIPTOR) .put("transport_client", new RoleDescriptor("transport_client", new String[] { "transport_client" }, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA)) - .put("kibana_user", new RoleDescriptor("kibana_user", new String[] { "cluster:admin/xpack/security/token/create" }, - null, - new RoleDescriptor.ApplicationResourcePrivileges[] { - RoleDescriptor.ApplicationResourcePrivileges.builder() - .application("kibana-.kibana").resources("*").privileges("all").build() - }, + .put("kibana_user", new RoleDescriptor("kibana_user", null, null, new RoleDescriptor.ApplicationResourcePrivileges[] { + RoleDescriptor.ApplicationResourcePrivileges.builder() + .application("kibana-.kibana").resources("*").privileges("all").build() }, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA, null)) .put("monitoring_user", new RoleDescriptor("monitoring_user", diff --git a/x-pack/qa/kerberos-tests/build.gradle b/x-pack/qa/kerberos-tests/build.gradle index f34112bcc5f64..e49facddb6373 100644 --- a/x-pack/qa/kerberos-tests/build.gradle +++ b/x-pack/qa/kerberos-tests/build.gradle @@ -44,7 +44,7 @@ integTestCluster { setupCommand 'setupTestAdmin', 'bin/elasticsearch-users', 'useradd', "test_admin", '-p', 'x-pack-test-password', '-r', "superuser" setupCommand 'setupTestKibanaUser', - 'bin/elasticsearch-users', 'useradd', "test_kibana_user", '-p', 'x-pack-test-password', '-r', "kibana_user" + 'bin/elasticsearch-users', 'useradd', "test_kibana_user", '-p', 'x-pack-test-password', '-r', "kibana_system" waitCondition = { node, ant -> File tmpFile = new File(node.cwd, 'wait.success') diff --git a/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosAuthenticationIT.java b/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosAuthenticationIT.java index 8cb358105a067..b10c24baa45f0 100644 --- a/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosAuthenticationIT.java +++ b/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosAuthenticationIT.java @@ -106,7 +106,7 @@ public void testLoginByUsernamePassword() throws IOException, PrivilegedActionEx executeRequestAndVerifyResponse(userPrincipalName, callbackHandler); } - public void testExchangeOauth2TokenForKerberosTickets() throws PrivilegedActionException, GSSException, IOException { + public void testGetOauth2TokenInExchangeForKerberosTickets() throws PrivilegedActionException, GSSException, IOException { final String userPrincipalName = System.getProperty(TEST_USER_WITH_PWD_KEY); final String password = System.getProperty(TEST_USER_WITH_PWD_PASSWD_KEY); final boolean enabledDebugLogs = Boolean.parseBoolean(System.getProperty(ENABLE_KERBEROS_DEBUG_LOGS_KEY)); From 670a7e903d63148bba1075787d0f50c2c78c3b04 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad <902768+bizybot@users.noreply.github.com> Date: Wed, 5 Jun 2019 11:45:14 +1000 Subject: [PATCH 03/17] Update x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java Co-Authored-By: Tim Vernum --- .../xpack/core/security/action/token/CreateTokenRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java index 19ce6867136de..aa2d7cb36eb71 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java @@ -127,7 +127,7 @@ public ActionRequestValidationException validate() { return validationException; } - private static ActionRequestValidationException missingFieldValidation(String field, String fieldValue, + 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); From e337bdbaa2219ba17347f48fa2e94a4188c8cf7c Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Wed, 5 Jun 2019 11:52:24 +1000 Subject: [PATCH 04/17] address review feedback --- .../en/rest-api/security/get-tokens.asciidoc | 22 +++++++++++++++++-- .../action/token/CreateTokenRequest.java | 2 +- .../action/token/CreateTokenRequestTests.java | 4 ++-- .../TransportCreateTokenActionTests.java | 2 +- .../KerberosTicketValidatorTests.java | 1 + .../kerberos/KerberosAuthenticationIT.java | 2 +- 6 files changed, 26 insertions(+), 7 deletions(-) diff --git a/x-pack/docs/en/rest-api/security/get-tokens.asciidoc b/x-pack/docs/en/rest-api/security/get-tokens.asciidoc index cba3b638bc10c..09dd849122473 100644 --- a/x-pack/docs/en/rest-api/security/get-tokens.asciidoc +++ b/x-pack/docs/en/rest-api/security/get-tokens.asciidoc @@ -41,7 +41,7 @@ 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), `client_credentials` and `refresh_token`. `password`:: @@ -49,6 +49,11 @@ pertain to creating a token: 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 @@ -160,4 +165,17 @@ be used one time. } -------------------------------------------------- // TESTRESPONSE[s/dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==/$body.access_token/] -// TESTRESPONSE[s/vLBPvmAB6KvwvJZr27cS/$body.refresh_token/] \ No newline at end of file +// 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 \ No newline at end of file diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java index 19ce6867136de..0f935c7a576e4 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java @@ -35,7 +35,7 @@ public final class CreateTokenRequest extends ActionRequest { public enum GrantType { PASSWORD("password"), - KERBEROS("kerberos"), + KERBEROS("_kerberos"), REFRESH_TOKEN("refresh_token"), AUTHORIZATION_CODE("authorization_code"), CLIENT_CREDENTIALS("client_credentials"); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequestTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequestTests.java index dae9b95277d9c..d5edbc8f1c34d 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequestTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequestTests.java @@ -19,7 +19,7 @@ public void testRequestValidation() { ActionRequestValidationException ve = request.validate(); assertNotNull(ve); assertEquals(1, ve.validationErrors().size()); - assertThat(ve.validationErrors().get(0), containsString("[password, kerberos, refresh_token, client_credentials]")); + assertThat(ve.validationErrors().get(0), containsString("[password, _kerberos, refresh_token, client_credentials]")); assertThat(ve.validationErrors().get(0), containsString("grant_type")); request.setGrantType("password"); @@ -92,7 +92,7 @@ public void testRequestValidation() { assertThat(ve.validationErrors(), hasItem(containsString("refresh_token is not supported"))); assertThat(ve.validationErrors(), hasItem(containsString("kerberos_ticket is not supported"))); - request.setGrantType("kerberos"); + request.setGrantType("_kerberos"); ve = request.validate(); assertNotNull(ve); assertEquals(3, ve.validationErrors().size()); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java index b019be317123f..e1fed72ed62da 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java @@ -222,7 +222,7 @@ public void testKerberosGrantTypeCreatesWithRefreshToken() throws Exception { mock(TransportService.class), new ActionFilters(Collections.emptySet()), tokenService, authenticationService); final CreateTokenRequest createTokenRequest = new CreateTokenRequest(); - createTokenRequest.setGrantType("kerberos"); + createTokenRequest.setGrantType("_kerberos"); createTokenRequest.setKerberosTicket(new SecureString(randomAlphaOfLengthBetween(3, 7).toCharArray())); PlainActionFuture tokenResponseFuture = new PlainActionFuture<>(); diff --git a/x-pack/qa/evil-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosTicketValidatorTests.java b/x-pack/qa/evil-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosTicketValidatorTests.java index c0886f953fee1..7c8186611a5bd 100644 --- a/x-pack/qa/evil-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosTicketValidatorTests.java +++ b/x-pack/qa/evil-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosTicketValidatorTests.java @@ -103,6 +103,7 @@ public void testValidKebrerosTicket() throws PrivilegedActionException, GSSExcep try (SpnegoClient spnegoClient = new SpnegoClient(principalName(clientUserName), password, servicePrincipalName, randomFrom(KerberosTicketValidator.SUPPORTED_OIDS))) { final String base64KerbToken = spnegoClient.getBase64EncodedTokenForSpnegoHeader(); + System.out.println("aijoajsaojojs : " + base64KerbToken); assertThat(base64KerbToken, is(notNullValue())); final Environment env = TestEnvironment.newEnvironment(globalSettings); diff --git a/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosAuthenticationIT.java b/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosAuthenticationIT.java index b10c24baa45f0..fd1d51e0f65d2 100644 --- a/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosAuthenticationIT.java +++ b/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosAuthenticationIT.java @@ -117,7 +117,7 @@ public void testGetOauth2TokenInExchangeForKerberosTickets() throws PrivilegedAc final Request request = new Request("POST", "/_security/oauth2/token"); String json = "{" + - " \"grant_type\" : \"kerberos\", " + + " \"grant_type\" : \"_kerberos\", " + " \"kerberos_ticket\" : \"" + kerberosTicket + "\"" + "}"; request.setJsonEntity(json); From fd5842ff5bf5165d4d984b21584a04bfe463ce8c Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Wed, 5 Jun 2019 11:58:18 +1000 Subject: [PATCH 05/17] change method name --- .../core/security/action/token/CreateTokenRequest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java index f19c409b99f3c..a4bb872c87a3c 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java @@ -93,20 +93,20 @@ public ActionRequestValidationException validate() { case PASSWORD: validationException = unsupportedFieldValidation(type, "kerberos_ticket", kerberosTicket, validationException); validationException = unsupportedFieldValidation(type, "refresh_token", refreshToken, validationException); - validationException = missingFieldValidation("username", username, validationException); - validationException = missingFieldValidation("password", password, validationException); + validationException = validateRequiredField("username", username, validationException); + validationException = validateRequiredField("password", password, validationException); break; case KERBEROS: validationException = unsupportedFieldValidation(type, "username", username, validationException); validationException = unsupportedFieldValidation(type, "password", password, validationException); validationException = unsupportedFieldValidation(type, "refresh_token", refreshToken, validationException); - validationException = missingFieldValidation("kerberos_ticket", kerberosTicket, validationException); + validationException = validateRequiredField("kerberos_ticket", kerberosTicket, validationException); break; case REFRESH_TOKEN: validationException = unsupportedFieldValidation(type, "username", username, validationException); validationException = unsupportedFieldValidation(type, "password", password, validationException); validationException = unsupportedFieldValidation(type, "kerberos_ticket", kerberosTicket, validationException); - validationException = missingFieldValidation("refresh_token", refreshToken, validationException); + validationException = validateRequiredField("refresh_token", refreshToken, validationException); break; case CLIENT_CREDENTIALS: validationException = unsupportedFieldValidation(type, "username", username, validationException); @@ -135,7 +135,7 @@ private static ActionRequestValidationException validateRequiredField(String fie return validationException; } - private static ActionRequestValidationException missingFieldValidation(String field, SecureString fieldValue, + private static ActionRequestValidationException validateRequiredField(String field, SecureString fieldValue, ActionRequestValidationException validationException) { if (fieldValue == null || fieldValue.getChars() == null || fieldValue.getChars().length == 0) { validationException = addValidationError(String.format(Locale.ROOT, "%s is missing", field), validationException); From 2f82754b0f46f1008565ef48bd732786a746b4c5 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Wed, 5 Jun 2019 12:20:05 +1000 Subject: [PATCH 06/17] remove sys out from debugging session --- .../security/authc/kerberos/KerberosTicketValidatorTests.java | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/qa/evil-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosTicketValidatorTests.java b/x-pack/qa/evil-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosTicketValidatorTests.java index 7c8186611a5bd..c0886f953fee1 100644 --- a/x-pack/qa/evil-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosTicketValidatorTests.java +++ b/x-pack/qa/evil-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosTicketValidatorTests.java @@ -103,7 +103,6 @@ public void testValidKebrerosTicket() throws PrivilegedActionException, GSSExcep try (SpnegoClient spnegoClient = new SpnegoClient(principalName(clientUserName), password, servicePrincipalName, randomFrom(KerberosTicketValidator.SUPPORTED_OIDS))) { final String base64KerbToken = spnegoClient.getBase64EncodedTokenForSpnegoHeader(); - System.out.println("aijoajsaojojs : " + base64KerbToken); assertThat(base64KerbToken, is(notNullValue())); final Environment env = TestEnvironment.newEnvironment(globalSettings); From 732b4af4a3b5cd1b0f2533dedceb6c01e0dd9458 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Wed, 5 Jun 2019 12:26:26 +1000 Subject: [PATCH 07/17] indentation --- .../core/security/action/token/CreateTokenRequest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java index a4bb872c87a3c..28c77e67c9806 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java @@ -128,7 +128,7 @@ public ActionRequestValidationException validate() { } private static ActionRequestValidationException validateRequiredField(String field, String fieldValue, - ActionRequestValidationException validationException) { + ActionRequestValidationException validationException) { if (Strings.isNullOrEmpty(fieldValue)) { validationException = addValidationError(String.format(Locale.ROOT, "%s is missing", field), validationException); } @@ -136,7 +136,7 @@ private static ActionRequestValidationException validateRequiredField(String fie } private static ActionRequestValidationException validateRequiredField(String field, SecureString fieldValue, - ActionRequestValidationException validationException) { + ActionRequestValidationException validationException) { if (fieldValue == null || fieldValue.getChars() == null || fieldValue.getChars().length == 0) { validationException = addValidationError(String.format(Locale.ROOT, "%s is missing", field), validationException); } @@ -144,7 +144,7 @@ private static ActionRequestValidationException validateRequiredField(String fie } private static ActionRequestValidationException unsupportedFieldValidation(GrantType grantType, String field, Object fieldValue, - ActionRequestValidationException validationException) { + ActionRequestValidationException validationException) { if (fieldValue != null) { validationException = addValidationError( String.format(Locale.ROOT, "%s is not supported with the %s grant_type", field, grantType.getValue()), From e82b288087e6c3794d29b8add2dfb0caeff62444 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Thu, 6 Jun 2019 12:48:10 +1000 Subject: [PATCH 08/17] handle spengo messages in context of kerberos oauth2 grant type --- .../action/token/CreateTokenResponse.java | 19 ++++++-- .../token/CreateTokenResponseTests.java | 4 +- .../token/TransportCreateTokenAction.java | 17 +++++-- .../token/TransportRefreshTokenAction.java | 2 +- .../action/oauth2/RestGetTokenAction.java | 4 ++ .../TransportCreateTokenActionTests.java | 45 ++++++++++++++----- .../oauth2/RestGetTokenActionTests.java | 6 ++- .../kerberos/KerberosAuthenticationIT.java | 8 +++- ...SpnegoHttpClientConfigCallbackHandler.java | 32 ++++++++++++- 9 files changed, 113 insertions(+), 24 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenResponse.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenResponse.java index f3b094b1fd141..966fd09d5c7db 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenResponse.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenResponse.java @@ -26,14 +26,17 @@ public final class CreateTokenResponse extends ActionResponse implements ToXCont private TimeValue expiresIn; private String scope; private String refreshToken; + private String kerberosAuthenticateResponseData; CreateTokenResponse() {} - public CreateTokenResponse(String tokenString, TimeValue expiresIn, String scope, String refreshToken) { + public CreateTokenResponse(String tokenString, TimeValue expiresIn, String scope, String refreshToken, + String kerberosAuthenticateResponseData) { this.tokenString = Objects.requireNonNull(tokenString); this.expiresIn = Objects.requireNonNull(expiresIn); this.scope = scope; this.refreshToken = refreshToken; + this.kerberosAuthenticateResponseData = kerberosAuthenticateResponseData; } public String getTokenString() { @@ -52,6 +55,10 @@ public String getRefreshToken() { return refreshToken; } + public String getKerberosAuthenticationResponseData() { + return kerberosAuthenticateResponseData; + } + @Override public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); @@ -59,6 +66,7 @@ public void writeTo(StreamOutput out) throws IOException { out.writeTimeValue(expiresIn); out.writeOptionalString(scope); out.writeOptionalString(refreshToken); + out.writeOptionalString(kerberosAuthenticateResponseData); } @Override @@ -68,6 +76,7 @@ public void readFrom(StreamInput in) throws IOException { expiresIn = in.readTimeValue(); scope = in.readOptionalString(); refreshToken = in.readOptionalString(); + kerberosAuthenticateResponseData = in.readOptionalString(); } @Override @@ -83,6 +92,9 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws if (scope != null) { builder.field("scope", scope); } + if (kerberosAuthenticateResponseData != null) { + builder.field("kerberos_authenticate_response_data", kerberosAuthenticateResponseData); + } return builder.endObject(); } @@ -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(kerberosAuthenticateResponseData, that.kerberosAuthenticateResponseData); } @Override public int hashCode() { - return Objects.hash(tokenString, expiresIn, scope, refreshToken); + return Objects.hash(tokenString, expiresIn, scope, refreshToken, kerberosAuthenticateResponseData); } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenResponseTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenResponseTests.java index ed73ef0562b43..e89357698c708 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenResponseTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenResponseTests.java @@ -14,7 +14,7 @@ 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)); + randomBoolean() ? null : "FULL", randomAlphaOfLengthBetween(1, 10), randomBoolean() ? null :randomAlphaOfLengthBetween(1, 10)); try (BytesStreamOutput output = new BytesStreamOutput()) { response.writeTo(output); try (StreamInput input = output.bytes().streamInput()) { @@ -25,7 +25,7 @@ public void testSerialization() throws Exception { } response = new CreateTokenResponse(randomAlphaOfLengthBetween(1, 10), TimeValue.timeValueMinutes(20L), - randomBoolean() ? null : "FULL", null); + randomBoolean() ? null : "FULL", null, null); try (BytesStreamOutput output = new BytesStreamOutput()) { response.writeTo(output); try (StreamInput input = output.bytes().streamInput()) { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenAction.java index df32ea9af6897..40a29466363d2 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenAction.java @@ -27,6 +27,7 @@ import java.util.Base64; import java.util.Collections; +import java.util.List; /** * Transport action responsible for creating a token based on a request. Requests provide user @@ -61,7 +62,7 @@ protected void doExecute(Task task, CreateTokenRequest request, ActionListener listener) { tokenService.createOAuth2Tokens(authentication, originatingAuth, Collections.emptyMap(), includeRefreshToken, ActionListener.wrap(tuple -> { final String scope = getResponseScopeValue(request.getScope()); + final String base64AuthenticateResponse = (grantType == GrantType.KERBEROS) ? extractOutToken() : null; final CreateTokenResponse response = new CreateTokenResponse(tuple.v1(), tokenService.getExpirationDelay(), scope, - tuple.v2()); + tuple.v2(), base64AuthenticateResponse); listener.onResponse(response); }, listener::onFailure)); } + private String extractOutToken() { + List values = threadPool.getThreadContext().getResponseHeaders().get(KerberosAuthenticationToken.WWW_AUTHENTICATE); + if (values != null && values.size() == 1) { + return values.get(0); + } + return null; + } static String getResponseScopeValue(String requestScope) { final String scope; // the OAuth2.0 RFC requires the scope to be provided in the diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportRefreshTokenAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportRefreshTokenAction.java index 5c161d889cfb1..9a99e6020ea36 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportRefreshTokenAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportRefreshTokenAction.java @@ -33,7 +33,7 @@ protected void doExecute(Task task, CreateTokenRequest request, ActionListener { final String scope = getResponseScopeValue(request.getScope()); final CreateTokenResponse response = - new CreateTokenResponse(tuple.v1(), tokenService.getExpirationDelay(), scope, tuple.v2()); + new CreateTokenResponse(tuple.v1(), tokenService.getExpirationDelay(), scope, tuple.v2(), null); listener.onResponse(response); }, listener::onFailure)); } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenAction.java index b094131bb178e..76b18621004a7 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenAction.java @@ -128,6 +128,10 @@ public void onFailure(Exception e) { ((ElasticsearchSecurityException) e).getHeader("error_description").size() == 1) { sendTokenErrorResponse(TokenRequestError.INVALID_GRANT, ((ElasticsearchSecurityException) e).getHeader("error_description").get(0), e); + } else if (e instanceof ElasticsearchSecurityException && "unauthorized_client".equals(e.getMessage()) && + ((ElasticsearchSecurityException) e).getHeader("error_description").size() == 1) { + sendTokenErrorResponse(TokenRequestError.UNAUTHORIZED_CLIENT, + ((ElasticsearchSecurityException) e).getHeader("error_description").get(0), e); } else { sendFailure(e); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java index e1fed72ed62da..22bce9492564d 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java @@ -6,6 +6,7 @@ package org.elasticsearch.xpack.security.action.token; +import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.get.GetAction; import org.elasticsearch.action.get.GetRequestBuilder; @@ -31,6 +32,7 @@ import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.node.Node; +import org.elasticsearch.rest.RestStatus; import org.elasticsearch.test.ClusterServiceUtils; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.threadpool.TestThreadPool; @@ -51,12 +53,15 @@ import org.junit.After; import org.junit.Before; +import java.nio.charset.StandardCharsets; import java.time.Clock; +import java.util.Base64; import java.util.Collections; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; +import static org.hamcrest.Matchers.is; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; @@ -131,17 +136,26 @@ public void setupClient() { doAnswer(invocationOnMock -> { AuthenticationToken authToken = (AuthenticationToken) invocationOnMock.getArguments()[2]; + ActionListener authListener = (ActionListener) invocationOnMock.getArguments()[3]; User user = null; if (authToken instanceof UsernamePasswordToken) { UsernamePasswordToken token = (UsernamePasswordToken) invocationOnMock.getArguments()[2]; user = new User(token.principal()); } else if (authToken instanceof KerberosAuthenticationToken) { KerberosAuthenticationToken token = (KerberosAuthenticationToken) invocationOnMock.getArguments()[2]; + if (token.credentials() instanceof byte[] + && new String((byte[]) token.credentials(), StandardCharsets.UTF_8).equals("fail")) { + String errorMessage = "failed to authenticate user, gss context negotiation not complete"; + ElasticsearchSecurityException ese = new ElasticsearchSecurityException(errorMessage, RestStatus.UNAUTHORIZED); + ese.addHeader(KerberosAuthenticationToken.WWW_AUTHENTICATE, "Negotiate FAIL"); + authListener.onFailure(ese); + return Void.TYPE; + } user = new User(token.principal()); + threadPool.getThreadContext().addResponseHeader(KerberosAuthenticationToken.WWW_AUTHENTICATE, "Negotiate SUCCESS"); } Authentication authentication = new Authentication(user, new Authentication.RealmRef("fake", "mock", "n1"), null); authentication.writeToContext(threadPool.getThreadContext()); - ActionListener authListener = (ActionListener) invocationOnMock.getArguments()[3]; authListener.onResponse(authentication); return Void.TYPE; }).when(authenticationService).authenticate(eq(CreateTokenAction.NAME), any(CreateTokenRequest.class), @@ -223,18 +237,29 @@ public void testKerberosGrantTypeCreatesWithRefreshToken() throws Exception { authenticationService); final CreateTokenRequest createTokenRequest = new CreateTokenRequest(); createTokenRequest.setGrantType("_kerberos"); - createTokenRequest.setKerberosTicket(new SecureString(randomAlphaOfLengthBetween(3, 7).toCharArray())); + String failOrSuccess = randomBoolean() ? "fail" : "success"; + String kerbCredentialsBase64 = Base64.getEncoder().encodeToString(failOrSuccess.getBytes(StandardCharsets.UTF_8)); + createTokenRequest.setKerberosTicket(new SecureString(kerbCredentialsBase64.toCharArray())); PlainActionFuture tokenResponseFuture = new PlainActionFuture<>(); action.doExecute(null, createTokenRequest, tokenResponseFuture); - CreateTokenResponse createTokenResponse = tokenResponseFuture.get(); - assertNotNull(createTokenResponse.getRefreshToken()); - assertNotNull(createTokenResponse.getTokenString()); + if (failOrSuccess.equals("fail")) { + ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class, () -> tokenResponseFuture.actionGet()); + assertNotNull(ese.getHeader(KerberosAuthenticationToken.WWW_AUTHENTICATE)); + assertThat(ese.getHeader(KerberosAuthenticationToken.WWW_AUTHENTICATE).size(), is(1)); + assertThat(ese.getHeader(KerberosAuthenticationToken.WWW_AUTHENTICATE).get(0), is("Negotiate FAIL")); + } else { + CreateTokenResponse createTokenResponse = tokenResponseFuture.get(); + assertNotNull(createTokenResponse.getRefreshToken()); + assertNotNull(createTokenResponse.getTokenString()); + assertNotNull(createTokenResponse.getKerberosAuthenticationResponseData()); + assertThat(createTokenResponse.getKerberosAuthenticationResponseData(), is("Negotiate SUCCESS")); - assertNotNull(idxReqReference.get()); - Map sourceMap = idxReqReference.get().sourceAsMap(); - assertNotNull(sourceMap); - assertNotNull(sourceMap.get("access_token")); - assertNotNull(sourceMap.get("refresh_token")); + assertNotNull(idxReqReference.get()); + Map sourceMap = idxReqReference.get().sourceAsMap(); + assertNotNull(sourceMap); + assertNotNull(sourceMap.get("access_token")); + assertNotNull(sourceMap.get("refresh_token")); + } } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java index 3fec0f65db2a8..ea67099ccb31d 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java @@ -67,7 +67,8 @@ public void sendResponse(RestResponse restResponse) { }; CreateTokenResponseActionListener listener = new CreateTokenResponseActionListener(restChannel, restRequest, NoOpLogger.INSTANCE); CreateTokenResponse createTokenResponse = - new CreateTokenResponse(randomAlphaOfLengthBetween(1, 256), TimeValue.timeValueHours(1L), null, randomAlphaOfLength(4)); + new CreateTokenResponse(randomAlphaOfLengthBetween(1, 256), TimeValue.timeValueHours(1L), null, randomAlphaOfLength(4), + randomAlphaOfLength(5)); listener.onResponse(createTokenResponse); RestResponse response = responseSetOnce.get(); @@ -80,7 +81,8 @@ public void sendResponse(RestResponse restResponse) { assertThat(map, hasEntry("access_token", createTokenResponse.getTokenString())); assertThat(map, hasEntry("expires_in", Math.toIntExact(createTokenResponse.getExpiresIn().seconds()))); assertThat(map, hasEntry("refresh_token", createTokenResponse.getRefreshToken())); - assertEquals(4, map.size()); + assertThat(map, hasEntry("kerberos_authenticate_response_data", createTokenResponse.getKerberosAuthenticationResponseData())); + assertEquals(5, map.size()); } public void testParser() throws Exception { diff --git a/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosAuthenticationIT.java b/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosAuthenticationIT.java index fd1d51e0f65d2..cdfaef1f31822 100644 --- a/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosAuthenticationIT.java +++ b/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosAuthenticationIT.java @@ -43,6 +43,7 @@ import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; /** * Integration test to demonstrate authentication against a real MIT Kerberos @@ -124,12 +125,17 @@ public void testGetOauth2TokenInExchangeForKerberosTickets() throws PrivilegedAc try (RestClient client = buildClientForUser("test_kibana_user")) { final Response response = client.performRequest(request); - // final Response response = adminClient().performRequest(request); assertOK(response); final Map map = parseResponseAsMap(response.getEntity()); assertThat(map.get("access_token"), notNullValue()); assertThat(map.get("type"), is("Bearer")); assertThat(map.get("refresh_token"), notNullValue()); + final Object base64OutToken = map.get("kerberos_authenticate_response_data"); + assertThat(base64OutToken, notNullValue()); + final String base64EncodedToken = ((String) base64OutToken).substring("Negotiate ".length()).trim(); + final String outToken = callbackHandler.handleResponse(base64EncodedToken); + assertThat(outToken, is(nullValue())); + assertThat(callbackHandler.isEstablished(), is(true)); } } diff --git a/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/SpnegoHttpClientConfigCallbackHandler.java b/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/SpnegoHttpClientConfigCallbackHandler.java index e98b06f51c652..4694340047467 100644 --- a/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/SpnegoHttpClientConfigCallbackHandler.java +++ b/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/SpnegoHttpClientConfigCallbackHandler.java @@ -77,6 +77,7 @@ private static Oid getSpnegoOid() { private final String keytabPath; private final boolean enableDebugLogs; private LoginContext loginContext; + private GSSContext gssContext; /** * Constructs {@link SpnegoHttpClientConfigCallbackHandler} with given @@ -337,7 +338,7 @@ String getBase64EncodedTokenForSpnegoHeader(final String serviceHost) throws Pri .doPrivileged((PrivilegedExceptionAction) () -> loginUsingPassword(userPrincipalName, password)); final GSSCredential userCreds = doAsWrapper(loginContext.getSubject(), (PrivilegedExceptionAction) () -> gssManager .createCredential(gssUserPrincipalName, GSSCredential.DEFAULT_LIFETIME, SPNEGO_OID, GSSCredential.INITIATE_ONLY)); - GSSContext gssContext = gssManager.createContext(gssServicePrincipalName, SPNEGO_OID, userCreds, GSSCredential.DEFAULT_LIFETIME); + gssContext = gssManager.createContext(gssServicePrincipalName, SPNEGO_OID, userCreds, GSSCredential.DEFAULT_LIFETIME); gssContext.requestMutualAuth(true); final byte[] outToken = doAsWrapper(loginContext.getSubject(), @@ -357,6 +358,35 @@ private LoginContext loginUsingPassword(final String principal, final SecureStri return loginContext; } + /** + * Handles server response and returns new token if any to be sent to server. + * + * @param base64Token inToken received from server passed to initSecContext for + * gss negotiation + * @return Base64 encoded token to be sent to server. May return {@code null} if + * nothing to be sent. + * @throws PrivilegedActionException when privileged action threw exception + */ + String handleResponse(final String base64Token) throws PrivilegedActionException { + if (gssContext.isEstablished()) { + throw new IllegalStateException("GSS Context has already been established"); + } + final byte[] token = Base64.getDecoder().decode(base64Token); + final byte[] outToken = doAsWrapper(loginContext.getSubject(), + (PrivilegedExceptionAction) () -> gssContext.initSecContext(token, 0, token.length)); + if (outToken == null || outToken.length == 0) { + return null; + } + return Base64.getEncoder().encodeToString(outToken); + } + + /** + * @return {@code true} If the gss security context was established + */ + boolean isEstablished() { + return gssContext.isEstablished(); + } + static T doAsWrapper(final Subject subject, final PrivilegedExceptionAction action) throws PrivilegedActionException { return AccessController.doPrivileged((PrivilegedExceptionAction) () -> Subject.doAs(subject, action)); } From a688e3223b0e5893126ebbbc431088f716448ac6 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Thu, 6 Jun 2019 13:00:18 +1000 Subject: [PATCH 09/17] add documentation of token response in case of kerberos --- .../en/rest-api/security/get-tokens.asciidoc | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/x-pack/docs/en/rest-api/security/get-tokens.asciidoc b/x-pack/docs/en/rest-api/security/get-tokens.asciidoc index 09dd849122473..dcc7fb0caf58e 100644 --- a/x-pack/docs/en/rest-api/security/get-tokens.asciidoc +++ b/x-pack/docs/en/rest-api/security/get-tokens.asciidoc @@ -43,6 +43,7 @@ pertain to creating a token: `grant_type`:: (string) The type of grant. Supported grant types are: `password`, `_kerberos` (beta), `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 @@ -178,4 +179,20 @@ POST /_security/oauth2/token "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 +be used one time. Along with this there might be an base64 encoded token for clients to consume and finalize the authentication. +One must extract the token from the `kerberos_authenticate_response_data` which is in the form `Negotiate `. + +[source,js] +-------------------------------------------------- +{ + "access_token" : "dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==", + "type" : "Bearer", + "expires_in" : 1200, + "refresh_token": "vLBPvmAB6KvwvJZr27cS" + "kerberos_authenticate_response_data": "Negotiate YIIB6wYJKoZIhvcSAQICAQBuggHaMIIB1qADAg" +} +-------------------------------------------------- // NOTCONSOLE \ No newline at end of file From 28f737edb3cc5c5ac0121e998394267e782a4368 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Thu, 6 Jun 2019 13:41:46 +1000 Subject: [PATCH 10/17] add rest response test case --- .../action/oauth2/RestGetTokenAction.java | 9 ++++-- .../oauth2/RestGetTokenActionTests.java | 29 +++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenAction.java index 76b18621004a7..d2cc2b72bff41 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenAction.java @@ -30,6 +30,7 @@ import org.elasticsearch.xpack.core.security.action.token.CreateTokenRequest; import org.elasticsearch.xpack.core.security.action.token.CreateTokenResponse; import org.elasticsearch.xpack.core.security.action.token.RefreshTokenAction; +import org.elasticsearch.xpack.security.authc.kerberos.KerberosAuthenticationToken; import java.io.IOException; import java.util.Arrays; @@ -128,10 +129,12 @@ public void onFailure(Exception e) { ((ElasticsearchSecurityException) e).getHeader("error_description").size() == 1) { sendTokenErrorResponse(TokenRequestError.INVALID_GRANT, ((ElasticsearchSecurityException) e).getHeader("error_description").get(0), e); - } else if (e instanceof ElasticsearchSecurityException && "unauthorized_client".equals(e.getMessage()) && - ((ElasticsearchSecurityException) e).getHeader("error_description").size() == 1) { + } else if (e instanceof ElasticsearchSecurityException + && "failed to authenticate user, gss context negotiation not complete".equals(e.getMessage()) + && ((ElasticsearchSecurityException) e).getHeader(KerberosAuthenticationToken.WWW_AUTHENTICATE) != null + && ((ElasticsearchSecurityException) e).getHeader(KerberosAuthenticationToken.WWW_AUTHENTICATE).size() == 1) { sendTokenErrorResponse(TokenRequestError.UNAUTHORIZED_CLIENT, - ((ElasticsearchSecurityException) e).getHeader("error_description").get(0), e); + ((ElasticsearchSecurityException) e).getHeader(KerberosAuthenticationToken.WWW_AUTHENTICATE).get(0), e); } else { sendFailure(e); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java index ea67099ccb31d..d2583de3093ef 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java @@ -6,6 +6,7 @@ package org.elasticsearch.xpack.security.rest.action.oauth2; import org.apache.lucene.util.SetOnce; +import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.DeprecationHandler; @@ -23,8 +24,10 @@ import org.elasticsearch.xpack.core.security.action.token.CreateTokenRequest; import org.elasticsearch.xpack.core.security.action.token.CreateTokenResponse; import org.elasticsearch.xpack.core.security.support.NoOpLogger; +import org.elasticsearch.xpack.security.authc.kerberos.KerberosAuthenticationToken; import org.elasticsearch.xpack.security.rest.action.oauth2.RestGetTokenAction.CreateTokenResponseActionListener; +import java.util.Locale; import java.util.Map; import static org.hamcrest.Matchers.hasEntry; @@ -85,6 +88,32 @@ public void sendResponse(RestResponse restResponse) { assertEquals(5, map.size()); } + public void testSendResponseKerberosError() { + FakeRestRequest restRequest = new FakeRestRequest.Builder(NamedXContentRegistry.EMPTY).build(); + final SetOnce responseSetOnce = new SetOnce<>(); + RestChannel restChannel = new AbstractRestChannel(restRequest, randomBoolean()) { + @Override + public void sendResponse(RestResponse restResponse) { + responseSetOnce.set(restResponse); + } + }; + CreateTokenResponseActionListener listener = new CreateTokenResponseActionListener(restChannel, restRequest, NoOpLogger.INSTANCE); + String errorMessage = "failed to authenticate user, gss context negotiation not complete"; + ElasticsearchSecurityException ese = new ElasticsearchSecurityException(errorMessage, RestStatus.UNAUTHORIZED); + ese.addHeader(KerberosAuthenticationToken.WWW_AUTHENTICATE, "Negotiate FAIL"); + listener.onFailure(ese); + + RestResponse response = responseSetOnce.get(); + assertNotNull(response); + + Map map = XContentHelper.convertToMap(response.content(), false, + XContentType.fromMediaType(response.contentType())).v2(); + assertThat(map, hasEntry("error", RestGetTokenAction.TokenRequestError.UNAUTHORIZED_CLIENT.name().toLowerCase(Locale.ROOT))); + assertThat(map, hasEntry("error_description", "Negotiate FAIL")); + assertEquals(2, map.size()); + assertEquals(RestStatus.BAD_REQUEST, response.status()); + } + public void testParser() throws Exception { final String request = "{" + "\"grant_type\": \"password\"," + From 25c6c9b55e7ed784b8b0b1363b89d13bde31822c Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Thu, 6 Jun 2019 13:47:15 +1000 Subject: [PATCH 11/17] rename field in response --- .../en/rest-api/security/get-tokens.asciidoc | 4 ++-- .../action/token/CreateTokenResponse.java | 20 +++++++++---------- .../oauth2/RestGetTokenActionTests.java | 2 +- .../kerberos/KerberosAuthenticationIT.java | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/x-pack/docs/en/rest-api/security/get-tokens.asciidoc b/x-pack/docs/en/rest-api/security/get-tokens.asciidoc index dcc7fb0caf58e..bfb5f94c32ffe 100644 --- a/x-pack/docs/en/rest-api/security/get-tokens.asciidoc +++ b/x-pack/docs/en/rest-api/security/get-tokens.asciidoc @@ -183,7 +183,7 @@ POST /_security/oauth2/token The API will return a new token and refresh token if kerberos authentication is successful. Each refresh token may only be used one time. Along with this there might be an base64 encoded token for clients to consume and finalize the authentication. -One must extract the token from the `kerberos_authenticate_response_data` which is in the form `Negotiate `. +One must extract the token from the `kerberos_authentication_response_token` which is in the form `Negotiate `. [source,js] -------------------------------------------------- @@ -192,7 +192,7 @@ One must extract the token from the `kerberos_authenticate_response_data` which "type" : "Bearer", "expires_in" : 1200, "refresh_token": "vLBPvmAB6KvwvJZr27cS" - "kerberos_authenticate_response_data": "Negotiate YIIB6wYJKoZIhvcSAQICAQBuggHaMIIB1qADAg" + "kerberos_authentication_response_token": "Negotiate YIIB6wYJKoZIhvcSAQICAQBuggHaMIIB1qADAg" } -------------------------------------------------- // NOTCONSOLE \ No newline at end of file diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenResponse.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenResponse.java index 966fd09d5c7db..7acc7d93fbad0 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenResponse.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenResponse.java @@ -26,17 +26,17 @@ public final class CreateTokenResponse extends ActionResponse implements ToXCont private TimeValue expiresIn; private String scope; private String refreshToken; - private String kerberosAuthenticateResponseData; + private String kerberosAuthenticationResponseToken; CreateTokenResponse() {} public CreateTokenResponse(String tokenString, TimeValue expiresIn, String scope, String refreshToken, - String kerberosAuthenticateResponseData) { + String kerberosAuthenticationResponseToken) { this.tokenString = Objects.requireNonNull(tokenString); this.expiresIn = Objects.requireNonNull(expiresIn); this.scope = scope; this.refreshToken = refreshToken; - this.kerberosAuthenticateResponseData = kerberosAuthenticateResponseData; + this.kerberosAuthenticationResponseToken = kerberosAuthenticationResponseToken; } public String getTokenString() { @@ -56,7 +56,7 @@ public String getRefreshToken() { } public String getKerberosAuthenticationResponseData() { - return kerberosAuthenticateResponseData; + return kerberosAuthenticationResponseToken; } @Override @@ -66,7 +66,7 @@ public void writeTo(StreamOutput out) throws IOException { out.writeTimeValue(expiresIn); out.writeOptionalString(scope); out.writeOptionalString(refreshToken); - out.writeOptionalString(kerberosAuthenticateResponseData); + out.writeOptionalString(kerberosAuthenticationResponseToken); } @Override @@ -76,7 +76,7 @@ public void readFrom(StreamInput in) throws IOException { expiresIn = in.readTimeValue(); scope = in.readOptionalString(); refreshToken = in.readOptionalString(); - kerberosAuthenticateResponseData = in.readOptionalString(); + kerberosAuthenticationResponseToken = in.readOptionalString(); } @Override @@ -92,8 +92,8 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws if (scope != null) { builder.field("scope", scope); } - if (kerberosAuthenticateResponseData != null) { - builder.field("kerberos_authenticate_response_data", kerberosAuthenticateResponseData); + if (kerberosAuthenticationResponseToken != null) { + builder.field("kerberos_authentication_response_token", kerberosAuthenticationResponseToken); } return builder.endObject(); } @@ -107,11 +107,11 @@ public boolean equals(Object o) { Objects.equals(expiresIn, that.expiresIn) && Objects.equals(scope, that.scope) && Objects.equals(refreshToken, that.refreshToken) && - Objects.equals(kerberosAuthenticateResponseData, that.kerberosAuthenticateResponseData); + Objects.equals(kerberosAuthenticationResponseToken, that.kerberosAuthenticationResponseToken); } @Override public int hashCode() { - return Objects.hash(tokenString, expiresIn, scope, refreshToken, kerberosAuthenticateResponseData); + return Objects.hash(tokenString, expiresIn, scope, refreshToken, kerberosAuthenticationResponseToken); } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java index d2583de3093ef..467a8bbc5bfd9 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java @@ -84,7 +84,7 @@ public void sendResponse(RestResponse restResponse) { assertThat(map, hasEntry("access_token", createTokenResponse.getTokenString())); assertThat(map, hasEntry("expires_in", Math.toIntExact(createTokenResponse.getExpiresIn().seconds()))); assertThat(map, hasEntry("refresh_token", createTokenResponse.getRefreshToken())); - assertThat(map, hasEntry("kerberos_authenticate_response_data", createTokenResponse.getKerberosAuthenticationResponseData())); + assertThat(map, hasEntry("kerberos_authentication_response_token", createTokenResponse.getKerberosAuthenticationResponseData())); assertEquals(5, map.size()); } diff --git a/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosAuthenticationIT.java b/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosAuthenticationIT.java index cdfaef1f31822..c671d96b4e2ed 100644 --- a/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosAuthenticationIT.java +++ b/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosAuthenticationIT.java @@ -130,7 +130,7 @@ public void testGetOauth2TokenInExchangeForKerberosTickets() throws PrivilegedAc assertThat(map.get("access_token"), notNullValue()); assertThat(map.get("type"), is("Bearer")); assertThat(map.get("refresh_token"), notNullValue()); - final Object base64OutToken = map.get("kerberos_authenticate_response_data"); + final Object base64OutToken = map.get("kerberos_authentication_response_token"); assertThat(base64OutToken, notNullValue()); final String base64EncodedToken = ((String) base64OutToken).substring("Negotiate ".length()).trim(); final String outToken = callbackHandler.handleResponse(base64EncodedToken); From 9e96fa40688bbc9390799ed7be3dcab1bcb9244d Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Wed, 12 Jun 2019 10:56:21 +1000 Subject: [PATCH 12/17] address review comments --- .../en/rest-api/security/get-tokens.asciidoc | 5 +-- .../action/token/CreateTokenRequest.java | 28 +++++++-------- .../token/TransportCreateTokenAction.java | 10 +++++- .../action/oauth2/RestGetTokenAction.java | 35 +++++++++++++++---- .../TransportCreateTokenActionTests.java | 2 +- .../oauth2/RestGetTokenActionTests.java | 11 ++++-- 6 files changed, 64 insertions(+), 27 deletions(-) diff --git a/x-pack/docs/en/rest-api/security/get-tokens.asciidoc b/x-pack/docs/en/rest-api/security/get-tokens.asciidoc index bfb5f94c32ffe..f2d9045003cc5 100644 --- a/x-pack/docs/en/rest-api/security/get-tokens.asciidoc +++ b/x-pack/docs/en/rest-api/security/get-tokens.asciidoc @@ -183,7 +183,8 @@ POST /_security/oauth2/token The API will return a new token and refresh token if kerberos authentication is successful. Each refresh token may only be used one time. Along with this there might be an base64 encoded token for clients to consume and finalize the authentication. -One must extract the token from the `kerberos_authentication_response_token` which is in the form `Negotiate `. +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] -------------------------------------------------- @@ -192,7 +193,7 @@ One must extract the token from the `kerberos_authentication_response_token` whi "type" : "Bearer", "expires_in" : 1200, "refresh_token": "vLBPvmAB6KvwvJZr27cS" - "kerberos_authentication_response_token": "Negotiate YIIB6wYJKoZIhvcSAQICAQBuggHaMIIB1qADAg" + "kerberos_authentication_response_token": "YIIB6wYJKoZIhvcSAQICAQBuggHaMIIB1qADAg" } -------------------------------------------------- // NOTCONSOLE \ No newline at end of file diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java index 28c77e67c9806..2467afe84ce05 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java @@ -91,28 +91,28 @@ public ActionRequestValidationException validate() { if (type != null) { switch (type) { case PASSWORD: - validationException = unsupportedFieldValidation(type, "kerberos_ticket", kerberosTicket, validationException); - validationException = unsupportedFieldValidation(type, "refresh_token", refreshToken, 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 = unsupportedFieldValidation(type, "username", username, validationException); - validationException = unsupportedFieldValidation(type, "password", password, validationException); - validationException = unsupportedFieldValidation(type, "refresh_token", refreshToken, validationException); + 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: - validationException = unsupportedFieldValidation(type, "username", username, validationException); - validationException = unsupportedFieldValidation(type, "password", password, validationException); - validationException = unsupportedFieldValidation(type, "kerberos_ticket", kerberosTicket, 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: - validationException = unsupportedFieldValidation(type, "username", username, validationException); - validationException = unsupportedFieldValidation(type, "password", password, validationException); - validationException = unsupportedFieldValidation(type, "kerberos_ticket", kerberosTicket, validationException); - validationException = unsupportedFieldValidation(type, "refresh_token", refreshToken, 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: [" + @@ -137,13 +137,13 @@ private static ActionRequestValidationException validateRequiredField(String fie private static ActionRequestValidationException validateRequiredField(String field, SecureString fieldValue, ActionRequestValidationException validationException) { - if (fieldValue == null || fieldValue.getChars() == null || fieldValue.getChars().length == 0) { + 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 unsupportedFieldValidation(GrantType grantType, String field, Object fieldValue, + private static ActionRequestValidationException validateUnsupportedField(GrantType grantType, String field, Object fieldValue, ActionRequestValidationException validationException) { if (fieldValue != null) { validationException = addValidationError( diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenAction.java index 40a29466363d2..252807779a1d6 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenAction.java @@ -139,10 +139,18 @@ private void createToken(GrantType grantType, CreateTokenRequest request, Authen private String extractOutToken() { List values = threadPool.getThreadContext().getResponseHeaders().get(KerberosAuthenticationToken.WWW_AUTHENTICATE); if (values != null && values.size() == 1) { - return values.get(0); + final String wwwAuthenticateHeaderValue = values.get(0); + // it may contain base64 encoded token that needs to be sent to client if mutual auth was requested + if (wwwAuthenticateHeaderValue.startsWith(KerberosAuthenticationToken.NEGOTIATE_AUTH_HEADER_PREFIX)) { + final String base64EncodedToken = wwwAuthenticateHeaderValue + .substring(KerberosAuthenticationToken.NEGOTIATE_AUTH_HEADER_PREFIX.length()).trim(); + return base64EncodedToken; + } } + threadPool.getThreadContext().getResponseHeaders().remove(KerberosAuthenticationToken.WWW_AUTHENTICATE); return null; } + static String getResponseScopeValue(String requestScope) { final String scope; // the OAuth2.0 RFC requires the scope to be provided in the diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenAction.java index d2cc2b72bff41..2c1adae7fb883 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenAction.java @@ -34,6 +34,7 @@ import java.io.IOException; import java.util.Arrays; +import java.util.List; import java.util.Locale; import static org.elasticsearch.rest.RestRequest.Method.POST; @@ -130,16 +131,27 @@ public void onFailure(Exception e) { sendTokenErrorResponse(TokenRequestError.INVALID_GRANT, ((ElasticsearchSecurityException) e).getHeader("error_description").get(0), e); } else if (e instanceof ElasticsearchSecurityException - && "failed to authenticate user, gss context negotiation not complete".equals(e.getMessage()) - && ((ElasticsearchSecurityException) e).getHeader(KerberosAuthenticationToken.WWW_AUTHENTICATE) != null - && ((ElasticsearchSecurityException) e).getHeader(KerberosAuthenticationToken.WWW_AUTHENTICATE).size() == 1) { - sendTokenErrorResponse(TokenRequestError.UNAUTHORIZED_CLIENT, - ((ElasticsearchSecurityException) e).getHeader(KerberosAuthenticationToken.WWW_AUTHENTICATE).get(0), e); + && "failed to authenticate user, gss context negotiation not complete".equals(e.getMessage())) { + sendTokenErrorResponse(TokenRequestError._UNAUTHORIZED, extractBase64EncodedToken((ElasticsearchSecurityException) e), e); } else { sendFailure(e); } } + private String extractBase64EncodedToken(ElasticsearchSecurityException e) { + String base64EncodedToken = null; + List values = e.getHeader(KerberosAuthenticationToken.WWW_AUTHENTICATE); + if (values != null && values.size() == 1) { + final String wwwAuthenticateHeaderValue = values.get(0); + // it may contain base64 encoded token that needs to be sent to client if Spnego GSS context negotiation failed + if (wwwAuthenticateHeaderValue.startsWith(KerberosAuthenticationToken.NEGOTIATE_AUTH_HEADER_PREFIX)) { + base64EncodedToken = wwwAuthenticateHeaderValue + .substring(KerberosAuthenticationToken.NEGOTIATE_AUTH_HEADER_PREFIX.length()).trim(); + } + } + return base64EncodedToken; + } + void sendTokenErrorResponse(TokenRequestError error, String description, Exception e) { try (XContentBuilder builder = channel.newErrorBuilder()) { // defined by https://tools.ietf.org/html/rfc6749#section-5.2 @@ -211,6 +223,17 @@ enum TokenRequestError { * The requested scope is invalid, unknown, malformed, or exceeds the * scope granted by the resource owner. */ - INVALID_SCOPE + INVALID_SCOPE, + + // Custom error code + /** + * When the request for authentication fails using custom grant type for given + * credentials. + * If the client attempted to authenticate via the "Authorization" request + * the authorization server MAY respond with an HTTP 401 + * (Unauthorized) status code and include the "WWW-Authenticate" + * response header field + */ + _UNAUTHORIZED, } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java index 22bce9492564d..6e9e2cba1be0a 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java @@ -253,7 +253,7 @@ public void testKerberosGrantTypeCreatesWithRefreshToken() throws Exception { assertNotNull(createTokenResponse.getRefreshToken()); assertNotNull(createTokenResponse.getTokenString()); assertNotNull(createTokenResponse.getKerberosAuthenticationResponseData()); - assertThat(createTokenResponse.getKerberosAuthenticationResponseData(), is("Negotiate SUCCESS")); + assertThat(createTokenResponse.getKerberosAuthenticationResponseData(), is("SUCCESS")); assertNotNull(idxReqReference.get()); Map sourceMap = idxReqReference.get().sourceAsMap(); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java index 467a8bbc5bfd9..f51230f2e884a 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java @@ -100,7 +100,8 @@ public void sendResponse(RestResponse restResponse) { CreateTokenResponseActionListener listener = new CreateTokenResponseActionListener(restChannel, restRequest, NoOpLogger.INSTANCE); String errorMessage = "failed to authenticate user, gss context negotiation not complete"; ElasticsearchSecurityException ese = new ElasticsearchSecurityException(errorMessage, RestStatus.UNAUTHORIZED); - ese.addHeader(KerberosAuthenticationToken.WWW_AUTHENTICATE, "Negotiate FAIL"); + boolean addBase64EncodedToken = randomBoolean(); + ese.addHeader(KerberosAuthenticationToken.WWW_AUTHENTICATE, "Negotiate" + ((addBase64EncodedToken) ? " FAIL" : "")); listener.onFailure(ese); RestResponse response = responseSetOnce.get(); @@ -108,8 +109,12 @@ public void sendResponse(RestResponse restResponse) { Map map = XContentHelper.convertToMap(response.content(), false, XContentType.fromMediaType(response.contentType())).v2(); - assertThat(map, hasEntry("error", RestGetTokenAction.TokenRequestError.UNAUTHORIZED_CLIENT.name().toLowerCase(Locale.ROOT))); - assertThat(map, hasEntry("error_description", "Negotiate FAIL")); + assertThat(map, hasEntry("error", RestGetTokenAction.TokenRequestError._UNAUTHORIZED.name().toLowerCase(Locale.ROOT))); + if (addBase64EncodedToken) { + assertThat(map, hasEntry("error_description", "FAIL")); + } else { + assertThat(map, hasEntry("error_description", null)); + } assertEquals(2, map.size()); assertEquals(RestStatus.BAD_REQUEST, response.status()); } From 50ee72234386233f5e30011ffb88eabdac9e959e Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Wed, 12 Jun 2019 11:20:44 +1000 Subject: [PATCH 13/17] HLRC changes --- .../client/security/CreateTokenResponse.java | 19 ++++++++++++++----- .../security/CreateTokenResponseTests.java | 5 +++++ .../action/token/CreateTokenResponse.java | 2 +- .../TransportCreateTokenActionTests.java | 4 ++-- .../oauth2/RestGetTokenActionTests.java | 2 +- 5 files changed, 23 insertions(+), 9 deletions(-) diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/CreateTokenResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/CreateTokenResponse.java index 32d298d1a9bc0..dc71d49f4b770 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/CreateTokenResponse.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/CreateTokenResponse.java @@ -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() { @@ -70,6 +73,10 @@ public String getRefreshToken() { return refreshToken; } + public String getKerberosAuthenticationResponseToken() { + return kerberosAuthenticationResponseToken; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -83,17 +90,18 @@ 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 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")); @@ -101,6 +109,7 @@ public int hashCode() { 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 { diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/CreateTokenResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/CreateTokenResponseTests.java index f99ea668665dd..34a03647f6060 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/CreateTokenResponseTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/CreateTokenResponseTests.java @@ -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); @@ -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); @@ -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)); } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenResponse.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenResponse.java index 7acc7d93fbad0..299d66d840c16 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenResponse.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenResponse.java @@ -55,7 +55,7 @@ public String getRefreshToken() { return refreshToken; } - public String getKerberosAuthenticationResponseData() { + public String getKerberosAuthenticationResponseToken() { return kerberosAuthenticationResponseToken; } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java index 6e9e2cba1be0a..b32ceee1a87d9 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java @@ -252,8 +252,8 @@ public void testKerberosGrantTypeCreatesWithRefreshToken() throws Exception { CreateTokenResponse createTokenResponse = tokenResponseFuture.get(); assertNotNull(createTokenResponse.getRefreshToken()); assertNotNull(createTokenResponse.getTokenString()); - assertNotNull(createTokenResponse.getKerberosAuthenticationResponseData()); - assertThat(createTokenResponse.getKerberosAuthenticationResponseData(), is("SUCCESS")); + assertNotNull(createTokenResponse.getKerberosAuthenticationResponseToken()); + assertThat(createTokenResponse.getKerberosAuthenticationResponseToken(), is("SUCCESS")); assertNotNull(idxReqReference.get()); Map sourceMap = idxReqReference.get().sourceAsMap(); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java index f51230f2e884a..a3e875fec2c02 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenActionTests.java @@ -84,7 +84,7 @@ public void sendResponse(RestResponse restResponse) { assertThat(map, hasEntry("access_token", createTokenResponse.getTokenString())); assertThat(map, hasEntry("expires_in", Math.toIntExact(createTokenResponse.getExpiresIn().seconds()))); assertThat(map, hasEntry("refresh_token", createTokenResponse.getRefreshToken())); - assertThat(map, hasEntry("kerberos_authentication_response_token", createTokenResponse.getKerberosAuthenticationResponseData())); + assertThat(map, hasEntry("kerberos_authentication_response_token", createTokenResponse.getKerberosAuthenticationResponseToken())); assertEquals(5, map.size()); } From 793413b40a218922250bb13803c2c788e749305d Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Thu, 13 Jun 2019 15:40:12 +1000 Subject: [PATCH 14/17] fix test --- .../security/authc/kerberos/KerberosAuthenticationIT.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosAuthenticationIT.java b/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosAuthenticationIT.java index c671d96b4e2ed..8363705d00cc1 100644 --- a/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosAuthenticationIT.java +++ b/x-pack/qa/kerberos-tests/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosAuthenticationIT.java @@ -132,8 +132,7 @@ public void testGetOauth2TokenInExchangeForKerberosTickets() throws PrivilegedAc assertThat(map.get("refresh_token"), notNullValue()); final Object base64OutToken = map.get("kerberos_authentication_response_token"); assertThat(base64OutToken, notNullValue()); - final String base64EncodedToken = ((String) base64OutToken).substring("Negotiate ".length()).trim(); - final String outToken = callbackHandler.handleResponse(base64EncodedToken); + final String outToken = callbackHandler.handleResponse((String) base64OutToken); assertThat(outToken, is(nullValue())); assertThat(callbackHandler.isEstablished(), is(true)); } From c5bfdaf322bb1181de3c16f47c514fc2bd8b76e0 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Fri, 14 Jun 2019 17:10:47 +1000 Subject: [PATCH 15/17] address review feedback --- x-pack/docs/en/rest-api/security/get-tokens.asciidoc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/docs/en/rest-api/security/get-tokens.asciidoc b/x-pack/docs/en/rest-api/security/get-tokens.asciidoc index f2d9045003cc5..1738e04b1f728 100644 --- a/x-pack/docs/en/rest-api/security/get-tokens.asciidoc +++ b/x-pack/docs/en/rest-api/security/get-tokens.asciidoc @@ -181,10 +181,10 @@ POST /_security/oauth2/token -------------------------------------------------- // NOTCONSOLE -The API will return a new token and refresh token if kerberos authentication is successful. Each refresh token may only -be used one time. Along with this there might be an base64 encoded token for clients to consume and finalize the authentication. -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. +The API will return a new token and refresh token if kerberos authentication is successful. +Each refresh token may only be used one time. When the mutual authentication is requested in the Spnego GSS context, + a base64 encoded token will be returned by the server in the `kerberos_authentication_response_token` + for clients to consume and finalize the authentication. [source,js] -------------------------------------------------- From f76feecedd65bbeb70bdc39060a810dfd5d1c04f Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Mon, 17 Jun 2019 13:34:47 +1000 Subject: [PATCH 16/17] document kerberos grant type as internal --- x-pack/docs/en/rest-api/security/get-tokens.asciidoc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/x-pack/docs/en/rest-api/security/get-tokens.asciidoc b/x-pack/docs/en/rest-api/security/get-tokens.asciidoc index 1738e04b1f728..16ff15e689f45 100644 --- a/x-pack/docs/en/rest-api/security/get-tokens.asciidoc +++ b/x-pack/docs/en/rest-api/security/get-tokens.asciidoc @@ -41,9 +41,10 @@ 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`, `_kerberos` (beta), -`client_credentials` and `refresh_token`. -`_kerberos` implements SPNEGO based Kerberos support. +(string) The type of grant. Supported grant types are: `password`, `_kerberos`, +`client_credentials` and `refresh_token`. The `_kerberos` grant type +is supported internally and implements SPNEGO based Kerberos support. The `kerberos` +grant type may change from version to version. `password`:: (string) The user's password. If you specify the `password` grant type, this From b39434a4126f7e496d16af66507351f357078084 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Mon, 17 Jun 2019 13:43:47 +1000 Subject: [PATCH 17/17] correct typo in the kerberos grant type name --- x-pack/docs/en/rest-api/security/get-tokens.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/docs/en/rest-api/security/get-tokens.asciidoc b/x-pack/docs/en/rest-api/security/get-tokens.asciidoc index 16ff15e689f45..44450dd43c806 100644 --- a/x-pack/docs/en/rest-api/security/get-tokens.asciidoc +++ b/x-pack/docs/en/rest-api/security/get-tokens.asciidoc @@ -43,7 +43,7 @@ pertain to creating a token: `grant_type`:: (string) The type of grant. Supported grant types are: `password`, `_kerberos`, `client_credentials` and `refresh_token`. The `_kerberos` grant type -is supported internally and implements SPNEGO based Kerberos support. The `kerberos` +is supported internally and implements SPNEGO based Kerberos support. The `_kerberos` grant type may change from version to version. `password`::