diff --git a/docs/en/settings/security-settings.asciidoc b/docs/en/settings/security-settings.asciidoc index 7a530c5d6ac..8c0102d852d 100644 --- a/docs/en/settings/security-settings.asciidoc +++ b/docs/en/settings/security-settings.asciidoc @@ -76,13 +76,6 @@ Set to `false` to disable the built-in token service. Defaults to `true` unless `xpack.security.http.ssl.enabled` is `false` and `http.enabled` is `true`. This prevents sniffing the token from a connection over plain http. -`xpack.security.authc.token.passphrase`:: -A secure passphrase that must be the same on each node and greater than -8 characters in length. This passphrase is used to derive a cryptographic key -with which the tokens will be encrypted and authenticated. If this setting is -not used, the cluster automatically generates a key, which is the recommended -method. - `xpack.security.authc.token.timeout`:: The length of time that a token is valid for. By default this value is `20m` or 20 minutes. The maximum value is 1 hour. diff --git a/plugin/build.gradle b/plugin/build.gradle index 8b7a7c83111..a801c2d6689 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -207,7 +207,6 @@ integTestCluster { setting 'xpack.monitoring.collection.interval', '-1' setting 'xpack.security.authc.token.enabled', 'true' keystoreSetting 'bootstrap.password', 'x-pack-test-password' - keystoreSetting 'xpack.security.authc.token.passphrase', 'x-pack-token-service-password' distribution = 'zip' // this is important since we use the reindex module in ML setupCommand 'setupTestUser', 'bin/x-pack/users', 'useradd', 'x_pack_rest_user', '-p', 'x-pack-test-password', '-r', 'superuser' diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/Security.java b/plugin/src/main/java/org/elasticsearch/xpack/security/Security.java index 61a26fb5bf4..b205683b2bc 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -243,7 +243,6 @@ public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin, Clus final List checks = new ArrayList<>(); checks.addAll(Arrays.asList( new SSLBootstrapCheck(sslService, settings, env), - new TokenPassphraseBootstrapCheck(settings), new TokenSSLBootstrapCheck(settings), new PkiRealmBootstrapCheck(settings, sslService))); checks.addAll(InternalRealms.getBootstrapChecks(settings)); @@ -491,7 +490,6 @@ public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin, Clus settingsList.add(CompositeRolesStore.CACHE_SIZE_SETTING); settingsList.add(FieldPermissionsCache.CACHE_SIZE_SETTING); settingsList.add(TokenService.TOKEN_EXPIRATION); - settingsList.add(TokenService.TOKEN_PASSPHRASE); settingsList.add(TokenService.DELETE_INTERVAL); settingsList.add(TokenService.DELETE_TIMEOUT); settingsList.add(SecurityServerTransportInterceptor.TRANSPORT_TYPE_PROFILE_SETTING); diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/TokenPassphraseBootstrapCheck.java b/plugin/src/main/java/org/elasticsearch/xpack/security/TokenPassphraseBootstrapCheck.java deleted file mode 100644 index ef6150188fd..00000000000 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/TokenPassphraseBootstrapCheck.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.security; - -import org.elasticsearch.bootstrap.BootstrapCheck; -import org.elasticsearch.common.settings.SecureString; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.xpack.XPackSettings; -import org.elasticsearch.xpack.security.authc.TokenService; - -/** - * Bootstrap check to ensure that the user has set the token passphrase setting and is not using - * the default value in production - */ -final class TokenPassphraseBootstrapCheck implements BootstrapCheck { - - static final int MINIMUM_PASSPHRASE_LENGTH = 8; - - private final boolean tokenServiceEnabled; - private final SecureString tokenPassphrase; - - TokenPassphraseBootstrapCheck(Settings settings) { - this.tokenServiceEnabled = XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.get(settings); - - this.tokenPassphrase = TokenService.TOKEN_PASSPHRASE.exists(settings) ? TokenService.TOKEN_PASSPHRASE.get(settings) : null; - } - - @Override - public boolean check() { - if (tokenPassphrase == null) { // that's fine we bootstrap it ourself - return false; - } - try (SecureString ignore = tokenPassphrase) { - if (tokenServiceEnabled) { - return tokenPassphrase.length() < MINIMUM_PASSPHRASE_LENGTH; - } - } - // service is not enabled so no need to check - return false; - } - - @Override - public String errorMessage() { - return "Please set a passphrase using the elasticsearch-keystore tool for the setting [" + TokenService.TOKEN_PASSPHRASE.getKey() + - "] that is at least " + MINIMUM_PASSPHRASE_LENGTH + " characters in length or " + - "disable the token service using the [" + XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey() + "] setting"; - } -} diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java b/plugin/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java index aa00a1d7f37..3228574c169 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java @@ -116,7 +116,6 @@ public final class TokenService extends AbstractComponent { private static final String TYPE = "doc"; public static final String THREAD_POOL_NAME = XPackPlugin.SECURITY + "-token-key"; - public static final Setting TOKEN_PASSPHRASE = SecureSetting.secureString("xpack.security.authc.token.passphrase", null); public static final Setting TOKEN_EXPIRATION = Setting.timeSetting("xpack.security.authc.token.timeout", TimeValue.timeValueMinutes(20L), TimeValue.timeValueSeconds(1L), Property.NodeScope); public static final Setting DELETE_INTERVAL = Setting.timeSetting("xpack.security.authc.token.delete.interval", @@ -155,14 +154,7 @@ public final class TokenService extends AbstractComponent { byte[] saltArr = new byte[SALT_BYTES]; secureRandom.nextBytes(saltArr); - final SecureString tokenPassphraseValue = TOKEN_PASSPHRASE.get(settings); - final SecureString tokenPassphrase; - if (tokenPassphraseValue.length() == 0) { - tokenPassphrase = generateTokenKey(); - } else { - tokenPassphrase = tokenPassphraseValue; - } - + final SecureString tokenPassphrase = generateTokenKey(); this.clock = clock.withZone(ZoneOffset.UTC); this.expirationDelay = TOKEN_EXPIRATION.get(settings); this.internalClient = internalClient; @@ -236,41 +228,32 @@ public final class TokenService extends AbstractComponent { } else { // the token exists and the value is at least as long as we'd expect final Version version = Version.readVersion(in); - if (version.before(Version.V_5_5_0)) { - listener.onResponse(null); - } else { - final BytesKey decodedSalt = new BytesKey(in.readByteArray()); - final BytesKey passphraseHash; - if (version.onOrAfter(Version.V_6_0_0_beta2)) { - passphraseHash = new BytesKey(in.readByteArray()); - } else { - passphraseHash = keyCache.currentTokenKeyHash; - } - KeyAndCache keyAndCache = keyCache.get(passphraseHash); - if (keyAndCache != null) { - final SecretKey decodeKey = keyAndCache.getKey(decodedSalt); - final byte[] iv = in.readByteArray(); - if (decodeKey != null) { - try { - decryptToken(in, getDecryptionCipher(iv, decodeKey, version, decodedSalt), version, listener); - } catch (GeneralSecurityException e) { - // could happen with a token that is not ours - logger.warn("invalid token", e); - listener.onResponse(null); - } - } else { - /* As a measure of protected against DOS, we can pass requests requiring a key - * computation off to a single thread executor. For normal usage, the initial - * request(s) that require a key computation will be delayed and there will be - * some additional latency. - */ - internalClient.threadPool().executor(THREAD_POOL_NAME) - .submit(new KeyComputingRunnable(in, iv, version, decodedSalt, listener, keyAndCache)); + final BytesKey decodedSalt = new BytesKey(in.readByteArray()); + final BytesKey passphraseHash = new BytesKey(in.readByteArray()); + KeyAndCache keyAndCache = keyCache.get(passphraseHash); + if (keyAndCache != null) { + final SecretKey decodeKey = keyAndCache.getKey(decodedSalt); + final byte[] iv = in.readByteArray(); + if (decodeKey != null) { + try { + decryptToken(in, getDecryptionCipher(iv, decodeKey, version, decodedSalt), version, listener); + } catch (GeneralSecurityException e) { + // could happen with a token that is not ours + logger.warn("invalid token", e); + listener.onResponse(null); } } else { - logger.debug("invalid key {} key: {}", passphraseHash, keyCache.cache.keySet()); - listener.onResponse(null); + /* As a measure of protected against DOS, we can pass requests requiring a key + * computation off to a single thread executor. For normal usage, the initial + * request(s) that require a key computation will be delayed and there will be + * some additional latency. + */ + internalClient.threadPool().executor(THREAD_POOL_NAME) + .submit(new KeyComputingRunnable(in, iv, version, decodedSalt, listener, keyAndCache)); } + } else { + logger.debug("invalid key {} key: {}", passphraseHash, keyCache.cache.keySet()); + listener.onResponse(null); } } } diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/TokenPassphraseBootstrapCheckTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/TokenPassphraseBootstrapCheckTests.java deleted file mode 100644 index 887475d5aa2..00000000000 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/TokenPassphraseBootstrapCheckTests.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.security; - -import org.elasticsearch.common.settings.MockSecureSettings; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.xpack.XPackSettings; -import org.elasticsearch.xpack.security.TokenPassphraseBootstrapCheck; -import org.elasticsearch.xpack.security.authc.TokenService; - -import static org.elasticsearch.xpack.security.TokenPassphraseBootstrapCheck.MINIMUM_PASSPHRASE_LENGTH; - -public class TokenPassphraseBootstrapCheckTests extends ESTestCase { - - public void testTokenPassphraseCheck() throws Exception { - assertFalse(new TokenPassphraseBootstrapCheck(Settings.EMPTY).check()); - MockSecureSettings secureSettings = new MockSecureSettings(); - secureSettings.setString("foo", "bar"); // leniency in setSecureSettings... if its empty it's skipped - Settings settings = Settings.builder() - .put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), true).setSecureSettings(secureSettings).build(); - assertFalse(new TokenPassphraseBootstrapCheck(settings).check()); - - secureSettings.setString(TokenService.TOKEN_PASSPHRASE.getKey(), randomAlphaOfLengthBetween(MINIMUM_PASSPHRASE_LENGTH, 30)); - assertFalse(new TokenPassphraseBootstrapCheck(settings).check()); - - secureSettings.setString(TokenService.TOKEN_PASSPHRASE.getKey(), randomAlphaOfLengthBetween(1, MINIMUM_PASSPHRASE_LENGTH - 1)); - assertTrue(new TokenPassphraseBootstrapCheck(settings).check()); - } - - public void testTokenPassphraseCheckServiceDisabled() throws Exception { - Settings settings = Settings.builder().put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), false) - .put(XPackSettings.HTTP_SSL_ENABLED.getKey(), true).build(); - assertFalse(new TokenPassphraseBootstrapCheck(settings).check()); - MockSecureSettings secureSettings = new MockSecureSettings(); - secureSettings.setString("foo", "bar"); // leniency in setSecureSettings... if its empty it's skipped - settings = Settings.builder().put(settings).setSecureSettings(secureSettings).build(); - assertFalse(new TokenPassphraseBootstrapCheck(settings).check()); - - secureSettings.setString(TokenService.TOKEN_PASSPHRASE.getKey(), randomAlphaOfLengthBetween(1, 30)); - assertFalse(new TokenPassphraseBootstrapCheck(settings).check()); - } - - public void testTokenPassphraseCheckAfterSecureSettingsClosed() throws Exception { - Settings settings = Settings.builder().put(XPackSettings.HTTP_SSL_ENABLED.getKey(), true).build(); - MockSecureSettings secureSettings = new MockSecureSettings(); - secureSettings.setString("foo", "bar"); // leniency in setSecureSettings... if its empty it's skipped - settings = Settings.builder().put(settings).setSecureSettings(secureSettings).build(); - secureSettings.setString(TokenService.TOKEN_PASSPHRASE.getKey(), randomAlphaOfLengthBetween(1, MINIMUM_PASSPHRASE_LENGTH - 1)); - final TokenPassphraseBootstrapCheck check = new TokenPassphraseBootstrapCheck(settings); - secureSettings.close(); - assertTrue(check.check()); - } -} diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/TokenServiceTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/TokenServiceTests.java index 8f5ea2d2c0c..31e8e1d924e 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/TokenServiceTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/TokenServiceTests.java @@ -6,7 +6,6 @@ package org.elasticsearch.xpack.security.authc; import org.elasticsearch.ElasticsearchSecurityException; -import org.elasticsearch.Version; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.get.GetAction; import org.elasticsearch.action.get.GetRequest; @@ -15,7 +14,6 @@ import org.elasticsearch.action.support.PlainActionFuture; import org.elasticsearch.client.Client; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.settings.ClusterSettings; -import org.elasticsearch.common.settings.MockSecureSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.util.concurrent.ThreadContext; @@ -36,12 +34,10 @@ import org.junit.Before; import org.junit.BeforeClass; import javax.crypto.SecretKey; -import java.io.IOException; import java.security.GeneralSecurityException; import java.time.Clock; import java.util.Base64; import java.util.Collections; -import java.util.concurrent.ExecutionException; import static org.elasticsearch.repositories.ESBlobStoreTestCase.randomBytes; import static org.hamcrest.Matchers.containsString; @@ -281,10 +277,8 @@ public class TokenServiceTests extends ESTestCase { try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) { // verify a second separate token service with its own passphrase cannot verify - MockSecureSettings secureSettings = new MockSecureSettings(); - secureSettings.setString(TokenService.TOKEN_PASSPHRASE.getKey(), randomAlphaOfLengthBetween(8, 30)); - Settings settings = Settings.builder().setSecureSettings(secureSettings).build(); - TokenService anotherService = new TokenService(settings, Clock.systemUTC(), internalClient, lifecycleService, clusterService); + TokenService anotherService = new TokenService(Settings.EMPTY, Clock.systemUTC(), internalClient, lifecycleService, + clusterService); PlainActionFuture future = new PlainActionFuture<>(); anotherService.getAndValidateToken(requestContext, future); assertNull(future.get()); @@ -456,27 +450,4 @@ public class TokenServiceTests extends ESTestCase { assertNull(future.get()); } } - - - public void testDecodePre6xToken() throws GeneralSecurityException, ExecutionException, InterruptedException, IOException { - String token = "g+y0AiDWsbLNzUGTywPa3VCz053RUPW7wAx4xTAonlcqjOmO1AzMhQDTUku/+ZtdtMgDobKqIrNdNvchvFMX0pvZLY6i4nAG2OhkApSstPfQQP" + - "J1fxg/JZNQDPufePg1GxV/RAQm2Gr8mYAelijEVlWIdYaQ3R76U+P/w6Q1v90dGVZQn6DKMOfgmkfwAFNY"; - MockSecureSettings secureSettings = new MockSecureSettings(); - secureSettings.setString(TokenService.TOKEN_PASSPHRASE.getKey(), "xpack_token_passpharse"); - Settings settings = Settings.builder().put(XPackSettings.HTTP_SSL_ENABLED.getKey(), true).setSecureSettings(secureSettings).build(); - TokenService tokenService = new TokenService(settings, Clock.systemUTC(), internalClient, lifecycleService, clusterService); - Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); - ThreadContext requestContext = new ThreadContext(Settings.EMPTY); - requestContext.putHeader("Authorization", "Bearer " + token); - - try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) { - PlainActionFuture future = new PlainActionFuture<>(); - tokenService.decodeToken(tokenService.getFromHeader(requestContext), future); - UserToken serialized = future.get(); - assertNotNull(serialized); - assertEquals("joe", serialized.getAuthentication().getUser().principal()); - assertEquals(Version.V_5_6_0, serialized.getAuthentication().getVersion()); - assertArrayEquals(new String[] {"admin"}, serialized.getAuthentication().getUser().roles()); - } - } }