Remove token passphrase setting (elastic/x-pack-elasticsearch#2318)
This change removes `xpack.security.authc.token.passphrase` entirely since from 6.0 onwards we use randomly generated keys by the master there is no need for this setting anymore. This setting will be deprecated from 6.0 onwards. Original commit: elastic/x-pack-elasticsearch@37ba90359e
This commit is contained in:
parent
c3f3ae5391
commit
2f5aeb6c6f
|
@ -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.
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -243,7 +243,6 @@ public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin, Clus
|
|||
final List<BootstrapCheck> 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);
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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<SecureString> TOKEN_PASSPHRASE = SecureSetting.secureString("xpack.security.authc.token.passphrase", null);
|
||||
public static final Setting<TimeValue> TOKEN_EXPIRATION = Setting.timeSetting("xpack.security.authc.token.timeout",
|
||||
TimeValue.timeValueMinutes(20L), TimeValue.timeValueSeconds(1L), Property.NodeScope);
|
||||
public static final Setting<TimeValue> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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<UserToken> 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<UserToken> 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue