diff --git a/CHANGELOG.md b/CHANGELOG.md index f8d61d8..99b6f5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ ### Deprecations * `read...Credentials()` methods for specific database mounts (#92) +### Features +* Support PEM certificate string from `VAULT_CACERT` environment variable (#93) + ### Dependencies * Updated Jackson to 2.18.3 (#90) diff --git a/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnectorBuilder.java b/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnectorBuilder.java index 534cf62..a4329e5 100644 --- a/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnectorBuilder.java +++ b/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnectorBuilder.java @@ -20,11 +20,13 @@ import de.stklcode.jvault.connector.exception.ConnectionException; import de.stklcode.jvault.connector.exception.TlsException; import de.stklcode.jvault.connector.exception.VaultConnectorException; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -326,7 +328,11 @@ public final class HTTPVaultConnectorBuilder { /* Parse certificate, if set */ if (System.getenv(ENV_VAULT_CACERT) != null && !System.getenv(ENV_VAULT_CACERT).isBlank()) { - return withTrustedCA(Paths.get(System.getenv(ENV_VAULT_CACERT))); + X509Certificate cert = certificateFromString(System.getenv(ENV_VAULT_CACERT)); + if (cert == null) { + cert = certificateFromFile(Paths.get(System.getenv(ENV_VAULT_CACERT))); + } + return withTrustedCA(cert); } return this; } @@ -398,6 +404,28 @@ public final class HTTPVaultConnectorBuilder { return con; } + /** + * Read given certificate file to X.509 certificate. + * + * @param cert Certificate string (optionally PEM) + * @return X.509 Certificate object if parseable, else {@code null} + * @throws TlsException on error + * @since 1.5.0 + */ + private X509Certificate certificateFromString(final String cert) throws TlsException { + // Check if PEM header is present in given string + if (cert.contains("-BEGIN ") && cert.contains("-END")) { + try (var is = new ByteArrayInputStream(cert.getBytes(StandardCharsets.UTF_8))) { + return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(is); + } catch (IOException | CertificateException e) { + throw new TlsException("Unable to read certificate.", e); + } + } + + // Not am PEM string, skip + return null; + } + /** * Read given certificate file to X.509 certificate. * diff --git a/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorBuilderTest.java b/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorBuilderTest.java index d31ba8e..1b030e6 100644 --- a/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorBuilderTest.java +++ b/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorBuilderTest.java @@ -25,7 +25,10 @@ import org.junit.jupiter.api.io.TempDir; import java.io.File; import java.lang.reflect.Field; import java.net.URISyntaxException; +import java.nio.file.Files; import java.nio.file.NoSuchFileException; +import java.nio.file.Paths; +import java.util.concurrent.atomic.AtomicReference; import static com.github.stefanbirkner.systemlambda.SystemLambda.withEnvironmentVariable; import static org.junit.jupiter.api.Assertions.*; @@ -128,19 +131,6 @@ class HTTPVaultConnectorBuilderTest { return null; }); - // Provide CA certificate. - String vaultCacert = tempDir.toString() + "/doesnotexist"; - withVaultEnv(VAULT_ADDR, vaultCacert, VAULT_MAX_RETRIES.toString(), null).execute(() -> { - TlsException e = assertThrows( - TlsException.class, - () -> HTTPVaultConnector.builder().fromEnv(), - "Creation with unknown cert path failed" - ); - assertEquals(vaultCacert, assertInstanceOf(NoSuchFileException.class, e.getCause()).getFile()); - - return null; - }); - // Automatic authentication. withVaultEnv(VAULT_ADDR, null, VAULT_MAX_RETRIES.toString(), VAULT_TOKEN).execute(() -> { HTTPVaultConnectorBuilder builder = assertDoesNotThrow( @@ -164,6 +154,59 @@ class HTTPVaultConnectorBuilderTest { }); } + /** + * Test CA certificate handling from environment variables + */ + @Test + void testCertificateFromEnv() throws Exception { + // From direct PEM content + String pem = Files.readString(Paths.get(getClass().getResource("/tls/ca.pem").toURI())); + AtomicReference certFromPem = new AtomicReference<>(); + withVaultEnv(VAULT_ADDR, pem, null, null).execute(() -> { + HTTPVaultConnectorBuilder builder = assertDoesNotThrow( + () -> HTTPVaultConnector.builder().fromEnv(), + "Builder with PEM certificate from environment failed" + ); + HTTPVaultConnector connector = builder.build(); + + certFromPem.set(getRequestHelperPrivate(connector, "trustedCaCert")); + assertNotNull(certFromPem.get(), "Trusted CA cert from PEM not set"); + + return null; + }); + + // From file path + String file = Paths.get(getClass().getResource("/tls/ca.pem").toURI()).toString(); + AtomicReference certFromFile = new AtomicReference<>(); + withVaultEnv(VAULT_ADDR, file, null, null).execute(() -> { + HTTPVaultConnectorBuilder builder = assertDoesNotThrow( + () -> HTTPVaultConnector.builder().fromEnv(), + "Builder with certificate path from environment failed" + ); + HTTPVaultConnector connector = builder.build(); + + certFromFile.set(getRequestHelperPrivate(connector, "trustedCaCert")); + assertNotNull(certFromFile.get(), "Trusted CA cert from file not set"); + + return null; + }); + + assertEquals(certFromPem.get(), certFromFile.get(), "Certificates from PEM and file should be equal"); + + // Non-existing path CA certificate path + String doesNotExist = tempDir.toString() + "/doesnotexist"; + withVaultEnv(VAULT_ADDR, doesNotExist, VAULT_MAX_RETRIES.toString(), null).execute(() -> { + TlsException e = assertThrows( + TlsException.class, + () -> HTTPVaultConnector.builder().fromEnv(), + "Creation with unknown cert path failed" + ); + assertEquals(doesNotExist, assertInstanceOf(NoSuchFileException.class, e.getCause()).getFile()); + + return null; + }); + } + private SystemLambda.WithEnvironmentVariables withVaultEnv(String vaultAddr, String vaultCacert, String vaultMaxRetries, String vaultToken) { return withEnvironmentVariable("VAULT_ADDR", vaultAddr) .and("VAULT_CACERT", vaultCacert)