diff options
author | Tor Brede Vekterli <vekterli@yahooinc.com> | 2022-10-31 11:41:27 +0100 |
---|---|---|
committer | Tor Brede Vekterli <vekterli@yahooinc.com> | 2022-10-31 12:09:27 +0100 |
commit | e1df7cc47c9ec0dedb17a5bc0ad2a14967ddd6a0 (patch) | |
tree | d4598676adf654e61780ba99905877dc4b76cfb0 /node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoredumpHandlerTest.java | |
parent | 80cec9a4412e65a756f8819c648071e4f15ca595 (diff) |
Add encryption capabilities to core dump handler
Once wired in (not currently the case), a Supplier of non-null
`SecretSharedKey` instances will trigger:
1. Wrapping the output stream with an encrypting output stream
using the secret component of the supplied key. Zstd compression
is handled on the input stream, so this should transparently
encrypt compressed data. To disambiguate, encrypted core dumps
are suffixed with an additional `.enc` file extension.
2. Emitting a public decryption token as part of the metadata
using the shared component of the supplied key.
Diffstat (limited to 'node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoredumpHandlerTest.java')
-rw-r--r-- | node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoredumpHandlerTest.java | 64 |
1 files changed, 54 insertions, 10 deletions
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoredumpHandlerTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoredumpHandlerTest.java index 082a1f3de58..1a1edbd597a 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoredumpHandlerTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoredumpHandlerTest.java @@ -1,6 +1,8 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.node.admin.maintenance.coredump; +import com.yahoo.security.SealedSharedKey; +import com.yahoo.security.SecretSharedKey; import com.yahoo.test.ManualClock; import com.yahoo.vespa.hosted.node.admin.container.metrics.DimensionMetrics; import com.yahoo.vespa.hosted.node.admin.container.metrics.Metrics; @@ -12,7 +14,9 @@ import com.yahoo.vespa.test.file.TestFileSystem; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import javax.crypto.spec.SecretKeySpec; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; @@ -53,8 +57,11 @@ public class CoredumpHandlerTest { private final ManualClock clock = new ManualClock(); @SuppressWarnings("unchecked") private final Supplier<String> coredumpIdSupplier = mock(Supplier.class); + @SuppressWarnings("unchecked") + private final Supplier<SecretSharedKey> secretSharedKeySupplier = mock(Supplier.class); private final CoredumpHandler coredumpHandler = new CoredumpHandler(coreCollector, coredumpReporter, - containerCrashPath.pathInContainer(), doneCoredumpsPath, metrics, clock, coredumpIdSupplier); + containerCrashPath.pathInContainer(), doneCoredumpsPath, metrics, clock, coredumpIdSupplier, + secretSharedKeySupplier); @Test @@ -136,8 +143,7 @@ public class CoredumpHandlerTest { verify(coredumpIdSupplier, times(1)).get(); } - @Test - void get_metadata_test() throws IOException { + void do_get_metadata_test(Optional<String> decryptionToken) throws IOException { Map<String, Object> metadata = new HashMap<>(); metadata.put("bin_path", "/bin/bash"); metadata.put("backtrace", List.of("call 1", "function 2", "something something")); @@ -156,6 +162,7 @@ public class CoredumpHandlerTest { "\"bin_path\":\"/bin/bash\"," + "\"coredump_path\":\"/home/docker/dumps/container-123/id-123/dump_core.456\"," + "\"docker_image\":\"vespa/ci:6.48.4\"" + + decryptionToken.map(",\"decryption_token\":\"%s\""::formatted).orElse("") + "}}"; @@ -165,33 +172,44 @@ public class CoredumpHandlerTest { when(coreCollector.collect(eq(context), eq(coredumpDirectory.resolve("dump_core.456")))) .thenReturn(metadata); - assertEquals(expectedMetadataStr, coredumpHandler.getMetadata(context, coredumpDirectory, () -> attributes)); + assertEquals(expectedMetadataStr, coredumpHandler.getMetadata(context, coredumpDirectory, () -> attributes, decryptionToken)); verify(coreCollector, times(1)).collect(any(), any()); // Calling it again will simply read the previously generated metadata from disk - assertEquals(expectedMetadataStr, coredumpHandler.getMetadata(context, coredumpDirectory, () -> attributes)); + assertEquals(expectedMetadataStr, coredumpHandler.getMetadata(context, coredumpDirectory, () -> attributes, decryptionToken)); verify(coreCollector, times(1)).collect(any(), any()); } @Test + void get_metadata_test_without_encryption() throws IOException { + do_get_metadata_test(Optional.empty()); // No token in metadata + } + + @Test + void get_metadata_test_with_encryption() throws IOException { + when(secretSharedKeySupplier.get()).thenReturn(makeFixedSecretSharedKey()); + do_get_metadata_test(Optional.of("AVeryCoolToken")); + } + + @Test void cant_get_metadata_if_no_core_file() { assertThrows(IllegalStateException.class, () -> { - coredumpHandler.getMetadata(context, context.paths().of("/fake/path"), Map::of); + coredumpHandler.getMetadata(context, context.paths().of("/fake/path"), Map::of, Optional.empty()); }); } @Test - void fails_to_get_core_file_if_only_compressed() { + void fails_to_get_core_file_if_only_compressed_or_encrypted() { assertThrows(IllegalStateException.class, () -> { ContainerPath coredumpDirectory = context.paths().of("/path/to/coredump/proccessing/id-123"); Files.createDirectories(coredumpDirectory); Files.createFile(coredumpDirectory.resolve("dump_bash.core.431.zstd")); + Files.createFile(coredumpDirectory.resolve("dump_bash.core.543.zstd.enc")); coredumpHandler.findCoredumpFileInProcessingDirectory(coredumpDirectory); }); } - @Test - void process_single_coredump_test() throws IOException { + void do_process_single_coredump_test(String expectedCoreFileName) throws IOException { ContainerPath coredumpDirectory = context.paths().of("/path/to/coredump/proccessing/id-123"); Files.createDirectories(coredumpDirectory); Files.write(coredumpDirectory.resolve("metadata.json"), "metadata".getBytes()); @@ -203,7 +221,18 @@ public class CoredumpHandlerTest { verify(coredumpReporter, times(1)).reportCoredump(eq("id-123"), eq("metadata")); assertFalse(Files.exists(coredumpDirectory)); assertFolderContents(doneCoredumpsPath.resolve("container-123"), "id-123"); - assertFolderContents(doneCoredumpsPath.resolve("container-123").resolve("id-123"), "metadata.json", "dump_bash.core.431.zstd"); + assertFolderContents(doneCoredumpsPath.resolve("container-123").resolve("id-123"), "metadata.json", expectedCoreFileName); + } + + @Test + void process_single_coredump_test_without_encryption() throws IOException { + do_process_single_coredump_test("dump_bash.core.431.zstd"); + } + + @Test + void process_single_coredump_test_with_encryption() throws IOException { + when(secretSharedKeySupplier.get()).thenReturn(makeFixedSecretSharedKey()); + do_process_single_coredump_test("dump_bash.core.431.zstd.enc"); } @Test @@ -248,4 +277,19 @@ public class CoredumpHandlerTest { Files.createFile(path), FileTime.from(clock.instant().minus(age)))); } + + private static byte[] bytesOf(String str) { + return str.getBytes(StandardCharsets.UTF_8); + } + + private static SecretSharedKey makeFixedSecretSharedKey() { + byte[] keyBytes = bytesOf("very secret yes!"); // 128 bits + var secretKey = new SecretKeySpec(keyBytes, "AES"); + int keyId = 123; + // We don't parse any of these fields in the test, so just use dummy contents. + byte[] enc = bytesOf("hello world"); + byte[] ciphertext = bytesOf("imaginary ciphertext"); + return new SecretSharedKey(secretKey, new SealedSharedKey(keyId, enc, ciphertext)); + } + } |