Settings: Add keystore.seed auto generated secure setting (#26149)
This commit adds a keystore.seed setting that is automatically generated when the ES keystore is created. This setting may be used by plugins as a secure, random value. This commit also auto creates the keystore upon startup to ensure the new setting is always available.
This commit is contained in:
parent
dfe1bc6883
commit
b2d6ff9116
|
@ -227,12 +227,14 @@ final class Bootstrap {
|
|||
} catch (IOException e) {
|
||||
throw new BootstrapException(e);
|
||||
}
|
||||
if (keystore == null) {
|
||||
return null; // no keystore
|
||||
}
|
||||
|
||||
try {
|
||||
keystore.decrypt(new char[0] /* TODO: read password from stdin */);
|
||||
if (keystore == null) {
|
||||
// create it, we always want one! we use an empty passphrase, but a user can change this later if they want.
|
||||
KeyStoreWrapper.create(new char[0]);
|
||||
} else {
|
||||
keystore.decrypt(new char[0] /* TODO: read password from stdin */);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new BootstrapException(e);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.elasticsearch.common.settings.Setting;
|
|||
import org.elasticsearch.common.settings.Settings;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
@ -109,6 +110,22 @@ public final class Randomness {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a secure source of randomness.
|
||||
*
|
||||
* This acts exactly similar to {@link #get()}, but returning a new {@link SecureRandom}.
|
||||
*/
|
||||
public static SecureRandom createSecure() {
|
||||
if (currentMethod != null && getRandomMethod != null) {
|
||||
// tests, so just use a seed from the non secure random
|
||||
byte[] seed = new byte[16];
|
||||
get().nextBytes(seed);
|
||||
return new SecureRandom(seed);
|
||||
} else {
|
||||
return new SecureRandom();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressForbidden(reason = "ThreadLocalRandom is okay when not running tests")
|
||||
private static Random getWithoutSeed() {
|
||||
assert currentMethod == null && getRandomMethod == null : "running under tests but tried to create non-reproducible random";
|
||||
|
|
|
@ -391,6 +391,7 @@ public final class ClusterSettings extends AbstractScopedSettings {
|
|||
BootstrapSettings.MEMORY_LOCK_SETTING,
|
||||
BootstrapSettings.SYSTEM_CALL_FILTER_SETTING,
|
||||
BootstrapSettings.CTRLHANDLER_SETTING,
|
||||
KeyStoreWrapper.SEED_SETTING,
|
||||
IndexingMemoryController.INDEX_BUFFER_SIZE_SETTING,
|
||||
IndexingMemoryController.MIN_INDEX_BUFFER_SIZE_SETTING,
|
||||
IndexingMemoryController.MAX_INDEX_BUFFER_SIZE_SETTING,
|
||||
|
|
|
@ -39,6 +39,7 @@ import java.security.GeneralSecurityException;
|
|||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Enumeration;
|
||||
|
@ -57,6 +58,8 @@ import org.apache.lucene.store.IndexInput;
|
|||
import org.apache.lucene.store.IndexOutput;
|
||||
import org.apache.lucene.store.SimpleFSDirectory;
|
||||
import org.apache.lucene.util.SetOnce;
|
||||
import org.elasticsearch.bootstrap.BootstrapSettings;
|
||||
import org.elasticsearch.common.Randomness;
|
||||
|
||||
/**
|
||||
* A wrapper around a Java KeyStore which provides supplements the keystore with extra metadata.
|
||||
|
@ -69,6 +72,12 @@ import org.apache.lucene.util.SetOnce;
|
|||
*/
|
||||
public class KeyStoreWrapper implements SecureSettings {
|
||||
|
||||
public static final Setting<SecureString> SEED_SETTING = SecureSetting.secureString("keystore.seed", null);
|
||||
|
||||
/** Characters that may be used in the bootstrap seed setting added to all keystores. */
|
||||
private static final char[] SEED_CHARS = ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" +
|
||||
"~!@#$%^&*-_=+?").toCharArray();
|
||||
|
||||
/** An identifier for the type of data that may be stored in a keystore entry. */
|
||||
private enum KeyType {
|
||||
STRING,
|
||||
|
@ -147,16 +156,29 @@ public class KeyStoreWrapper implements SecureSettings {
|
|||
}
|
||||
|
||||
/** Constructs a new keystore with the given password. */
|
||||
static KeyStoreWrapper create(char[] password) throws Exception {
|
||||
public static KeyStoreWrapper create(char[] password) throws Exception {
|
||||
KeyStoreWrapper wrapper = new KeyStoreWrapper(FORMAT_VERSION, password.length != 0, NEW_KEYSTORE_TYPE,
|
||||
NEW_KEYSTORE_STRING_KEY_ALGO, NEW_KEYSTORE_FILE_KEY_ALGO, new HashMap<>(), null);
|
||||
KeyStore keyStore = KeyStore.getInstance(NEW_KEYSTORE_TYPE);
|
||||
keyStore.load(null, null);
|
||||
wrapper.keystore.set(keyStore);
|
||||
wrapper.keystorePassword.set(new KeyStore.PasswordProtection(password));
|
||||
addBootstrapSeed(wrapper);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/** Add the bootstrap seed setting, which may be used as a unique, secure, random value by the node */
|
||||
private static void addBootstrapSeed(KeyStoreWrapper wrapper) throws GeneralSecurityException {
|
||||
SecureRandom random = Randomness.createSecure();
|
||||
int passwordLength = 20; // Generate 20 character passwords
|
||||
char[] characters = new char[passwordLength];
|
||||
for (int i = 0; i < passwordLength; ++i) {
|
||||
characters[i] = SEED_CHARS[random.nextInt(SEED_CHARS.length)];
|
||||
}
|
||||
wrapper.setString(SEED_SETTING.getKey(), characters);
|
||||
Arrays.fill(characters, (char)0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads information about the Elasticsearch keystore from the provided config directory.
|
||||
*
|
||||
|
@ -253,7 +275,7 @@ public class KeyStoreWrapper implements SecureSettings {
|
|||
}
|
||||
|
||||
/** Write the keystore to the given config directory. */
|
||||
void save(Path configDir) throws Exception {
|
||||
public void save(Path configDir) throws Exception {
|
||||
char[] password = this.keystorePassword.get().getPassword();
|
||||
|
||||
SimpleFSDirectory directory = new SimpleFSDirectory(configDir);
|
||||
|
|
|
@ -26,6 +26,7 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
|
||||
import org.apache.lucene.util.IOUtils;
|
||||
import org.elasticsearch.bootstrap.BootstrapSettings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.junit.After;
|
||||
|
@ -67,4 +68,9 @@ public class KeyStoreWrapperTests extends ESTestCase {
|
|||
assertEquals(-1, stream.read()); // nothing left
|
||||
}
|
||||
}
|
||||
|
||||
public void testKeystoreSeed() throws Exception {
|
||||
KeyStoreWrapper keystore = KeyStoreWrapper.create(new char[0]);
|
||||
assertTrue(keystore.getSettingNames().contains(KeyStoreWrapper.SEED_SETTING.getKey()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,18 +50,18 @@ public class ListKeyStoreCommandTests extends KeyStoreCommandTestCase {
|
|||
public void testEmpty() throws Exception {
|
||||
createKeystore("");
|
||||
execute();
|
||||
assertTrue(terminal.getOutput(), terminal.getOutput().isEmpty());
|
||||
assertEquals("keystore.seed\n", terminal.getOutput());
|
||||
}
|
||||
|
||||
public void testOne() throws Exception {
|
||||
createKeystore("", "foo", "bar");
|
||||
execute();
|
||||
assertEquals("foo\n", terminal.getOutput());
|
||||
assertEquals("foo\nkeystore.seed\n", terminal.getOutput());
|
||||
}
|
||||
|
||||
public void testMultiple() throws Exception {
|
||||
createKeystore("", "foo", "1", "baz", "2", "bar", "3");
|
||||
execute();
|
||||
assertEquals("bar\nbaz\nfoo\n", terminal.getOutput()); // sorted
|
||||
assertEquals("bar\nbaz\nfoo\nkeystore.seed\n", terminal.getOutput()); // sorted
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,6 +75,6 @@ public class RemoveSettingKeyStoreCommandTests extends KeyStoreCommandTestCase {
|
|||
assertFalse(settings.contains("foo"));
|
||||
assertFalse(settings.contains("baz"));
|
||||
assertTrue(settings.contains("bar"));
|
||||
assertEquals(1, settings.size());
|
||||
assertEquals(2, settings.size()); // account for keystore.seed too
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue