diff --git a/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/ClusterFormationTasks.groovy b/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/ClusterFormationTasks.groovy index 7a0a7d9436ee2..d7f59fffb216e 100644 --- a/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/ClusterFormationTasks.groovy +++ b/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/ClusterFormationTasks.groovy @@ -434,7 +434,7 @@ class ClusterFormationTasks { * getting the short name requiring the path to already exist. */ final Object esKeystoreUtil = "${-> node.binPath().resolve('elasticsearch-keystore').toString()}" - return configureExecTask(name, project, setup, node, esKeystoreUtil, 'create') + return configureExecTask(name, project, setup, node, esKeystoreUtil, 'create', '--nopass') } } diff --git a/server/src/main/java/org/elasticsearch/common/settings/AddFileKeyStoreCommand.java b/server/src/main/java/org/elasticsearch/common/settings/AddFileKeyStoreCommand.java index f5b3cb9cf7104..8d4c03c7ee0cf 100644 --- a/server/src/main/java/org/elasticsearch/common/settings/AddFileKeyStoreCommand.java +++ b/server/src/main/java/org/elasticsearch/common/settings/AddFileKeyStoreCommand.java @@ -53,45 +53,40 @@ class AddFileKeyStoreCommand extends EnvironmentAwareCommand { @Override protected void execute(Terminal terminal, OptionSet options, Environment env) throws Exception { - KeyStoreWrapper keystore = KeyStoreWrapper.load(env.configFile()); - if (keystore == null) { - if (options.has(forceOption) == false && - terminal.promptYesNo("The elasticsearch keystore does not exist. Do you want to create it?", false) == false) { - terminal.println("Exiting without creating keystore."); + try (KeystoreAndPassphrase keyAndPass = KeyStoreWrapper.readOrCreate(terminal, env.configFile(), options.has(forceOption))) { + if (null == keyAndPass) { return; } - keystore = KeyStoreWrapper.create(); - keystore.save(env.configFile(), new char[0] /* always use empty passphrase for auto created keystore */); - terminal.println("Created elasticsearch keystore in " + env.configFile()); - } else { - keystore.decrypt(new char[0] /* TODO: prompt for password when they are supported */); - } + KeyStoreWrapper keystore = keyAndPass.getKeystore(); - List argumentValues = arguments.values(options); - if (argumentValues.size() == 0) { - throw new UserException(ExitCodes.USAGE, "Missing setting name"); - } - String setting = argumentValues.get(0); - if (keystore.getSettingNames().contains(setting) && options.has(forceOption) == false) { - if (terminal.promptYesNo("Setting " + setting + " already exists. Overwrite?", false) == false) { - terminal.println("Exiting without modifying keystore."); - return; + List argumentValues = arguments.values(options); + if (argumentValues.size() == 0) { + throw new UserException(ExitCodes.USAGE, "Missing setting name"); + } + String setting = argumentValues.get(0); + if (keystore.getSettingNames().contains(setting) && options.has(forceOption) == false) { + if (terminal.promptYesNo("Setting " + setting + " already exists. Overwrite?", false) == false) { + terminal.println("Exiting without modifying keystore."); + return; + } } - } - if (argumentValues.size() == 1) { - throw new UserException(ExitCodes.USAGE, "Missing file name"); - } - Path file = getPath(argumentValues.get(1)); - if (Files.exists(file) == false) { - throw new UserException(ExitCodes.IO_ERROR, "File [" + file.toString() + "] does not exist"); - } - if (argumentValues.size() > 2) { - throw new UserException(ExitCodes.USAGE, "Unrecognized extra arguments [" + - String.join(", ", argumentValues.subList(2, argumentValues.size())) + "] after filepath"); + if (argumentValues.size() == 1) { + throw new UserException(ExitCodes.USAGE, "Missing file name"); + } + Path file = getPath(argumentValues.get(1)); + if (Files.exists(file) == false) { + throw new UserException(ExitCodes.IO_ERROR, "File [" + file.toString() + "] does not exist"); + } + if (argumentValues.size() > 2) { + throw new UserException(ExitCodes.USAGE, "Unrecognized extra arguments [" + + String.join(", ", argumentValues.subList(2, argumentValues.size())) + "] after filepath"); + } + keystore.setFile(setting, Files.readAllBytes(file)); + keystore.save(env.configFile(), keyAndPass.getPassphrase()); + } catch (SecurityException e) { + throw new UserException(ExitCodes.DATA_ERROR, "Failed to access the keystore. Please make sure the passphrase was correct."); } - keystore.setFile(setting, Files.readAllBytes(file)); - keystore.save(env.configFile(), new char[0]); } @SuppressForbidden(reason="file arg for cli") diff --git a/server/src/main/java/org/elasticsearch/common/settings/AddStringKeyStoreCommand.java b/server/src/main/java/org/elasticsearch/common/settings/AddStringKeyStoreCommand.java index ee6614618010b..8faad5c3cf501 100644 --- a/server/src/main/java/org/elasticsearch/common/settings/AddStringKeyStoreCommand.java +++ b/server/src/main/java/org/elasticsearch/common/settings/AddStringKeyStoreCommand.java @@ -56,44 +56,39 @@ InputStream getStdin() { @Override protected void execute(Terminal terminal, OptionSet options, Environment env) throws Exception { - KeyStoreWrapper keystore = KeyStoreWrapper.load(env.configFile()); - if (keystore == null) { - if (options.has(forceOption) == false && - terminal.promptYesNo("The elasticsearch keystore does not exist. Do you want to create it?", false) == false) { - terminal.println("Exiting without creating keystore."); + try (KeystoreAndPassphrase keyAndPass = KeyStoreWrapper.readOrCreate(terminal, env.configFile(), options.has(forceOption))) { + if (null == keyAndPass) { return; } - keystore = KeyStoreWrapper.create(); - keystore.save(env.configFile(), new char[0] /* always use empty passphrase for auto created keystore */); - terminal.println("Created elasticsearch keystore in " + env.configFile()); - } else { - keystore.decrypt(new char[0] /* TODO: prompt for password when they are supported */); - } + KeyStoreWrapper keystore = keyAndPass.getKeystore(); - String setting = arguments.value(options); - if (setting == null) { - throw new UserException(ExitCodes.USAGE, "The setting name can not be null"); - } - if (keystore.getSettingNames().contains(setting) && options.has(forceOption) == false) { - if (terminal.promptYesNo("Setting " + setting + " already exists. Overwrite?", false) == false) { - terminal.println("Exiting without modifying keystore."); - return; + String setting = arguments.value(options); + if (setting == null) { + throw new UserException(ExitCodes.USAGE, "The setting name can not be null"); + } + if (keystore.getSettingNames().contains(setting) && options.has(forceOption) == false) { + if (terminal.promptYesNo("Setting " + setting + " already exists. Overwrite?", false) == false) { + terminal.println("Exiting without modifying keystore."); + return; + } } - } - final char[] value; - if (options.has(stdinOption)) { - BufferedReader stdinReader = new BufferedReader(new InputStreamReader(getStdin(), StandardCharsets.UTF_8)); - value = stdinReader.readLine().toCharArray(); - } else { - value = terminal.readSecret("Enter value for " + setting + ": "); - } + final char[] value; + if (options.has(stdinOption)) { + BufferedReader stdinReader = new BufferedReader(new InputStreamReader(getStdin(), StandardCharsets.UTF_8)); + value = stdinReader.readLine().toCharArray(); + } else { + value = terminal.readSecret("Enter value for " + setting + ": "); + } - try { - keystore.setString(setting, value); - } catch (IllegalArgumentException e) { - throw new UserException(ExitCodes.DATA_ERROR, "String value must contain only ASCII"); + try { + keystore.setString(setting, value); + } catch (IllegalArgumentException e) { + throw new UserException(ExitCodes.DATA_ERROR, "String value must contain only ASCII"); + } + keystore.save(env.configFile(), keyAndPass.getPassphrase()); + } catch (SecurityException e) { + throw new UserException(ExitCodes.DATA_ERROR, "Failed to access the keystore. Please make sure the passphrase was correct."); } - keystore.save(env.configFile(), new char[0]); } } diff --git a/server/src/main/java/org/elasticsearch/common/settings/ChangeKeyStorePassphraseCommand.java b/server/src/main/java/org/elasticsearch/common/settings/ChangeKeyStorePassphraseCommand.java new file mode 100644 index 0000000000000..348b2b03caf43 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/common/settings/ChangeKeyStorePassphraseCommand.java @@ -0,0 +1,57 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.settings; + +import joptsimple.OptionSet; +import org.elasticsearch.cli.EnvironmentAwareCommand; +import org.elasticsearch.cli.ExitCodes; +import org.elasticsearch.cli.Terminal; +import org.elasticsearch.cli.UserException; +import org.elasticsearch.env.Environment; + +import java.util.Arrays; + +public class ChangeKeyStorePassphraseCommand extends EnvironmentAwareCommand { + + + ChangeKeyStorePassphraseCommand() { + super("Changes the passphrase of a keystore"); + } + + @Override + protected void execute(Terminal terminal, OptionSet options, Environment env) throws Exception { + char[] newPassphrase = null; + try (KeystoreAndPassphrase keyAndPass = KeyStoreWrapper.readOrCreate(terminal, env.configFile(), false)) { + if (null == keyAndPass) { + return; + } + KeyStoreWrapper keystore = keyAndPass.getKeystore(); + newPassphrase = KeyStoreWrapper.readPassphrase(terminal, true); + keystore.save(env.configFile(), newPassphrase); + terminal.println("Elasticsearch keystore passphrase changed successfully." + env.configFile()); + } catch (SecurityException e) { + throw new UserException(ExitCodes.DATA_ERROR, "Failed to access the keystore. Please make sure the passphrase was correct."); + } finally { + if (null != newPassphrase) { + Arrays.fill(newPassphrase, '\u0000'); + } + } + } +} diff --git a/server/src/main/java/org/elasticsearch/common/settings/CreateKeyStoreCommand.java b/server/src/main/java/org/elasticsearch/common/settings/CreateKeyStoreCommand.java index 3529d7f6810bd..2917ec87f5cd1 100644 --- a/server/src/main/java/org/elasticsearch/common/settings/CreateKeyStoreCommand.java +++ b/server/src/main/java/org/elasticsearch/common/settings/CreateKeyStoreCommand.java @@ -21,10 +21,14 @@ import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; import joptsimple.OptionSet; +import joptsimple.OptionSpec; import org.elasticsearch.cli.EnvironmentAwareCommand; +import org.elasticsearch.cli.ExitCodes; import org.elasticsearch.cli.Terminal; +import org.elasticsearch.cli.UserException; import org.elasticsearch.env.Environment; /** @@ -32,30 +36,34 @@ */ class CreateKeyStoreCommand extends EnvironmentAwareCommand { + private final OptionSpec noPassOption; + CreateKeyStoreCommand() { super("Creates a new elasticsearch keystore"); + this.noPassOption = parser.acceptsAll(Arrays.asList("n", "nopass"), "Creates an obfuscated (not passphrase protected) keystore"); } @Override protected void execute(Terminal terminal, OptionSet options, Environment env) throws Exception { - Path keystoreFile = KeyStoreWrapper.keystorePath(env.configFile()); - if (Files.exists(keystoreFile)) { - if (terminal.promptYesNo("An elasticsearch keystore already exists. Overwrite?", false) == false) { - terminal.println("Exiting without creating keystore."); - return; + char[] passphrase = null; + try { + Path keystoreFile = KeyStoreWrapper.keystorePath(env.configFile()); + if (Files.exists(keystoreFile)) { + if (terminal.promptYesNo("An elasticsearch keystore already exists. Overwrite?", false) == false) { + terminal.println("Exiting without creating keystore."); + return; + } + } + KeyStoreWrapper keystore = KeyStoreWrapper.create(); + passphrase = options.has(noPassOption) ? new char[0] : KeyStoreWrapper.readPassphrase(terminal, true); + keystore.save(env.configFile(), passphrase); + terminal.println("Created elasticsearch keystore in " + env.configFile()); + } catch (SecurityException e) { + throw new UserException(ExitCodes.IO_ERROR, "Error creating the elasticsearch keystore.", e); + } finally { + if (null != passphrase) { + Arrays.fill(passphrase, '\u0000'); } } - - - char[] password = new char[0];// terminal.readSecret("Enter passphrase (empty for no passphrase): "); - /* TODO: uncomment when entering passwords on startup is supported - char[] passwordRepeat = terminal.readSecret("Enter same passphrase again: "); - if (Arrays.equals(password, passwordRepeat) == false) { - throw new UserException(ExitCodes.DATA_ERROR, "Passphrases are not equal, exiting."); - }*/ - - KeyStoreWrapper keystore = KeyStoreWrapper.create(); - keystore.save(env.configFile(), password); - terminal.println("Created elasticsearch keystore in " + env.configFile()); } } diff --git a/server/src/main/java/org/elasticsearch/common/settings/KeyStoreCli.java b/server/src/main/java/org/elasticsearch/common/settings/KeyStoreCli.java index 3deb5f19c95fe..439ceed854b93 100644 --- a/server/src/main/java/org/elasticsearch/common/settings/KeyStoreCli.java +++ b/server/src/main/java/org/elasticsearch/common/settings/KeyStoreCli.java @@ -34,6 +34,7 @@ private KeyStoreCli() { subcommands.put("add", new AddStringKeyStoreCommand()); subcommands.put("add-file", new AddFileKeyStoreCommand()); subcommands.put("remove", new RemoveSettingKeyStoreCommand()); + subcommands.put("passwd", new ChangeKeyStorePassphraseCommand()); } public static void main(String[] args) throws Exception { diff --git a/server/src/main/java/org/elasticsearch/common/settings/KeyStoreWrapper.java b/server/src/main/java/org/elasticsearch/common/settings/KeyStoreWrapper.java index e017e9e7ca93f..ce91329c4146a 100644 --- a/server/src/main/java/org/elasticsearch/common/settings/KeyStoreWrapper.java +++ b/server/src/main/java/org/elasticsearch/common/settings/KeyStoreWrapper.java @@ -65,6 +65,7 @@ import org.apache.lucene.store.SimpleFSDirectory; import org.apache.lucene.util.SetOnce; import org.elasticsearch.cli.ExitCodes; +import org.elasticsearch.cli.Terminal; import org.elasticsearch.cli.UserException; import org.elasticsearch.common.Randomness; @@ -495,9 +496,9 @@ public synchronized void save(Path configDir, char[] password) throws Exception } catch (final AccessDeniedException e) { final String message = String.format( - Locale.ROOT, - "unable to create temporary keystore at [%s], please check filesystem permissions", - configDir.resolve(tmpFile)); + Locale.ROOT, + "unable to create temporary keystore at [%s], please check filesystem permissions", + configDir.resolve(tmpFile)); throw new UserException(ExitCodes.CONFIG, message, e); } @@ -510,6 +511,62 @@ public synchronized void save(Path configDir, char[] password) throws Exception } } + /** + * Reads the keystore passphrase from the {@link Terminal}, prompting for verification where applicable and returns it as a + * {@code char[]}. The caller is responsible to clear the char array after use. + * + * @param terminal the terminal to use for user inputs + * @param withVerification whether the user should be prompted for passphrase verification + * @return a char array with the passphrase the user entered + * @throws UserException If the user is prompted for verification and enters a different passphrase + */ + static char[] readPassphrase(Terminal terminal, boolean withVerification) throws UserException { + final char[] passphrase; + if (withVerification) { + passphrase = terminal.readSecret("Enter new passphrase for the elasticsearch keystore (empty for no passphrase): "); + char[] passphraseVerification = terminal.readSecret("Enter same passphrase again: "); + if (Arrays.equals(passphrase, passphraseVerification) == false) { + throw new UserException(ExitCodes.DATA_ERROR, "Passphrases are not equal, exiting."); + } + Arrays.fill(passphraseVerification, '\u0000'); + } else { + passphrase = terminal.readSecret("Enter passphrase for the elasticsearch keystore : "); + } + return passphrase; + } + + /** + * Reads the keystore from disk or attempts to create it if it doesn't already exist. If the keystore is password protected + * it also reads the passphrase from user input and decrypts the keystore. Returns both in a POJO. The caller is responsible to clear + * the char array with the passphrase after use, calling {@link KeystoreAndPassphrase#close()} + * + * @param terminal the terminal to use for user inputs + * @param configFile the Path from where to attempt and read the existing keystore + * @param forceCreate if set, the keystore is created without prompting the user + * @return a POJO with the {@link KeyStoreWrapper} and it's passphrase, null if the user elected to not create a keystore + */ + static KeystoreAndPassphrase readOrCreate(Terminal terminal, Path configFile, boolean forceCreate) throws Exception { + KeyStoreWrapper keystore = load(configFile); + final char[] passphrase; + if (keystore == null) { + if (forceCreate == false && + terminal.promptYesNo("The elasticsearch keystore does not exist. Do you want to create it?", false) == false) { + terminal.println("Exiting without creating keystore."); + return null; + } else { + passphrase = readPassphrase(terminal, true); + keystore = KeyStoreWrapper.create(); + keystore.save(configFile, passphrase); + terminal.println("Created elasticsearch keystore in " + configFile); + return new KeystoreAndPassphrase(keystore, passphrase); + } + } else { + passphrase = keystore.hasPassword ? readPassphrase(terminal, false) : new char[0]; + keystore.decrypt(passphrase); + return new KeystoreAndPassphrase(keystore, passphrase); + } + } + /** * It is possible to retrieve the setting names even if the keystore is closed. * This allows {@link SecureSetting} to correctly determine that a entry exists even though it cannot be read. Thus attempting to @@ -557,7 +614,9 @@ public static void validateSettingName(String setting) { } } - /** Set a string setting. */ + /** + * Set a string setting. + */ synchronized void setString(String setting, char[] value) { ensureOpen(); validateSettingName(setting); @@ -566,27 +625,31 @@ synchronized void setString(String setting, char[] value) { byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit()); Entry oldEntry = entries.get().put(setting, new Entry(EntryType.STRING, bytes)); if (oldEntry != null) { - Arrays.fill(oldEntry.bytes, (byte)0); + Arrays.fill(oldEntry.bytes, (byte) 0); } } - /** Set a file setting. */ + /** + * Set a file setting. + */ synchronized void setFile(String setting, byte[] bytes) { ensureOpen(); validateSettingName(setting); Entry oldEntry = entries.get().put(setting, new Entry(EntryType.FILE, Arrays.copyOf(bytes, bytes.length))); if (oldEntry != null) { - Arrays.fill(oldEntry.bytes, (byte)0); + Arrays.fill(oldEntry.bytes, (byte) 0); } } - /** Remove the given setting from the keystore. */ + /** + * Remove the given setting from the keystore. + */ void remove(String setting) { ensureOpen(); Entry oldEntry = entries.get().remove(setting); if (oldEntry != null) { - Arrays.fill(oldEntry.bytes, (byte)0); + Arrays.fill(oldEntry.bytes, (byte) 0); } } diff --git a/server/src/main/java/org/elasticsearch/common/settings/KeystoreAndPassphrase.java b/server/src/main/java/org/elasticsearch/common/settings/KeystoreAndPassphrase.java new file mode 100644 index 0000000000000..c8130b9d6e138 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/common/settings/KeystoreAndPassphrase.java @@ -0,0 +1,49 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.settings; + +import java.io.Closeable; +import java.io.IOException; +import java.util.Arrays; + +public class KeystoreAndPassphrase implements Closeable { + private final KeyStoreWrapper keystore; + private final char[] passphrase; + + public KeystoreAndPassphrase(KeyStoreWrapper keystore, char[] passphrase) { + this.keystore = keystore; + this.passphrase = passphrase; + } + + public KeyStoreWrapper getKeystore() { + return keystore; + } + + public char[] getPassphrase() { + return passphrase; + } + + @Override + public void close() throws IOException { + if (null != passphrase) { + Arrays.fill(passphrase, '\u0000'); + } + } +} diff --git a/server/src/main/java/org/elasticsearch/common/settings/ListKeyStoreCommand.java b/server/src/main/java/org/elasticsearch/common/settings/ListKeyStoreCommand.java index 8eef02f213189..04001f816b879 100644 --- a/server/src/main/java/org/elasticsearch/common/settings/ListKeyStoreCommand.java +++ b/server/src/main/java/org/elasticsearch/common/settings/ListKeyStoreCommand.java @@ -21,6 +21,7 @@ import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -46,13 +47,19 @@ protected void execute(Terminal terminal, OptionSet options, Environment env) th if (keystore == null) { throw new UserException(ExitCodes.DATA_ERROR, "Elasticsearch keystore not found. Use 'create' command to create one."); } - - keystore.decrypt(new char[0] /* TODO: prompt for password when they are supported */); - - List sortedEntries = new ArrayList<>(keystore.getSettingNames()); - Collections.sort(sortedEntries); - for (String entry : sortedEntries) { - terminal.println(entry); + char[] passphrase; + passphrase = keystore.hasPassword() ? KeyStoreWrapper.readPassphrase(terminal, false) : new char[0]; + try { + keystore.decrypt(passphrase); + List sortedEntries = new ArrayList<>(keystore.getSettingNames()); + Collections.sort(sortedEntries); + for (String entry : sortedEntries) { + terminal.println(entry); + } + } catch (SecurityException e) { + throw new UserException(ExitCodes.DATA_ERROR, "Failed to access the keystore. Please make sure the passphrase was correct."); + } finally { + Arrays.fill(passphrase, '\u0000'); } } } diff --git a/server/src/main/java/org/elasticsearch/common/settings/RemoveSettingKeyStoreCommand.java b/server/src/main/java/org/elasticsearch/common/settings/RemoveSettingKeyStoreCommand.java index 9a83375e6e01a..d8e74f7250a87 100644 --- a/server/src/main/java/org/elasticsearch/common/settings/RemoveSettingKeyStoreCommand.java +++ b/server/src/main/java/org/elasticsearch/common/settings/RemoveSettingKeyStoreCommand.java @@ -19,6 +19,7 @@ package org.elasticsearch.common.settings; +import java.util.Arrays; import java.util.List; import joptsimple.OptionSet; @@ -52,15 +53,23 @@ protected void execute(Terminal terminal, OptionSet options, Environment env) th if (keystore == null) { throw new UserException(ExitCodes.DATA_ERROR, "Elasticsearch keystore not found. Use 'create' command to create one."); } - - keystore.decrypt(new char[0] /* TODO: prompt for password when they are supported */); - - for (String setting : arguments.values(options)) { - if (keystore.getSettingNames().contains(setting) == false) { - throw new UserException(ExitCodes.CONFIG, "Setting [" + setting + "] does not exist in the keystore."); + char[] passphrase = null; + try { + passphrase = keystore.hasPassword() ? KeyStoreWrapper.readPassphrase(terminal, false) : new char[0]; + keystore.decrypt(passphrase); + for (String setting : arguments.values(options)) { + if (keystore.getSettingNames().contains(setting) == false) { + throw new UserException(ExitCodes.CONFIG, "Setting [" + setting + "] does not exist in the keystore."); + } + keystore.remove(setting); + } + keystore.save(env.configFile(), passphrase); + } catch (SecurityException e) { + throw new UserException(ExitCodes.DATA_ERROR, "Failed to access the keystore. Please make sure the passphrase was correct."); + } finally { + if (null != passphrase) { + Arrays.fill(passphrase, '\u0000'); } - keystore.remove(setting); } - keystore.save(env.configFile(), new char[0]); } } diff --git a/server/src/test/java/org/elasticsearch/common/settings/AddFileKeyStoreCommandTests.java b/server/src/test/java/org/elasticsearch/common/settings/AddFileKeyStoreCommandTests.java index 6cfa2c1fdf255..16ade3543f322 100644 --- a/server/src/test/java/org/elasticsearch/common/settings/AddFileKeyStoreCommandTests.java +++ b/server/src/test/java/org/elasticsearch/common/settings/AddFileKeyStoreCommandTests.java @@ -53,110 +53,171 @@ private Path createRandomFile() throws IOException { return file; } - private void addFile(KeyStoreWrapper keystore, String setting, Path file) throws Exception { + private void addFile(KeyStoreWrapper keystore, String setting, Path file, String password) throws Exception { keystore.setFile(setting, Files.readAllBytes(file)); - keystore.save(env.configFile(), new char[0]); + keystore.save(env.configFile(), password.toCharArray()); } public void testMissingPromptCreate() throws Exception { + String passphrase = "keystorepassphrase"; Path file1 = createRandomFile(); + terminal.addSecretInput(passphrase); + terminal.addSecretInput(passphrase); terminal.addTextInput("y"); execute("foo", file1.toString()); - assertSecureFile("foo", file1); + assertSecureFile("foo", file1, passphrase); + } + + public void testMissingPromptCreateNotMatchingPasswpord() throws Exception { + String passphrase = "keystorepassphrase"; + Path file1 = createRandomFile(); + terminal.addTextInput("y"); + terminal.addSecretInput(passphrase); + terminal.addSecretInput("adifferentpassphase"); + UserException e = expectThrows(UserException.class, () -> execute("foo", file1.toString())); + assertEquals(e.getMessage(), ExitCodes.DATA_ERROR, e.exitCode); + assertThat(e.getMessage(), containsString("Passphrases are not equal, exiting")); } public void testMissingForceCreate() throws Exception { Path file1 = createRandomFile(); - terminal.addSecretInput("bar"); + String passphrase = "keystorepassphrase"; + terminal.addSecretInput(passphrase); + terminal.addSecretInput(passphrase); execute("-f", "foo", file1.toString()); - assertSecureFile("foo", file1); + assertSecureFile("foo", file1, passphrase); } public void testMissingNoCreate() throws Exception { + terminal.addSecretInput(randomFrom("", "keystorepassphrase")); terminal.addTextInput("n"); // explicit no execute("foo"); assertNull(KeyStoreWrapper.load(env.configFile())); } public void testOverwritePromptDefault() throws Exception { + String passphrase = "keystorepassphrase"; Path file = createRandomFile(); - KeyStoreWrapper keystore = createKeystore(""); - addFile(keystore, "foo", file); + KeyStoreWrapper keystore = createKeystore(passphrase); + addFile(keystore, "foo", file, passphrase); + terminal.addSecretInput(passphrase); + terminal.addSecretInput(passphrase); terminal.addTextInput(""); execute("foo", "path/dne"); - assertSecureFile("foo", file); + assertSecureFile("foo", file, passphrase); } public void testOverwritePromptExplicitNo() throws Exception { + String passphrase = "keystorepassphrase"; Path file = createRandomFile(); - KeyStoreWrapper keystore = createKeystore(""); - addFile(keystore, "foo", file); + KeyStoreWrapper keystore = createKeystore(passphrase); + addFile(keystore, "foo", file, passphrase); + terminal.addSecretInput(passphrase); terminal.addTextInput("n"); // explicit no execute("foo", "path/dne"); - assertSecureFile("foo", file); + assertSecureFile("foo", file, passphrase); } public void testOverwritePromptExplicitYes() throws Exception { + String passphrase = "keystorepassphrase"; Path file1 = createRandomFile(); - KeyStoreWrapper keystore = createKeystore(""); - addFile(keystore, "foo", file1); + KeyStoreWrapper keystore = createKeystore(passphrase); + addFile(keystore, "foo", file1, passphrase); + terminal.addSecretInput(passphrase); + terminal.addSecretInput(passphrase); terminal.addTextInput("y"); Path file2 = createRandomFile(); execute("foo", file2.toString()); - assertSecureFile("foo", file2); + assertSecureFile("foo", file2, passphrase); } public void testOverwriteForceShort() throws Exception { + String passphrase = "keystorepassphrase"; Path file1 = createRandomFile(); - KeyStoreWrapper keystore = createKeystore(""); - addFile(keystore, "foo", file1); + KeyStoreWrapper keystore = createKeystore(passphrase); + addFile(keystore, "foo", file1, passphrase); Path file2 = createRandomFile(); + terminal.addSecretInput(passphrase); + terminal.addSecretInput(passphrase); execute("-f", "foo", file2.toString()); - assertSecureFile("foo", file2); + assertSecureFile("foo", file2, passphrase); } public void testOverwriteForceLong() throws Exception { + String passphrase = "keystorepassphrase"; Path file1 = createRandomFile(); - KeyStoreWrapper keystore = createKeystore(""); - addFile(keystore, "foo", file1); + KeyStoreWrapper keystore = createKeystore(passphrase); + addFile(keystore, "foo", file1, passphrase); Path file2 = createRandomFile(); + terminal.addSecretInput(passphrase); execute("--force", "foo", file2.toString()); - assertSecureFile("foo", file2); + assertSecureFile("foo", file2, passphrase); } public void testForceNonExistent() throws Exception { - createKeystore(""); + String passphrase = "keystorepassphrase"; + createKeystore(passphrase); Path file = createRandomFile(); + terminal.addSecretInput(passphrase); execute("--force", "foo", file.toString()); - assertSecureFile("foo", file); + assertSecureFile("foo", file, passphrase); } public void testMissingSettingName() throws Exception { - createKeystore(""); + String passphrase = "keystorepassphrase"; + createKeystore(passphrase); + terminal.addSecretInput(passphrase); UserException e = expectThrows(UserException.class, this::execute); assertEquals(ExitCodes.USAGE, e.exitCode); assertThat(e.getMessage(), containsString("Missing setting name")); } public void testMissingFileName() throws Exception { - createKeystore(""); + String passphrase = "keystorepassphrase"; + createKeystore(passphrase); + terminal.addSecretInput(passphrase); UserException e = expectThrows(UserException.class, () -> execute("foo")); assertEquals(ExitCodes.USAGE, e.exitCode); assertThat(e.getMessage(), containsString("Missing file name")); } public void testFileDNE() throws Exception { - createKeystore(""); + String passphrase = "keystorepassphrase"; + createKeystore(passphrase); + terminal.addSecretInput(passphrase); UserException e = expectThrows(UserException.class, () -> execute("foo", "path/dne")); assertEquals(ExitCodes.IO_ERROR, e.exitCode); assertThat(e.getMessage(), containsString("File [path/dne] does not exist")); } public void testExtraArguments() throws Exception { - createKeystore(""); + String passphrase = "keystorepassphrase"; + createKeystore(passphrase); Path file = createRandomFile(); + terminal.addSecretInput(passphrase); UserException e = expectThrows(UserException.class, () -> execute("foo", file.toString(), "bar")); assertEquals(e.getMessage(), ExitCodes.USAGE, e.exitCode); assertThat(e.getMessage(), containsString("Unrecognized extra arguments [bar]")); } + + public void testIncorrectPassword() throws Exception { + String passphrase = "keystorepassphrase"; + createKeystore(passphrase); + Path file = createRandomFile(); + terminal.addSecretInput("thewrongkeystorepassphrase"); + UserException e = expectThrows(UserException.class, () -> execute("foo", file.toString())); + assertEquals(e.getMessage(), ExitCodes.DATA_ERROR, e.exitCode); + assertThat(e.getMessage(), containsString("Please make sure the passphrase was correct")); + } + + public void testAddToUnprotectedKeystore() throws Exception { + String passphrase = ""; + Path file = createRandomFile(); + KeyStoreWrapper keystore = createKeystore(passphrase); + addFile(keystore, "foo", file, passphrase); + terminal.addTextInput(""); + // will not be prompted for a passphrase + execute("foo", "path/dne"); + assertSecureFile("foo", file, passphrase); + } } diff --git a/server/src/test/java/org/elasticsearch/common/settings/AddStringKeyStoreCommandTests.java b/server/src/test/java/org/elasticsearch/common/settings/AddStringKeyStoreCommandTests.java index 07ce84b0b7599..f77a50f3153d0 100644 --- a/server/src/test/java/org/elasticsearch/common/settings/AddStringKeyStoreCommandTests.java +++ b/server/src/test/java/org/elasticsearch/common/settings/AddStringKeyStoreCommandTests.java @@ -48,17 +48,44 @@ InputStream getStdin() { }; } + public void testInvalidPassphrease() throws Exception { + String passphrase = "keystorepassphrase"; + createKeystore(passphrase, "foo", "bar"); + terminal.addSecretInput("thewrongpassphrase"); + UserException e = expectThrows(UserException.class, () -> execute("foo2")); + assertEquals(e.getMessage(), ExitCodes.DATA_ERROR, e.exitCode); + assertThat(e.getMessage(), containsString("Please make sure the passphrase was correct")); + + } + public void testMissingPromptCreate() throws Exception { + String passphrase = "keystorepassphrase"; terminal.addTextInput("y"); + terminal.addSecretInput(passphrase); + terminal.addSecretInput(passphrase); terminal.addSecretInput("bar"); execute("foo"); - assertSecureString("foo", "bar"); + assertSecureString("foo", "bar", passphrase); + } + + public void testMissingPromptCreateNotMatchingPasswords() throws Exception { + String passphrase = "keystorepassphrase"; + terminal.addTextInput("y"); + terminal.addSecretInput(passphrase); + terminal.addSecretInput("anotherpassphrase"); + terminal.addSecretInput("bar"); + UserException e = expectThrows(UserException.class, () -> execute("foo")); + assertEquals(e.getMessage(), ExitCodes.DATA_ERROR, e.exitCode); + assertThat(e.getMessage(), containsString("Passphrases are not equal, exiting")); } public void testMissingForceCreate() throws Exception { + String passphrase = "keystorepassphrase"; + terminal.addSecretInput(passphrase); + terminal.addSecretInput(passphrase); terminal.addSecretInput("bar"); execute("-f", "foo"); - assertSecureString("foo", "bar"); + assertSecureString("foo", "bar", passphrase); } public void testMissingNoCreate() throws Exception { @@ -68,77 +95,107 @@ public void testMissingNoCreate() throws Exception { } public void testOverwritePromptDefault() throws Exception { - createKeystore("", "foo", "bar"); + String passphrase = "keystorepassphrase"; + createKeystore(passphrase, "foo", "bar"); + terminal.addSecretInput(passphrase); terminal.addTextInput(""); execute("foo"); - assertSecureString("foo", "bar"); + assertSecureString("foo", "bar", passphrase); } public void testOverwritePromptExplicitNo() throws Exception { - createKeystore("", "foo", "bar"); + String passphrase = "keystorepassphrase"; + createKeystore(passphrase, "foo", "bar"); + terminal.addSecretInput(passphrase); terminal.addTextInput("n"); // explicit no execute("foo"); - assertSecureString("foo", "bar"); + assertSecureString("foo", "bar", passphrase); } public void testOverwritePromptExplicitYes() throws Exception { - createKeystore("", "foo", "bar"); + String passphrase = "keystorepassphrase"; + createKeystore(passphrase, "foo", "bar"); terminal.addTextInput("y"); + terminal.addSecretInput(passphrase); terminal.addSecretInput("newvalue"); execute("foo"); - assertSecureString("foo", "newvalue"); + assertSecureString("foo", "newvalue", passphrase); } public void testOverwriteForceShort() throws Exception { - createKeystore("", "foo", "bar"); + String passphrase = "keystorepassphrase"; + createKeystore(passphrase, "foo", "bar"); + terminal.addSecretInput(passphrase); terminal.addSecretInput("newvalue"); execute("-f", "foo"); // force - assertSecureString("foo", "newvalue"); + assertSecureString("foo", "newvalue", passphrase); } public void testOverwriteForceLong() throws Exception { - createKeystore("", "foo", "bar"); + String passphrase = "keystorepassphrase"; + createKeystore(passphrase, "foo", "bar"); + terminal.addSecretInput(passphrase); terminal.addSecretInput("and yet another secret value"); execute("--force", "foo"); // force - assertSecureString("foo", "and yet another secret value"); + assertSecureString("foo", "and yet another secret value", passphrase); } public void testForceNonExistent() throws Exception { - createKeystore(""); + String passphrase = "keystorepassphrase"; + createKeystore(passphrase); + terminal.addSecretInput(passphrase); terminal.addSecretInput("value"); execute("--force", "foo"); // force - assertSecureString("foo", "value"); + assertSecureString("foo", "value", passphrase); } public void testPromptForValue() throws Exception { - KeyStoreWrapper.create().save(env.configFile(), new char[0]); + String passphrase = "keystorepassphrase"; + KeyStoreWrapper.create().save(env.configFile(), passphrase.toCharArray()); + terminal.addSecretInput(passphrase); terminal.addSecretInput("secret value"); execute("foo"); - assertSecureString("foo", "secret value"); + assertSecureString("foo", "secret value", passphrase); } public void testStdinShort() throws Exception { - KeyStoreWrapper.create().save(env.configFile(), new char[0]); + String passphrase = "keystorepassphrase"; + KeyStoreWrapper.create().save(env.configFile(), passphrase.toCharArray()); + terminal.addSecretInput(passphrase); setInput("secret value 1"); execute("-x", "foo"); - assertSecureString("foo", "secret value 1"); + assertSecureString("foo", "secret value 1", passphrase); } public void testStdinLong() throws Exception { - KeyStoreWrapper.create().save(env.configFile(), new char[0]); + String passphrase = "keystorepassphrase"; + KeyStoreWrapper.create().save(env.configFile(), passphrase.toCharArray()); + terminal.addSecretInput(passphrase); setInput("secret value 2"); execute("--stdin", "foo"); - assertSecureString("foo", "secret value 2"); + assertSecureString("foo", "secret value 2", passphrase); } public void testMissingSettingName() throws Exception { - createKeystore(""); + String passphrase = "keystorepassphrase"; + createKeystore(passphrase); + terminal.addSecretInput(passphrase); + terminal.addSecretInput(passphrase); terminal.addTextInput(""); UserException e = expectThrows(UserException.class, this::execute); assertEquals(ExitCodes.USAGE, e.exitCode); assertThat(e.getMessage(), containsString("The setting name can not be null")); } + public void testAddToUnprotectedKeystore() throws Exception { + String passphrase = ""; + createKeystore(passphrase, "foo", "bar"); + terminal.addTextInput(""); + // will not be prompted for a passphrase + execute("foo"); + assertSecureString("foo", "bar", passphrase); + } + void setInput(String inputStr) { input = new ByteArrayInputStream(inputStr.getBytes(StandardCharsets.UTF_8)); } diff --git a/server/src/test/java/org/elasticsearch/common/settings/ChangeKeyStorePassphraseCommandTests.java b/server/src/test/java/org/elasticsearch/common/settings/ChangeKeyStorePassphraseCommandTests.java new file mode 100644 index 0000000000000..b630e0f1be7e9 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/common/settings/ChangeKeyStorePassphraseCommandTests.java @@ -0,0 +1,95 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.settings; + +import org.elasticsearch.cli.Command; +import org.elasticsearch.cli.ExitCodes; +import org.elasticsearch.cli.UserException; +import org.elasticsearch.env.Environment; + +import java.util.Map; + +import static org.hamcrest.Matchers.containsString; + +public class ChangeKeyStorePassphraseCommandTests extends KeyStoreCommandTestCase { + @Override + protected Command newCommand() { + return new ChangeKeyStorePassphraseCommand() { + @Override + protected Environment createEnv(Map settings) throws UserException { + return env; + } + }; + } + + public void testSetKeyStorePassword() throws Exception { + createKeystore(""); + loadKeystore(""); + terminal.addSecretInput("thepassphrase"); + terminal.addSecretInput("thepassphrase"); + // Prompted twice for the new password, since we didn't have an existing password + execute(); + loadKeystore("thepassphrase"); + } + + public void testChangeKeyStorePassword() throws Exception { + createKeystore("theoldpassphrase"); + loadKeystore("theoldpassphrase"); + terminal.addSecretInput("theoldpassphrase"); + terminal.addSecretInput("thepassphrase"); + terminal.addSecretInput("thepassphrase"); + // Prompted thrice: Once for the existing and twice for the new password + execute(); + loadKeystore("thepassphrase"); + } + + public void testChangeKeyStorePasswordToEmpty() throws Exception { + createKeystore("theoldpassphrase"); + loadKeystore("theoldpassphrase"); + terminal.addSecretInput("theoldpassphrase"); + terminal.addSecretInput(""); + terminal.addSecretInput(""); + // Prompted thrice: Once for the existing and twice for the new password + execute(); + loadKeystore(""); + } + + public void testChangeKeyStorePasswordWrongVerification() throws Exception { + createKeystore("theoldpassphrase"); + loadKeystore("theoldpassphrase"); + terminal.addSecretInput("theoldpassphrase"); + terminal.addSecretInput("thepassphrase"); + terminal.addSecretInput("themisspelledpassphrase"); + // Prompted thrice: Once for the existing and twice for the new password + UserException e = expectThrows(UserException.class, this::execute); + assertEquals(e.getMessage(), ExitCodes.DATA_ERROR, e.exitCode); + assertThat(e.getMessage(), containsString("Passphrases are not equal, exiting")); + } + + public void testChangeKeyStorePasswordWrongExistingPassword() throws Exception { + createKeystore("theoldpassphrase"); + loadKeystore("theoldpassphrase"); + terminal.addSecretInput("theoldmisspelledpassphrase"); + // We'll only be prompted once (for the old password) + UserException e = expectThrows(UserException.class, this::execute); + assertEquals(e.getMessage(), ExitCodes.DATA_ERROR, e.exitCode); + assertThat(e.getMessage(), containsString("Please make sure the passphrase was correct")); + } +} diff --git a/server/src/test/java/org/elasticsearch/common/settings/CreateKeyStoreCommandTests.java b/server/src/test/java/org/elasticsearch/common/settings/CreateKeyStoreCommandTests.java index aefedf86e7761..60f10009b3930 100644 --- a/server/src/test/java/org/elasticsearch/common/settings/CreateKeyStoreCommandTests.java +++ b/server/src/test/java/org/elasticsearch/common/settings/CreateKeyStoreCommandTests.java @@ -25,9 +25,12 @@ import java.util.Map; import org.elasticsearch.cli.Command; +import org.elasticsearch.cli.ExitCodes; import org.elasticsearch.cli.UserException; import org.elasticsearch.env.Environment; +import static org.hamcrest.Matchers.containsString; + public class CreateKeyStoreCommandTests extends KeyStoreCommandTestCase { @Override @@ -40,13 +43,40 @@ protected Environment createEnv(Map settings) throws UserExcepti }; } + public void testNotMatchingPasswords() throws Exception { + String passphrase = randomFrom("", "keystorepassphrase"); + terminal.addSecretInput(passphrase); + terminal.addSecretInput("notthekeystorepassphraseyouarelookingfor"); + UserException e = expectThrows(UserException.class, this::execute); + assertEquals(e.getMessage(), ExitCodes.DATA_ERROR, e.exitCode); + assertThat(e.getMessage(), containsString("Passphrases are not equal, exiting")); + } + + public void testNotPromptForPassword() throws Exception { + execute("-n"); + Path configDir = env.configFile(); + assertNotNull(KeyStoreWrapper.load(configDir)); + } + + public void testNotPromptForPasswordFullOptionName() throws Exception { + execute("--nopass"); + Path configDir = env.configFile(); + assertNotNull(KeyStoreWrapper.load(configDir)); + } + public void testPosix() throws Exception { + String passphrase = randomFrom("", "keystorepassphrase"); + terminal.addSecretInput(passphrase); + terminal.addSecretInput(passphrase); execute(); Path configDir = env.configFile(); assertNotNull(KeyStoreWrapper.load(configDir)); } public void testNotPosix() throws Exception { + String passphrase = randomFrom("", "keystorepassphrase"); + terminal.addSecretInput(passphrase); + terminal.addSecretInput(passphrase); env = setupEnv(false, fileSystems); execute(); Path configDir = env.configFile(); @@ -54,6 +84,7 @@ public void testNotPosix() throws Exception { } public void testOverwrite() throws Exception { + String passphrase = randomFrom("", "keystorepassphrase"); Path keystoreFile = KeyStoreWrapper.keystorePath(env.configFile()); byte[] content = "not a keystore".getBytes(StandardCharsets.UTF_8); Files.write(keystoreFile, content); @@ -67,6 +98,8 @@ public void testOverwrite() throws Exception { assertArrayEquals(content, Files.readAllBytes(keystoreFile)); terminal.addTextInput("y"); + terminal.addSecretInput(passphrase); + terminal.addSecretInput(passphrase); execute(); assertNotNull(KeyStoreWrapper.load(env.configFile())); } diff --git a/server/src/test/java/org/elasticsearch/common/settings/KeyStoreCommandTestCase.java b/server/src/test/java/org/elasticsearch/common/settings/KeyStoreCommandTestCase.java index 7f8c71889e038..1e5527a1e245b 100644 --- a/server/src/test/java/org/elasticsearch/common/settings/KeyStoreCommandTestCase.java +++ b/server/src/test/java/org/elasticsearch/common/settings/KeyStoreCommandTestCase.java @@ -89,16 +89,16 @@ KeyStoreWrapper loadKeystore(String password) throws Exception { return keystore; } - void assertSecureString(String setting, String value) throws Exception { - assertSecureString(loadKeystore(""), setting, value); + void assertSecureString(String setting, String value, String password) throws Exception { + assertSecureString(loadKeystore(password), setting, value); } void assertSecureString(KeyStoreWrapper keystore, String setting, String value) throws Exception { assertEquals(value, keystore.getString(setting).toString()); } - void assertSecureFile(String setting, Path file) throws Exception { - assertSecureFile(loadKeystore(""), setting, file); + void assertSecureFile(String setting, Path file, String password) throws Exception { + assertSecureFile(loadKeystore(password), setting, file); } void assertSecureFile(KeyStoreWrapper keystore, String setting, Path file) throws Exception { diff --git a/server/src/test/java/org/elasticsearch/common/settings/ListKeyStoreCommandTests.java b/server/src/test/java/org/elasticsearch/common/settings/ListKeyStoreCommandTests.java index 27c30d3aa8f58..8f72b1ea31665 100644 --- a/server/src/test/java/org/elasticsearch/common/settings/ListKeyStoreCommandTests.java +++ b/server/src/test/java/org/elasticsearch/common/settings/ListKeyStoreCommandTests.java @@ -47,20 +47,42 @@ public void testMissing() throws Exception { } public void testEmpty() throws Exception { - createKeystore(""); + String passphrase = randomFrom("", "keystorepassphrase"); + createKeystore(passphrase); + terminal.addSecretInput(passphrase); execute(); assertEquals("keystore.seed\n", terminal.getOutput()); } public void testOne() throws Exception { - createKeystore("", "foo", "bar"); + String passphrase = randomFrom("", "keystorepassphrase"); + createKeystore(passphrase, "foo", "bar"); + terminal.addSecretInput(passphrase); execute(); assertEquals("foo\nkeystore.seed\n", terminal.getOutput()); } public void testMultiple() throws Exception { - createKeystore("", "foo", "1", "baz", "2", "bar", "3"); + String passphrase = randomFrom("", "keystorepassphrase"); + createKeystore(passphrase, "foo", "1", "baz", "2", "bar", "3"); + terminal.addSecretInput(passphrase); execute(); assertEquals("bar\nbaz\nfoo\nkeystore.seed\n", terminal.getOutput()); // sorted } + + public void testListWithIncorrectPassphrase() throws Exception { + String passphrase = "keystorepassphrase"; + createKeystore(passphrase, "foo", "bar"); + terminal.addSecretInput("thewrongkeystorepassphrase"); + UserException e = expectThrows(UserException.class, this::execute); + assertEquals(e.getMessage(), ExitCodes.DATA_ERROR, e.exitCode); + assertThat(e.getMessage(), containsString("Please make sure the passphrase was correct")); + } + + public void testListWithUnprotectedKeystore() throws Exception { + createKeystore("", "foo", "bar"); + execute(); + // Not prompted for a password + assertEquals("foo\nkeystore.seed\n", terminal.getOutput()); + } } diff --git a/server/src/test/java/org/elasticsearch/common/settings/RemoveSettingKeyStoreCommandTests.java b/server/src/test/java/org/elasticsearch/common/settings/RemoveSettingKeyStoreCommandTests.java index 2259dee31a8cb..8969e42cf3fd3 100644 --- a/server/src/test/java/org/elasticsearch/common/settings/RemoveSettingKeyStoreCommandTests.java +++ b/server/src/test/java/org/elasticsearch/common/settings/RemoveSettingKeyStoreCommandTests.java @@ -41,39 +41,66 @@ protected Environment createEnv(Map settings) throws UserExcepti }; } - public void testMissing() throws Exception { + public void testMissing() { + String passphrase = "keystorepassphrase"; + terminal.addSecretInput(passphrase); UserException e = expectThrows(UserException.class, () -> execute("foo")); assertEquals(ExitCodes.DATA_ERROR, e.exitCode); assertThat(e.getMessage(), containsString("keystore not found")); } public void testNoSettings() throws Exception { - createKeystore(""); + String passphrase = "keystorepassphrase"; + createKeystore(passphrase); + terminal.addSecretInput(passphrase); UserException e = expectThrows(UserException.class, this::execute); assertEquals(ExitCodes.USAGE, e.exitCode); assertThat(e.getMessage(), containsString("Must supply at least one setting")); } public void testNonExistentSetting() throws Exception { - createKeystore(""); + String passphrase = "keystorepassphrase"; + createKeystore(passphrase); + terminal.addSecretInput(passphrase); UserException e = expectThrows(UserException.class, () -> execute("foo")); assertEquals(ExitCodes.CONFIG, e.exitCode); assertThat(e.getMessage(), containsString("[foo] does not exist")); } public void testOne() throws Exception { - createKeystore("", "foo", "bar"); + String passphrase = "keystorepassphrase"; + createKeystore(passphrase, "foo", "bar"); + terminal.addSecretInput(passphrase); execute("foo"); - assertFalse(loadKeystore("").getSettingNames().contains("foo")); + assertFalse(loadKeystore(passphrase).getSettingNames().contains("foo")); } public void testMany() throws Exception { - createKeystore("", "foo", "1", "bar", "2", "baz", "3"); + String passphrase = "keystorepassphrase"; + createKeystore(passphrase, "foo", "1", "bar", "2", "baz", "3"); + terminal.addSecretInput(passphrase); execute("foo", "baz"); - Set settings = loadKeystore("").getSettingNames(); + Set settings = loadKeystore(passphrase).getSettingNames(); assertFalse(settings.contains("foo")); assertFalse(settings.contains("baz")); assertTrue(settings.contains("bar")); assertEquals(2, settings.size()); // account for keystore.seed too } + + public void testRemoveWithIncorrectPassword() throws Exception { + String passphrase = "keystorepassphrase"; + createKeystore(passphrase, "foo", "bar"); + terminal.addSecretInput("thewrongpassphrase"); + UserException e = expectThrows(UserException.class, () -> execute("foo")); + assertEquals(e.getMessage(), ExitCodes.DATA_ERROR, e.exitCode); + assertThat(e.getMessage(), containsString("Please make sure the passphrase was correct")); + } + + public void testRemoveFromUnprotectedKeystore() throws Exception { + String passphrase = ""; + createKeystore(passphrase, "foo", "bar"); + // will not be prompted for a passphrase + execute("foo"); + assertFalse(loadKeystore(passphrase).getSettingNames().contains("foo")); + } }