Skip to content

Password Protected Keystore (Feature Branch) (cleaned up) #49268

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
93485d6
Reload secure settings with password (#43197)
jkakavas Jul 11, 2019
1e802e1
Add passphrase support to elasticsearch-keystore (#38498)
jkakavas Jul 23, 2019
0aac95d
Restore behavior for force parameter (#44847)
jkakavas Jul 26, 2019
863bc4c
Handle pwd protected keystores in all CLI tools (#45289)
jkakavas Aug 16, 2019
6bb0e45
Modify docs for setup passwords and saml metadata cli (#45797)
jkakavas Aug 28, 2019
fafaead
Elasticsearch keystore passphrase for startup scripts (#44775)
williamrandolph Nov 11, 2019
f38a911
Adjust docs for password protected keystore (#45054)
jkakavas Aug 1, 2019
5a167b6
Cleanup after feature branch reconstruction
williamrandolph Nov 18, 2019
0a91e3a
Fix test failures after rebase
williamrandolph Nov 19, 2019
ee1d089
Add null check for docker case
williamrandolph Nov 19, 2019
63763b5
Add pause for tty tests to emit status
williamrandolph Nov 21, 2019
3536251
Merge branch 'master' into feature-pwd-protected-keystore-cleanup
williamrandolph Nov 22, 2019
590171b
Match style for other nodes docs
williamrandolph Nov 22, 2019
3b79cf8
Muting flaky feature tests
williamrandolph Nov 24, 2019
961881f
Merging upstream changes from master
williamrandolph Dec 2, 2019
26ca909
Adjust code from master for tests
williamrandolph Dec 5, 2019
8e8271d
Merge master into feature branch
williamrandolph Dec 9, 2019
a40da44
Merge branch 'feature-pwd-protected-keystore-2' into feature-pwd-prot…
williamrandolph Dec 10, 2019
7002570
Ignore failing test
williamrandolph Dec 11, 2019
9d413fc
Merge branch 'feature-pwd-protected-keystore-2' into feature-pwd-prot…
williamrandolph Dec 11, 2019
27fce2d
Temporarily ignore failing Docker test
williamrandolph Dec 11, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Vagrantfile
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@ JAVA
ensure curl
ensure unzip
ensure rsync
ensure expect

installed bats || {
# Bats lives in a git repository....
Expand Down
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,8 @@ task verifyVersions {
* after the backport of the backcompat code is complete.
*/

boolean bwc_tests_enabled = true
final String bwc_tests_disabled_issue = "" /* place a PR link here when committing bwc changes */
boolean bwc_tests_enabled = false
final String bwc_tests_disabled_issue = "https://github.com/elastic/elasticsearch/pull/43197"
if (bwc_tests_enabled == false) {
if (bwc_tests_disabled_issue.isEmpty()) {
throw new GradleException("bwc_tests_disabled_issue must be set when bwc_tests_enabled == false")
Expand Down
2 changes: 1 addition & 1 deletion distribution/docker/docker-test-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/bash
cd /usr/share/elasticsearch/bin/
./elasticsearch-users useradd x_pack_rest_user -p x-pack-test-password -r superuser || true
./elasticsearch-users useradd x_pack_rest_user -p x-pack-test-password -r superuser || true
echo "testnode" > /tmp/password
cat /tmp/password | ./elasticsearch-keystore add -x -f -v 'xpack.security.transport.ssl.keystore.secure_password'
cat /tmp/password | ./elasticsearch-keystore add -x -f -v 'xpack.security.http.ssl.keystore.secure_password'
Expand Down
16 changes: 13 additions & 3 deletions distribution/docker/src/docker/bin/docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,18 @@ if [[ -f bin/elasticsearch-users ]]; then
# honor the variable if it's present.
if [[ -n "$ELASTIC_PASSWORD" ]]; then
[[ -f /usr/share/elasticsearch/config/elasticsearch.keystore ]] || (run_as_other_user_if_needed elasticsearch-keystore create)
if ! (run_as_other_user_if_needed elasticsearch-keystore list | grep -q '^bootstrap.password$'); then
(run_as_other_user_if_needed echo "$ELASTIC_PASSWORD" | elasticsearch-keystore add -x 'bootstrap.password')
if ! (run_as_other_user_if_needed elasticsearch-keystore has-passwd --silent) ; then
# keystore is unencrypted
if ! (run_as_other_user_if_needed elasticsearch-keystore list | grep -q '^bootstrap.password$'); then
(run_as_other_user_if_needed echo "$ELASTIC_PASSWORD" | elasticsearch-keystore add -x 'bootstrap.password')
fi
else
# keystore requires password
if ! (run_as_other_user_if_needed echo "$KEYSTORE_PASSWORD" \
| elasticsearch-keystore list | grep -q '^bootstrap.password$') ; then
COMMANDS="$(printf "%s\n%s" "$KEYSTORE_PASSWORD" "$ELASTIC_PASSWORD")"
(run_as_other_user_if_needed echo "$COMMANDS" | elasticsearch-keystore add -x 'bootstrap.password')
fi
fi
fi
fi
Expand All @@ -130,4 +140,4 @@ if [[ "$(id -u)" == "0" ]]; then
fi
fi

run_as_other_user_if_needed /usr/share/elasticsearch/bin/elasticsearch "${es_opts[@]}"
run_as_other_user_if_needed /usr/share/elasticsearch/bin/elasticsearch "${es_opts[@]}" <<<"$KEYSTORE_PASSWORD"
4 changes: 4 additions & 0 deletions distribution/packages/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,10 @@ Closure commonPackageConfig(String type, boolean oss, boolean jdk) {
from "${packagingFiles}/systemd/sysctl/elasticsearch.conf"
fileMode 0644
}
into('/usr/share/elasticsearch/bin') {
from "${packagingFiles}/systemd/systemd-entrypoint"
fileMode 0755
}

// ========= sysV init =========
configurationFile '/etc/init.d/elasticsearch'
Expand Down
7 changes: 6 additions & 1 deletion distribution/packages/src/common/scripts/posttrans
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ if [ ! -f /etc/elasticsearch/elasticsearch.keystore ]; then
chmod 660 /etc/elasticsearch/elasticsearch.keystore
md5sum /etc/elasticsearch/elasticsearch.keystore > /etc/elasticsearch/.elasticsearch.keystore.initial_md5sum
else
/usr/share/elasticsearch/bin/elasticsearch-keystore upgrade
if /usr/share/elasticsearch/bin/elasticsearch-keystore has-passwd --silent ; then
echo "### Warning: unable to upgrade encrypted keystore" 1>&2
echo " Please run elasticsearch-keystore upgrade and enter password" 1>&2
else
/usr/share/elasticsearch/bin/elasticsearch-keystore upgrade
fi
fi

${scripts.footer}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ WorkingDirectory=/usr/share/elasticsearch
User=elasticsearch
Group=elasticsearch

ExecStart=/usr/share/elasticsearch/bin/elasticsearch -p ${PID_DIR}/elasticsearch.pid --quiet
ExecStart=/usr/share/elasticsearch/bin/systemd-entrypoint -p ${PID_DIR}/elasticsearch.pid --quiet

# StandardOutput is configured to redirect to journalctl since
# some error messages may be logged in standard output before
Expand Down
10 changes: 10 additions & 0 deletions distribution/packages/src/common/systemd/systemd-entrypoint
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/sh

# This wrapper script allows SystemD to feed a file containing a passphrase into
# the main Elasticsearch startup script

if [ -n "$ES_KEYSTORE_PASSPHRASE_FILE" ] ; then
exec /usr/share/elasticsearch/bin/elasticsearch "$@" < "$ES_KEYSTORE_PASSPHRASE_FILE"
else
exec /usr/share/elasticsearch/bin/elasticsearch "$@"
fi
17 changes: 15 additions & 2 deletions distribution/src/bin/elasticsearch
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@ if [ -z "$ES_TMPDIR" ]; then
ES_TMPDIR=`"$JAVA" -cp "$ES_CLASSPATH" org.elasticsearch.tools.launchers.TempDirectory`
fi

# get keystore password before setting java options to avoid
# conflicting GC configurations for the keystore tools
unset KEYSTORE_PASSWORD
KEYSTORE_PASSWORD=
if ! echo $* | grep -E -q '(^-h |-h$| -h |--help$|--help |^-V |-V$| -V |--version$|--version )' \
&& "`dirname "$0"`"/elasticsearch-keystore has-passwd --silent
then
if ! read -s -r -p "Elasticsearch keystore password: " KEYSTORE_PASSWORD ; then
echo "Failed to read keystore password on console" 1>&2
exit 1
fi
fi

ES_JVM_OPTIONS="$ES_PATH_CONF"/jvm.options
ES_JAVA_OPTS=`export ES_TMPDIR; "$JAVA" -cp "$ES_CLASSPATH" org.elasticsearch.tools.launchers.JvmOptionsParser "$ES_JVM_OPTIONS"`

Expand All @@ -35,7 +48,7 @@ if ! echo $* | grep -E '(^-d |-d$| -d |--daemonize$|--daemonize )' > /dev/null;
-Des.bundled_jdk="$ES_BUNDLED_JDK" \
-cp "$ES_CLASSPATH" \
org.elasticsearch.bootstrap.Elasticsearch \
"$@"
"$@" <<<"$KEYSTORE_PASSWORD"
else
exec \
"$JAVA" \
Expand All @@ -48,7 +61,7 @@ else
-cp "$ES_CLASSPATH" \
org.elasticsearch.bootstrap.Elasticsearch \
"$@" \
<&- &
<<<"$KEYSTORE_PASSWORD" &
retval=$?
pid=$!
[ $retval -eq 0 ] || exit $retval
Expand Down
2 changes: 1 addition & 1 deletion distribution/src/bin/elasticsearch-cli.bat
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ set ES_JAVA_OPTS=-Xms4m -Xmx64m -XX:+UseSerialGC %ES_JAVA_OPTS%
-cp "%ES_CLASSPATH%" ^
"%ES_MAIN_CLASS%" ^
%*

exit /b %ERRORLEVEL%
42 changes: 41 additions & 1 deletion distribution/src/bin/elasticsearch.bat
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ setlocal enabledelayedexpansion
setlocal enableextensions

SET params='%*'
SET checkpassword=Y

:loop
FOR /F "usebackq tokens=1* delims= " %%A IN (!params!) DO (
Expand All @@ -18,6 +19,20 @@ FOR /F "usebackq tokens=1* delims= " %%A IN (!params!) DO (
SET silent=Y
)

IF "!current!" == "-h" (
SET checkpassword=N
)
IF "!current!" == "--help" (
SET checkpassword=N
)

IF "!current!" == "-V" (
SET checkpassword=N
)
IF "!current!" == "--version" (
SET checkpassword=N
)

IF "!silent!" == "Y" (
SET nopauseonerror=Y
) ELSE (
Expand All @@ -41,6 +56,18 @@ IF ERRORLEVEL 1 (
EXIT /B %ERRORLEVEL%
)

SET KEYSTORE_PASSWORD=
IF "%checkpassword%"=="Y" (
CALL "%~dp0elasticsearch-keystore.bat" has-passwd --silent
IF !ERRORLEVEL! EQU 0 (
SET /P KEYSTORE_PASSWORD=Elasticsearch keystore password:
IF !ERRORLEVEL! NEQ 0 (
ECHO Failed to read keystore password on standard input
EXIT /B !ERRORLEVEL!
)
)
)

if not defined ES_TMPDIR (
for /f "tokens=* usebackq" %%a in (`CALL %JAVA% -cp "!ES_CLASSPATH!" "org.elasticsearch.tools.launchers.TempDirectory"`) do set ES_TMPDIR=%%a
)
Expand All @@ -54,7 +81,20 @@ if "%MAYBE_JVM_OPTIONS_PARSER_FAILED%" == "jvm_options_parser_failed" (
exit /b 1
)

%JAVA% %ES_JAVA_OPTS% -Delasticsearch -Des.path.home="%ES_HOME%" -Des.path.conf="%ES_PATH_CONF%" -Des.distribution.flavor="%ES_DISTRIBUTION_FLAVOR%" -Des.distribution.type="%ES_DISTRIBUTION_TYPE%" -Des.bundled_jdk="%ES_BUNDLED_JDK%" -cp "%ES_CLASSPATH%" "org.elasticsearch.bootstrap.Elasticsearch" !newparams!
rem windows batch pipe will choke on special characters in strings
SET KEYSTORE_PASSWORD=!KEYSTORE_PASSWORD:^^=^^^^!
SET KEYSTORE_PASSWORD=!KEYSTORE_PASSWORD:^&=^^^&!
SET KEYSTORE_PASSWORD=!KEYSTORE_PASSWORD:^|=^^^|!
SET KEYSTORE_PASSWORD=!KEYSTORE_PASSWORD:^<=^^^<!
SET KEYSTORE_PASSWORD=!KEYSTORE_PASSWORD:^>=^^^>!
SET KEYSTORE_PASSWORD=!KEYSTORE_PASSWORD:^\=^^^\!

ECHO.!KEYSTORE_PASSWORD!| %JAVA% %ES_JAVA_OPTS% -Delasticsearch ^
-Des.path.home="%ES_HOME%" -Des.path.conf="%ES_PATH_CONF%" ^
-Des.distribution.flavor="%ES_DISTRIBUTION_FLAVOR%" ^
-Des.distribution.type="%ES_DISTRIBUTION_TYPE%" ^
-Des.bundled_jdk="%ES_BUNDLED_JDK%" ^
-cp "%ES_CLASSPATH%" "org.elasticsearch.bootstrap.Elasticsearch" !newparams!

endlocal
endlocal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@

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;
Expand All @@ -37,42 +36,29 @@
/**
* A subcommand for the keystore cli which adds a file setting.
*/
class AddFileKeyStoreCommand extends EnvironmentAwareCommand {
class AddFileKeyStoreCommand extends BaseKeyStoreCommand {

private final OptionSpec<Void> forceOption;
private final OptionSpec<String> arguments;

AddFileKeyStoreCommand() {
super("Add a file setting to the keystore");
this.forceOption = parser.acceptsAll(Arrays.asList("f", "force"), "Overwrite existing setting without prompting");
super("Add a file setting to the keystore", false);
this.forceOption = parser.acceptsAll(Arrays.asList("f", "force"),
"Overwrite existing setting without prompting, creating keystore if necessary");
// jopt simple has issue with multiple non options, so we just get one set of them here
// and convert to File when necessary
// see https://github.com/jopt-simple/jopt-simple/issues/103
this.arguments = parser.nonOptions("setting [filepath]");
}

@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.");
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 */);
}

protected void executeCommand(Terminal terminal, OptionSet options, Environment env) throws Exception {
List<String> 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) {
final KeyStoreWrapper keyStore = getKeyStore();
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;
Expand All @@ -90,11 +76,11 @@ protected void execute(Terminal terminal, OptionSet options, Environment env) th
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(), new char[0]);
keyStore.setFile(setting, Files.readAllBytes(file));
keyStore.save(env.configFile(), getKeyStorePassword().getChars());
}

@SuppressForbidden(reason="file arg for cli")
@SuppressForbidden(reason = "file arg for cli")
private Path getPath(String file) {
return PathUtils.get(file);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,13 @@
package org.elasticsearch.common.settings;

import java.io.BufferedReader;
import java.io.CharArrayWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
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;
Expand All @@ -37,16 +35,16 @@
/**
* A subcommand for the keystore cli which adds a string setting.
*/
class AddStringKeyStoreCommand extends EnvironmentAwareCommand {
class AddStringKeyStoreCommand extends BaseKeyStoreCommand {

private final OptionSpec<Void> stdinOption;
private final OptionSpec<Void> forceOption;
private final OptionSpec<String> arguments;

AddStringKeyStoreCommand() {
super("Add a string setting to the keystore");
super("Add a string setting to the keystore", false);
this.stdinOption = parser.acceptsAll(Arrays.asList("x", "stdin"), "Read setting value from stdin");
this.forceOption = parser.acceptsAll(Arrays.asList("f", "force"), "Overwrite existing setting without prompting");
this.forceOption = parser.acceptsAll(Arrays.asList("f", "force"),
"Overwrite existing setting without prompting, creating keystore if necessary");
this.arguments = parser.nonOptions("setting name");
}

Expand All @@ -56,26 +54,13 @@ 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.");
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 */);
}

protected void executeCommand(Terminal terminal, OptionSet options, Environment env) throws Exception {
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) {
final KeyStoreWrapper keyStore = getKeyStore();
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;
Expand All @@ -84,26 +69,18 @@ protected void execute(Terminal terminal, OptionSet options, Environment env) th

final char[] value;
if (options.has(stdinOption)) {
try (BufferedReader stdinReader = new BufferedReader(new InputStreamReader(getStdin(), StandardCharsets.UTF_8));
CharArrayWriter writer = new CharArrayWriter()) {
int charInt;
while ((charInt = stdinReader.read()) != -1) {
if ((char) charInt == '\r' || (char) charInt == '\n') {
break;
}
writer.write((char) charInt);
}
value = writer.toCharArray();
}
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 (final IllegalArgumentException e) {
keyStore.setString(setting, value);
} catch (IllegalArgumentException e) {
throw new UserException(ExitCodes.DATA_ERROR, e.getMessage());
}
keystore.save(env.configFile(), new char[0]);
keyStore.save(env.configFile(), getKeyStorePassword().getChars());

}
}
Loading