Limit _FILE env var support to specific vars (#52645)

Backport of #52525.

Closes #52503. Implement a list of `_FILE` env vars that will be used to
populate env vars with file content, instead of processing all `_FILE`
vars in the environment.
This commit is contained in:
Rory Hunter 2020-02-21 19:36:15 +00:00 committed by GitHub
parent 376932a47d
commit ce7ebb2d39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 66 additions and 51 deletions

View File

@ -7,11 +7,15 @@ set -e -o pipefail
# point to it. This can be used to provide secrets to a container, without # point to it. This can be used to provide secrets to a container, without
# the values being specified explicitly when running the container. # the values being specified explicitly when running the container.
# #
# Note that only supported environment variables are processed, in order
# to avoid unexpected failures when an environment sets a "*_FILE" variable
# that doesn't contain a filename.
#
# This script is intended to be sourced, not executed, and modifies the # This script is intended to be sourced, not executed, and modifies the
# environment. # environment.
for VAR_NAME_FILE in $(env | cut -f1 -d= | grep '_FILE$'); do for VAR_NAME_FILE in ELASTIC_PASSWORD_FILE KEYSTORE_PASSWORD_FILE ; do
if [[ -n "$VAR_NAME_FILE" ]]; then if [[ -n "${!VAR_NAME_FILE}" ]]; then
VAR_NAME="${VAR_NAME_FILE%_FILE}" VAR_NAME="${VAR_NAME_FILE%_FILE}"
if env | grep "^${VAR_NAME}="; then if env | grep "^${VAR_NAME}="; then

View File

@ -57,7 +57,6 @@ import static org.elasticsearch.packaging.util.FileMatcher.p775;
import static org.elasticsearch.packaging.util.FileUtils.append; import static org.elasticsearch.packaging.util.FileUtils.append;
import static org.elasticsearch.packaging.util.FileUtils.getTempDir; import static org.elasticsearch.packaging.util.FileUtils.getTempDir;
import static org.elasticsearch.packaging.util.FileUtils.rm; import static org.elasticsearch.packaging.util.FileUtils.rm;
import static org.elasticsearch.packaging.util.ServerUtils.makeRequest;
import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.arrayWithSize;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.emptyString; import static org.hamcrest.Matchers.emptyString;
@ -219,38 +218,10 @@ public class DockerTests extends PackagingTestCase {
}); });
} }
/**
* Check that environment variables can be populated by setting variables with the suffix "_FILE",
* which point to files that hold the required values.
*/
public void test080SetEnvironmentVariablesUsingFiles() throws Exception {
final String optionsFilename = "esJavaOpts.txt";
// ES_JAVA_OPTS_FILE
append(tempDir.resolve(optionsFilename), "-XX:-UseCompressedOops\n");
Map<String, String> envVars = singletonMap("ES_JAVA_OPTS_FILE", "/run/secrets/" + optionsFilename);
// File permissions need to be secured in order for the ES wrapper to accept
// them for populating env var values
Files.setPosixFilePermissions(tempDir.resolve(optionsFilename), p600);
final Map<Path, Path> volumes = singletonMap(tempDir, Paths.get("/run/secrets"));
// Restart the container
runContainer(distribution(), volumes, envVars);
waitForElasticsearch(installation);
final String nodesResponse = makeRequest(Request.Get("http://localhost:9200/_nodes"));
assertThat(nodesResponse, containsString("\"using_compressed_ordinary_object_pointers\":\"false\""));
}
/** /**
* Check that the elastic user's password can be configured via a file and the ELASTIC_PASSWORD_FILE environment variable. * Check that the elastic user's password can be configured via a file and the ELASTIC_PASSWORD_FILE environment variable.
*/ */
public void test081ConfigurePasswordThroughEnvironmentVariableFile() throws Exception { public void test080ConfigurePasswordThroughEnvironmentVariableFile() throws Exception {
// Test relies on configuring security // Test relies on configuring security
assumeTrue(distribution.isDefault()); assumeTrue(distribution.isDefault());
@ -293,7 +264,7 @@ public class DockerTests extends PackagingTestCase {
* Check that when verifying the file permissions of _FILE environment variables, symlinks * Check that when verifying the file permissions of _FILE environment variables, symlinks
* are followed. * are followed.
*/ */
public void test082SymlinksAreFollowedWithEnvironmentVariableFiles() throws Exception { public void test081SymlinksAreFollowedWithEnvironmentVariableFiles() throws Exception {
// Test relies on configuring security // Test relies on configuring security
assumeTrue(distribution.isDefault()); assumeTrue(distribution.isDefault());
// Test relies on symlinks // Test relies on symlinks
@ -330,19 +301,18 @@ public class DockerTests extends PackagingTestCase {
/** /**
* Check that environment variables cannot be used with _FILE environment variables. * Check that environment variables cannot be used with _FILE environment variables.
*/ */
public void test083CannotUseEnvVarsAndFiles() throws Exception { public void test082CannotUseEnvVarsAndFiles() throws Exception {
final String optionsFilename = "esJavaOpts.txt"; final String passwordFilename = "password.txt";
// ES_JAVA_OPTS_FILE Files.write(tempDir.resolve(passwordFilename), "other_hunter2\n".getBytes(StandardCharsets.UTF_8));
append(tempDir.resolve(optionsFilename), "-XX:-UseCompressedOops\n");
Map<String, String> envVars = new HashMap<>(); Map<String, String> envVars = new HashMap<>();
envVars.put("ES_JAVA_OPTS", "-XX:+UseCompressedOops"); envVars.put("ELASTIC_PASSWORD", "hunter2");
envVars.put("ES_JAVA_OPTS_FILE", "/run/secrets/" + optionsFilename); envVars.put("ELASTIC_PASSWORD_FILE", "/run/secrets/" + passwordFilename);
// File permissions need to be secured in order for the ES wrapper to accept // File permissions need to be secured in order for the ES wrapper to accept
// them for populating env var values // them for populating env var values
Files.setPosixFilePermissions(tempDir.resolve(optionsFilename), p600); Files.setPosixFilePermissions(tempDir.resolve(passwordFilename), p600);
final Map<Path, Path> volumes = singletonMap(tempDir, Paths.get("/run/secrets")); final Map<Path, Path> volumes = singletonMap(tempDir, Paths.get("/run/secrets"));
@ -350,7 +320,7 @@ public class DockerTests extends PackagingTestCase {
assertThat( assertThat(
dockerLogs.stderr, dockerLogs.stderr,
containsString("ERROR: Both ES_JAVA_OPTS_FILE and ES_JAVA_OPTS are set. These are mutually " + "exclusive.") containsString("ERROR: Both ELASTIC_PASSWORD_FILE and ELASTIC_PASSWORD are set. These are mutually exclusive.")
); );
} }
@ -358,16 +328,16 @@ public class DockerTests extends PackagingTestCase {
* Check that when populating environment variables by setting variables with the suffix "_FILE", * Check that when populating environment variables by setting variables with the suffix "_FILE",
* the files' permissions are checked. * the files' permissions are checked.
*/ */
public void test084EnvironmentVariablesUsingFilesHaveCorrectPermissions() throws Exception { public void test083EnvironmentVariablesUsingFilesHaveCorrectPermissions() throws Exception {
final String optionsFilename = "esJavaOpts.txt"; final String passwordFilename = "password.txt";
// ES_JAVA_OPTS_FILE Files.write(tempDir.resolve(passwordFilename), "hunter2\n".getBytes(StandardCharsets.UTF_8));
append(tempDir.resolve(optionsFilename), "-XX:-UseCompressedOops\n");
Map<String, String> envVars = singletonMap("ES_JAVA_OPTS_FILE", "/run/secrets/" + optionsFilename); Map<String, String> envVars = new HashMap<>();
envVars.put("ELASTIC_PASSWORD_FILE", "/run/secrets/" + passwordFilename);
// Set invalid file permissions // Set invalid file permissions
Files.setPosixFilePermissions(tempDir.resolve(optionsFilename), p660); Files.setPosixFilePermissions(tempDir.resolve(passwordFilename), p660);
final Map<Path, Path> volumes = singletonMap(tempDir, Paths.get("/run/secrets")); final Map<Path, Path> volumes = singletonMap(tempDir, Paths.get("/run/secrets"));
@ -377,7 +347,7 @@ public class DockerTests extends PackagingTestCase {
assertThat( assertThat(
dockerLogs.stderr, dockerLogs.stderr,
containsString( containsString(
"ERROR: File /run/secrets/" + optionsFilename + " from ES_JAVA_OPTS_FILE must have " + "file permissions 400 or 600" "ERROR: File /run/secrets/" + passwordFilename + " from ELASTIC_PASSWORD_FILE must have file permissions 400 or 600"
) )
); );
} }
@ -386,7 +356,7 @@ public class DockerTests extends PackagingTestCase {
* Check that when verifying the file permissions of _FILE environment variables, symlinks * Check that when verifying the file permissions of _FILE environment variables, symlinks
* are followed, and that invalid target permissions are detected. * are followed, and that invalid target permissions are detected.
*/ */
public void test085SymlinkToFileWithInvalidPermissionsIsRejected() throws Exception { public void test084SymlinkToFileWithInvalidPermissionsIsRejected() throws Exception {
// Test relies on configuring security // Test relies on configuring security
assumeTrue(distribution.isDefault()); assumeTrue(distribution.isDefault());
// Test relies on symlinks // Test relies on symlinks
@ -432,7 +402,7 @@ public class DockerTests extends PackagingTestCase {
* Check that environment variables are translated to -E options even for commands invoked under * Check that environment variables are translated to -E options even for commands invoked under
* `docker exec`, where the Docker image's entrypoint is not executed. * `docker exec`, where the Docker image's entrypoint is not executed.
*/ */
public void test086EnvironmentVariablesAreRespectedUnderDockerExec() { public void test085EnvironmentVariablesAreRespectedUnderDockerExec() {
// This test relies on a CLI tool attempting to connect to Elasticsearch, and the // This test relies on a CLI tool attempting to connect to Elasticsearch, and the
// tool in question is only in the default distribution. // tool in question is only in the default distribution.
assumeTrue(distribution.isDefault()); assumeTrue(distribution.isDefault());

View File

@ -49,6 +49,7 @@ import static org.elasticsearch.packaging.util.Docker.waitForElasticsearch;
import static org.elasticsearch.packaging.util.Docker.waitForPathToExist; import static org.elasticsearch.packaging.util.Docker.waitForPathToExist;
import static org.elasticsearch.packaging.util.FileMatcher.Fileness.File; import static org.elasticsearch.packaging.util.FileMatcher.Fileness.File;
import static org.elasticsearch.packaging.util.FileMatcher.file; import static org.elasticsearch.packaging.util.FileMatcher.file;
import static org.elasticsearch.packaging.util.FileMatcher.p600;
import static org.elasticsearch.packaging.util.FileMatcher.p660; import static org.elasticsearch.packaging.util.FileMatcher.p660;
import static org.elasticsearch.packaging.util.FileUtils.getTempDir; import static org.elasticsearch.packaging.util.FileUtils.getTempDir;
import static org.elasticsearch.packaging.util.FileUtils.rm; import static org.elasticsearch.packaging.util.FileUtils.rm;
@ -309,11 +310,51 @@ public class KeystoreManagementTests extends PackagingTestCase {
ServerUtils.runElasticsearchTests(); ServerUtils.runElasticsearchTests();
} }
/**
* Check that we can mount a password-protected keystore to a docker image
* and provide a password via a file, pointed at from an environment variable.
*/
public void test61DockerEnvironmentVariablePasswordFromFile() throws Exception {
assumeTrue(distribution().isDocker());
Path tempDir = null;
try {
tempDir = Files.createTempDirectory(getTempDir(), DockerTests.class.getSimpleName());
String password = "password";
String passwordFilename = "password.txt";
Files.write(tempDir.resolve(passwordFilename), (password + "\n").getBytes(StandardCharsets.UTF_8));
Files.setPosixFilePermissions(tempDir.resolve(passwordFilename), p600);
Path dockerKeystore = installation.config("elasticsearch.keystore");
Path localKeystoreFile = getKeystoreFileFromDockerContainer(password, dockerKeystore);
// restart ES with password and mounted keystore
Map<Path, Path> volumes = new HashMap<>();
volumes.put(localKeystoreFile, dockerKeystore);
volumes.put(tempDir, Paths.get("/run/secrets"));
Map<String, String> envVars = new HashMap<>();
envVars.put("KEYSTORE_PASSWORD_FILE", "/run/secrets/" + passwordFilename);
runContainer(distribution(), volumes, envVars);
waitForElasticsearch(installation);
ServerUtils.runElasticsearchTests();
}
finally {
if (tempDir != null) {
rm(tempDir);
}
}
}
/** /**
* Check that if we provide the wrong password for a mounted and password-protected * Check that if we provide the wrong password for a mounted and password-protected
* keystore, Elasticsearch doesn't start. * keystore, Elasticsearch doesn't start.
*/ */
public void test61DockerEnvironmentVariableBadPassword() throws Exception { public void test62DockerEnvironmentVariableBadPassword() throws Exception {
assumeTrue(distribution().isDocker()); assumeTrue(distribution().isDocker());
String password = "password"; String password = "password";
Path dockerKeystore = installation.config("elasticsearch.keystore"); Path dockerKeystore = installation.config("elasticsearch.keystore");