Skip to content

Commit c58f3aa

Browse files
committed
Add passphrase support to elasticsearch-keystore
- Subcommands of elasticsearch-keystore can handle (open and create) passphrase protected keystores - When reading a keystore, a user is only prompted for a passphrase only if the keystore is passphrase protected. Relates to: elastic#32691
1 parent bf49f54 commit c58f3aa

11 files changed

+430
-159
lines changed

server/src/main/java/org/elasticsearch/common/settings/AddFileKeyStoreCommand.java

+57-34
Original file line numberDiff line numberDiff line change
@@ -53,45 +53,68 @@ class AddFileKeyStoreCommand extends EnvironmentAwareCommand {
5353

5454
@Override
5555
protected void execute(Terminal terminal, OptionSet options, Environment env) throws Exception {
56-
KeyStoreWrapper keystore = KeyStoreWrapper.load(env.configFile());
57-
if (keystore == null) {
58-
if (options.has(forceOption) == false &&
59-
terminal.promptYesNo("The elasticsearch keystore does not exist. Do you want to create it?", false) == false) {
60-
terminal.println("Exiting without creating keystore.");
61-
return;
56+
char[] password = null;
57+
char[] passwordVerification = null;
58+
try {
59+
KeyStoreWrapper keystore = KeyStoreWrapper.load(env.configFile());
60+
if (keystore == null) {
61+
if (options.has(forceOption) == false &&
62+
terminal.promptYesNo("The elasticsearch keystore does not exist. Do you want to create it?", false) == false) {
63+
terminal.println("Exiting without creating keystore.");
64+
return;
65+
}
66+
password = terminal.readSecret("Enter passphrase for the elasticsearch keystore (empty for no passphrase): ");
67+
passwordVerification = terminal.readSecret("Enter same passphrase again: ");
68+
if (Arrays.equals(password, passwordVerification) == false) {
69+
throw new UserException(ExitCodes.DATA_ERROR, "Passphrases are not equal, exiting.");
70+
}
71+
keystore = KeyStoreWrapper.create();
72+
keystore.save(env.configFile(), password);
73+
terminal.println("Created elasticsearch keystore in " + env.configFile());
74+
} else {
75+
if (keystore.hasPassword()) {
76+
password = terminal.readSecret("Enter passphrase for the elasticsearch keystore: ");
77+
} else {
78+
password = new char[0];
79+
}
80+
keystore.decrypt(password);
6281
}
63-
keystore = KeyStoreWrapper.create();
64-
keystore.save(env.configFile(), new char[0] /* always use empty passphrase for auto created keystore */);
65-
terminal.println("Created elasticsearch keystore in " + env.configFile());
66-
} else {
67-
keystore.decrypt(new char[0] /* TODO: prompt for password when they are supported */);
68-
}
6982

70-
List<String> argumentValues = arguments.values(options);
71-
if (argumentValues.size() == 0) {
72-
throw new UserException(ExitCodes.USAGE, "Missing setting name");
73-
}
74-
String setting = argumentValues.get(0);
75-
if (keystore.getSettingNames().contains(setting) && options.has(forceOption) == false) {
76-
if (terminal.promptYesNo("Setting " + setting + " already exists. Overwrite?", false) == false) {
77-
terminal.println("Exiting without modifying keystore.");
78-
return;
83+
List<String> argumentValues = arguments.values(options);
84+
if (argumentValues.size() == 0) {
85+
throw new UserException(ExitCodes.USAGE, "Missing setting name");
86+
}
87+
String setting = argumentValues.get(0);
88+
if (keystore.getSettingNames().contains(setting) && options.has(forceOption) == false) {
89+
if (terminal.promptYesNo("Setting " + setting + " already exists. Overwrite?", false) == false) {
90+
terminal.println("Exiting without modifying keystore.");
91+
return;
92+
}
7993
}
80-
}
8194

82-
if (argumentValues.size() == 1) {
83-
throw new UserException(ExitCodes.USAGE, "Missing file name");
84-
}
85-
Path file = getPath(argumentValues.get(1));
86-
if (Files.exists(file) == false) {
87-
throw new UserException(ExitCodes.IO_ERROR, "File [" + file.toString() + "] does not exist");
88-
}
89-
if (argumentValues.size() > 2) {
90-
throw new UserException(ExitCodes.USAGE, "Unrecognized extra arguments [" +
91-
String.join(", ", argumentValues.subList(2, argumentValues.size())) + "] after filepath");
95+
if (argumentValues.size() == 1) {
96+
throw new UserException(ExitCodes.USAGE, "Missing file name");
97+
}
98+
Path file = getPath(argumentValues.get(1));
99+
if (Files.exists(file) == false) {
100+
throw new UserException(ExitCodes.IO_ERROR, "File [" + file.toString() + "] does not exist");
101+
}
102+
if (argumentValues.size() > 2) {
103+
throw new UserException(ExitCodes.USAGE, "Unrecognized extra arguments [" +
104+
String.join(", ", argumentValues.subList(2, argumentValues.size())) + "] after filepath");
105+
}
106+
keystore.setFile(setting, Files.readAllBytes(file));
107+
keystore.save(env.configFile(), password);
108+
} catch (SecurityException e) {
109+
throw new UserException(ExitCodes.DATA_ERROR, "Failed to access the keystore. Please make sure the passphrase was correct.");
110+
} finally {
111+
if (null != password) {
112+
Arrays.fill(password, '\u0000');
113+
}
114+
if (null != passwordVerification) {
115+
Arrays.fill(passwordVerification, '\u0000');
116+
}
92117
}
93-
keystore.setFile(setting, Files.readAllBytes(file));
94-
keystore.save(env.configFile(), new char[0]);
95118
}
96119

97120
@SuppressForbidden(reason="file arg for cli")

server/src/main/java/org/elasticsearch/common/settings/AddStringKeyStoreCommand.java

+56-33
Original file line numberDiff line numberDiff line change
@@ -56,44 +56,67 @@ InputStream getStdin() {
5656

5757
@Override
5858
protected void execute(Terminal terminal, OptionSet options, Environment env) throws Exception {
59-
KeyStoreWrapper keystore = KeyStoreWrapper.load(env.configFile());
60-
if (keystore == null) {
61-
if (options.has(forceOption) == false &&
62-
terminal.promptYesNo("The elasticsearch keystore does not exist. Do you want to create it?", false) == false) {
63-
terminal.println("Exiting without creating keystore.");
64-
return;
59+
char[] password = null;
60+
char[] passwordVerification = null;
61+
try {
62+
KeyStoreWrapper keystore = KeyStoreWrapper.load(env.configFile());
63+
if (keystore == null) {
64+
if (options.has(forceOption) == false &&
65+
terminal.promptYesNo("The elasticsearch keystore does not exist. Do you want to create it?", false) == false) {
66+
terminal.println("Exiting without creating keystore.");
67+
return;
68+
}
69+
password = terminal.readSecret("Enter passphrase for the elasticsearch keystore (empty for no passphrase): ");
70+
passwordVerification = terminal.readSecret("Enter same passphrase again: ");
71+
if (Arrays.equals(password, passwordVerification) == false) {
72+
throw new UserException(ExitCodes.DATA_ERROR, "Passphrases are not equal, exiting.");
73+
}
74+
keystore = KeyStoreWrapper.create();
75+
keystore.save(env.configFile(), password);
76+
terminal.println("Created elasticsearch keystore in " + env.configFile());
77+
} else {
78+
if (keystore.hasPassword()) {
79+
password = terminal.readSecret("Enter passphrase for the elasticsearch keystore: ");
80+
} else {
81+
password = new char[0];
82+
}
83+
keystore.decrypt(password);
6584
}
66-
keystore = KeyStoreWrapper.create();
67-
keystore.save(env.configFile(), new char[0] /* always use empty passphrase for auto created keystore */);
68-
terminal.println("Created elasticsearch keystore in " + env.configFile());
69-
} else {
70-
keystore.decrypt(new char[0] /* TODO: prompt for password when they are supported */);
71-
}
7285

73-
String setting = arguments.value(options);
74-
if (setting == null) {
75-
throw new UserException(ExitCodes.USAGE, "The setting name can not be null");
76-
}
77-
if (keystore.getSettingNames().contains(setting) && options.has(forceOption) == false) {
78-
if (terminal.promptYesNo("Setting " + setting + " already exists. Overwrite?", false) == false) {
79-
terminal.println("Exiting without modifying keystore.");
80-
return;
86+
String setting = arguments.value(options);
87+
if (setting == null) {
88+
throw new UserException(ExitCodes.USAGE, "The setting name can not be null");
89+
}
90+
if (keystore.getSettingNames().contains(setting) && options.has(forceOption) == false) {
91+
if (terminal.promptYesNo("Setting " + setting + " already exists. Overwrite?", false) == false) {
92+
terminal.println("Exiting without modifying keystore.");
93+
return;
94+
}
8195
}
82-
}
8396

84-
final char[] value;
85-
if (options.has(stdinOption)) {
86-
BufferedReader stdinReader = new BufferedReader(new InputStreamReader(getStdin(), StandardCharsets.UTF_8));
87-
value = stdinReader.readLine().toCharArray();
88-
} else {
89-
value = terminal.readSecret("Enter value for " + setting + ": ");
90-
}
97+
final char[] value;
98+
if (options.has(stdinOption)) {
99+
BufferedReader stdinReader = new BufferedReader(new InputStreamReader(getStdin(), StandardCharsets.UTF_8));
100+
value = stdinReader.readLine().toCharArray();
101+
} else {
102+
value = terminal.readSecret("Enter value for " + setting + ": ");
103+
}
91104

92-
try {
93-
keystore.setString(setting, value);
94-
} catch (IllegalArgumentException e) {
95-
throw new UserException(ExitCodes.DATA_ERROR, "String value must contain only ASCII");
105+
try {
106+
keystore.setString(setting, value);
107+
} catch (IllegalArgumentException e) {
108+
throw new UserException(ExitCodes.DATA_ERROR, "String value must contain only ASCII");
109+
}
110+
keystore.save(env.configFile(), password);
111+
} catch (SecurityException e) {
112+
throw new UserException(ExitCodes.DATA_ERROR, "Failed to access the keystore. Please make sure the passphrase was correct.");
113+
} finally {
114+
if (null != password) {
115+
Arrays.fill(password, '\u0000');
116+
}
117+
if (null != passwordVerification) {
118+
Arrays.fill(passwordVerification, '\u0000');
119+
}
96120
}
97-
keystore.save(env.configFile(), new char[0]);
98121
}
99122
}

server/src/main/java/org/elasticsearch/common/settings/CreateKeyStoreCommand.java

+29-17
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@
2121

2222
import java.nio.file.Files;
2323
import java.nio.file.Path;
24+
import java.util.Arrays;
2425

2526
import joptsimple.OptionSet;
2627
import org.elasticsearch.cli.EnvironmentAwareCommand;
28+
import org.elasticsearch.cli.ExitCodes;
2729
import org.elasticsearch.cli.Terminal;
30+
import org.elasticsearch.cli.UserException;
2831
import org.elasticsearch.env.Environment;
2932

3033
/**
@@ -38,24 +41,33 @@ class CreateKeyStoreCommand extends EnvironmentAwareCommand {
3841

3942
@Override
4043
protected void execute(Terminal terminal, OptionSet options, Environment env) throws Exception {
41-
Path keystoreFile = KeyStoreWrapper.keystorePath(env.configFile());
42-
if (Files.exists(keystoreFile)) {
43-
if (terminal.promptYesNo("An elasticsearch keystore already exists. Overwrite?", false) == false) {
44-
terminal.println("Exiting without creating keystore.");
45-
return;
44+
char[] password = null;
45+
char[] passwordVerification = null;
46+
try {
47+
Path keystoreFile = KeyStoreWrapper.keystorePath(env.configFile());
48+
if (Files.exists(keystoreFile)) {
49+
if (terminal.promptYesNo("An elasticsearch keystore already exists. Overwrite?", false) == false) {
50+
terminal.println("Exiting without creating keystore.");
51+
return;
52+
}
53+
}
54+
password = terminal.readSecret("Enter passphrase (empty for no passphrase): ");
55+
passwordVerification = terminal.readSecret("Enter same passphrase again: ");
56+
if (Arrays.equals(password, passwordVerification) == false) {
57+
throw new UserException(ExitCodes.DATA_ERROR, "Passphrases are not equal, exiting.");
58+
}
59+
KeyStoreWrapper keystore = KeyStoreWrapper.create();
60+
keystore.save(env.configFile(), password);
61+
terminal.println("Created elasticsearch keystore in " + env.configFile());
62+
} catch (SecurityException e) {
63+
throw new UserException(ExitCodes.IO_ERROR, "Error creating the elasticsearch keystore.", e);
64+
} finally {
65+
if (null != password) {
66+
Arrays.fill(password, '\u0000');
67+
}
68+
if (null != passwordVerification) {
69+
Arrays.fill(passwordVerification, '\u0000');
4670
}
4771
}
48-
49-
50-
char[] password = new char[0];// terminal.readSecret("Enter passphrase (empty for no passphrase): ");
51-
/* TODO: uncomment when entering passwords on startup is supported
52-
char[] passwordRepeat = terminal.readSecret("Enter same passphrase again: ");
53-
if (Arrays.equals(password, passwordRepeat) == false) {
54-
throw new UserException(ExitCodes.DATA_ERROR, "Passphrases are not equal, exiting.");
55-
}*/
56-
57-
KeyStoreWrapper keystore = KeyStoreWrapper.create();
58-
keystore.save(env.configFile(), password);
59-
terminal.println("Created elasticsearch keystore in " + env.configFile());
6072
}
6173
}

server/src/main/java/org/elasticsearch/common/settings/ListKeyStoreCommand.java

+18-7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222

2323
import java.util.ArrayList;
24+
import java.util.Arrays;
2425
import java.util.Collections;
2526
import java.util.List;
2627

@@ -46,13 +47,23 @@ protected void execute(Terminal terminal, OptionSet options, Environment env) th
4647
if (keystore == null) {
4748
throw new UserException(ExitCodes.DATA_ERROR, "Elasticsearch keystore not found. Use 'create' command to create one.");
4849
}
49-
50-
keystore.decrypt(new char[0] /* TODO: prompt for password when they are supported */);
51-
52-
List<String> sortedEntries = new ArrayList<>(keystore.getSettingNames());
53-
Collections.sort(sortedEntries);
54-
for (String entry : sortedEntries) {
55-
terminal.println(entry);
50+
char[] password;
51+
if (keystore.hasPassword()) {
52+
password = terminal.readSecret("Enter elasticsearch keystore passphrase (empty for no passphrase): ");
53+
} else {
54+
password = new char[0];
55+
}
56+
try {
57+
keystore.decrypt(password);
58+
List<String> sortedEntries = new ArrayList<>(keystore.getSettingNames());
59+
Collections.sort(sortedEntries);
60+
for (String entry : sortedEntries) {
61+
terminal.println(entry);
62+
}
63+
} catch (SecurityException e) {
64+
throw new UserException(ExitCodes.DATA_ERROR, "Failed to access the keystore. Please make sure the passphrase was correct.");
65+
} finally {
66+
Arrays.fill(password, '\u0000');
5667
}
5768
}
5869
}

server/src/main/java/org/elasticsearch/common/settings/RemoveSettingKeyStoreCommand.java

+21-7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.elasticsearch.common.settings;
2121

22+
import java.util.Arrays;
2223
import java.util.List;
2324

2425
import joptsimple.OptionSet;
@@ -52,15 +53,28 @@ protected void execute(Terminal terminal, OptionSet options, Environment env) th
5253
if (keystore == null) {
5354
throw new UserException(ExitCodes.DATA_ERROR, "Elasticsearch keystore not found. Use 'create' command to create one.");
5455
}
56+
char[] password = null;
57+
try {
58+
if (keystore.hasPassword()) {
59+
password = terminal.readSecret("Enter passphrase for the elasticsearch keystore: ");
60+
} else {
61+
password = new char[0];
62+
}
63+
keystore.decrypt(password);
5564

56-
keystore.decrypt(new char[0] /* TODO: prompt for password when they are supported */);
57-
58-
for (String setting : arguments.values(options)) {
59-
if (keystore.getSettingNames().contains(setting) == false) {
60-
throw new UserException(ExitCodes.CONFIG, "Setting [" + setting + "] does not exist in the keystore.");
65+
for (String setting : arguments.values(options)) {
66+
if (keystore.getSettingNames().contains(setting) == false) {
67+
throw new UserException(ExitCodes.CONFIG, "Setting [" + setting + "] does not exist in the keystore.");
68+
}
69+
keystore.remove(setting);
70+
}
71+
keystore.save(env.configFile(), password);
72+
} catch (SecurityException e) {
73+
throw new UserException(ExitCodes.DATA_ERROR, "Failed to access the keystore. Please make sure the passphrase was correct.");
74+
} finally {
75+
if (null != password) {
76+
Arrays.fill(password, '\u0000');
6177
}
62-
keystore.remove(setting);
6378
}
64-
keystore.save(env.configFile(), new char[0]);
6579
}
6680
}

0 commit comments

Comments
 (0)