Skip to content

Commit 3d7cf1f

Browse files
Test password-protected keystore with Docker (#50803)
This commit adds two tests for the case where we mount a password-protected keystore into a Docker container and provide a password via a Docker environment variable. We also fix a logging bug where we were logging the identifier for an array of strings rather than the contents of that array.
1 parent 1c656f2 commit 3d7cf1f

File tree

3 files changed

+91
-1
lines changed

3 files changed

+91
-1
lines changed

qa/os/src/test/java/org/elasticsearch/packaging/test/KeystoreManagementTests.java

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,25 @@
2828
import org.elasticsearch.packaging.util.Shell;
2929
import org.junit.Ignore;
3030

31+
import java.io.IOException;
3132
import java.nio.charset.StandardCharsets;
3233
import java.nio.file.Files;
3334
import java.nio.file.Path;
3435
import java.nio.file.StandardOpenOption;
36+
import java.util.Map;
3537

3638
import static org.elasticsearch.packaging.util.Archives.ARCHIVE_OWNER;
3739
import static org.elasticsearch.packaging.util.Archives.installArchive;
3840
import static org.elasticsearch.packaging.util.Archives.verifyArchiveInstallation;
3941
import static org.elasticsearch.packaging.util.Docker.assertPermissionsAndOwnership;
42+
import static org.elasticsearch.packaging.util.Docker.runContainer;
43+
import static org.elasticsearch.packaging.util.Docker.runContainerExpectingFailure;
44+
import static org.elasticsearch.packaging.util.Docker.waitForElasticsearch;
4045
import static org.elasticsearch.packaging.util.Docker.waitForPathToExist;
4146
import static org.elasticsearch.packaging.util.FileMatcher.Fileness.File;
4247
import static org.elasticsearch.packaging.util.FileMatcher.file;
4348
import static org.elasticsearch.packaging.util.FileMatcher.p660;
49+
import static org.elasticsearch.packaging.util.FileUtils.getTempDir;
4450
import static org.elasticsearch.packaging.util.FileUtils.rm;
4551
import static org.elasticsearch.packaging.util.Packages.assertInstalled;
4652
import static org.elasticsearch.packaging.util.Packages.assertRemoved;
@@ -253,6 +259,83 @@ public void test51WrongKeystorePasswordFromFile() throws Exception {
253259
}
254260
}
255261

262+
/**
263+
* Check that we can mount a password-protected keystore to a docker image
264+
* and provide a password via an environment variable.
265+
*/
266+
public void test60DockerEnvironmentVariablePassword() throws Exception {
267+
assumeTrue(distribution().isDocker());
268+
String password = "password";
269+
Path dockerKeystore = installation.config("elasticsearch.keystore");
270+
271+
Path localKeystoreFile = getKeystoreFileFromDockerContainer(password, dockerKeystore);
272+
273+
// restart ES with password and mounted keystore
274+
Map<Path, Path> volumes = Map.of(localKeystoreFile, dockerKeystore);
275+
Map<String, String> envVars = Map.of("KEYSTORE_PASSWORD", password);
276+
runContainer(distribution(), volumes, envVars);
277+
waitForElasticsearch(installation);
278+
ServerUtils.runElasticsearchTests();
279+
}
280+
281+
/**
282+
* Check that if we provide the wrong password for a mounted and password-protected
283+
* keystore, Elasticsearch doesn't start.
284+
*/
285+
public void test61DockerEnvironmentVariableBadPassword() throws Exception {
286+
assumeTrue(distribution().isDocker());
287+
String password = "password";
288+
Path dockerKeystore = installation.config("elasticsearch.keystore");
289+
290+
Path localKeystoreFile = getKeystoreFileFromDockerContainer(password, dockerKeystore);
291+
292+
// restart ES with password and mounted keystore
293+
Map<Path, Path> volumes = Map.of(localKeystoreFile, dockerKeystore);
294+
Map<String, String> envVars = Map.of("KEYSTORE_PASSWORD", "wrong");
295+
Shell.Result r = runContainerExpectingFailure(distribution(), volumes, envVars);
296+
assertThat(r.stderr, containsString(PASSWORD_ERROR_MESSAGE));
297+
}
298+
299+
/**
300+
* In the Docker context, it's a little bit tricky to get a password-protected
301+
* keystore. All of the utilities we'd want to use are on the Docker image.
302+
* This method mounts a temporary directory to a Docker container, password-protects
303+
* the keystore, and then returns the path of the file that appears in the
304+
* mounted directory (now accessible from the local filesystem).
305+
*/
306+
private Path getKeystoreFileFromDockerContainer(String password, Path dockerKeystore) throws IOException {
307+
// Mount a temporary directory for copying the keystore
308+
Path dockerTemp = Path.of("/usr/tmp/keystore-tmp");
309+
Path tempDirectory = Files.createTempDirectory(getTempDir(), KeystoreManagementTests.class.getSimpleName());
310+
Map<Path, Path> volumes = Map.of(tempDirectory, dockerTemp);
311+
312+
// It's very tricky to properly quote a pipeline that you're passing to
313+
// a docker exec command, so we're just going to put a small script in the
314+
// temp folder.
315+
String setPasswordScript = "echo \"" + password + "\n" + password
316+
+ "\n\" | " + installation.executables().keystoreTool.toString() + " passwd";
317+
Files.writeString(tempDirectory.resolve("set-pass.sh"), setPasswordScript);
318+
319+
runContainer(distribution(), volumes, null);
320+
try {
321+
waitForPathToExist(dockerTemp);
322+
waitForPathToExist(dockerKeystore);
323+
} catch (InterruptedException e) {
324+
throw new RuntimeException(e);
325+
}
326+
327+
// We need a local shell to put the correct permissions on our mounted directory.
328+
Shell localShell = new Shell();
329+
localShell.run("docker exec --tty " + Docker.getContainerId() + " chown elasticsearch:root " + dockerTemp);
330+
localShell.run("docker exec --tty " + Docker.getContainerId() + " chown elasticsearch:root " + dockerTemp.resolve("set-pass.sh"));
331+
332+
sh.run("bash " + dockerTemp.resolve("set-pass.sh"));
333+
334+
// copy keystore to temp file to make it available to docker host
335+
sh.run("cp " + dockerKeystore + " " + dockerTemp);
336+
return tempDirectory.resolve("elasticsearch.keystore");
337+
}
338+
256339
private void createKeystore() throws Exception {
257340
Path keystore = installation.config("elasticsearch.keystore");
258341
final Installation.Executables bin = installation.executables();

qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,13 @@ private static <E extends Exception> void withLogging(CheckedRunnable<E> r) thro
503503
}
504504
}
505505

506+
/**
507+
* @return The ID of the container that this class will be operating on.
508+
*/
509+
public static String getContainerId() {
510+
return containerId;
511+
}
512+
506513
public static JsonNode getJson(String path) throws IOException {
507514
final String pluginsResponse = makeRequest(Request.Get("http://localhost:9200/" + path));
508515

qa/os/src/test/java/org/elasticsearch/packaging/util/Shell.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ private Result runScriptIgnoreExitCode(String[] command) {
162162
readFileIfExists(stdErr)
163163
);
164164
throw new IllegalStateException(
165-
"Timed out running shell command: " + command + "\n" +
165+
"Timed out running shell command: " + Arrays.toString(command) + "\n" +
166166
"Result:\n" + result
167167
);
168168
}

0 commit comments

Comments
 (0)