Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
package org.elasticsearch.xpack.security.authc;

import org.apache.directory.api.util.Strings;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.Version;
import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse;
import org.elasticsearch.action.search.SearchRequest;
Expand All @@ -21,7 +20,6 @@
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.ResponseException;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.internal.Client;
import org.elasticsearch.common.CheckedSupplier;
import org.elasticsearch.common.UUIDs;
import org.elasticsearch.common.settings.SecureString;
Expand All @@ -36,15 +34,8 @@
import org.elasticsearch.test.TestSecurityClient;
import org.elasticsearch.test.TestSecurityClient.OAuth2Token;
import org.elasticsearch.test.TestSecurityClient.TokenInvalidation;
import org.elasticsearch.test.rest.ObjectPath;
import org.elasticsearch.xpack.core.XPackSettings;
import org.elasticsearch.xpack.core.security.action.token.CreateTokenAction;
import org.elasticsearch.xpack.core.security.action.token.CreateTokenResponse;
import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenAction;
import org.elasticsearch.xpack.core.security.action.token.RefreshTokenAction;
import org.elasticsearch.xpack.core.security.action.user.AuthenticateAction;
import org.elasticsearch.xpack.core.security.action.user.AuthenticateRequest;
import org.elasticsearch.xpack.core.security.action.user.AuthenticateResponse;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authc.AuthenticationServiceField;
import org.elasticsearch.xpack.core.security.authc.TokenMetadata;
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
Expand Down Expand Up @@ -649,66 +640,55 @@ public void testCreatorRealmCaptureWillWorkWithClientRunAs() throws IOException
final String nativeTokenUsername = "native_token_user";
getSecurityClient().putUser(new User(nativeTokenUsername, "superuser"), SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING);
// File realm user run-as a native realm user
final Client runAsClient = client().filterWithHeader(
Map.of(
"Authorization",
UsernamePasswordToken.basicAuthHeaderValue(ES_TEST_ROOT_USER, TEST_PASSWORD_SECURE_STRING.clone()),
AuthenticationServiceField.RUN_AS_USER_HEADER,
nativeTokenUsername
)
final TestSecurityClient runAsClient = getSecurityClient(
RequestOptions.DEFAULT.toBuilder()
.addHeader(
"Authorization",
UsernamePasswordToken.basicAuthHeaderValue(ES_TEST_ROOT_USER, TEST_PASSWORD_SECURE_STRING.clone())
)
.addHeader(AuthenticationServiceField.RUN_AS_USER_HEADER, nativeTokenUsername)
.build()
);

// Create a token with client credentials and run-as, the token should be owned by the run-as user (native realm)
var createTokenRequest1 = new org.elasticsearch.xpack.core.security.action.token.CreateTokenRequest();
createTokenRequest1.setGrantType("client_credentials");
final PlainActionFuture<CreateTokenResponse> future1 = new PlainActionFuture<>();
runAsClient.execute(CreateTokenAction.INSTANCE, createTokenRequest1, future1);
final String accessToken = future1.actionGet().getTokenString();
final OAuth2Token oAuth2Token1 = runAsClient.createTokenWithClientCredentialsGrant();
// Token is usable
final AuthenticateResponse authenticateResponse = client().filterWithHeader(Map.of("Authorization", "Bearer " + accessToken))
.execute(AuthenticateAction.INSTANCE, new AuthenticateRequest(nativeTokenUsername))
.actionGet();
assertThat(authenticateResponse.authentication().getUser().principal(), equalTo(nativeTokenUsername));
assertThat(authenticateResponse.authentication().getLookedUpBy().getName(), equalTo("index"));
assertThat(authenticateResponse.authentication().getAuthenticatedBy().getName(), equalTo("file"));
assertThat(authenticateResponse.authentication().getAuthenticationType(), is(Authentication.AuthenticationType.TOKEN));
// Invalidate tokens by realm and username respect the run-as user's username and realm
var invalidateTokenRequest = new org.elasticsearch.xpack.core.security.action.token.InvalidateTokenRequest(
null,
null,
"index",
nativeTokenUsername
);
final PlainActionFuture<org.elasticsearch.xpack.core.security.action.token.InvalidateTokenResponse> future2 =
new PlainActionFuture<>();
client().execute(InvalidateTokenAction.INSTANCE, invalidateTokenRequest, future2);
assertThat(future2.actionGet().getResult().getInvalidatedTokens().size(), equalTo(1));
final Map<String, Object> authenticateMap = getSecurityClient(oAuth2Token1.accessToken()).authenticate();
assertThat(ObjectPath.evaluate(authenticateMap, "username"), equalTo(nativeTokenUsername));
assertThat(ObjectPath.evaluate(authenticateMap, "lookup_realm.name"), equalTo("index"));
assertThat(ObjectPath.evaluate(authenticateMap, "authentication_realm.name"), equalTo("file"));
assertThat(ObjectPath.evaluate(authenticateMap, "authentication_type"), is("token"));

final TokenInvalidation tokenInvalidation = getSecurityClient().invalidateTokens("""
{
"realm_name":"%s",
"username":"%s"
}""".formatted("index", nativeTokenUsername));
assertThat(tokenInvalidation.invalidated(), equalTo(1));

// Create a token with password grant and run-as user (native realm)
var createTokenRequest2 = new org.elasticsearch.xpack.core.security.action.token.CreateTokenRequest();
createTokenRequest2.setGrantType("password");
createTokenRequest2.setUsername(ES_TEST_ROOT_USER);
createTokenRequest2.setPassword(TEST_PASSWORD_SECURE_STRING.clone());
final PlainActionFuture<CreateTokenResponse> future3 = new PlainActionFuture<>();
runAsClient.execute(CreateTokenAction.INSTANCE, createTokenRequest2, future3);
final String refreshToken = future3.actionGet().getRefreshToken();

var createTokenRequest3 = new org.elasticsearch.xpack.core.security.action.token.CreateTokenRequest();
createTokenRequest3.setGrantType("refresh_token");
createTokenRequest3.setRefreshToken(refreshToken);
final PlainActionFuture<CreateTokenResponse> future4 = new PlainActionFuture<>();
final OAuth2Token oAuth2Token2 = runAsClient.createToken(
new UsernamePasswordToken(ES_TEST_ROOT_USER, TEST_PASSWORD_SECURE_STRING.clone())
);

// Refresh token is bound to the original user that creates it. In this case, it is the run-as user
// refresh without run-as should fail
client().filterWithHeader(
Map.of("Authorization", UsernamePasswordToken.basicAuthHeaderValue(ES_TEST_ROOT_USER, TEST_PASSWORD_SECURE_STRING.clone()))
).execute(RefreshTokenAction.INSTANCE, createTokenRequest3, future4);
expectThrows(ElasticsearchSecurityException.class, future4::actionGet);
final ResponseException e1 = expectThrows(
ResponseException.class,
() -> getSecurityClient(
RequestOptions.DEFAULT.toBuilder()
.addHeader(
"Authorization",
UsernamePasswordToken.basicAuthHeaderValue(ES_TEST_ROOT_USER, TEST_PASSWORD_SECURE_STRING.clone())
)
.build()
).refreshToken(oAuth2Token2.getRefreshToken())
);
assertThat(e1.getMessage(), containsString("tokens must be refreshed by the creating client"));

// refresh with run-as should work
final PlainActionFuture<CreateTokenResponse> future5 = new PlainActionFuture<>();
runAsClient.execute(RefreshTokenAction.INSTANCE, createTokenRequest3, future5);
assertThat(future5.actionGet().getTokenString(), notNullValue());
final OAuth2Token oAuth2Token3 = runAsClient.refreshToken(oAuth2Token2.getRefreshToken());
assertThat(oAuth2Token3.accessToken(), notNullValue());
}

private OAuth2Token createToken(RequestOptions options) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ public TokenInvalidation invalidateTokensForRealm(String realmName) throws IOExc
}

@SuppressWarnings("unchecked")
private TokenInvalidation invalidateTokens(String requestBody) throws IOException {
public TokenInvalidation invalidateTokens(String requestBody) throws IOException {
final String endpoint = "/_security/oauth2/token";
final Request request = new Request(HttpDelete.METHOD_NAME, endpoint);
// This API returns 404 (with the same body as a 200 response) if there's nothing to delete.
Expand Down