Skip to content

Commit

Permalink
Settings: Add keystore.seed auto generated secure setting (#26149)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
rjernst authored Aug 15, 2017
1 parent dfe1bc6 commit b2d6ff9
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 10 deletions.
10 changes: 6 additions & 4 deletions core/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java
Original file line number Diff line number Diff line change
Expand Up @@ -227,12 +227,14 @@ private static SecureSettings loadSecureSettings(Environment initialEnv) throws
} 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);
}
Expand Down
17 changes: 17 additions & 0 deletions core/src/main/java/org/elasticsearch/common/Randomness.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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;
Expand Down Expand Up @@ -109,6 +110,22 @@ public static Random get() {
}
}

/**
* 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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ public void apply(Settings value, Settings current, Settings previous) {
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
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;
Expand All @@ -57,6 +58,8 @@
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.
Expand All @@ -69,6 +72,12 @@
*/
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,
Expand Down Expand Up @@ -147,16 +156,29 @@ static Path keystorePath(Path configDir) {
}

/** 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.
*
Expand Down Expand Up @@ -253,7 +275,7 @@ public void decrypt(char[] password) throws GeneralSecurityException, IOExceptio
}

/** 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
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;
Expand Down Expand Up @@ -67,4 +68,9 @@ public void testFileSettingExhaustiveBytes() throws Exception {
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()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,18 @@ public void testMissing() throws Exception {
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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,6 @@ public void testMany() throws Exception {
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
}
}

0 comments on commit b2d6ff9

Please sign in to comment.