diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java index 8b9fda5b9c3f3..36144899d2842 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java @@ -656,9 +656,9 @@ public void refreshToken(String refreshToken, ActionListener { - final Authentication userAuth = Authentication.readFromContext(client.threadPool().getThreadContext()); + final Authentication clientAuth = Authentication.readFromContext(client.threadPool().getThreadContext()); final String tokenDocId = tuple.v1().getHits().getHits()[0].getId(); - innerRefresh(tokenDocId, userAuth, listener, tuple.v2()); + innerRefresh(tokenDocId, clientAuth, listener, tuple.v2()); }, listener::onFailure), new AtomicInteger(0)); } @@ -719,7 +719,7 @@ private void findTokenFromRefreshToken(String refreshToken, ActionListener> listener, + private void innerRefresh(String tokenDocId, Authentication clientAuth, ActionListener> listener, AtomicInteger attemptCount) { if (attemptCount.getAndIncrement() > MAX_RETRY_ATTEMPTS) { logger.warn("Failed to refresh token for doc [{}] after [{}] attempts", tokenDocId, attemptCount.get()); @@ -731,7 +731,7 @@ private void innerRefresh(String tokenDocId, Authentication userAuth, ActionList ActionListener.wrap(response -> { if (response.isExists()) { final Map source = response.getSource(); - final Optional invalidSource = checkTokenDocForRefresh(source, userAuth); + final Optional invalidSource = checkTokenDocForRefresh(source, clientAuth); if (invalidSource.isPresent()) { onFailure.accept(invalidSource.get()); @@ -754,12 +754,12 @@ private void innerRefresh(String tokenDocId, Authentication userAuth, ActionList updateRequest.setIfPrimaryTerm(response.getPrimaryTerm()); executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, updateRequest.request(), ActionListener.wrap( - updateResponse -> createUserToken(authentication, userAuth, listener, metadata, true), + updateResponse -> createUserToken(authentication, clientAuth, listener, metadata, true), e -> { Throwable cause = ExceptionsHelper.unwrapCause(e); if (cause instanceof VersionConflictEngineException || isShardNotAvailableException(e)) { - innerRefresh(tokenDocId, userAuth, + innerRefresh(tokenDocId, clientAuth, listener, attemptCount); } else { onFailure.accept(e); @@ -774,7 +774,7 @@ private void innerRefresh(String tokenDocId, Authentication userAuth, ActionList } }, e -> { if (isShardNotAvailableException(e)) { - innerRefresh(tokenDocId, userAuth, listener, attemptCount); + innerRefresh(tokenDocId, clientAuth, listener, attemptCount); } else { listener.onFailure(e); } @@ -786,7 +786,7 @@ private void innerRefresh(String tokenDocId, Authentication userAuth, ActionList * Performs checks on the retrieved source and returns an {@link Optional} with the exception * if there is an issue */ - private Optional checkTokenDocForRefresh(Map source, Authentication userAuth) { + private Optional checkTokenDocForRefresh(Map source, Authentication clientAuth) { final Map refreshTokenSrc = (Map) source.get("refresh_token"); final Map accessTokenSrc = (Map) source.get("access_token"); if (refreshTokenSrc == null || refreshTokenSrc.isEmpty()) { @@ -820,18 +820,18 @@ private Optional checkTokenDocForRefresh(Map checkClient(Map refreshTokenSource, Authentication userAuth) { + private Optional checkClient(Map refreshTokenSource, Authentication clientAuth) { Map clientInfo = (Map) refreshTokenSource.get("client"); if (clientInfo == null) { return Optional.of(invalidGrantException("token is missing client information")); - } else if (userAuth.getUser().principal().equals(clientInfo.get("user")) == false) { + } else if (clientAuth.getUser().principal().equals(clientInfo.get("user")) == false) { return Optional.of(invalidGrantException("tokens must be refreshed by the creating client")); - } else if (userAuth.getAuthenticatedBy().getName().equals(clientInfo.get("realm")) == false) { + } else if (clientAuth.getAuthenticatedBy().getName().equals(clientInfo.get("realm")) == false) { return Optional.of(invalidGrantException("tokens must be refreshed by the creating client")); } else { return Optional.empty(); diff --git a/x-pack/qa/rolling-upgrade/build.gradle b/x-pack/qa/rolling-upgrade/build.gradle index f7ccc0d1ddf13..001afe34560ec 100644 --- a/x-pack/qa/rolling-upgrade/build.gradle +++ b/x-pack/qa/rolling-upgrade/build.gradle @@ -237,6 +237,15 @@ subprojects { setting 'node.name', "upgraded-node-${stopNode}" dependsOn copyTestNodeKeystore extraConfigFile 'testnode.jks', new File(outputDir + '/testnode.jks') + if (version.onOrAfter('7.0.0')) { + setting 'xpack.security.authc.realms.file.file1.order', '0' + setting 'xpack.security.authc.realms.native.native1.order', '1' + } else { + setting 'xpack.security.authc.realms.file1.type', 'file' + setting 'xpack.security.authc.realms.file1.order', '0' + setting 'xpack.security.authc.realms.native1.type', 'native' + setting 'xpack.security.authc.realms.native1.order', '1' + } if (withSystemKey) { setting 'xpack.watcher.encrypt_sensitive_data', 'true' keystoreFile 'xpack.watcher.encryption_key', "${mainProject.projectDir}/src/test/resources/system_key" diff --git a/x-pack/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/TokenBackwardsCompatibilityIT.java b/x-pack/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/TokenBackwardsCompatibilityIT.java index 6c5fa0c1a9704..06697b4e679a3 100644 --- a/x-pack/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/TokenBackwardsCompatibilityIT.java +++ b/x-pack/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/TokenBackwardsCompatibilityIT.java @@ -24,7 +24,8 @@ public class TokenBackwardsCompatibilityIT extends AbstractUpgradeTestCase { public void testGeneratingTokenInOldCluster() throws Exception { assumeTrue("this test should only run against the old cluster", CLUSTER_TYPE == ClusterType.OLD); - + // Create a couple of tokens and store them in the token_backwards_compatibility_it index to be used for tests in the mixed/upgraded + // clusters Request createTokenRequest = new Request("POST", "/_security/oauth2/token"); createTokenRequest.setJsonEntity( "{\n" + @@ -44,8 +45,8 @@ public void testGeneratingTokenInOldCluster() throws Exception { "{\n" + " \"token\": \"" + token + "\"\n" + "}"); - client().performRequest(indexRequest1); - + Response indexResponse1 = client().performRequest(indexRequest1); + assertOK(indexResponse1); Request createSecondTokenRequest = new Request("POST", "/_security/oauth2/token"); createSecondTokenRequest.setEntity(createTokenRequest.getEntity()); response = client().performRequest(createSecondTokenRequest); @@ -58,12 +59,13 @@ public void testGeneratingTokenInOldCluster() throws Exception { "{\n" + " \"token\": \"" + token + "\"\n" + "}"); - client().performRequest(indexRequest2); + Response indexResponse2 = client().performRequest(indexRequest2); + assertOK(indexResponse2); } - public void testTokenWorksInMixedOrUpgradedCluster() throws Exception { - assumeTrue("this test should only run against the mixed or upgraded cluster", - CLUSTER_TYPE == ClusterType.MIXED || CLUSTER_TYPE == ClusterType.UPGRADED); + public void testTokenWorksInMixedCluster() throws Exception { + // Verify that an old token continues to work during all stages of the rolling upgrade + assumeTrue("this test should only run against the mixed cluster", CLUSTER_TYPE == ClusterType.MIXED); Request getRequest = new Request("GET", "token_backwards_compatibility_it/_doc/old_cluster_token1"); Response getResponse = client().performRequest(getRequest); assertOK(getResponse); @@ -71,21 +73,9 @@ public void testTokenWorksInMixedOrUpgradedCluster() throws Exception { assertTokenWorks((String) source.get("token")); } - public void testMixedCluster() throws Exception { + public void testMixedClusterWithUpgradedMaster() throws Exception { assumeTrue("this test should only run against the mixed cluster", CLUSTER_TYPE == ClusterType.MIXED); assumeTrue("the master must be on the latest version before we can write", isMasterOnLatestVersion()); - Request getRequest = new Request("GET", "token_backwards_compatibility_it/_doc/old_cluster_token2"); - - Response getResponse = client().performRequest(getRequest); - Map source = (Map) entityAsMap(getResponse).get("_source"); - final String token = (String) source.get("token"); - assertTokenWorks(token); - - Request invalidateRequest = new Request("DELETE", "/_security/oauth2/token"); - invalidateRequest.setJsonEntity("{\"token\": \"" + token + "\"}"); - invalidateRequest.addParameter("error_trace", "true"); - client().performRequest(invalidateRequest); - assertTokenDoesNotWork(token); // create token and refresh on version that supports it Request createTokenRequest = new Request("POST", "/_security/oauth2/token"); @@ -124,60 +114,21 @@ public void testMixedCluster() throws Exception { } public void testUpgradedCluster() throws Exception { - assumeTrue("this test should only run against the mixed cluster", CLUSTER_TYPE == ClusterType.UPGRADED); - Request getRequest = new Request("GET", "token_backwards_compatibility_it/_doc/old_cluster_token2"); + assumeTrue("this test should only run against the upgraded cluster", CLUSTER_TYPE == ClusterType.UPGRADED); + // Use an old token to authenticate, then invalidate it and verify that it can no longer be used + Request getRequest = new Request("GET", "token_backwards_compatibility_it/_doc/old_cluster_token1"); Response getResponse = client().performRequest(getRequest); assertOK(getResponse); Map source = (Map) entityAsMap(getResponse).get("_source"); final String token = (String) source.get("token"); - // invalidate again since this may not have been invalidated in the mixed cluster Request invalidateRequest = new Request("DELETE", "/_security/oauth2/token"); invalidateRequest.setJsonEntity("{\"token\": \"" + token + "\"}"); invalidateRequest.addParameter("error_trace", "true"); Response invalidationResponse = client().performRequest(invalidateRequest); assertOK(invalidationResponse); assertTokenDoesNotWork(token); - - getRequest = new Request("GET", "token_backwards_compatibility_it/_doc/old_cluster_token1"); - - getResponse = client().performRequest(getRequest); - source = (Map) entityAsMap(getResponse).get("_source"); - final String workingToken = (String) source.get("token"); - assertTokenWorks(workingToken); - - Request getTokenRequest = new Request("POST", "/_security/oauth2/token"); - getTokenRequest.setJsonEntity( - "{\n" + - " \"username\": \"test_user\",\n" + - " \"password\": \"x-pack-test-password\",\n" + - " \"grant_type\": \"password\"\n" + - "}"); - Response response = client().performRequest(getTokenRequest); - Map responseMap = entityAsMap(response); - String accessToken = (String) responseMap.get("access_token"); - String refreshToken = (String) responseMap.get("refresh_token"); - assertNotNull(accessToken); - assertNotNull(refreshToken); - assertTokenWorks(accessToken); - - Request refreshTokenRequest = new Request("POST", "/_security/oauth2/token"); - refreshTokenRequest.setJsonEntity( - "{\n" + - " \"refresh_token\": \"" + refreshToken + "\",\n" + - " \"grant_type\": \"refresh_token\"\n" + - "}"); - response = client().performRequest(refreshTokenRequest); - responseMap = entityAsMap(response); - String updatedAccessToken = (String) responseMap.get("access_token"); - String updatedRefreshToken = (String) responseMap.get("refresh_token"); - assertNotNull(updatedAccessToken); - assertNotNull(updatedRefreshToken); - assertTokenWorks(updatedAccessToken); - assertTokenWorks(accessToken); - assertNotEquals(accessToken, updatedAccessToken); - assertNotEquals(refreshToken, updatedRefreshToken); } private void assertTokenWorks(String token) throws IOException {