Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Always return 401 for not valid tokens #49736

Merged
merged 4 commits into from
Dec 6, 2019
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 @@ -421,7 +421,7 @@ private void getUserTokenFromId(String userTokenId, Version tokenVersion, Action
} else {
final GetRequest getRequest = client.prepareGet(tokensIndex.aliasName(),
getTokenDocumentId(userTokenId)).request();
final Consumer<Exception> onFailure = ex -> listener.onFailure(traceLog("decode token", userTokenId, ex));
final Consumer<Exception> onFailure = ex -> listener.onFailure(traceLog("get token from id", userTokenId, ex));
tokensIndex.checkIndexVersionThenExecute(
ex -> listener.onFailure(traceLog("prepare tokens index [" + tokensIndex.aliasName() +"]", userTokenId, ex)),
() -> executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, getRequest,
Expand All @@ -441,8 +441,10 @@ private void getUserTokenFromId(String userTokenId, Version tokenVersion, Action
listener.onResponse(UserToken.fromSourceMap(userTokenSource));
}
} else {
onFailure.accept(
new IllegalStateException("token document is missing and must be present"));
// The chances of a random token string decoding to something that we can read is minimal, so
// we assume that this was a token we have created but is now expired/revoked and deleted
logger.trace("The access token [{}] is expired and already deleted", userTokenId);
listener.onResponse(null);
}
}, e -> {
// if the index or the shard is not there / available we assume that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import org.apache.directory.api.util.Strings;
import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.Version;
import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse;
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
import org.elasticsearch.action.search.SearchRequest;
Expand All @@ -23,6 +24,7 @@
import org.elasticsearch.client.security.InvalidateTokenRequest;
import org.elasticsearch.client.security.InvalidateTokenResponse;
import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse;
import org.elasticsearch.common.UUIDs;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.QueryBuilders;
Expand Down Expand Up @@ -171,6 +173,7 @@ public void testExpiredTokensDeletedAfterExpiration() throws Exception {
restClient.security().invalidateToken(new InvalidateTokenRequest("fooobar", null, null, null),
SECURITY_REQUEST_OPTIONS));
assertThat(e.getMessage(), containsString("token malformed"));
assertThat(e.status(), equalTo(RestStatus.UNAUTHORIZED));
}
restClient.indices().refresh(new RefreshRequest(RestrictedIndicesNames.SECURITY_TOKENS_ALIAS), SECURITY_REQUEST_OPTIONS);
SearchResponse searchResponse = restClient.search(new SearchRequest(RestrictedIndicesNames.SECURITY_TOKENS_ALIAS)
Expand Down Expand Up @@ -455,7 +458,36 @@ public void testClientCredentialsGrant() throws Exception {

ElasticsearchStatusException e = expectThrows(ElasticsearchStatusException.class,
() -> restClient.security().authenticate(tokenAuthOptions));
assertEquals(RestStatus.UNAUTHORIZED, e.status());
assertThat(e.status(), equalTo(RestStatus.UNAUTHORIZED));
}

public void testAuthenticateWithWrongToken() throws Exception {
final RestHighLevelClient restClient = new TestRestHighLevelClient();
CreateTokenResponse response = restClient.security().createToken(CreateTokenRequest.passwordGrant(
SecuritySettingsSource.TEST_USER_NAME, SecuritySettingsSourceField.TEST_PASSWORD.toCharArray()), SECURITY_REQUEST_OPTIONS);
assertNotNull(response.getRefreshToken());
// First check that the correct access token works by getting cluster health with token
assertNoTimeout(client()
.filterWithHeader(Collections.singletonMap("Authorization", "Bearer " + response.getAccessToken()))
.admin().cluster().prepareHealth().get());
// Now attempt to authenticate with an invalid access token string
RequestOptions wrongAuthOptions =
RequestOptions.DEFAULT.toBuilder().addHeader("Authorization", "Bearer " + randomAlphaOfLengthBetween(0, 128)).build();
ElasticsearchStatusException e = expectThrows(ElasticsearchStatusException.class,
() -> restClient.security().authenticate(wrongAuthOptions));
assertThat(e.status(), equalTo(RestStatus.UNAUTHORIZED));
// Now attempt to authenticate with an invalid access token with valid structure (pre 7.2)
RequestOptions wrongAuthOptionsPre72 =
RequestOptions.DEFAULT.toBuilder().addHeader("Authorization", "Bearer " + generateAccessToken(Version.V_7_1_0)).build();
ElasticsearchStatusException e1 = expectThrows(ElasticsearchStatusException.class,
() -> restClient.security().authenticate(wrongAuthOptionsPre72));
assertThat(e1.status(), equalTo(RestStatus.UNAUTHORIZED));
// Now attempt to authenticate with an invalid access token with valid structure (after 7.2)
RequestOptions wrongAuthOptionsAfter72 =
RequestOptions.DEFAULT.toBuilder().addHeader("Authorization", "Bearer " + generateAccessToken(Version.V_7_4_0)).build();
ElasticsearchStatusException e2 = expectThrows(ElasticsearchStatusException.class,
() -> restClient.security().authenticate(wrongAuthOptionsAfter72));
assertThat(e2.status(), equalTo(RestStatus.UNAUTHORIZED));
}

@Before
Expand All @@ -476,4 +508,13 @@ public void testMetadataIsNotSentToClient() {
ClusterStateResponse clusterStateResponse = client().admin().cluster().prepareState().setCustoms(true).get();
assertFalse(clusterStateResponse.getState().customs().containsKey(TokenMetaData.TYPE));
}

private String generateAccessToken(Version version) throws Exception {
TokenService tokenService = internalCluster().getInstance(TokenService.class);
String accessTokenString = UUIDs.randomBase64UUID();
if (version.onOrAfter(TokenService.VERSION_ACCESS_TOKENS_AS_UUIDS)) {
accessTokenString = TokenService.hashTokenString(accessTokenString);
}
return tokenService.prependVersionAndEncodeAccessToken(version, accessTokenString);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,7 @@ public void testMalformedToken() throws Exception {
final int numBytes = randomIntBetween(1, TokenService.MINIMUM_BYTES + 32);
final byte[] randomBytes = new byte[numBytes];
random().nextBytes(randomBytes);
TokenService tokenService = createTokenService(Settings.EMPTY, systemUTC());
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC());

ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
storeTokenHeader(requestContext, Base64.getEncoder().encodeToString(randomBytes));
Expand All @@ -610,6 +610,36 @@ public void testMalformedToken() throws Exception {
}
}

public void testNotValidPre72Tokens() throws Exception {
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC());
// mock another random token so that we don't find a token in TokenService#getUserTokenFromId
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
mockGetTokenFromId(tokenService, UUIDs.randomBase64UUID(), authentication, false);
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
storeTokenHeader(requestContext, generateAccessToken(tokenService, Version.V_7_1_0));

try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
tokenService.getAndValidateToken(requestContext, future);
assertNull(future.get());
}
}

public void testNotValidAfter72Tokens() throws Exception {
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC());
// mock another random token so that we don't find a token in TokenService#getUserTokenFromId
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
mockGetTokenFromId(tokenService, UUIDs.randomBase64UUID(), authentication, false);
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
storeTokenHeader(requestContext, generateAccessToken(tokenService, randomFrom(Version.V_7_2_0, Version.V_7_3_2)));

try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
tokenService.getAndValidateToken(requestContext, future);
assertNull(future.get());
}
}

public void testIndexNotAvailable() throws Exception {
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC());
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
Expand Down Expand Up @@ -821,4 +851,12 @@ private DiscoveryNode addAnotherDataNodeWithVersion(ClusterService clusterServic
return anotherDataNode;
}

private String generateAccessToken(TokenService tokenService, Version version) throws Exception {
String accessTokenString = UUIDs.randomBase64UUID();
if (version.onOrAfter(TokenService.VERSION_ACCESS_TOKENS_AS_UUIDS)) {
accessTokenString = TokenService.hashTokenString(accessTokenString);
}
return tokenService.prependVersionAndEncodeAccessToken(version, accessTokenString);
}

}