From 43dd63688087d8f0199f86b255e1bd7d5c947874 Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Tue, 13 Jul 2021 00:16:53 -0700 Subject: [PATCH] =?UTF-8?q?Add=20support=20for=20Vault=E2=80=99s=20PKI=20s?= =?UTF-8?q?ecret=20engine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Supports all endpoints of PKI secret engine, except prvileged endpoints. * Has complete test coverage for all endpoints and options. --- .../java/io/quarkus/vault/VaultProcessor.java | 6 + .../client/dto/pki/VaultPKICRLRotateData.java | 7 + .../dto/pki/VaultPKICRLRotateResult.java | 6 + .../dto/pki/VaultPKICertificateData.java | 7 + .../dto/pki/VaultPKICertificateListData.java | 9 + .../pki/VaultPKICertificateListResult.java | 6 + .../dto/pki/VaultPKICertificateResult.java | 6 + .../client/dto/pki/VaultPKIConfigCABody.java | 12 + .../client/dto/pki/VaultPKIConfigCRLBody.java | 4 + .../client/dto/pki/VaultPKIConfigCRLData.java | 11 + .../dto/pki/VaultPKIConfigCRLResult.java | 6 + .../dto/pki/VaultPKIConfigURLsBody.java | 4 + .../dto/pki/VaultPKIConfigURLsData.java | 20 + .../dto/pki/VaultPKIConfigURLsResult.java | 6 + .../pki/VaultPKIGenerateCertificateBody.java | 37 + .../pki/VaultPKIGenerateCertificateData.java | 28 + .../VaultPKIGenerateCertificateResult.java | 6 + .../VaultPKIGenerateIntermediateCSRBody.java | 64 ++ .../VaultPKIGenerateIntermediateCSRData.java | 17 + ...VaultPKIGenerateIntermediateCSRResult.java | 6 + .../dto/pki/VaultPKIGenerateRootBody.java | 73 ++ .../dto/pki/VaultPKIGenerateRootData.java | 23 + .../dto/pki/VaultPKIGenerateRootResult.java | 6 + .../pki/VaultPKIRevokeCertificateBody.java | 12 + .../pki/VaultPKIRevokeCertificateData.java | 14 + .../pki/VaultPKIRevokeCertificateResult.java | 6 + .../dto/pki/VaultPKIRoleOptionsData.java | 123 +++ .../dto/pki/VaultPKIRoleReadResult.java | 6 + .../dto/pki/VaultPKIRoleUpdateBody.java | 4 + .../client/dto/pki/VaultPKIRolesListData.java | 9 + .../dto/pki/VaultPKIRolesListResult.java | 6 + .../VaultPKISetSignedIntermediateCABody.java | 9 + .../VaultPKISignCertificateRequestBody.java | 36 + .../VaultPKISignCertificateRequestData.java | 22 + .../VaultPKISignCertificateRequestResult.java | 6 + .../pki/VaultPKISignIntermediateCABody.java | 69 ++ .../client/dto/pki/VaultPKITidyBody.java | 18 + .../quarkus/vault/VaultPKISecretEngine.java | 218 +++++ .../vault/VaultPKISecretEngineFactory.java | 19 + .../pki/CertificateExtendedKeyUsage.java | 17 + .../quarkus/vault/pki/CertificateKeyType.java | 6 + .../vault/pki/CertificateKeyUsage.java | 13 + .../quarkus/vault/pki/ConfigCRLOptions.java | 18 + .../quarkus/vault/pki/ConfigURLsOptions.java | 25 + .../vault/pki/GenerateCertificateOptions.java | 52 ++ .../pki/GenerateIntermediateCSROptions.java | 102 +++ .../vault/pki/GenerateRootOptions.java | 119 +++ .../vault/pki/GeneratedCertificate.java | 41 + .../pki/GeneratedIntermediateCSRResult.java | 27 + .../vault/pki/GeneratedRootCertificate.java | 35 + .../io/quarkus/vault/pki/RoleOptions.java | 222 +++++ .../vault/pki/SignIntermediateCAOptions.java | 115 +++ .../quarkus/vault/pki/SignedCertificate.java | 31 + .../io/quarkus/vault/pki/TidyOptions.java | 27 + .../vault/runtime/VaultPKIManager.java | 615 +++++++++++++ .../vault/runtime/VaultPKIManagerFactory.java | 23 + .../VaultInternalPKISecretEngine.java | 155 ++++ .../java/io/quarkus/vault/VaultPKIITCase.java | 861 ++++++++++++++++++ .../application-vault-pki.properties | 13 + .../vault/test/VaultTestExtension.java | 7 + 60 files changed, 3471 insertions(+) create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKICRLRotateData.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKICRLRotateResult.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKICertificateData.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKICertificateListData.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKICertificateListResult.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKICertificateResult.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigCABody.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigCRLBody.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigCRLData.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigCRLResult.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigURLsBody.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigURLsData.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigURLsResult.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateCertificateBody.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateCertificateData.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateCertificateResult.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateIntermediateCSRBody.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateIntermediateCSRData.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateIntermediateCSRResult.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateRootBody.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateRootData.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateRootResult.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRevokeCertificateBody.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRevokeCertificateData.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRevokeCertificateResult.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRoleOptionsData.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRoleReadResult.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRoleUpdateBody.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRolesListData.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRolesListResult.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKISetSignedIntermediateCABody.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKISignCertificateRequestBody.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKISignCertificateRequestData.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKISignCertificateRequestResult.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKISignIntermediateCABody.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKITidyBody.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultPKISecretEngine.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultPKISecretEngineFactory.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CertificateExtendedKeyUsage.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CertificateKeyType.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CertificateKeyUsage.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/ConfigCRLOptions.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/ConfigURLsOptions.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GenerateCertificateOptions.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GenerateIntermediateCSROptions.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GenerateRootOptions.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GeneratedCertificate.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GeneratedIntermediateCSRResult.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GeneratedRootCertificate.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/RoleOptions.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/SignIntermediateCAOptions.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/SignedCertificate.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/TidyOptions.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/VaultPKIManager.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/VaultPKIManagerFactory.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/secretengine/VaultInternalPKISecretEngine.java create mode 100644 integration-tests/vault/src/test/java/io/quarkus/vault/VaultPKIITCase.java create mode 100644 integration-tests/vault/src/test/resources/application-vault-pki.properties diff --git a/extensions/vault/deployment/src/main/java/io/quarkus/vault/VaultProcessor.java b/extensions/vault/deployment/src/main/java/io/quarkus/vault/VaultProcessor.java index a4415fadae92f1..bef92cf0ee02fd 100644 --- a/extensions/vault/deployment/src/main/java/io/quarkus/vault/VaultProcessor.java +++ b/extensions/vault/deployment/src/main/java/io/quarkus/vault/VaultProcessor.java @@ -24,6 +24,8 @@ import io.quarkus.vault.runtime.VaultDbManager; import io.quarkus.vault.runtime.VaultKubernetesAuthManager; import io.quarkus.vault.runtime.VaultKvManager; +import io.quarkus.vault.runtime.VaultPKIManager; +import io.quarkus.vault.runtime.VaultPKIManagerFactory; import io.quarkus.vault.runtime.VaultRecorder; import io.quarkus.vault.runtime.VaultSystemBackendManager; import io.quarkus.vault.runtime.VaultTOTPManager; @@ -38,6 +40,7 @@ import io.quarkus.vault.runtime.client.secretengine.VaultInternalDatabaseSecretEngine; import io.quarkus.vault.runtime.client.secretengine.VaultInternalKvV1SecretEngine; import io.quarkus.vault.runtime.client.secretengine.VaultInternalKvV2SecretEngine; +import io.quarkus.vault.runtime.client.secretengine.VaultInternalPKISecretEngine; import io.quarkus.vault.runtime.client.secretengine.VaultInternalTOPTSecretEngine; import io.quarkus.vault.runtime.client.secretengine.VaultInternalTransitSecretEngine; import io.quarkus.vault.runtime.config.VaultBootstrapConfig; @@ -87,6 +90,8 @@ AdditionalBeanBuildItem registerAdditionalBeans() { .addBeanClass(VaultDbManager.class) .addBeanClass(VertxVaultClient.class) .addBeanClass(VaultConfigHolder.class) + .addBeanClass(VaultPKIManager.class) + .addBeanClass(VaultPKIManagerFactory.class) .addBeanClass(VaultInternalKvV1SecretEngine.class) .addBeanClass(VaultInternalKvV2SecretEngine.class) .addBeanClass(VaultInternalTransitSecretEngine.class) @@ -97,6 +102,7 @@ AdditionalBeanBuildItem registerAdditionalBeans() { .addBeanClass(VaultInternalTokenAuthMethod.class) .addBeanClass(VaultInternalUserpassAuthMethod.class) .addBeanClass(VaultInternalDatabaseSecretEngine.class) + .addBeanClass(VaultInternalPKISecretEngine.class) .build(); } diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKICRLRotateData.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKICRLRotateData.java new file mode 100644 index 00000000000000..39df87ee6cf823 --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKICRLRotateData.java @@ -0,0 +1,7 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import io.quarkus.vault.runtime.client.dto.VaultModel; + +public class VaultPKICRLRotateData implements VaultModel { + public boolean success; +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKICRLRotateResult.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKICRLRotateResult.java new file mode 100644 index 00000000000000..aa8808434b5e74 --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKICRLRotateResult.java @@ -0,0 +1,6 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import io.quarkus.vault.runtime.client.dto.AbstractVaultDTO; + +public class VaultPKICRLRotateResult extends AbstractVaultDTO { +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKICertificateData.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKICertificateData.java new file mode 100644 index 00000000000000..b2370836df0c6a --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKICertificateData.java @@ -0,0 +1,7 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import io.quarkus.vault.runtime.client.dto.VaultModel; + +public class VaultPKICertificateData implements VaultModel { + public String certificate; +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKICertificateListData.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKICertificateListData.java new file mode 100644 index 00000000000000..990a776cbfbbca --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKICertificateListData.java @@ -0,0 +1,9 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import java.util.List; + +import io.quarkus.vault.runtime.client.dto.VaultModel; + +public class VaultPKICertificateListData implements VaultModel { + public List keys; +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKICertificateListResult.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKICertificateListResult.java new file mode 100644 index 00000000000000..b79fa94f24c97a --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKICertificateListResult.java @@ -0,0 +1,6 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import io.quarkus.vault.runtime.client.dto.AbstractVaultDTO; + +public class VaultPKICertificateListResult extends AbstractVaultDTO { +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKICertificateResult.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKICertificateResult.java new file mode 100644 index 00000000000000..9a4f24edebb32c --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKICertificateResult.java @@ -0,0 +1,6 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import io.quarkus.vault.runtime.client.dto.AbstractVaultDTO; + +public class VaultPKICertificateResult extends AbstractVaultDTO { +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigCABody.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigCABody.java new file mode 100644 index 00000000000000..d7ec4fb79cc2a8 --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigCABody.java @@ -0,0 +1,12 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.quarkus.vault.runtime.client.dto.VaultModel; + +public class VaultPKIConfigCABody implements VaultModel { + + @JsonProperty("pem_bundle") + public String pemBundle; + +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigCRLBody.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigCRLBody.java new file mode 100644 index 00000000000000..211694c3a13892 --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigCRLBody.java @@ -0,0 +1,4 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +public class VaultPKIConfigCRLBody extends VaultPKIConfigCRLData { +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigCRLData.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigCRLData.java new file mode 100644 index 00000000000000..2feeb76e4f5cfa --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigCRLData.java @@ -0,0 +1,11 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import io.quarkus.vault.runtime.client.dto.VaultModel; + +public class VaultPKIConfigCRLData implements VaultModel { + + public String expiry; + + public Boolean disable; + +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigCRLResult.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigCRLResult.java new file mode 100644 index 00000000000000..c14b9e86d83b70 --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigCRLResult.java @@ -0,0 +1,6 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import io.quarkus.vault.runtime.client.dto.AbstractVaultDTO; + +public class VaultPKIConfigCRLResult extends AbstractVaultDTO { +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigURLsBody.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigURLsBody.java new file mode 100644 index 00000000000000..2737b24d85ca2e --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigURLsBody.java @@ -0,0 +1,4 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +public class VaultPKIConfigURLsBody extends VaultPKIConfigURLsData { +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigURLsData.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigURLsData.java new file mode 100644 index 00000000000000..8a68a2bdb13e06 --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigURLsData.java @@ -0,0 +1,20 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.quarkus.vault.runtime.client.dto.VaultModel; + +public class VaultPKIConfigURLsData implements VaultModel { + + @JsonProperty("issuing_certificates") + public List issuingCertificates; + + @JsonProperty("crl_distribution_points") + public List crlDistributionPoints; + + @JsonProperty("ocsp_servers") + public List ocspServers; + +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigURLsResult.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigURLsResult.java new file mode 100644 index 00000000000000..37b31f267569b4 --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigURLsResult.java @@ -0,0 +1,6 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import io.quarkus.vault.runtime.client.dto.AbstractVaultDTO; + +public class VaultPKIConfigURLsResult extends AbstractVaultDTO { +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateCertificateBody.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateCertificateBody.java new file mode 100644 index 00000000000000..0f3411546ec685 --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateCertificateBody.java @@ -0,0 +1,37 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.quarkus.vault.runtime.client.dto.VaultModel; + +public class VaultPKIGenerateCertificateBody implements VaultModel { + + @JsonProperty("common_name") + public String subjectCommonName; + + @JsonProperty("alt_names") + public String subjectAlternativeNames; + + @JsonProperty("ip_sans") + public String ipSubjectAlternativeNames; + + @JsonProperty("uri_sans") + public String uriSubjectAlternativeNames; + + @JsonProperty("other_sans") + public List otherSubjectAlternativeNames; + + @JsonProperty("ttl") + public String timeToLive; + + public String format = "pem"; + + @JsonProperty("private_key_format") + public String privateKeyFormat = "pkcs8"; + + @JsonProperty("exclude_cn_from_sans") + public Boolean excludeCommonNameFromSubjectAlternativeNames; + +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateCertificateData.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateCertificateData.java new file mode 100644 index 00000000000000..b868d5da63b3a0 --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateCertificateData.java @@ -0,0 +1,28 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.quarkus.vault.runtime.client.dto.VaultModel; + +public class VaultPKIGenerateCertificateData implements VaultModel { + + public String certificate; + + @JsonProperty("issuing_ca") + public String issuingCA; + + @JsonProperty("ca_chain") + public List caChain; + + @JsonProperty("private_key") + public String privateKey; + + @JsonProperty("private_key_type") + public String privateKeyType; + + @JsonProperty("serial_number") + public String serialNumber; + +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateCertificateResult.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateCertificateResult.java new file mode 100644 index 00000000000000..c48f80a5143965 --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateCertificateResult.java @@ -0,0 +1,6 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import io.quarkus.vault.runtime.client.dto.AbstractVaultDTO; + +public class VaultPKIGenerateCertificateResult extends AbstractVaultDTO { +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateIntermediateCSRBody.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateIntermediateCSRBody.java new file mode 100644 index 00000000000000..3120a7d74fca63 --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateIntermediateCSRBody.java @@ -0,0 +1,64 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.quarkus.vault.runtime.client.dto.VaultModel; + +public class VaultPKIGenerateIntermediateCSRBody implements VaultModel { + + @JsonProperty("common_name") + public String subjectCommonName; + + @JsonProperty("organization") + public String subjectOrganization; + + @JsonProperty("ou") + public String subjectOrganizationalUnit; + + @JsonProperty("street_address") + public String subjectStreetAddress; + + @JsonProperty("postal_code") + public String subjectPostalCode; + + @JsonProperty("locality") + public String subjectLocality; + + @JsonProperty("province") + public String subjectProvince; + + @JsonProperty("country") + public String subjectCountry; + + @JsonProperty("alt_names") + public String subjectAlternativeNames; + + @JsonProperty("ip_sans") + public String ipSubjectAlternativeNames; + + @JsonProperty("uri_sans") + public String uriSubjectAlternativeNames; + + @JsonProperty("other_sans") + public List otherSubjectAlternativeNames; + + @JsonProperty("serial_number") + public String subjectSerialNumber; + + public String format = "pem"; + + @JsonProperty("private_key_format") + public String privateKeyFormat = "pkcs8"; + + @JsonProperty("key_type") + public String keyType; + + @JsonProperty("key_bits") + public Integer keyBits; + + @JsonProperty("exclude_cn_from_sans") + public Boolean excludeCommonNameFromSubjectAlternativeNames; + +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateIntermediateCSRData.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateIntermediateCSRData.java new file mode 100644 index 00000000000000..f4c5a3cac6755e --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateIntermediateCSRData.java @@ -0,0 +1,17 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.quarkus.vault.runtime.client.dto.VaultModel; + +public class VaultPKIGenerateIntermediateCSRData implements VaultModel { + + public String csr; + + @JsonProperty("private_key") + public String privateKey; + + @JsonProperty("private_key_type") + public String privateKeyType; + +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateIntermediateCSRResult.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateIntermediateCSRResult.java new file mode 100644 index 00000000000000..2795f6e4ff9fc8 --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateIntermediateCSRResult.java @@ -0,0 +1,6 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import io.quarkus.vault.runtime.client.dto.AbstractVaultDTO; + +public class VaultPKIGenerateIntermediateCSRResult extends AbstractVaultDTO { +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateRootBody.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateRootBody.java new file mode 100644 index 00000000000000..ed9b5c6b9677ec --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateRootBody.java @@ -0,0 +1,73 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.quarkus.vault.runtime.client.dto.VaultModel; + +public class VaultPKIGenerateRootBody implements VaultModel { + + @JsonProperty("common_name") + public String subjectCommonName; + + @JsonProperty("organization") + public String subjectOrganization; + + @JsonProperty("ou") + public String subjectOrganizationalUnit; + + @JsonProperty("street_address") + public String subjectStreetAddress; + + @JsonProperty("postal_code") + public String subjectPostalCode; + + @JsonProperty("locality") + public String subjectLocality; + + @JsonProperty("province") + public String subjectProvince; + + @JsonProperty("country") + public String subjectCountry; + + @JsonProperty("alt_names") + public String subjectAlternativeNames; + + @JsonProperty("ip_sans") + public String ipSubjectAlternativeNames; + + @JsonProperty("uri_sans") + public String uriSubjectAlternativeNames; + + @JsonProperty("other_sans") + public List otherSubjectAlternativeNames; + + @JsonProperty("serial_number") + public String subjectSerialNumber; + + @JsonProperty("ttl") + public String timeToLive; + + public String format = "pem"; + + @JsonProperty("private_key_format") + public String privateKeyFormat = "pkcs8"; + + @JsonProperty("key_type") + public String keyType; + + @JsonProperty("key_bits") + public Integer keyBits; + + @JsonProperty("max_path_length") + public Integer maxPathLength; + + @JsonProperty("exclude_cn_from_sans") + public Boolean excludeCommonNameFromSubjectAlternativeNames; + + @JsonProperty("permitted_dns_domains") + public List permittedDnsDomains; + +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateRootData.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateRootData.java new file mode 100644 index 00000000000000..56b36d44efaa7a --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateRootData.java @@ -0,0 +1,23 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.quarkus.vault.runtime.client.dto.VaultModel; + +public class VaultPKIGenerateRootData implements VaultModel { + + public String certificate; + + @JsonProperty("issuing_ca") + public String issuingCA; + + @JsonProperty("serial_number") + public String serialNumber; + + @JsonProperty("private_key") + public String privateKey; + + @JsonProperty("private_key_type") + public String privateKeyType; + +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateRootResult.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateRootResult.java new file mode 100644 index 00000000000000..7e2ca14483744c --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIGenerateRootResult.java @@ -0,0 +1,6 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import io.quarkus.vault.runtime.client.dto.AbstractVaultDTO; + +public class VaultPKIGenerateRootResult extends AbstractVaultDTO { +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRevokeCertificateBody.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRevokeCertificateBody.java new file mode 100644 index 00000000000000..6e6645b3dea7cd --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRevokeCertificateBody.java @@ -0,0 +1,12 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.quarkus.vault.runtime.client.dto.VaultModel; + +public class VaultPKIRevokeCertificateBody implements VaultModel { + + @JsonProperty("serial_number") + public String serialNumber; + +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRevokeCertificateData.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRevokeCertificateData.java new file mode 100644 index 00000000000000..70cd16b9c29d65 --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRevokeCertificateData.java @@ -0,0 +1,14 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import java.time.OffsetDateTime; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.quarkus.vault.runtime.client.dto.VaultModel; + +public class VaultPKIRevokeCertificateData implements VaultModel { + + @JsonProperty("revocation_time") + public OffsetDateTime revocationTime; + +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRevokeCertificateResult.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRevokeCertificateResult.java new file mode 100644 index 00000000000000..bf3416ca543aef --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRevokeCertificateResult.java @@ -0,0 +1,6 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import io.quarkus.vault.runtime.client.dto.AbstractVaultDTO; + +public class VaultPKIRevokeCertificateResult extends AbstractVaultDTO { +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRoleOptionsData.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRoleOptionsData.java new file mode 100644 index 00000000000000..e68abed641969c --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRoleOptionsData.java @@ -0,0 +1,123 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class VaultPKIRoleOptionsData { + + @JsonProperty("ttl") + public String timeToLive; + + @JsonProperty("max_ttl") + public String maxTimeToLive; + + @JsonProperty("allow_localhost") + public Boolean allowLocalhost; + + @JsonProperty("allowed_domains") + public List allowedDomains; + + @JsonProperty("allowed_domains_template") + public Boolean allowTemplatesInAllowedDomains; + + @JsonProperty("allow_bare_domains") + public Boolean allowBareDomains; + + @JsonProperty("allow_subdomains") + public Boolean allowSubdomains; + + @JsonProperty("allow_glob_domains") + public Boolean allowGlobsInAllowedDomains; + + @JsonProperty("allow_any_name") + public Boolean allowAnyName; + + @JsonProperty("enforce_hostnames") + public Boolean enforceHostnames; + + @JsonProperty("allow_ip_sans") + public Boolean allowIpSubjectAlternativeNames; + + @JsonProperty("allowed_uri_sans") + public List allowedUriSubjectAlternativeNames; + + @JsonProperty("allowed_other_sans") + public List allowedOtherSubjectAlternativeNames; + + @JsonProperty("server_flag") + public Boolean serverFlag; + + @JsonProperty("client_flag") + public Boolean clientFlag; + + @JsonProperty("code_signing_flag") + public Boolean codeSigningFlag; + + @JsonProperty("email_protection_flag") + public Boolean emailProtectionFlag; + + @JsonProperty("key_type") + public String keyType; + + @JsonProperty("key_bits") + public Integer keyBits; + + @JsonProperty("key_usage") + public List keyUsages; + + @JsonProperty("ext_key_usage") + public List extendedKeyUsages; + + @JsonProperty("ext_key_usage_oids") + public List extendedKeyUsageOIDs; + + @JsonProperty("use_csr_common_name") + public Boolean useCSRCommonName; + + @JsonProperty("use_csr_sans") + public Boolean useCSRSubjectAlternativeNames; + + @JsonProperty("organization") + public List subjectOrganization; + + @JsonProperty("ou") + public List subjectOrganizationalUnit; + + @JsonProperty("street_address") + public List subjectStreetAddress; + + @JsonProperty("postal_code") + public List subjectPostalCode; + + @JsonProperty("locality") + public List subjectLocality; + + @JsonProperty("province") + public List subjectProvince; + + @JsonProperty("country") + public List subjectCountry; + + @JsonProperty("allowed_serial_numbers") + public List allowedSubjectSerialNumbers; + + @JsonProperty("generate_lease") + public Boolean generateLease; + + @JsonProperty("no_store") + public Boolean noStore; + + @JsonProperty("require_cn") + public Boolean requireCommonName; + + @JsonProperty("policy_identifiers") + public List policyOIDs; + + @JsonProperty("basic_constraints_valid_for_non_ca") + public Boolean basicConstraintsValidForNonCA; + + @JsonProperty("not_before_duration") + public String notBeforeDuration; + +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRoleReadResult.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRoleReadResult.java new file mode 100644 index 00000000000000..7ef9ad4294fe0b --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRoleReadResult.java @@ -0,0 +1,6 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import io.quarkus.vault.runtime.client.dto.AbstractVaultDTO; + +public class VaultPKIRoleReadResult extends AbstractVaultDTO { +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRoleUpdateBody.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRoleUpdateBody.java new file mode 100644 index 00000000000000..a88c34e8f2f510 --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRoleUpdateBody.java @@ -0,0 +1,4 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +public class VaultPKIRoleUpdateBody extends VaultPKIRoleOptionsData { +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRolesListData.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRolesListData.java new file mode 100644 index 00000000000000..ade81f308d5fca --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRolesListData.java @@ -0,0 +1,9 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import java.util.List; + +import io.quarkus.vault.runtime.client.dto.VaultModel; + +public class VaultPKIRolesListData implements VaultModel { + public List keys; +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRolesListResult.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRolesListResult.java new file mode 100644 index 00000000000000..2ef8f70b247cbe --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRolesListResult.java @@ -0,0 +1,6 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import io.quarkus.vault.runtime.client.dto.AbstractVaultDTO; + +public class VaultPKIRolesListResult extends AbstractVaultDTO { +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKISetSignedIntermediateCABody.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKISetSignedIntermediateCABody.java new file mode 100644 index 00000000000000..fd6d72f1698b77 --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKISetSignedIntermediateCABody.java @@ -0,0 +1,9 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import io.quarkus.vault.runtime.client.dto.VaultModel; + +public class VaultPKISetSignedIntermediateCABody implements VaultModel { + + public String certificate; + +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKISignCertificateRequestBody.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKISignCertificateRequestBody.java new file mode 100644 index 00000000000000..79dbb7fe892acb --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKISignCertificateRequestBody.java @@ -0,0 +1,36 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.quarkus.vault.runtime.client.dto.VaultModel; + +public class VaultPKISignCertificateRequestBody implements VaultModel { + + public String csr; + + @JsonProperty("common_name") + public String subjectCommonName; + + @JsonProperty("alt_names") + public String subjectAlternativeNames; + + @JsonProperty("ip_sans") + public String ipSubjectAlternativeNames; + + @JsonProperty("uri_sans") + public String uriSubjectAlternativeNames; + + @JsonProperty("other_sans") + public List otherSubjectAlternativeNames; + + @JsonProperty("ttl") + public String timeToLive; + + public String format = "pem"; + + @JsonProperty("exclude_cn_from_sans") + public Boolean excludeCommonNameFromSubjectAlternativeNames; + +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKISignCertificateRequestData.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKISignCertificateRequestData.java new file mode 100644 index 00000000000000..389d9d3df9a0e6 --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKISignCertificateRequestData.java @@ -0,0 +1,22 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.quarkus.vault.runtime.client.dto.VaultModel; + +public class VaultPKISignCertificateRequestData implements VaultModel { + + public String certificate; + + @JsonProperty("issuing_ca") + public String issuingCA; + + @JsonProperty("ca_chain") + public List caChain; + + @JsonProperty("serial_number") + public String serialNumber; + +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKISignCertificateRequestResult.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKISignCertificateRequestResult.java new file mode 100644 index 00000000000000..89b4921ca399bc --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKISignCertificateRequestResult.java @@ -0,0 +1,6 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import io.quarkus.vault.runtime.client.dto.AbstractVaultDTO; + +public class VaultPKISignCertificateRequestResult extends AbstractVaultDTO { +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKISignIntermediateCABody.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKISignIntermediateCABody.java new file mode 100644 index 00000000000000..c988c2c5078381 --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKISignIntermediateCABody.java @@ -0,0 +1,69 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.quarkus.vault.runtime.client.dto.VaultModel; + +public class VaultPKISignIntermediateCABody implements VaultModel { + + public String csr; + + @JsonProperty("common_name") + public String subjectCommonName; + + @JsonProperty("organization") + public String subjectOrganization; + + @JsonProperty("ou") + public String subjectOrganizationalUnit; + + @JsonProperty("street_address") + public String subjectStreetAddress; + + @JsonProperty("postal_code") + public String subjectPostalCode; + + @JsonProperty("locality") + public String subjectLocality; + + @JsonProperty("province") + public String subjectProvince; + + @JsonProperty("country") + public String subjectCountry; + + @JsonProperty("alt_names") + public String subjectAlternativeNames; + + @JsonProperty("ip_sans") + public String ipSubjectAlternativeNames; + + @JsonProperty("uri_sans") + public String uriSubjectAlternativeNames; + + @JsonProperty("other_sans") + public List otherSubjectAlternativeNames; + + @JsonProperty("serial_number") + public String subjectSerialNumber; + + @JsonProperty("ttl") + public String timeToLive; + + public String format = "pem"; + + @JsonProperty("max_path_length") + public Integer maxPathLength; + + @JsonProperty("exclude_cn_from_sans") + public Boolean excludeCommonNameFromSubjectAlternativeNames; + + @JsonProperty("use_csr_values") + public Boolean useCSRValues; + + @JsonProperty("permitted_dns_domains") + public List permittedDnsDomains; + +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKITidyBody.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKITidyBody.java new file mode 100644 index 00000000000000..3d476b07e512b9 --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKITidyBody.java @@ -0,0 +1,18 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.quarkus.vault.runtime.client.dto.VaultModel; + +public class VaultPKITidyBody implements VaultModel { + + @JsonProperty("tidy_cert_store") + public Boolean tidyCertStore; + + @JsonProperty("tidy_revoked_certs") + public Boolean tidyRevokedCerts; + + @JsonProperty("safety_buffer") + public String safetyBuffer; + +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultPKISecretEngine.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultPKISecretEngine.java new file mode 100644 index 00000000000000..2efad46de253d7 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultPKISecretEngine.java @@ -0,0 +1,218 @@ +package io.quarkus.vault; + +import java.time.OffsetDateTime; +import java.util.List; + +import io.quarkus.vault.pki.ConfigCRLOptions; +import io.quarkus.vault.pki.ConfigURLsOptions; +import io.quarkus.vault.pki.GenerateCertificateOptions; +import io.quarkus.vault.pki.GenerateIntermediateCSROptions; +import io.quarkus.vault.pki.GenerateRootOptions; +import io.quarkus.vault.pki.GeneratedCertificate; +import io.quarkus.vault.pki.GeneratedIntermediateCSRResult; +import io.quarkus.vault.pki.GeneratedRootCertificate; +import io.quarkus.vault.pki.RoleOptions; +import io.quarkus.vault.pki.SignIntermediateCAOptions; +import io.quarkus.vault.pki.SignedCertificate; +import io.quarkus.vault.pki.TidyOptions; + +/** + * A service that interacts with Hashicorp's Vault PKI secret engine to issue certificates & manage certificate + * authorities. + * + * @see PKI + */ +public interface VaultPKISecretEngine { + + /** + * Retrieves the engine's PEM encoded CA certificate. + * + * @return PEM encoded certificate authority certificate . + */ + String getCertificateAuthority(); + + /** + * Configures the engine's CA. + * + * @param pemBundle PEM encoded bundle including the CA, with optional chain, and private key. + */ + void configCertificateAuthority(String pemBundle); + + /** + * Configures engine's URLs for issuing certificates, CRL distribution points, and OCSP servers. + * + * @param options URL options + */ + void configURLs(ConfigURLsOptions options); + + /** + * Read engine's configured URLs for issuing certificates, CRL distribution points, and OCSP servers. + * + * @return URL options + */ + ConfigURLsOptions readURLsConfig(); + + /** + * Configures engine's CRL. + * + * @param options CRL options + */ + void configCRL(ConfigCRLOptions options); + + /** + * Read engine's CRL configuration. + * + * @return URL options + */ + ConfigCRLOptions readCRLConfig(); + + /** + * Retrieves the engine's PEM encoded CA chain. + * + * @return PEM encoded certificate authority chain. + */ + String getCertificateAuthorityChain(); + + /** + * Retrieves the engine's PEM encoded CRL. + * + * @return PEM encoded certificate revocation list. + */ + String getCertificateRevocationList(); + + /** + * Forces a rotation of the associated CRL. + */ + boolean rotateCertificateRevocationList(); + + /** + * List all issued certificate serial numbers. + * + * @return List of certificate serialize numbers. + */ + List listCertificates(); + + /** + * Retrieve a specific PEM encoded certificate. + * + * @param serial Serial number of certificate. + * @return PEM encoded certificate or null if no certificate exists. + */ + String getCertificate(String serial); + + /** + * Generates a public/private key pair and certificate issued from the engine's CA using the + * provided options. + * + * @param role Name of role used to create certificate. + * @param options Certificate generation options. + * @return Generated certificate and private key. + */ + GeneratedCertificate generateCertificate(String role, GenerateCertificateOptions options); + + /** + * Generates a certificate issued from the engine's CA using the provided Certificate Signing Request and options. + * + * @param role Name of role used to create certificate. + * @param pemSigningRequest Certificate Signing Request (PEM encoded). + * @param options Certificate generation options. + * @return Generated certificate. + */ + SignedCertificate signRequest(String role, String pemSigningRequest, GenerateCertificateOptions options); + + /** + * Revokes a certificate. + * + * @param serialNumber Serial number of certificate. + * @return Time of certificates revocation. + */ + OffsetDateTime revokeCertificate(String serialNumber); + + /** + * Updates, or creates, a role. + * + * @param role Name of role. + * @param options Options for role. + */ + void updateRole(String role, RoleOptions options); + + /** + * Retrieve current options for a role. + * + * @param role Name of role. + * @return Options for the role or null if role does not exist. + */ + RoleOptions getRole(String role); + + /** + * Lists existing role names. + * + * @return List of role names. + */ + List listRoles(); + + /** + * Deletes a role. + * + * @param role Name of role. + */ + void deleteRole(String role); + + /** + * Generates a self-signed root as the engine's CA. + * + * @param options Generation options + * @return Generated root certificate. + */ + GeneratedRootCertificate generateRoot(GenerateRootOptions options); + + /** + * Deletes the engine's current CA. + */ + void deleteRoot(); + + /** + * Generates an intermediate CA certificate issued from the engine's CA using the provided Certificate Signing + * Request and options. + * + * @param pemSigningRequest Certificate Signing Request (PEM encoded). + * @param options Signing options + * @return Generated certificate. + */ + SignedCertificate signIntermediateCA(String pemSigningRequest, SignIntermediateCAOptions options); + + /** + * Generates a Certificate Signing Request and private key for the engine's CA. + * + * Use this to generate a CSR and for the engine's CA that can be used by another + * CA to issue an intermediate CA certificate. After generating the intermediate CA + * {@link #setSignedIntermediateCA(String)} must be used to set the engine's CA certificate. + * + * This will overwrite any previously existing CA private key for the engine. + * + * @see #setSignedIntermediateCA(String) + * @param options Options for CSR generation. + * @return Generated CSR and, if key export is enabled, private key. + */ + GeneratedIntermediateCSRResult generateIntermediateCSR(GenerateIntermediateCSROptions options); + + /** + * Sets the engine's intermediate CA certificate, signed by another CA. + * + * After generating a CSR (via {@link #generateIntermediateCSR(GenerateIntermediateCSROptions)}), + * this method must be used to set the engine's CA. + * + * @see #generateIntermediateCSR(GenerateIntermediateCSROptions) + * @param pemCert Signed certificate (PEM encoded). + */ + void setSignedIntermediateCA(String pemCert); + + /** + * Tidy up the storage backend and/or CRL by removing certificates that have expired and are past a certain buffer + * period beyond their expiration time. + * + * @param options Tidy options + */ + void tidy(TidyOptions options); + +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultPKISecretEngineFactory.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultPKISecretEngineFactory.java new file mode 100644 index 00000000000000..d51830a9fbd62e --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultPKISecretEngineFactory.java @@ -0,0 +1,19 @@ +package io.quarkus.vault; + +/** + * Allows obtaining PKI engines for specific mount paths. + * + * @see VaultPKISecretEngine + */ +public interface VaultPKISecretEngineFactory { + + /** + * Get a PKI engine for a specific mount. + * + * @param mount Engine mount path. + * + * @return PKI engine interface. + */ + VaultPKISecretEngine engine(String mount); + +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CertificateExtendedKeyUsage.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CertificateExtendedKeyUsage.java new file mode 100644 index 00000000000000..2dfac75d6bc45c --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CertificateExtendedKeyUsage.java @@ -0,0 +1,17 @@ +package io.quarkus.vault.pki; + +public enum CertificateExtendedKeyUsage { + ServerAuth, + ClientAuth, + CodeSigning, + EmailProtection, + IPSECEndSystem, + IPSECTunnel, + IPSECUser, + TimeStamping, + OCSPSigning, + MicrosoftServerGatedCrypto, + NetscapeServerGatedCrypto, + MicrosoftCommercialCodeSigning, + MicrosoftKernelCodeSigning +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CertificateKeyType.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CertificateKeyType.java new file mode 100644 index 00000000000000..cdf2dfdc6b64cd --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CertificateKeyType.java @@ -0,0 +1,6 @@ +package io.quarkus.vault.pki; + +public enum CertificateKeyType { + RSA, + EC +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CertificateKeyUsage.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CertificateKeyUsage.java new file mode 100644 index 00000000000000..3fb731084375f6 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CertificateKeyUsage.java @@ -0,0 +1,13 @@ +package io.quarkus.vault.pki; + +public enum CertificateKeyUsage { + DigitalSignature, + ContentCommitment, + KeyEncipherment, + DataEncipherment, + KeyAgreement, + CertSign, + CRLSign, + EncipherOnly, + DecipherOnly +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/ConfigCRLOptions.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/ConfigCRLOptions.java new file mode 100644 index 00000000000000..e7a1885ce69e9a --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/ConfigCRLOptions.java @@ -0,0 +1,18 @@ +package io.quarkus.vault.pki; + +/** + * Options for configuration the CRL + */ +public class ConfigCRLOptions { + + /** + * Specifies the time until expiration. + */ + public String expiry; + + /** + * Disables or enables CRL building. + */ + public Boolean disable; + +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/ConfigURLsOptions.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/ConfigURLsOptions.java new file mode 100644 index 00000000000000..1ed9af357b9ee0 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/ConfigURLsOptions.java @@ -0,0 +1,25 @@ +package io.quarkus.vault.pki; + +import java.util.List; + +/** + * Options for configuring URLs + */ +public class ConfigURLsOptions { + + /** + * Specifies the URL values for the Issuing Certificate field. + */ + public List issuingCertificates; + + /** + * Specifies the URL values for the CRL Distribution Points field. + */ + public List crlDistributionPoints; + + /** + * Specifies the URL values for the OCSP Servers field. + */ + public List ocspServers; + +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GenerateCertificateOptions.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GenerateCertificateOptions.java new file mode 100644 index 00000000000000..a922faddf06b6a --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GenerateCertificateOptions.java @@ -0,0 +1,52 @@ +package io.quarkus.vault.pki; + +import java.util.List; + +/** + * Options for generating a certificate issued by the engine's CA. + */ +public class GenerateCertificateOptions { + + /** + * Specifies Common Name (CN) of the certificate's subject. + */ + public String subjectCommonName; + + /** + * Specifies Subject Alternative Names. + *

+ * These can be host names or email addresses; they will be parsed into their respective fields. + */ + public List subjectAlternativeNames; + + /** + * Flag determining if the Common Name (CN) of the subject will be included + * by default in the Subject Alternative Names of issued certificates. + */ + public Boolean excludeCommonNameFromSubjectAlternativeNames; + + /** + * Specifies IP Subject Alternative Names. + */ + public List ipSubjectAlternativeNames; + + /** + * Specifies URI Subject Alternative Names. + */ + public List uriSubjectAlternativeNames; + + /** + * Specifies custom OID/UTF8-string Subject Alternative Names. + *

+ * The format is the same as OpenSSL: ;: where the only current valid type is UTF8. This + * can be a comma-delimited list or a JSON string slice. Must match allowed_other_sans specified on the role. + */ + public List otherSubjectAlternativeNames; + + /** + * Specifies request time-to-live. If not specified, the role's TTL will be used. + *

+ * Value is specified as a string duration with time suffix. Hour is the largest supported suffix. + */ + public String timeToLive; +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GenerateIntermediateCSROptions.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GenerateIntermediateCSROptions.java new file mode 100644 index 00000000000000..03772cc94fa077 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GenerateIntermediateCSROptions.java @@ -0,0 +1,102 @@ +package io.quarkus.vault.pki; + +import java.util.List; + +/** + * Options for generating a CSR for an intermediate CA. + */ +public class GenerateIntermediateCSROptions { + + /** + * Specifies Common Name (CN) of the subject. + */ + public String subjectCommonName; + + /** + * Specifies Organization (O) of the subject. + */ + public String subjectOrganization; + + /** + * Specifies Organizational Unit (OU) of the subject. + */ + public String subjectOrganizationalUnit; + + /** + * Specifies Street Address of the subject. + */ + public String subjectStreetAddress; + + /** + * Specifies Postal Code of the subject. + */ + public String subjectPostalCode; + + /** + * Specifies Locality (L) of the subject. + */ + public String subjectLocality; + + /** + * Specifies Province (ST) of the subject. + */ + public String subjectProvince; + + /** + * Specifies Country (C) of the subject. + */ + public String subjectCountry; + + /** + * Specifies the Serial Number (SERIALNUMBER) of the subject. + */ + public String subjectSerialNumber; + + /** + * Specifies Subject Alternative Names. + *

+ * These can be host names or email addresses; they will be parsed into their respective fields. + */ + public List subjectAlternativeNames; + + /** + * Flag determining if the Common Name (CN) of the subject will be included + * by default in the Subject Alternative Names of issued certificates. + */ + public Boolean excludeCommonNameFromSubjectAlternativeNames; + + /** + * Specifies IP Subject Alternative Names. + */ + public List ipSubjectAlternativeNames; + + /** + * Specifies URI Subject Alternative Names. + */ + public List uriSubjectAlternativeNames; + + /** + * Specifies custom OID/UTF8-string Subject Alternative Names. + *

+ * The format is the same as OpenSSL: ;: where the only current valid type is UTF8. Must match + * {@link RoleOptions#allowedOtherSubjectAlternativeNames} specified on the role. + */ + public List otherSubjectAlternativeNames; + + /** + * Specifies the desired type of private key to generate, RSA or EC. + */ + public CertificateKeyType keyType; + + /** + * Specifies the number of bits for the generated private key. + *

+ * If {@link #keyType} is {@link CertificateKeyType#EC}, this value must be specified as well. + */ + public Integer keyBits; + + /** + * Flag determining if the generated private key should be exported or kept internally. + */ + public boolean exportPrivateKey = false; +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GenerateRootOptions.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GenerateRootOptions.java new file mode 100644 index 00000000000000..e30fd00ff4e939 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GenerateRootOptions.java @@ -0,0 +1,119 @@ +package io.quarkus.vault.pki; + +import java.util.List; + +/** + * Options for generating a self-signed root CA. + */ +public class GenerateRootOptions { + + /** + * Specifies Common Name (CN) of the subject. + */ + public String subjectCommonName; + + /** + * Specifies Organization (O) of the subject. + */ + public String subjectOrganization; + + /** + * Specifies Organizational Unit (OU) of the subject. + */ + public String subjectOrganizationalUnit; + + /** + * Specifies Street Address of the subject. + */ + public String subjectStreetAddress; + + /** + * Specifies Postal Code of the subject. + */ + public String subjectPostalCode; + + /** + * Specifies Locality (L) of the subject. + */ + public String subjectLocality; + + /** + * Specifies Province (ST) of the subject. + */ + public String subjectProvince; + + /** + * Specifies Country (C) of the subject. + */ + public String subjectCountry; + + /** + * Specifies the Serial Number (SERIALNUMBER) of the subject. + */ + public String subjectSerialNumber; + + /** + * Specifies Subject Alternative Names. + *

+ * These can be host names or email addresses; they will be parsed into their respective fields. + */ + public List subjectAlternativeNames; + + /** + * Flag determining if the Common Name (CN) of the subject will be included + * by default in the Subject Alternative Names of issued certificates. + */ + public Boolean excludeCommonNameFromSubjectAlternativeNames; + + /** + * Specifies IP Subject Alternative Names. + */ + public List ipSubjectAlternativeNames; + + /** + * Specifies URI Subject Alternative Names. + */ + public List uriSubjectAlternativeNames; + + /** + * Specifies custom OID/UTF8-string Subject Alternative Names. + *

+ * The format is the same as OpenSSL: ;: where the only current valid type is UTF8. + */ + public List otherSubjectAlternativeNames; + + /** + * Specifies time-to-live. + *

+ * Value is specified as a string duration with time suffix. Hour is the largest supported suffix. + */ + public String timeToLive; + + /** + * Specifies the desired type of private key to generate, RSA or EC. + */ + public CertificateKeyType keyType; + + /** + * Specifies the number of bits for the generated private key. + *

+ * If {@link #keyType} is {@link CertificateKeyType#EC}, this value must be specified as well. + */ + public Integer keyBits; + + /** + * Flag determining if the generated private key should be exported or kept internally. + */ + public boolean exportPrivateKey = false; + + /** + * Specifies the maximum path length for generated certificate. + */ + public Integer maxPathLength; + + /** + * DNS domains for which certificates are allowed to be issued or signed by this CA certificate. Subdomains + * are allowed, as per RFC. + */ + public List permittedDnsDomains; +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GeneratedCertificate.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GeneratedCertificate.java new file mode 100644 index 00000000000000..d965ed8c729baf --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GeneratedCertificate.java @@ -0,0 +1,41 @@ +package io.quarkus.vault.pki; + +import java.util.List; + +import io.quarkus.vault.VaultPKISecretEngine; + +/** + * Result of {@link VaultPKISecretEngine#generateCertificate(String, GenerateCertificateOptions)}. + */ +public class GeneratedCertificate { + + /** + * Serial number of generated certificate. + */ + public String serialNumber; + + /** + * Generated certificate (PEM encoded). + */ + public String certificate; + + /** + * Issuing CA of generated certificate (PEM encoded). + */ + public String issuingCA; + + /** + * Complete CA chain of generated certificate (elements are PEM encoded). + */ + public List caChain; + + /** + * Type of generated private key + */ + public CertificateKeyType privateKeyType; + + /** + * Generated private Key (PEM Encoded). + */ + public String privateKey; +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GeneratedIntermediateCSRResult.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GeneratedIntermediateCSRResult.java new file mode 100644 index 00000000000000..d7cb65c7b52f67 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GeneratedIntermediateCSRResult.java @@ -0,0 +1,27 @@ +package io.quarkus.vault.pki; + +import io.quarkus.vault.VaultPKISecretEngine; + +/** + * Result of {@link VaultPKISecretEngine#generateIntermediateCSR(GenerateIntermediateCSROptions)}. + */ +public class GeneratedIntermediateCSRResult { + + /** + * Certificate Signing Request (PEM encoded). + */ + public String csr; + + /** + * Type of generated private key. + */ + public CertificateKeyType privateKeyType; + + /** + * Generated private key (PEM Encoded). + *

+ * Only valid if {@link GenerateIntermediateCSROptions#exportPrivateKey} was true. + */ + public String privateKey; + +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GeneratedRootCertificate.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GeneratedRootCertificate.java new file mode 100644 index 00000000000000..eb0db731f41591 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GeneratedRootCertificate.java @@ -0,0 +1,35 @@ +package io.quarkus.vault.pki; + +import io.quarkus.vault.VaultPKISecretEngine; + +/** + * Result of {@link VaultPKISecretEngine#generateRoot(GenerateRootOptions)}. + */ +public class GeneratedRootCertificate { + + /** + * Serial number of generated certificate. + */ + public String serialNumber; + + /** + * Generated certificate (PEM encoded). + */ + public String certificate; + + /** + * Issuing CA of generated certificate (PEM encoded). + */ + public String issuingCA; + + /** + * Type of generated private key + */ + public CertificateKeyType privateKeyType; + + /** + * Generated private Key (PEM Encoded). + */ + public String privateKey; + +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/RoleOptions.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/RoleOptions.java new file mode 100644 index 00000000000000..6dcaf86a50216a --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/RoleOptions.java @@ -0,0 +1,222 @@ +package io.quarkus.vault.pki; + +import java.util.List; + +/** + * Options for PKI roles. + */ +public class RoleOptions { + + /** + * Specifies default request time-to-live. + *

+ * Value is specified as a string duration with time suffix. Hour is the largest supported suffix. + */ + public String timeToLive; + + /** + * Specifies maximum allowed time-to-live. + *

+ * Value is specified as a string duration with time suffix. Hour is the largest supported suffix. + */ + public String maxTimeToLive; + + /** + * Specifies if clients can request certificates for localhost as one of the requested common names. + */ + public Boolean allowLocalhost; + + /** + * Specifies domains allowed on issued certificates. + */ + public List allowedDomains; + + /** + * Flag allowing templates to be used in {@link #allowedDomains}. + * + * @see ACL Path Templating + */ + public Boolean allowTemplatesInAllowedDomains; + + /** + * Specifies if clients can request certificates matching the value of the actual domains themselves. + */ + public Boolean allowBareDomains; + + /** + * Specifies if clients can request certificates with a Common Name (CN) that is a subdomain of the domains + * allowed by the other role options. This includes wildcard subdomains. + */ + public Boolean allowSubdomains; + + /** + * Allows names specified in {@link #allowedDomains} to contain glob patterns (e.g. ftp*.example.com). + */ + public Boolean allowGlobsInAllowedDomains; + + /** + * Specifies if clients can request any Common Name (CN). + */ + public Boolean allowAnyName; + + /** + * Specifies if only valid host names are allowed for Common Names, DNS Subject Alternative Names, and the host + * part of email addresses. + */ + public Boolean enforceHostnames; + + /** + * Specifies if clients can request IP Subject Alternative Names. + */ + public Boolean allowIpSubjectAlternativeNames; + + /** + * Defines allowed URI Subject Alternative Names. + *

+ * Values can contain glob patterns (e.g. spiffe://hostname/*). + */ + public List allowedUriSubjectAlternativeNames; + + /** + * Defines allowed custom OID/UTF8-string Subject Alternative Names. + *

+ * The format is the same as OpenSSL: ;: where the only current valid type is UTF8. + */ + public List allowedOtherSubjectAlternativeNames; + + /** + * Specifies if certificates are flagged for server use. + */ + public Boolean serverFlag; + + /** + * Specifies if certificates are flagged for client use. + */ + public Boolean clientFlag; + + /** + * Specifies if certificates are flagged for code signing use. + */ + public Boolean codeSigningFlag; + + /** + * Specifies if certificates are flagged for email protection use. + */ + public Boolean emailProtectionFlag; + + /** + * Specifies the type of private keys to generate and the type of key expected for submitted CSRs. + */ + public CertificateKeyType keyType; + + /** + * Specifies the number of bits to use for the generated keys. + *

+ * If {@link #keyType} is {@link CertificateKeyType#EC}, this value must be specified as well. + */ + public Integer keyBits; + + /** + * Specifies the allowed key usage constraint on issued certificates + */ + public List keyUsages; + + /** + * Specifies the allowed extended key usage constraint on issued certificates. + */ + public List extendedKeyUsages; + + /** + * Specifies extended key usage OIDs. + */ + public List extendedKeyUsageOIDs; + + /** + * Flag determining if the Common Name in the CSR will be used instead of that specified in request data. + *

+ * Only applies to certificates signed using + * {@link io.quarkus.vault.VaultPKISecretEngine#signRequest(String, String, GenerateCertificateOptions)} + */ + public Boolean useCSRCommonName; + + /** + * Flag determining if the Subject Alternative Names in the CSR will be used instead of that specified in + * request data. + *

+ * Only applies to certificates signed using + * {@link io.quarkus.vault.VaultPKISecretEngine#signRequest(String, String, GenerateCertificateOptions)} + */ + public Boolean useCSRSubjectAlternativeNames; + + /** + * Specifies Organization (O) of the subject on issued certificates. + */ + public String subjectOrganization; + + /** + * Specifies Organizational Unit (OU) of the subject on issued certificates. + */ + public String subjectOrganizationalUnit; + + /** + * Specifies Street Address of the subject on issued certificates. + */ + public String subjectStreetAddress; + + /** + * Specifies Postal Code of the subject on issued certificates. + */ + public String subjectPostalCode; + + /** + * Specifies Locality (L) of the subject on issued certificates. + */ + public String subjectLocality; + + /** + * Specifies Province (ST) of the subject on issued certificates. + */ + public String subjectProvince; + + /** + * Specifies Country (C) of the subject on issued certificates. + */ + public String subjectCountry; + + /** + * Specifies allowed Serial Number (SERIALNUMBER) values of the subject on issued certificates. + */ + public List allowedSubjectSerialNumbers; + + /** + * Specifies if certificates issued/signed against this role will have Vault leases attached to them. + */ + public Boolean generateLease; + + /** + * Flag determining if certificates issued/signed against this role will be stored in the storage backend. + */ + public Boolean noStore; + + /** + * Flag determining if the Common Name (CN) field is required when generating a certificate. + */ + public Boolean requireCommonName; + + /** + * List of policy OIDs. + */ + public List policyOIDs; + + /** + * Mark Basic Constraints valid when issuing non-CA certificates. + */ + public Boolean basicConstraintsValidForNonCA; + + /** + * Specifies the duration by which to backdate on issued certificates not-before. + *

+ * Value is specified as a string duration with time suffix. Hour is the largest supported suffix. + */ + public String notBeforeDuration; +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/SignIntermediateCAOptions.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/SignIntermediateCAOptions.java new file mode 100644 index 00000000000000..b118ea8583d135 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/SignIntermediateCAOptions.java @@ -0,0 +1,115 @@ +package io.quarkus.vault.pki; + +import java.util.List; + +/** + * Options for signing an intermediate CA certificate. + */ +public class SignIntermediateCAOptions { + + /** + * Specifies Common Name (CN) of the subject. + */ + public String subjectCommonName; + + /** + * Specifies Organization (O) of the subject. + */ + public String subjectOrganization; + + /** + * Specifies Organizational Unit (OU) of the subject. + */ + public String subjectOrganizationalUnit; + + /** + * Specifies Street Address of the subject. + */ + public String subjectStreetAddress; + + /** + * Specifies Postal Code of the subject. + */ + public String subjectPostalCode; + + /** + * Specifies Locality (L) of the subject. + */ + public String subjectLocality; + + /** + * Specifies Province (ST) of the subject. + */ + public String subjectProvince; + + /** + * Specifies Country (C) of the subject. + */ + public String subjectCountry; + + /** + * Specifies the Serial Number (SERIALNUMBER) of the subject. + */ + public String subjectSerialNumber; + + /** + * Specifies Subject Alternative Names. + *

+ * These can be host names or email addresses; they will be parsed into their respective fields. + */ + public List subjectAlternativeNames; + + /** + * Flag determining if the Common Name (CN) of the subject will be included + * by default in the Subject Alternative Names of issued certificates. + */ + public Boolean excludeCommonNameFromSubjectAlternativeNames; + + /** + * Specifies IP Subject Alternative Names. + */ + public List ipSubjectAlternativeNames; + + /** + * Specifies URI Subject Alternative Names. + */ + public List uriSubjectAlternativeNames; + + /** + * Specifies custom OID/UTF8-string Subject Alternative Names. + *

+ * The format is the same as OpenSSL: ;: where the only current valid type is UTF8. + */ + public List otherSubjectAlternativeNames; + + /** + * Specifies time-to-live. + *

+ * Value is specified as a string duration with time suffix. Hour is the largest supported suffix. + */ + public String timeToLive; + + /** + * Specifies the maximum path length for generated certificate. + */ + public Integer maxPathLength; + + /** + * Flag determining if CSR values are used instead of configured default values. + *

+ * Enables the following handling: + *

    + *
  • Subject information, including names and alternate names, will be preserved from the CSR.
  • + *
  • Any key usages (for instance, non-repudiation) requested in the CSR will be added to the set of CA key + * usages.
  • + *
  • Extensions requested in the CSR will be copied into the issued certificate.
  • + *
+ */ + public Boolean useCSRValues; + + /** + * DNS domains for which certificates are allowed to be issued or signed by this CA certificate. Subdomains + * are allowed, as per RFC. + */ + public List permittedDnsDomains; +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/SignedCertificate.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/SignedCertificate.java new file mode 100644 index 00000000000000..2b0b7af284f270 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/SignedCertificate.java @@ -0,0 +1,31 @@ +package io.quarkus.vault.pki; + +import java.util.List; + +import io.quarkus.vault.VaultPKISecretEngine; + +/** + * Result of {@link VaultPKISecretEngine#signRequest(String, String, GenerateCertificateOptions)}. + */ +public class SignedCertificate { + + /** + * Serial number of generated certificate. + */ + public String serialNumber; + + /** + * Generated certificate (PEM encoded). + */ + public String certificate; + + /** + * Issuing CA of generated certificate (PEM encoded). + */ + public String issuingCA; + + /** + * Complete CA chain of generated certificate (elements are PEM encoded). + */ + public List caChain; +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/TidyOptions.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/TidyOptions.java new file mode 100644 index 00000000000000..6c08a65d80e5ff --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/TidyOptions.java @@ -0,0 +1,27 @@ +package io.quarkus.vault.pki; + +/** + * Options for tidying up the engine's storage. + */ +public class TidyOptions { + + /** + * Tidy up the certificate store? + */ + public Boolean tidyCertStore; + + /** + * Tidy up the revoked certificates? + */ + public Boolean tidyRevokedCerts; + + /** + * A duration used as a safety buffer to ensure certificates are not expunged prematurely; as an example, this + * can keep certificates from being removed from the CRL that, due to clock skew, might still be considered valid + * on other hosts. + *

+ * Value is specified as a string duration with time suffix. Hour is the largest supported suffix. + */ + public String safetyBuffer; + +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/VaultPKIManager.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/VaultPKIManager.java new file mode 100644 index 00000000000000..35f75d0d172c80 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/VaultPKIManager.java @@ -0,0 +1,615 @@ +package io.quarkus.vault.runtime; + +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toList; + +import java.time.OffsetDateTime; +import java.util.List; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import io.quarkus.vault.VaultException; +import io.quarkus.vault.VaultPKISecretEngine; +import io.quarkus.vault.pki.CertificateExtendedKeyUsage; +import io.quarkus.vault.pki.CertificateKeyType; +import io.quarkus.vault.pki.CertificateKeyUsage; +import io.quarkus.vault.pki.ConfigCRLOptions; +import io.quarkus.vault.pki.ConfigURLsOptions; +import io.quarkus.vault.pki.GenerateCertificateOptions; +import io.quarkus.vault.pki.GenerateIntermediateCSROptions; +import io.quarkus.vault.pki.GenerateRootOptions; +import io.quarkus.vault.pki.GeneratedCertificate; +import io.quarkus.vault.pki.GeneratedIntermediateCSRResult; +import io.quarkus.vault.pki.GeneratedRootCertificate; +import io.quarkus.vault.pki.RoleOptions; +import io.quarkus.vault.pki.SignIntermediateCAOptions; +import io.quarkus.vault.pki.SignedCertificate; +import io.quarkus.vault.pki.TidyOptions; +import io.quarkus.vault.runtime.client.dto.AbstractVaultDTO; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKICRLRotateResult; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKICertificateListResult; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKICertificateResult; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIConfigCABody; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIConfigCRLBody; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIConfigCRLData; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIConfigCRLResult; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIConfigURLsBody; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIConfigURLsData; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIConfigURLsResult; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIGenerateCertificateBody; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIGenerateCertificateData; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIGenerateCertificateResult; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIGenerateIntermediateCSRBody; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIGenerateIntermediateCSRData; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIGenerateIntermediateCSRResult; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIGenerateRootBody; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIGenerateRootData; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIGenerateRootResult; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIRevokeCertificateBody; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIRevokeCertificateResult; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIRoleOptionsData; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIRoleReadResult; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIRoleUpdateBody; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIRolesListResult; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKISetSignedIntermediateCABody; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKISignCertificateRequestBody; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKISignCertificateRequestData; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKISignCertificateRequestResult; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKISignIntermediateCABody; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKITidyBody; +import io.quarkus.vault.runtime.client.secretengine.VaultInternalPKISecretEngine; + +@ApplicationScoped +public class VaultPKIManager implements VaultPKISecretEngine { + + private final String mount; + private final VaultAuthManager vaultAuthManager; + private final VaultInternalPKISecretEngine vaultInternalPKISecretEngine; + + @Inject + public VaultPKIManager( + VaultAuthManager vaultAuthManager, + VaultInternalPKISecretEngine vaultInternalPKISecretEngine) { + this("pki", vaultAuthManager, vaultInternalPKISecretEngine); + } + + VaultPKIManager( + String mount, + VaultAuthManager vaultAuthManager, + VaultInternalPKISecretEngine vaultInternalPKISecretEngine) { + this.mount = mount; + this.vaultAuthManager = vaultAuthManager; + this.vaultInternalPKISecretEngine = vaultInternalPKISecretEngine; + } + + private String getToken() { + return vaultAuthManager.getClientToken(); + } + + @Override + public String getCertificateAuthority() { + VaultPKICertificateResult internalResult = vaultInternalPKISecretEngine.getCertificate(getToken(), mount, "ca"); + if (internalResult.data == null) { + error(internalResult); + } + + return internalResult.data.certificate; + } + + @Override + public void configCertificateAuthority(String pemBundle) { + VaultPKIConfigCABody body = new VaultPKIConfigCABody(); + body.pemBundle = pemBundle; + vaultInternalPKISecretEngine.configCertificateAuthority(getToken(), mount, body); + } + + @Override + public void configURLs(ConfigURLsOptions options) { + VaultPKIConfigURLsBody body = new VaultPKIConfigURLsBody(); + body.issuingCertificates = options.issuingCertificates; + body.crlDistributionPoints = options.crlDistributionPoints; + body.ocspServers = options.ocspServers; + + vaultInternalPKISecretEngine.configURLs(getToken(), mount, body); + } + + @Override + public ConfigURLsOptions readURLsConfig() { + VaultPKIConfigURLsResult internalResult = vaultInternalPKISecretEngine.readURLs(getToken(), mount); + if (internalResult.data == null) { + error(internalResult); + } + + VaultPKIConfigURLsData internalResultData = internalResult.data; + + ConfigURLsOptions result = new ConfigURLsOptions(); + result.issuingCertificates = internalResultData.issuingCertificates; + result.crlDistributionPoints = internalResultData.crlDistributionPoints; + result.ocspServers = internalResultData.ocspServers; + return result; + } + + @Override + public void configCRL(ConfigCRLOptions options) { + VaultPKIConfigCRLBody body = new VaultPKIConfigCRLBody(); + body.expiry = options.expiry; + body.disable = options.disable; + + vaultInternalPKISecretEngine.configCRL(getToken(), mount, body); + } + + @Override + public ConfigCRLOptions readCRLConfig() { + VaultPKIConfigCRLResult internalResult = vaultInternalPKISecretEngine.readCRL(getToken(), mount); + if (internalResult.data == null) { + error(internalResult); + } + + VaultPKIConfigCRLData internalResultData = internalResult.data; + + ConfigCRLOptions result = new ConfigCRLOptions(); + result.expiry = internalResultData.expiry; + result.disable = internalResultData.disable; + return result; + } + + @Override + public String getCertificateAuthorityChain() { + VaultPKICertificateResult internalResult = vaultInternalPKISecretEngine.getCertificate(getToken(), mount, "ca_chain"); + if (internalResult.data == null) { + error(internalResult); + } + + return internalResult.data.certificate; + } + + @Override + public String getCertificateRevocationList() { + VaultPKICertificateResult internalResult = vaultInternalPKISecretEngine.getCertificate(getToken(), mount, "crl"); + if (internalResult.data == null) { + error(internalResult); + } + + return internalResult.data.certificate; + } + + @Override + public boolean rotateCertificateRevocationList() { + VaultPKICRLRotateResult internalResult = vaultInternalPKISecretEngine.rotateCertificateRevocationList(getToken(), + mount); + if (internalResult.data == null) { + error(internalResult); + } + + return internalResult.data.success; + } + + @Override + public List listCertificates() { + VaultPKICertificateListResult internalResult = vaultInternalPKISecretEngine.listCertificates(getToken(), mount); + if (internalResult.data == null) { + error(internalResult); + } + + // Return serials corrected to colon format (to match those returned by generateCertificate/signRequest) + return internalResult.data.keys.stream().map(serial -> serial.replaceAll("-", ":")).collect(toList()); + } + + @Override + public String getCertificate(String serial) { + VaultPKICertificateResult internalResult = vaultInternalPKISecretEngine.getCertificate(getToken(), mount, serial); + if (internalResult.data == null) { + error(internalResult); + } + + return internalResult.data.certificate; + } + + @Override + public GeneratedCertificate generateCertificate(String role, GenerateCertificateOptions options) { + VaultPKIGenerateCertificateBody body = new VaultPKIGenerateCertificateBody(); + body.subjectCommonName = options.subjectCommonName; + body.subjectAlternativeNames = options.subjectAlternativeNames != null + ? String.join(",", options.subjectAlternativeNames) + : null; + body.ipSubjectAlternativeNames = options.subjectAlternativeNames != null + ? String.join(",", options.ipSubjectAlternativeNames) + : null; + body.uriSubjectAlternativeNames = options.subjectAlternativeNames != null + ? String.join(",", options.uriSubjectAlternativeNames) + : null; + body.otherSubjectAlternativeNames = options.otherSubjectAlternativeNames; + body.timeToLive = options.timeToLive; + body.excludeCommonNameFromSubjectAlternativeNames = options.excludeCommonNameFromSubjectAlternativeNames; + + VaultPKIGenerateCertificateResult internalResult = vaultInternalPKISecretEngine.generateCertificate(getToken(), mount, + role, body); + if (internalResult.data == null) { + error(internalResult); + } + + VaultPKIGenerateCertificateData internalResultData = internalResult.data; + + GeneratedCertificate result = new GeneratedCertificate(); + result.certificate = internalResultData.certificate; + result.issuingCA = internalResultData.issuingCA; + result.caChain = internalResultData.caChain; + result.serialNumber = internalResultData.serialNumber; + result.privateKeyType = internalResultData.privateKeyType != null + ? CertificateKeyType.valueOf(internalResultData.privateKeyType.toUpperCase()) + : null; + result.privateKey = internalResultData.privateKey; + return result; + } + + @Override + public SignedCertificate signRequest(String role, String pemSigningRequest, GenerateCertificateOptions options) { + VaultPKISignCertificateRequestBody body = new VaultPKISignCertificateRequestBody(); + body.csr = pemSigningRequest; + body.subjectCommonName = options.subjectCommonName; + body.subjectAlternativeNames = options.subjectAlternativeNames != null + ? String.join(",", options.subjectAlternativeNames) + : null; + body.ipSubjectAlternativeNames = options.subjectAlternativeNames != null + ? String.join(",", options.ipSubjectAlternativeNames) + : null; + body.uriSubjectAlternativeNames = options.subjectAlternativeNames != null + ? String.join(",", options.uriSubjectAlternativeNames) + : null; + body.otherSubjectAlternativeNames = options.otherSubjectAlternativeNames; + body.timeToLive = options.timeToLive; + body.excludeCommonNameFromSubjectAlternativeNames = options.excludeCommonNameFromSubjectAlternativeNames; + + VaultPKISignCertificateRequestResult internalResult = vaultInternalPKISecretEngine.signCertificate(getToken(), mount, + role, body); + if (internalResult.data == null) { + error(internalResult); + } + + VaultPKISignCertificateRequestData internalResultData = internalResult.data; + + SignedCertificate result = new SignedCertificate(); + result.certificate = internalResultData.certificate; + result.issuingCA = internalResultData.issuingCA; + result.caChain = internalResultData.caChain; + result.serialNumber = internalResultData.serialNumber; + return result; + } + + @Override + public OffsetDateTime revokeCertificate(String serialNumber) { + VaultPKIRevokeCertificateBody body = new VaultPKIRevokeCertificateBody(); + body.serialNumber = serialNumber; + + VaultPKIRevokeCertificateResult internalResult = vaultInternalPKISecretEngine.revokeCertificate(getToken(), mount, + body); + if (internalResult.data == null) { + error(internalResult); + } + + return internalResult.data.revocationTime; + } + + @Override + public void updateRole(String role, RoleOptions options) { + VaultPKIRoleUpdateBody body = new VaultPKIRoleUpdateBody(); + body.timeToLive = options.timeToLive; + body.maxTimeToLive = options.maxTimeToLive; + body.allowLocalhost = options.allowLocalhost; + body.allowedDomains = options.allowedDomains; + body.allowTemplatesInAllowedDomains = options.allowTemplatesInAllowedDomains; + body.allowBareDomains = options.allowBareDomains; + body.allowSubdomains = options.allowSubdomains; + body.allowGlobsInAllowedDomains = options.allowGlobsInAllowedDomains; + body.allowAnyName = options.allowAnyName; + body.enforceHostnames = options.enforceHostnames; + body.allowIpSubjectAlternativeNames = options.allowIpSubjectAlternativeNames; + body.allowedUriSubjectAlternativeNames = options.allowedUriSubjectAlternativeNames; + body.allowedOtherSubjectAlternativeNames = options.allowedOtherSubjectAlternativeNames; + body.serverFlag = options.serverFlag; + body.clientFlag = options.clientFlag; + body.codeSigningFlag = options.codeSigningFlag; + body.emailProtectionFlag = options.emailProtectionFlag; + body.keyType = options.keyType != null + ? options.keyType.name().toLowerCase() + : null; + body.keyBits = options.keyBits; + body.keyUsages = options.keyUsages != null + ? options.keyUsages.stream().map(CertificateKeyUsage::name).collect(toList()) + : null; + body.extendedKeyUsages = options.extendedKeyUsages != null + ? options.extendedKeyUsages.stream().map(CertificateExtendedKeyUsage::name).collect(toList()) + : null; + body.extendedKeyUsageOIDs = options.extendedKeyUsageOIDs; + body.useCSRCommonName = options.useCSRCommonName; + body.useCSRSubjectAlternativeNames = options.useCSRSubjectAlternativeNames; + body.subjectOrganization = options.subjectOrganization != null + ? asList(options.subjectOrganization.split(",")) + : null; + body.subjectOrganizationalUnit = options.subjectOrganizationalUnit != null + ? asList(options.subjectOrganizationalUnit.split(",")) + : null; + body.subjectStreetAddress = options.subjectStreetAddress != null + ? asList(options.subjectStreetAddress.split(",")) + : null; + body.subjectPostalCode = options.subjectPostalCode != null + ? asList(options.subjectPostalCode.split(",")) + : null; + body.subjectLocality = options.subjectLocality != null + ? asList(options.subjectLocality.split(",")) + : null; + body.subjectProvince = options.subjectProvince != null + ? asList(options.subjectProvince.split(",")) + : null; + body.subjectCountry = options.subjectCountry != null + ? asList(options.subjectCountry.split(",")) + : null; + body.allowedSubjectSerialNumbers = options.allowedSubjectSerialNumbers; + body.generateLease = options.generateLease; + body.noStore = options.noStore; + body.requireCommonName = options.requireCommonName; + body.policyOIDs = options.policyOIDs; + body.basicConstraintsValidForNonCA = options.basicConstraintsValidForNonCA; + body.notBeforeDuration = options.notBeforeDuration; + + vaultInternalPKISecretEngine.updateRole(getToken(), mount, role, body); + } + + @Override + public RoleOptions getRole(String role) { + VaultPKIRoleReadResult internalResult = vaultInternalPKISecretEngine.readRole(getToken(), mount, role); + if (internalResult.data == null) { + error(internalResult); + } + + VaultPKIRoleOptionsData internalResultData = internalResult.data; + + RoleOptions result = new RoleOptions(); + result.timeToLive = internalResultData.timeToLive; + result.maxTimeToLive = internalResultData.maxTimeToLive; + result.allowLocalhost = internalResultData.allowLocalhost; + result.allowedDomains = internalResultData.allowedDomains; + result.allowTemplatesInAllowedDomains = internalResultData.allowTemplatesInAllowedDomains; + result.allowBareDomains = internalResultData.allowBareDomains; + result.allowSubdomains = internalResultData.allowSubdomains; + result.allowGlobsInAllowedDomains = internalResultData.allowGlobsInAllowedDomains; + result.allowAnyName = internalResultData.allowAnyName; + result.enforceHostnames = internalResultData.enforceHostnames; + result.allowIpSubjectAlternativeNames = internalResultData.allowIpSubjectAlternativeNames; + result.allowedUriSubjectAlternativeNames = internalResultData.allowedUriSubjectAlternativeNames; + result.allowedOtherSubjectAlternativeNames = internalResultData.allowedOtherSubjectAlternativeNames; + result.serverFlag = internalResultData.serverFlag; + result.clientFlag = internalResultData.clientFlag; + result.codeSigningFlag = internalResultData.codeSigningFlag; + result.emailProtectionFlag = internalResultData.emailProtectionFlag; + result.keyType = internalResultData.keyType != null + ? CertificateKeyType.valueOf(internalResultData.keyType.toUpperCase()) + : null; + result.keyBits = internalResultData.keyBits; + result.keyUsages = internalResultData.keyUsages != null + ? internalResultData.keyUsages.stream().map(CertificateKeyUsage::valueOf).collect(toList()) + : null; + result.extendedKeyUsages = internalResultData.extendedKeyUsages != null + ? internalResultData.extendedKeyUsages.stream().map(CertificateExtendedKeyUsage::valueOf).collect(toList()) + : null; + result.extendedKeyUsageOIDs = internalResultData.extendedKeyUsageOIDs; + result.useCSRCommonName = internalResultData.useCSRCommonName; + result.useCSRSubjectAlternativeNames = internalResultData.useCSRSubjectAlternativeNames; + result.subjectOrganization = internalResultData.subjectOrganization != null + ? String.join(",", internalResultData.subjectOrganization) + : null; + result.subjectOrganizationalUnit = internalResultData.subjectOrganizationalUnit != null + ? String.join(",", internalResultData.subjectOrganizationalUnit) + : null; + result.subjectStreetAddress = internalResultData.subjectStreetAddress != null + ? String.join(",", internalResultData.subjectStreetAddress) + : null; + result.subjectPostalCode = internalResultData.subjectPostalCode != null + ? String.join(",", internalResultData.subjectPostalCode) + : null; + result.subjectLocality = internalResultData.subjectLocality != null + ? String.join(",", internalResultData.subjectLocality) + : null; + result.subjectProvince = internalResultData.subjectProvince != null + ? String.join(",", internalResultData.subjectProvince) + : null; + result.subjectCountry = internalResultData.subjectCountry != null + ? String.join(",", internalResultData.subjectCountry) + : null; + result.allowedSubjectSerialNumbers = internalResultData.allowedSubjectSerialNumbers; + result.generateLease = internalResultData.generateLease; + result.noStore = internalResultData.noStore; + result.requireCommonName = internalResultData.requireCommonName; + result.policyOIDs = internalResultData.policyOIDs; + result.basicConstraintsValidForNonCA = internalResultData.basicConstraintsValidForNonCA; + result.notBeforeDuration = internalResultData.notBeforeDuration; + return result; + } + + @Override + public List listRoles() { + VaultPKIRolesListResult internalResult = vaultInternalPKISecretEngine.listRoles(getToken(), mount); + if (internalResult.data == null) { + error(internalResult); + } + + return internalResult.data.keys; + } + + @Override + public void deleteRole(String role) { + vaultInternalPKISecretEngine.deleteRole(getToken(), mount, role); + } + + @Override + public GeneratedRootCertificate generateRoot(GenerateRootOptions options) { + String type = options.exportPrivateKey ? "exported" : "internal"; + VaultPKIGenerateRootBody body = new VaultPKIGenerateRootBody(); + body.subjectCommonName = options.subjectCommonName; + body.subjectAlternativeNames = options.subjectAlternativeNames != null + ? String.join(",", options.subjectAlternativeNames) + : null; + body.ipSubjectAlternativeNames = options.ipSubjectAlternativeNames != null + ? String.join(",", options.ipSubjectAlternativeNames) + : null; + body.uriSubjectAlternativeNames = options.uriSubjectAlternativeNames != null + ? String.join(",", options.uriSubjectAlternativeNames) + : null; + body.otherSubjectAlternativeNames = options.otherSubjectAlternativeNames; + body.timeToLive = options.timeToLive; + body.keyType = options.keyType != null + ? options.keyType.name().toLowerCase() + : null; + body.keyBits = options.keyBits; + body.maxPathLength = options.maxPathLength; + body.excludeCommonNameFromSubjectAlternativeNames = options.excludeCommonNameFromSubjectAlternativeNames; + body.permittedDnsDomains = options.permittedDnsDomains; + body.subjectOrganization = options.subjectOrganization; + body.subjectOrganizationalUnit = options.subjectOrganizationalUnit; + body.subjectStreetAddress = options.subjectStreetAddress; + body.subjectPostalCode = options.subjectPostalCode; + body.subjectLocality = options.subjectLocality; + body.subjectProvince = options.subjectProvince; + body.subjectCountry = options.subjectCountry; + body.subjectSerialNumber = options.subjectSerialNumber; + + VaultPKIGenerateRootResult internalResult = vaultInternalPKISecretEngine.generateRoot(getToken(), mount, type, body); + if (internalResult.data == null) { + error(internalResult); + } + + VaultPKIGenerateRootData internalResultData = internalResult.data; + + GeneratedRootCertificate result = new GeneratedRootCertificate(); + result.certificate = internalResultData.certificate; + result.issuingCA = internalResultData.issuingCA; + result.serialNumber = internalResultData.serialNumber; + result.privateKeyType = internalResultData.privateKeyType != null + ? CertificateKeyType.valueOf(internalResultData.privateKeyType.toUpperCase()) + : null; + result.privateKey = internalResultData.privateKey; + return result; + } + + @Override + public void deleteRoot() { + vaultInternalPKISecretEngine.deleteRoot(getToken(), mount); + } + + @Override + public SignedCertificate signIntermediateCA(String pemSigningRequest, SignIntermediateCAOptions options) { + VaultPKISignIntermediateCABody body = new VaultPKISignIntermediateCABody(); + body.csr = pemSigningRequest; + body.subjectCommonName = options.subjectCommonName; + body.subjectAlternativeNames = options.subjectAlternativeNames != null + ? String.join(",", options.subjectAlternativeNames) + : null; + body.ipSubjectAlternativeNames = options.ipSubjectAlternativeNames != null + ? String.join(",", options.ipSubjectAlternativeNames) + : null; + body.uriSubjectAlternativeNames = options.uriSubjectAlternativeNames != null + ? String.join(",", options.uriSubjectAlternativeNames) + : null; + body.otherSubjectAlternativeNames = options.otherSubjectAlternativeNames; + body.timeToLive = options.timeToLive; + body.maxPathLength = options.maxPathLength; + body.excludeCommonNameFromSubjectAlternativeNames = options.excludeCommonNameFromSubjectAlternativeNames; + body.useCSRValues = options.useCSRValues; + body.permittedDnsDomains = options.permittedDnsDomains; + body.subjectOrganization = options.subjectOrganization; + body.subjectOrganizationalUnit = options.subjectOrganizationalUnit; + body.subjectStreetAddress = options.subjectStreetAddress; + body.subjectPostalCode = options.subjectPostalCode; + body.subjectLocality = options.subjectLocality; + body.subjectProvince = options.subjectProvince; + body.subjectCountry = options.subjectCountry; + body.subjectSerialNumber = options.subjectSerialNumber; + + VaultPKISignCertificateRequestResult internalResult = vaultInternalPKISecretEngine.signIntermediateCA(getToken(), mount, + body); + if (internalResult.data == null) { + error(internalResult); + } + + VaultPKISignCertificateRequestData internalResultData = internalResult.data; + + SignedCertificate result = new SignedCertificate(); + result.certificate = internalResultData.certificate; + result.issuingCA = internalResultData.issuingCA; + result.caChain = internalResultData.caChain; + result.serialNumber = internalResultData.serialNumber; + return result; + } + + @Override + public GeneratedIntermediateCSRResult generateIntermediateCSR(GenerateIntermediateCSROptions options) { + String type = options.exportPrivateKey ? "exported" : "internal"; + VaultPKIGenerateIntermediateCSRBody body = new VaultPKIGenerateIntermediateCSRBody(); + body.subjectCommonName = options.subjectCommonName; + body.subjectAlternativeNames = options.subjectAlternativeNames != null + ? String.join(",", options.subjectAlternativeNames) + : null; + body.ipSubjectAlternativeNames = options.ipSubjectAlternativeNames != null + ? String.join(",", options.ipSubjectAlternativeNames) + : null; + body.uriSubjectAlternativeNames = options.uriSubjectAlternativeNames != null + ? String.join(",", options.uriSubjectAlternativeNames) + : null; + body.otherSubjectAlternativeNames = options.otherSubjectAlternativeNames; + body.keyType = options.keyType != null + ? options.keyType.name().toLowerCase() + : null; + body.keyBits = options.keyBits; + body.excludeCommonNameFromSubjectAlternativeNames = options.excludeCommonNameFromSubjectAlternativeNames; + body.subjectOrganization = options.subjectOrganization; + body.subjectOrganizationalUnit = options.subjectOrganizationalUnit; + body.subjectStreetAddress = options.subjectStreetAddress; + body.subjectPostalCode = options.subjectPostalCode; + body.subjectLocality = options.subjectLocality; + body.subjectProvince = options.subjectProvince; + body.subjectCountry = options.subjectCountry; + body.subjectSerialNumber = options.subjectSerialNumber; + + VaultPKIGenerateIntermediateCSRResult internalResult = vaultInternalPKISecretEngine.generateIntermediateCSR(getToken(), + mount, type, body); + + VaultPKIGenerateIntermediateCSRData internalResultData = internalResult.data; + + GeneratedIntermediateCSRResult result = new GeneratedIntermediateCSRResult(); + result.csr = internalResultData.csr; + result.privateKeyType = internalResultData.privateKeyType != null + ? CertificateKeyType.valueOf(internalResultData.privateKeyType.toUpperCase()) + : null; + result.privateKey = internalResultData.privateKey; + return result; + } + + @Override + public void setSignedIntermediateCA(String pemCert) { + VaultPKISetSignedIntermediateCABody body = new VaultPKISetSignedIntermediateCABody(); + body.certificate = pemCert; + + vaultInternalPKISecretEngine.setSignedIntermediateCA(getToken(), mount, body); + } + + @Override + public void tidy(TidyOptions options) { + VaultPKITidyBody body = new VaultPKITidyBody(); + body.tidyCertStore = options.tidyCertStore; + body.tidyRevokedCerts = options.tidyRevokedCerts; + body.safetyBuffer = options.safetyBuffer; + + vaultInternalPKISecretEngine.tidy(getToken(), mount, body); + } + + private void error(AbstractVaultDTO dto) { + if (dto.warnings instanceof List) { + List warnings = (List) dto.warnings; + if (!warnings.isEmpty()) { + throw new VaultException(warnings.get(0).toString()); + } + } + throw new VaultException("Unknown vault error"); + } +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/VaultPKIManagerFactory.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/VaultPKIManagerFactory.java new file mode 100644 index 00000000000000..faecf174681479 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/VaultPKIManagerFactory.java @@ -0,0 +1,23 @@ +package io.quarkus.vault.runtime; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import io.quarkus.vault.VaultPKISecretEngine; +import io.quarkus.vault.VaultPKISecretEngineFactory; +import io.quarkus.vault.runtime.client.secretengine.VaultInternalPKISecretEngine; + +@ApplicationScoped +public class VaultPKIManagerFactory implements VaultPKISecretEngineFactory { + + @Inject + private VaultAuthManager vaultAuthManager; + @Inject + private VaultInternalPKISecretEngine vaultInternalPKISecretEngine; + + @Override + public VaultPKISecretEngine engine(String mount) { + return new VaultPKIManager(mount, vaultAuthManager, vaultInternalPKISecretEngine); + } + +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/secretengine/VaultInternalPKISecretEngine.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/secretengine/VaultInternalPKISecretEngine.java new file mode 100644 index 00000000000000..e305181b0ea4ab --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/secretengine/VaultInternalPKISecretEngine.java @@ -0,0 +1,155 @@ +package io.quarkus.vault.runtime.client.secretengine; + +import static java.util.Collections.emptyList; + +import javax.inject.Singleton; + +import io.quarkus.vault.runtime.client.VaultClientException; +import io.quarkus.vault.runtime.client.VaultInternalBase; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKICRLRotateResult; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKICertificateListResult; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKICertificateResult; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIConfigCABody; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIConfigCRLBody; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIConfigCRLResult; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIConfigURLsBody; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIConfigURLsResult; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIGenerateCertificateBody; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIGenerateCertificateResult; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIGenerateIntermediateCSRBody; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIGenerateIntermediateCSRResult; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIGenerateRootBody; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIGenerateRootResult; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIRevokeCertificateBody; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIRevokeCertificateResult; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIRoleReadResult; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIRoleUpdateBody; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIRolesListData; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIRolesListResult; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKISetSignedIntermediateCABody; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKISignCertificateRequestBody; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKISignCertificateRequestResult; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKISignIntermediateCABody; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKITidyBody; + +@Singleton +public class VaultInternalPKISecretEngine extends VaultInternalBase { + + private String getPath(String mount, String path) { + return mount + "/" + path; + } + + public VaultPKICertificateResult getCertificate(String token, String mount, String serial) { + return vaultClient.get(getPath(mount, "cert/" + serial), token, VaultPKICertificateResult.class); + } + + public VaultPKICertificateListResult listCertificates(String token, String mount) { + return vaultClient.list(getPath(mount, "certs"), token, VaultPKICertificateListResult.class); + } + + public void configCertificateAuthority(String token, String mount, VaultPKIConfigCABody body) { + vaultClient.post(getPath(mount, "config/ca"), token, body, 204); + } + + public VaultPKICRLRotateResult rotateCertificateRevocationList(String token, String mount) { + return vaultClient.get(getPath(mount, "crl/rotate"), token, VaultPKICRLRotateResult.class); + } + + public VaultPKIGenerateCertificateResult generateCertificate( + String token, + String mount, + String role, + VaultPKIGenerateCertificateBody body) { + return vaultClient.post(getPath(mount, "issue/" + role), token, body, VaultPKIGenerateCertificateResult.class); + } + + public VaultPKISignCertificateRequestResult signCertificate( + String token, + String mount, + String role, + VaultPKISignCertificateRequestBody body) { + return vaultClient.post(getPath(mount, "sign/" + role), token, body, VaultPKISignCertificateRequestResult.class); + } + + public VaultPKIRevokeCertificateResult revokeCertificate(String token, String mount, + VaultPKIRevokeCertificateBody body) { + return vaultClient.post(getPath(mount, "revoke"), token, body, VaultPKIRevokeCertificateResult.class); + } + + public void updateRole(String token, String mount, String role, VaultPKIRoleUpdateBody body) { + vaultClient.post(getPath(mount, "roles/" + role), token, body, 204); + } + + public VaultPKIRoleReadResult readRole(String token, String mount, String role) { + return vaultClient.get(getPath(mount, "roles/" + role), token, VaultPKIRoleReadResult.class); + } + + public VaultPKIRolesListResult listRoles(String token, String mount) { + try { + return vaultClient.list(getPath(mount, "roles"), token, VaultPKIRolesListResult.class); + } catch (VaultClientException x) { + // Translate 404 to empty list + if (x.getStatus() == 404) { + VaultPKIRolesListResult empty = new VaultPKIRolesListResult(); + empty.data = new VaultPKIRolesListData(); + empty.data.keys = emptyList(); + return empty; + } else { + throw x; + } + } + } + + public void deleteRole(String token, String mount, String role) { + vaultClient.delete(getPath(mount, "roles/" + role), token, 204); + } + + public VaultPKIGenerateRootResult generateRoot(String token, String mount, String type, + VaultPKIGenerateRootBody body) { + return vaultClient.post(getPath(mount, "root/generate/" + type), token, body, VaultPKIGenerateRootResult.class); + } + + public void deleteRoot(String token, String mount) { + vaultClient.delete(getPath(mount, "root"), token, 204); + } + + public VaultPKISignCertificateRequestResult signIntermediateCA(String token, String mount, + VaultPKISignIntermediateCABody body) { + return vaultClient.post(getPath(mount, "root/sign-intermediate"), + token, + body, + VaultPKISignCertificateRequestResult.class); + } + + public VaultPKIGenerateIntermediateCSRResult generateIntermediateCSR(String token, String mount, String type, + VaultPKIGenerateIntermediateCSRBody body) { + return vaultClient.post(getPath(mount, "intermediate/generate/" + type), + token, + body, + VaultPKIGenerateIntermediateCSRResult.class); + } + + public void setSignedIntermediateCA(String token, String mount, VaultPKISetSignedIntermediateCABody body) { + vaultClient.post(getPath(mount, "intermediate/set-signed"), token, body, 204); + } + + public void tidy(String token, String mount, VaultPKITidyBody body) { + vaultClient.post(getPath(mount, "tidy"), token, body, 202); + } + + public void configURLs(String token, String mount, VaultPKIConfigURLsBody body) { + vaultClient.post(getPath(mount, "config/urls"), token, body, 204); + } + + public VaultPKIConfigURLsResult readURLs(String token, String mount) { + return vaultClient.get(getPath(mount, "config/urls"), token, VaultPKIConfigURLsResult.class); + } + + public void configCRL(String token, String mount, VaultPKIConfigCRLBody body) { + vaultClient.post(getPath(mount, "config/crl"), token, body, 204); + } + + public VaultPKIConfigCRLResult readCRL(String token, String mount) { + return vaultClient.get(getPath(mount, "config/crl"), token, VaultPKIConfigCRLResult.class); + } +} diff --git a/integration-tests/vault/src/test/java/io/quarkus/vault/VaultPKIITCase.java b/integration-tests/vault/src/test/java/io/quarkus/vault/VaultPKIITCase.java new file mode 100644 index 00000000000000..018346afd3fa3d --- /dev/null +++ b/integration-tests/vault/src/test/java/io/quarkus/vault/VaultPKIITCase.java @@ -0,0 +1,861 @@ +package io.quarkus.vault; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.testcontainers.shaded.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers.pkcs_9_at_extensionRequest; +import static org.testcontainers.shaded.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers.rsaEncryption; +import static org.testcontainers.shaded.org.bouncycastle.asn1.x509.Extension.nameConstraints; +import static org.testcontainers.shaded.org.bouncycastle.asn1.x509.Extension.subjectAlternativeName; + +import java.io.StringReader; +import java.math.BigInteger; +import java.time.Duration; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.util.Arrays; +import java.util.List; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.testcontainers.shaded.org.bouncycastle.asn1.ASN1Encodable; +import org.testcontainers.shaded.org.bouncycastle.asn1.x509.BasicConstraints; +import org.testcontainers.shaded.org.bouncycastle.asn1.x509.Extensions; +import org.testcontainers.shaded.org.bouncycastle.asn1.x509.GeneralName; +import org.testcontainers.shaded.org.bouncycastle.asn1.x509.GeneralNames; +import org.testcontainers.shaded.org.bouncycastle.asn1.x509.GeneralSubtree; +import org.testcontainers.shaded.org.bouncycastle.asn1.x509.NameConstraints; +import org.testcontainers.shaded.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.testcontainers.shaded.org.bouncycastle.asn1.x9.X9ObjectIdentifiers; +import org.testcontainers.shaded.org.bouncycastle.cert.X509CRLHolder; +import org.testcontainers.shaded.org.bouncycastle.cert.X509CertificateHolder; +import org.testcontainers.shaded.org.bouncycastle.openssl.PEMParser; +import org.testcontainers.shaded.org.bouncycastle.pkcs.PKCS10CertificationRequest; + +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.vault.pki.CertificateExtendedKeyUsage; +import io.quarkus.vault.pki.CertificateKeyType; +import io.quarkus.vault.pki.CertificateKeyUsage; +import io.quarkus.vault.pki.ConfigCRLOptions; +import io.quarkus.vault.pki.ConfigURLsOptions; +import io.quarkus.vault.pki.GenerateCertificateOptions; +import io.quarkus.vault.pki.GenerateIntermediateCSROptions; +import io.quarkus.vault.pki.GenerateRootOptions; +import io.quarkus.vault.pki.GeneratedCertificate; +import io.quarkus.vault.pki.GeneratedIntermediateCSRResult; +import io.quarkus.vault.pki.GeneratedRootCertificate; +import io.quarkus.vault.pki.RoleOptions; +import io.quarkus.vault.pki.SignIntermediateCAOptions; +import io.quarkus.vault.pki.SignedCertificate; +import io.quarkus.vault.pki.TidyOptions; + +public class VaultPKIITCase { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource("application-vault-pki.properties", "application.properties")); + + @Inject + VaultPKISecretEngine pkiSecretEngine; + + @Inject + VaultPKISecretEngineFactory pkiSecretEngineFactory; + + @AfterEach + public void cleanup() { + try { + pkiSecretEngine.deleteRoot(); + } catch (Throwable x) { + // ignore + } + + try { + pkiSecretEngineFactory.engine("pki2").deleteRoot(); + } catch (Throwable x) { + // ignore + } + + try { + for (String role : pkiSecretEngine.listRoles()) { + try { + pkiSecretEngine.deleteRole(role); + } catch (Throwable x) { + // ignore + } + } + } catch (Throwable x) { + // ignore + } + } + + @Test + public void testGenerateRootOptions() throws Exception { + GenerateRootOptions options = new GenerateRootOptions(); + options.subjectCommonName = "test.example.com"; + options.subjectOrganization = "Test Org"; + options.subjectOrganizationalUnit = "Test Unit"; + options.subjectStreetAddress = "123 Main Street"; + options.subjectLocality = "New York"; + options.subjectProvince = "NY"; + options.subjectCountry = "USA"; + options.subjectPostalCode = "10030"; + options.subjectSerialNumber = "9876543210"; + options.subjectAlternativeNames = singletonList("alt.example.com"); + options.ipSubjectAlternativeNames = singletonList("1.2.3.4"); + options.uriSubjectAlternativeNames = singletonList("ex:12345"); + options.otherSubjectAlternativeNames = singletonList("1.3.6.1.4.1.311.20.2.3;UTF8:test"); + options.excludeCommonNameFromSubjectAlternativeNames = true; + options.timeToLive = "8760h"; + options.keyType = CertificateKeyType.EC; + options.keyBits = 256; + options.maxPathLength = 3; + options.permittedDnsDomains = asList("subs1.example.com", "subs2.example.com"); + + GeneratedRootCertificate result = pkiSecretEngine.generateRoot(options); + + X509CertificateHolder certificate = (X509CertificateHolder) new PEMParser(new StringReader(result.certificate)) + .readObject(); + X509CertificateHolder issuingCA = (X509CertificateHolder) new PEMParser(new StringReader(result.issuingCA)) + .readObject(); + + // Check all subject name component options + assertEquals( + "C=USA,ST=NY,L=New York,STREET=123 Main Street,PostalCode=10030," + + "O=Test Org,OU=Test Unit,CN=test.example.com,SERIALNUMBER=9876543210", + certificate.getSubject().toString()); + + // Check subjectAlternativeNames, ipSubjectAlternativeNames, uriSubjectAlternativeNames, + // otherSubjectAlternativeNames & excludeCommonNameFromSubjectAlternativeNames options + assertNotNull(certificate.getExtension(subjectAlternativeName)); + GeneralNames generalNames = GeneralNames.getInstance(certificate.getExtension(subjectAlternativeName).getParsedValue()); + List subjectAlternativeNames = Arrays.stream(generalNames.getNames()) + .map(GeneralName::getName) + .map(ASN1Encodable::toString) + .collect(toList()); + assertEquals(asList("[1.3.6.1.4.1.311.20.2.3, [0]test]", "alt.example.com", "#01020304", "ex:12345"), + subjectAlternativeNames); + + // Check timeToLive option + assertEquals(8759, Duration.between(Instant.now().plusSeconds(30), certificate.getNotAfter().toInstant()).toHours()); + + // Check keyType option + SubjectPublicKeyInfo subPKI = certificate.getSubjectPublicKeyInfo(); + assertEquals(X9ObjectIdentifiers.id_ecPublicKey, subPKI.getAlgorithm().getAlgorithm()); + + // Check keyBits option + assertEquals(65, subPKI.getPublicKeyData().getOctets().length); + + // Check maxPathLength option + BasicConstraints basicCons = BasicConstraints.fromExtensions(certificate.getExtensions()); + assertEquals(BigInteger.valueOf(3), basicCons.getPathLenConstraint()); + + // Check permittedDnsDomains option + assertNotNull(certificate.getExtension(nameConstraints)); + NameConstraints nameCons = NameConstraints.getInstance(certificate.getExtension(nameConstraints).getParsedValue()); + List permittedDnsDomains = Arrays.stream(nameCons.getPermittedSubtrees()) + .map(GeneralSubtree::getBase) + .map(GeneralName::getName) + .map(ASN1Encodable::toString) + .collect(toList()); + assertEquals(asList("subs1.example.com", "subs2.example.com"), permittedDnsDomains); + + // Check returned cert is self-signed + assertEquals(certificate.getSubject(), issuingCA.getSubject()); + + // Check returned a serial number + assertNotNull(result.serialNumber); + assertFalse(result.serialNumber.isEmpty()); + } + + @Test + public void deleteRoot() { + GenerateRootOptions options = new GenerateRootOptions(); + options.subjectCommonName = "test.example.com"; + + GeneratedRootCertificate result = pkiSecretEngine.generateRoot(options); + assertNotNull(result.certificate); + + assertDoesNotThrow(() -> pkiSecretEngine.deleteRoot()); + } + + @Test + public void testGenerateIntermediateCSROptions() throws Exception { + GenerateIntermediateCSROptions options = new GenerateIntermediateCSROptions(); + options.subjectCommonName = "test.example.com"; + options.subjectOrganization = "Test Org"; + options.subjectOrganizationalUnit = "Test Unit"; + options.subjectStreetAddress = "123 Main Street"; + options.subjectLocality = "New York"; + options.subjectProvince = "NY"; + options.subjectCountry = "USA"; + options.subjectPostalCode = "10030"; + options.subjectSerialNumber = "9876543210"; + options.subjectAlternativeNames = singletonList("alt.example.com"); + options.ipSubjectAlternativeNames = singletonList("1.2.3.4"); + options.uriSubjectAlternativeNames = singletonList("ex:12345"); + options.otherSubjectAlternativeNames = singletonList("1.3.6.1.4.1.311.20.2.3;UTF8:test"); + options.excludeCommonNameFromSubjectAlternativeNames = true; + options.keyType = CertificateKeyType.EC; + options.keyBits = 256; + + GeneratedIntermediateCSRResult result = pkiSecretEngine.generateIntermediateCSR(options); + + PKCS10CertificationRequest csr = (PKCS10CertificationRequest) new PEMParser(new StringReader(result.csr)).readObject(); + // Check all subject name component options + assertEquals( + "C=USA,ST=NY,L=New York,STREET=123 Main Street,PostalCode=10030," + + "O=Test Org,OU=Test Unit,CN=test.example.com,SERIALNUMBER=9876543210", + csr.getSubject().toString()); + + // Check subjectAlternativeNames, ipSubjectAlternativeNames, uriSubjectAlternativeNames, + // otherSubjectAlternativeNames & excludeCommonNameFromSubjectAlternativeNames options + assertEquals(1, csr.getAttributes(pkcs_9_at_extensionRequest).length); + Extensions extReq = Extensions.getInstance(csr.getAttributes(pkcs_9_at_extensionRequest)[0].getAttributeValues()[0]); + + assertNotNull(extReq.getExtension(subjectAlternativeName)); + GeneralNames generalNames = GeneralNames.getInstance(extReq.getExtension(subjectAlternativeName).getParsedValue()); + List subjectAlternativeNames = Arrays.stream(generalNames.getNames()) + .map(GeneralName::getName) + .map(ASN1Encodable::toString) + .collect(toList()); + assertEquals(asList("[1.3.6.1.4.1.311.20.2.3, [0]test]", "alt.example.com", "#01020304", "ex:12345"), + subjectAlternativeNames); + + // Check keyType option + SubjectPublicKeyInfo subPKI = csr.getSubjectPublicKeyInfo(); + assertEquals(X9ObjectIdentifiers.id_ecPublicKey, subPKI.getAlgorithm().getAlgorithm()); + + // Check keyBits option + assertEquals(65, subPKI.getPublicKeyData().getOctets().length); + } + + @Test + public void testSignIntermediateCAOptions() throws Exception { + // Generate root CA in "pki" + GenerateRootOptions genRootOptions = new GenerateRootOptions(); + genRootOptions.subjectCommonName = "root.example.com"; + + GeneratedRootCertificate generatedRoot = pkiSecretEngine.generateRoot(genRootOptions); + assertNotNull(generatedRoot.certificate); + + // Generate intermediate CA CSR in "pki2" + VaultPKISecretEngine pkiSecretEngine2 = pkiSecretEngineFactory.engine("pki2"); + + GenerateIntermediateCSROptions genIntCSROptions = new GenerateIntermediateCSROptions(); + genIntCSROptions.subjectCommonName = "test1.example.com"; + + GeneratedIntermediateCSRResult csrResult = pkiSecretEngine2.generateIntermediateCSR(genIntCSROptions); + assertNotNull(csrResult.csr); + + // Sign the intermediate CA using "pki" + SignIntermediateCAOptions options = new SignIntermediateCAOptions(); + options.subjectCommonName = "test.example.com"; + options.subjectOrganization = "Test Org"; + options.subjectOrganizationalUnit = "Test Unit"; + options.subjectStreetAddress = "123 Main Street"; + options.subjectLocality = "New York"; + options.subjectProvince = "NY"; + options.subjectCountry = "USA"; + options.subjectPostalCode = "10030"; + options.subjectSerialNumber = "9876543210"; + options.subjectAlternativeNames = singletonList("alt.example.com"); + options.ipSubjectAlternativeNames = singletonList("1.2.3.4"); + options.uriSubjectAlternativeNames = singletonList("ex:12345"); + //options.otherSubjectAlternativeNames = singletonList("1.3.6.1.4.1.311.20.2.3;UTF8:test"); + options.excludeCommonNameFromSubjectAlternativeNames = true; + options.timeToLive = "8760h"; + options.maxPathLength = 3; + options.permittedDnsDomains = asList("subs1.example.com", "subs2.example.com"); + options.useCSRValues = false; + + SignedCertificate result = pkiSecretEngine.signIntermediateCA(csrResult.csr, options); + + X509CertificateHolder certificate = (X509CertificateHolder) new PEMParser(new StringReader(result.certificate)) + .readObject(); + X509CertificateHolder issuingCA = (X509CertificateHolder) new PEMParser(new StringReader(result.issuingCA)) + .readObject(); + + // Check all subject name component options + assertEquals( + "C=USA,ST=NY,L=New York,STREET=123 Main Street,PostalCode=10030," + + "O=Test Org,OU=Test Unit,CN=test.example.com,SERIALNUMBER=9876543210", + certificate.getSubject().toString()); + + // Check subjectAlternativeNames, ipSubjectAlternativeNames, uriSubjectAlternativeNames, + // otherSubjectAlternativeNames & excludeCommonNameFromSubjectAlternativeNames options + assertNotNull(certificate.getExtension(subjectAlternativeName)); + GeneralNames generalNames = GeneralNames.getInstance(certificate.getExtension(subjectAlternativeName).getParsedValue()); + List subjectAlternativeNames = Arrays.stream(generalNames.getNames()) + .map(GeneralName::getName) + .map(ASN1Encodable::toString) + .collect(toList()); + assertEquals(asList("alt.example.com", "#01020304", "ex:12345"), + subjectAlternativeNames); + + // Check timeToLive option + assertEquals(8759, Duration.between(Instant.now().plusSeconds(30), certificate.getNotAfter().toInstant()).toHours()); + + // Check keyType option + SubjectPublicKeyInfo subPKI = certificate.getSubjectPublicKeyInfo(); + assertEquals(rsaEncryption, subPKI.getAlgorithm().getAlgorithm()); + + // Check keyBits option + assertEquals(270, subPKI.getPublicKeyData().getOctets().length); + + // Check maxPathLength option + BasicConstraints basicCons = BasicConstraints.fromExtensions(certificate.getExtensions()); + assertEquals(BigInteger.valueOf(3), basicCons.getPathLenConstraint()); + + // Check permittedDnsDomains option + assertNotNull(certificate.getExtension(nameConstraints)); + NameConstraints nameCons = NameConstraints.getInstance(certificate.getExtension(nameConstraints).getParsedValue()); + List permittedDnsDomains = Arrays.stream(nameCons.getPermittedSubtrees()) + .map(GeneralSubtree::getBase) + .map(GeneralName::getName) + .map(ASN1Encodable::toString) + .collect(toList()); + assertEquals(asList("subs1.example.com", "subs2.example.com"), permittedDnsDomains); + + // Check returned cert is self-signed + assertEquals("CN=root.example.com", issuingCA.getSubject().toString()); + + // Check returned a serial number + assertNotNull(result.serialNumber); + assertFalse(result.serialNumber.isEmpty()); + } + + @Test + public void testSetSignedIntermediaCA() throws Exception { + // Generate root CA in "pki" + GenerateRootOptions genRootOptions = new GenerateRootOptions(); + genRootOptions.subjectCommonName = "root.example.com"; + + GeneratedRootCertificate generatedRoot = pkiSecretEngine.generateRoot(genRootOptions); + assertNotNull(generatedRoot.certificate); + + // Generate intermediate CA CSR in "pki2" + VaultPKISecretEngine pkiSecretEngine2 = pkiSecretEngineFactory.engine("pki2"); + + GenerateIntermediateCSROptions genIntCSROptions = new GenerateIntermediateCSROptions(); + genIntCSROptions.subjectCommonName = "test1.example.com"; + + GeneratedIntermediateCSRResult csrResult = pkiSecretEngine2.generateIntermediateCSR(genIntCSROptions); + assertNotNull(csrResult.csr); + + // Sign the intermediate CA using "pki" + SignIntermediateCAOptions options = new SignIntermediateCAOptions(); + SignedCertificate result = pkiSecretEngine.signIntermediateCA(csrResult.csr, options); + + // Set signed intermediate CA into "pki2" + pkiSecretEngine2.setSignedIntermediateCA(result.certificate); + + // Get CA cert and check subject + X509CertificateHolder certificate = (X509CertificateHolder) new PEMParser( + new StringReader(pkiSecretEngine2.getCertificateAuthority())).readObject(); + + assertEquals("CN=test1.example.com", certificate.getSubject().toString()); + } + + @Test + public void testUpdateAndGetRole() { + // Test all non-default values (except generateLease due to conflict with noStore) + RoleOptions options = new RoleOptions(); + options.timeToLive = "150m"; + options.maxTimeToLive = "300m"; + options.allowLocalhost = false; + options.allowedDomains = asList("a.example.com", "b.example.com"); + options.allowTemplatesInAllowedDomains = true; + options.allowBareDomains = true; + options.allowSubdomains = true; + options.allowGlobsInAllowedDomains = true; + options.allowAnyName = true; + options.enforceHostnames = false; + options.allowIpSubjectAlternativeNames = false; + options.allowedUriSubjectAlternativeNames = asList("ex:54321", "ex:12345"); + options.allowedOtherSubjectAlternativeNames = singletonList("1.3.6.1.4.1.311.20.2.3;UTF8:test"); + options.serverFlag = false; + options.clientFlag = false; + options.codeSigningFlag = true; + options.emailProtectionFlag = true; + options.keyType = CertificateKeyType.EC; + options.keyBits = 256; + options.keyUsages = asList(CertificateKeyUsage.CertSign, CertificateKeyUsage.CRLSign); + options.extendedKeyUsages = asList(CertificateExtendedKeyUsage.ClientAuth, CertificateExtendedKeyUsage.CodeSigning); + options.extendedKeyUsageOIDs = emptyList(); + options.useCSRCommonName = false; + options.useCSRSubjectAlternativeNames = false; + options.subjectOrganization = "Test Org"; + options.subjectOrganizationalUnit = "Test Unit"; + options.subjectStreetAddress = "123 Main Street"; + options.subjectLocality = "New York"; + options.subjectProvince = "NY"; + options.subjectCountry = "USA"; + options.subjectPostalCode = "10030"; + options.allowedSubjectSerialNumbers = asList("*_9876543210", "9876543210_*"); + options.generateLease = false; + options.noStore = true; + options.requireCommonName = false; + options.basicConstraintsValidForNonCA = true; + options.notBeforeDuration = "90s"; + + pkiSecretEngine.updateRole("test", options); + + RoleOptions result = pkiSecretEngine.getRole("test"); + assertEquals("9000", result.timeToLive); + assertEquals("18000", result.maxTimeToLive); + assertEquals(options.allowLocalhost, result.allowLocalhost); + assertEquals(options.allowedDomains, result.allowedDomains); + assertEquals(options.allowTemplatesInAllowedDomains, result.allowTemplatesInAllowedDomains); + assertEquals(options.allowBareDomains, result.allowBareDomains); + assertEquals(options.allowSubdomains, result.allowSubdomains); + assertEquals(options.allowGlobsInAllowedDomains, result.allowGlobsInAllowedDomains); + assertEquals(options.allowAnyName, result.allowAnyName); + assertEquals(options.enforceHostnames, result.enforceHostnames); + assertEquals(options.allowIpSubjectAlternativeNames, result.allowIpSubjectAlternativeNames); + assertEquals(options.allowedUriSubjectAlternativeNames, result.allowedUriSubjectAlternativeNames); + assertEquals(options.allowedOtherSubjectAlternativeNames, result.allowedOtherSubjectAlternativeNames); + assertEquals(options.serverFlag, result.serverFlag); + assertEquals(options.clientFlag, result.clientFlag); + assertEquals(options.codeSigningFlag, result.codeSigningFlag); + assertEquals(options.emailProtectionFlag, result.emailProtectionFlag); + assertEquals(options.keyType, result.keyType); + assertEquals(options.keyBits, result.keyBits); + assertEquals(options.keyUsages, result.keyUsages); + assertEquals(options.extendedKeyUsages, result.extendedKeyUsages); + assertEquals(options.extendedKeyUsageOIDs, result.extendedKeyUsageOIDs); + assertEquals(options.useCSRCommonName, result.useCSRCommonName); + assertEquals(options.useCSRSubjectAlternativeNames, result.useCSRSubjectAlternativeNames); + assertEquals(options.subjectOrganization, result.subjectOrganization); + assertEquals(options.subjectOrganizationalUnit, result.subjectOrganizationalUnit); + assertEquals(options.subjectStreetAddress, result.subjectStreetAddress); + assertEquals(options.subjectLocality, result.subjectLocality); + assertEquals(options.subjectProvince, result.subjectProvince); + assertEquals(options.subjectCountry, result.subjectCountry); + assertEquals(options.subjectPostalCode, result.subjectPostalCode); + assertEquals(options.allowedSubjectSerialNumbers, result.allowedSubjectSerialNumbers); + assertEquals(options.generateLease, result.generateLease); + assertEquals(options.noStore, result.noStore); + assertEquals(options.requireCommonName, result.requireCommonName); + assertEquals(options.basicConstraintsValidForNonCA, result.basicConstraintsValidForNonCA); + assertEquals("90", result.notBeforeDuration); + + // Test non-default generateLease option + RoleOptions options2 = new RoleOptions(); + options2.generateLease = true; + options2.noStore = false; + + pkiSecretEngine.updateRole("test", options2); + + RoleOptions result2 = pkiSecretEngine.getRole("test"); + assertEquals(true, result2.generateLease); + } + + @Test + public void testListRoles() { + RoleOptions options = new RoleOptions(); + pkiSecretEngine.updateRole("test1", options); + pkiSecretEngine.updateRole("test2", options); + + assertEquals(asList("test1", "test2"), pkiSecretEngine.listRoles()); + } + + @Test + public void testDeleteRole() { + RoleOptions options = new RoleOptions(); + pkiSecretEngine.updateRole("test1", options); + + assertEquals(singletonList("test1"), pkiSecretEngine.listRoles()); + + pkiSecretEngine.deleteRole("test1"); + + assertEquals(emptyList(), pkiSecretEngine.listRoles()); + } + + @Test + public void testGenerateCertificate() throws Exception { + // Generate root + GenerateRootOptions generateRootOptions = new GenerateRootOptions(); + generateRootOptions.subjectCommonName = "root.example.com"; + generateRootOptions.timeToLive = "24h"; + assertNotNull(pkiSecretEngine.generateRoot(generateRootOptions).certificate); + + // Generate role + RoleOptions roleOptions = new RoleOptions(); + roleOptions.allowedDomains = singletonList("example.com"); + roleOptions.allowSubdomains = true; + roleOptions.allowIpSubjectAlternativeNames = true; + roleOptions.allowedUriSubjectAlternativeNames = singletonList("ex:*"); + roleOptions.allowedOtherSubjectAlternativeNames = singletonList("1.3.6.1.4.1.311.20.2.3;UTF8:*"); + pkiSecretEngine.updateRole("test", roleOptions); + + // Test cert generation + GenerateCertificateOptions options = new GenerateCertificateOptions(); + options.subjectCommonName = "test.example.com"; + options.subjectAlternativeNames = singletonList("alt.example.com"); + options.ipSubjectAlternativeNames = singletonList("1.2.3.4"); + options.uriSubjectAlternativeNames = singletonList("ex:12345"); + options.otherSubjectAlternativeNames = singletonList("1.3.6.1.4.1.311.20.2.3;UTF8:test"); + options.excludeCommonNameFromSubjectAlternativeNames = true; + options.timeToLive = "333m"; + + GeneratedCertificate result = pkiSecretEngine.generateCertificate("test", options); + + X509CertificateHolder certificate = (X509CertificateHolder) new PEMParser(new StringReader(result.certificate)) + .readObject(); + X509CertificateHolder issuingCA = (X509CertificateHolder) new PEMParser(new StringReader(result.issuingCA)) + .readObject(); + + // Check all subject name component options + assertEquals("CN=test.example.com", certificate.getSubject().toString()); + + // Check subjectAlternativeNames, ipSubjectAlternativeNames, uriSubjectAlternativeNames, + // otherSubjectAlternativeNames & excludeCommonNameFromSubjectAlternativeNames options + assertNotNull(certificate.getExtension(subjectAlternativeName)); + GeneralNames generalNames = GeneralNames.getInstance(certificate.getExtension(subjectAlternativeName).getParsedValue()); + List subjectAlternativeNames = Arrays.stream(generalNames.getNames()) + .map(GeneralName::getName) + .map(ASN1Encodable::toString) + .collect(toList()); + assertEquals(asList("[1.3.6.1.4.1.311.20.2.3, [0]test]", "alt.example.com", "#01020304", "ex:12345"), + subjectAlternativeNames); + + // Check timeToLive option + assertEquals(332, Duration.between(Instant.now().plusSeconds(30), certificate.getNotAfter().toInstant()).toMinutes()); + + // Check returned cert is not self-signed + assertNotEquals(certificate.getSubject(), issuingCA.getSubject()); + + // Check returned a serial number + assertNotNull(result.serialNumber); + assertFalse(result.serialNumber.isEmpty()); + + // Check returned a private key type + assertEquals(CertificateKeyType.RSA, result.privateKeyType); + + // Check returned a private key + assertNotNull(result.privateKey); + assertFalse(result.privateKey.isEmpty()); + } + + @Test + public void testSignCSR() throws Exception { + // Generate root + GenerateRootOptions generateRootOptions = new GenerateRootOptions(); + generateRootOptions.subjectCommonName = "root.example.com"; + generateRootOptions.timeToLive = "24h"; + assertNotNull(pkiSecretEngine.generateRoot(generateRootOptions).certificate); + + // Generate role + RoleOptions roleOptions = new RoleOptions(); + roleOptions.allowedDomains = singletonList("example.com"); + roleOptions.allowSubdomains = true; + roleOptions.allowIpSubjectAlternativeNames = true; + roleOptions.allowedUriSubjectAlternativeNames = singletonList("ex:*"); + roleOptions.allowedOtherSubjectAlternativeNames = singletonList("1.3.6.1.4.1.311.20.2.3;UTF8:*"); + roleOptions.useCSRCommonName = false; + roleOptions.useCSRSubjectAlternativeNames = false; + pkiSecretEngine.updateRole("test", roleOptions); + + // Test CSR signing + + // CSR with "csr.example.com" and CN + String pemCSR = "-----BEGIN CERTIFICATE REQUEST-----\n" + + "MIICszCCAZsCAQAwbjELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD2Nzci5leGFtcGxl\n" + + "LmNvbTERMA8GA1UEBwwITmV3IFlvdXIxETAPBgNVBAoMCFRlc3QgT3JnMQswCQYD\n" + + "VQQIDAJOWTESMBAGA1UECwwJVGVzdCBVbml0MIIBIjANBgkqhkiG9w0BAQEFAAOC\n" + + "AQ8AMIIBCgKCAQEAn8G6AIZe/FBzLM7ALBt1Z5CcE64dkjYADVLUlSUBX5aPOycg\n" + + "oPm5RB0DMdgjRjsGvPRvz0NdpH7KsFYfeOh9ltI/YAiGITaHUlEDGH4fi4ZDSpWX\n" + + "jg4+DhqP3E4/krl9jpeV8PRRTNlrSh5X9wVWsL5rd1q+g5aBTav36/duyTpfwDkL\n" + + "MKGcaQVFqy+ChNWrj929EuahO2Sw8WLOGDqOQIYF0QlltdPil9YiumUoWUkYjZP9\n" + + "iunstT0yX+daEhxgahROAen6r7/rj8hCmNBw4CCAVsGHb8u8Ti+yn0Rov7SQxgd3\n" + + "1smQBvCXKk3HYnlwHHKORFI8v0q/NsE4PswBfwIDAQABoAAwDQYJKoZIhvcNAQEL\n" + + "BQADggEBABdPp6/5zTGTY/GxONbmKHByVUH4VPPXAFQMUEOitIJI8SxAmoNnGCW2\n" + + "VTeefrJsyixyLpgG7w5YXnd1947SOa9IN/1BWIMBVhhetPRSNAF+jM6paFmfAiVM\n" + + "kGPgpHQ1Tk4aPGVwXL51qZP8xwBUMjG+tx0RzRG1fgVUc2NWWNGlYx223xQuwsEg\n" + + "C3N5T+3bboAvMKTftaKtc43VOqw75iYnY+rOsvjPgPlBRyNuzRtVDhdvL5OlI8AL\n" + + "Y0EZ2xRrYf2m+BnAGInOThIHqfFsRE7sdNJemE5jJsB5y/tpH4MQi2DZIJce45bu\n" + + "VVlwf9Wg4h289zEGKPbz35MPUMoQfec=\n" + + "-----END CERTIFICATE REQUEST-----\n"; + + GenerateCertificateOptions options = new GenerateCertificateOptions(); + options.subjectCommonName = "test.example.com"; + options.subjectAlternativeNames = singletonList("alt.example.com"); + options.ipSubjectAlternativeNames = singletonList("1.2.3.4"); + options.uriSubjectAlternativeNames = singletonList("ex:12345"); + options.otherSubjectAlternativeNames = singletonList("1.3.6.1.4.1.311.20.2.3;UTF8:test"); + options.excludeCommonNameFromSubjectAlternativeNames = true; + options.timeToLive = "333m"; + + SignedCertificate result = pkiSecretEngine.signRequest("test", pemCSR, options); + + X509CertificateHolder certificate = (X509CertificateHolder) new PEMParser(new StringReader(result.certificate)) + .readObject(); + X509CertificateHolder issuingCA = (X509CertificateHolder) new PEMParser(new StringReader(result.issuingCA)) + .readObject(); + + // Check all subject name component options + assertEquals("CN=test.example.com", certificate.getSubject().toString()); + + // Check subjectAlternativeNames, ipSubjectAlternativeNames, uriSubjectAlternativeNames, + // otherSubjectAlternativeNames & excludeCommonNameFromSubjectAlternativeNames options + assertNotNull(certificate.getExtension(subjectAlternativeName)); + GeneralNames generalNames = GeneralNames.getInstance(certificate.getExtension(subjectAlternativeName).getParsedValue()); + List subjectAlternativeNames = Arrays.stream(generalNames.getNames()) + .map(GeneralName::getName) + .map(ASN1Encodable::toString) + .collect(toList()); + assertEquals(asList("[1.3.6.1.4.1.311.20.2.3, [0]test]", "alt.example.com", "#01020304", "ex:12345"), + subjectAlternativeNames); + + // Check timeToLive option + assertEquals(332, Duration.between(Instant.now().plusSeconds(30), certificate.getNotAfter().toInstant()).toMinutes()); + + // Check returned cert is not self-signed + assertNotEquals(certificate.getSubject(), issuingCA.getSubject()); + + // Check returned a serial number + assertNotNull(result.serialNumber); + assertFalse(result.serialNumber.isEmpty()); + } + + @Test + public void testGetCertificate() { + // Generate root + GenerateRootOptions generateRootOptions = new GenerateRootOptions(); + generateRootOptions.subjectCommonName = "root.example.com"; + generateRootOptions.timeToLive = "24h"; + assertNotNull(pkiSecretEngine.generateRoot(generateRootOptions).certificate); + + // Generate role + RoleOptions roleOptions = new RoleOptions(); + roleOptions.allowedDomains = singletonList("test.example.com"); + roleOptions.allowBareDomains = true; + pkiSecretEngine.updateRole("test", roleOptions); + + // Generate cert + GenerateCertificateOptions options = new GenerateCertificateOptions(); + options.subjectCommonName = "test.example.com"; + String certSerialNumber = pkiSecretEngine.generateCertificate("test", options).serialNumber; + + // Test get cert + String pemCert = pkiSecretEngine.getCertificate(certSerialNumber); + assertNotNull(pemCert); + assertDoesNotThrow(() -> new PEMParser(new StringReader(pemCert)).readObject()); + } + + @Test + public void testListCertificates() { + // Generate root + GenerateRootOptions generateRootOptions = new GenerateRootOptions(); + generateRootOptions.subjectCommonName = "root.example.com"; + generateRootOptions.timeToLive = "24h"; + assertNotNull(pkiSecretEngine.generateRoot(generateRootOptions).certificate); + + // Generate role + RoleOptions roleOptions = new RoleOptions(); + roleOptions.allowedDomains = singletonList("example.com"); + roleOptions.allowSubdomains = true; + pkiSecretEngine.updateRole("test", roleOptions); + + // Generate certs + GenerateCertificateOptions options = new GenerateCertificateOptions(); + options.subjectCommonName = "test1.example.com"; + String certSerialNumber1 = pkiSecretEngine.generateCertificate("test", options).serialNumber; + options.subjectCommonName = "test2.example.com"; + String certSerialNumber2 = pkiSecretEngine.generateCertificate("test", options).serialNumber; + + // Test list certs + List certSerials = pkiSecretEngine.listCertificates(); + assertTrue(certSerials.containsAll(asList(certSerialNumber1, certSerialNumber2))); + } + + @Test + public void testRevokeCertificate() { + // Generate root + GenerateRootOptions generateRootOptions = new GenerateRootOptions(); + generateRootOptions.subjectCommonName = "root.example.com"; + generateRootOptions.timeToLive = "24h"; + assertNotNull(pkiSecretEngine.generateRoot(generateRootOptions).certificate); + + // Generate role + RoleOptions roleOptions = new RoleOptions(); + roleOptions.allowedDomains = singletonList("test.example.com"); + roleOptions.allowBareDomains = true; + pkiSecretEngine.updateRole("test", roleOptions); + + // Generate cert + GenerateCertificateOptions options = new GenerateCertificateOptions(); + options.subjectCommonName = "test.example.com"; + String certSerialNumber = pkiSecretEngine.generateCertificate("test", options).serialNumber; + + // Test revoke + OffsetDateTime revokedAt = pkiSecretEngine.revokeCertificate(certSerialNumber); + assertEquals(Duration.between(revokedAt, OffsetDateTime.now()).toMinutes(), 0); + } + + @Test + public void testGetCRL() throws Exception { + // Generate root + GenerateRootOptions generateRootOptions = new GenerateRootOptions(); + generateRootOptions.subjectCommonName = "root.example.com"; + generateRootOptions.timeToLive = "24h"; + assertNotNull(pkiSecretEngine.generateRoot(generateRootOptions).certificate); + + // Generate role + RoleOptions roleOptions = new RoleOptions(); + roleOptions.allowedDomains = singletonList("test.example.com"); + roleOptions.allowBareDomains = true; + pkiSecretEngine.updateRole("test", roleOptions); + + // Generate cert + GenerateCertificateOptions options = new GenerateCertificateOptions(); + options.subjectCommonName = "test.example.com"; + String certSerialNumber = pkiSecretEngine.generateCertificate("test", options).serialNumber; + + // Revoke cert + pkiSecretEngine.revokeCertificate(certSerialNumber); + + // Test CRL get + String pemCRL = pkiSecretEngine.getCertificateRevocationList(); + assertDoesNotThrow(() -> (X509CRLHolder) new PEMParser(new StringReader(pemCRL)).readObject()); + } + + @Test + public void testRotateCRL() { + // Generate root + GenerateRootOptions generateRootOptions = new GenerateRootOptions(); + generateRootOptions.subjectCommonName = "root.example.com"; + generateRootOptions.timeToLive = "24h"; + assertNotNull(pkiSecretEngine.generateRoot(generateRootOptions).certificate); + + // Test CRL rotate + assertTrue(pkiSecretEngine.rotateCertificateRevocationList()); + } + + @Test + public void testGetCAChain() throws Exception { + // Generate root CA in "pki" + GenerateRootOptions genRootOptions = new GenerateRootOptions(); + genRootOptions.subjectCommonName = "root.example.com"; + GeneratedRootCertificate generatedRootCertificate = pkiSecretEngine.generateRoot(genRootOptions); + assertNotNull(generatedRootCertificate.certificate); + + // Generate intermediate CA CSR in "pki2" + VaultPKISecretEngine pkiSecretEngine2 = pkiSecretEngineFactory.engine("pki2"); + + GenerateIntermediateCSROptions genIntCSROptions = new GenerateIntermediateCSROptions(); + genIntCSROptions.subjectCommonName = "test1.example.com"; + + GeneratedIntermediateCSRResult csrResult = pkiSecretEngine2.generateIntermediateCSR(genIntCSROptions); + assertNotNull(csrResult.csr); + + // Sign the intermediate CA using "pki" + SignIntermediateCAOptions options = new SignIntermediateCAOptions(); + SignedCertificate result = pkiSecretEngine.signIntermediateCA(csrResult.csr, options); + + // Set signed intermediate CA & root CA chain into "pki2" + pkiSecretEngine2.setSignedIntermediateCA(result.certificate + "\n" + generatedRootCertificate.certificate); + + // Get CA chain and check subjects + PEMParser pemParser = new PEMParser(new StringReader(pkiSecretEngine2.getCertificateAuthorityChain())); + + X509CertificateHolder intCACert = (X509CertificateHolder) pemParser.readObject(); + assertEquals("CN=test1.example.com", intCACert.getSubject().toString()); + + X509CertificateHolder rootCACert = (X509CertificateHolder) pemParser.readObject(); + assertEquals("CN=root.example.com", rootCACert.getSubject().toString()); + } + + @Test + public void testConfigureCA() throws Exception { + // Generate root CA in "pki" + GenerateRootOptions genRootOptions = new GenerateRootOptions(); + genRootOptions.subjectCommonName = "root.example.com"; + genRootOptions.exportPrivateKey = true; + GeneratedRootCertificate generatedRootCertificate = pkiSecretEngine.generateRoot(genRootOptions); + assertNotNull(generatedRootCertificate.certificate); + assertNotNull(generatedRootCertificate.privateKey); + + // Set root CA from "pki" into "pki2" + VaultPKISecretEngine pkiSecretEngine2 = pkiSecretEngineFactory.engine("pki2"); + pkiSecretEngine2 + .configCertificateAuthority(generatedRootCertificate.certificate + "\n" + generatedRootCertificate.privateKey); + + // Get CA cert and check subject + X509CertificateHolder certificate = (X509CertificateHolder) new PEMParser( + new StringReader(pkiSecretEngine2.getCertificateAuthority())).readObject(); + + assertEquals("CN=root.example.com", certificate.getSubject().toString()); + } + + @Test + public void testConfigureURLs() { + // Generate root + GenerateRootOptions generateRootOptions = new GenerateRootOptions(); + generateRootOptions.subjectCommonName = "root.example.com"; + generateRootOptions.timeToLive = "24h"; + assertNotNull(pkiSecretEngine.generateRoot(generateRootOptions).certificate); + + // Update URLs & read them + ConfigURLsOptions options = new ConfigURLsOptions(); + options.issuingCertificates = asList("certs1.example.com", "certs2.example.com"); + options.crlDistributionPoints = asList("crl1.example.com", "crl2.example.com"); + options.ocspServers = asList("ocsp1.example.com", "ocsp2.example.com"); + + pkiSecretEngine.configURLs(options); + + ConfigURLsOptions result = pkiSecretEngine.readURLsConfig(); + assertEquals(options.issuingCertificates, result.issuingCertificates); + assertEquals(options.crlDistributionPoints, result.crlDistributionPoints); + assertEquals(options.ocspServers, result.ocspServers); + } + + @Test + public void testConfigureCRL() { + // Generate root + GenerateRootOptions generateRootOptions = new GenerateRootOptions(); + generateRootOptions.subjectCommonName = "root.example.com"; + generateRootOptions.timeToLive = "24h"; + assertNotNull(pkiSecretEngine.generateRoot(generateRootOptions).certificate); + + // Update CRL & read them + ConfigCRLOptions options = new ConfigCRLOptions(); + options.expiry = "123h"; + options.disable = true; + + pkiSecretEngine.configCRL(options); + + ConfigCRLOptions result = pkiSecretEngine.readCRLConfig(); + assertEquals(options.expiry, result.expiry); + assertEquals(options.disable, result.disable); + } + + @Test + public void testTidy() { + // Generate root + GenerateRootOptions generateRootOptions = new GenerateRootOptions(); + generateRootOptions.subjectCommonName = "root.example.com"; + generateRootOptions.timeToLive = "24h"; + assertNotNull(pkiSecretEngine.generateRoot(generateRootOptions).certificate); + + // Execute tidy + TidyOptions options = new TidyOptions(); + options.tidyCertStore = true; + options.tidyRevokedCerts = true; + options.safetyBuffer = "24h"; + assertDoesNotThrow(() -> pkiSecretEngine.tidy(options)); + } +} diff --git a/integration-tests/vault/src/test/resources/application-vault-pki.properties b/integration-tests/vault/src/test/resources/application-vault-pki.properties new file mode 100644 index 00000000000000..fdcc775e0ca40e --- /dev/null +++ b/integration-tests/vault/src/test/resources/application-vault-pki.properties @@ -0,0 +1,13 @@ +quarkus.vault.url=https://localhost:8200 +quarkus.vault.authentication.client-token=pkiroot + +#quarkus.vault.tls.skip-verify=true +quarkus.vault.tls.ca-cert=src/test/resources/vault-tls.crt + +quarkus.vault.log-confidentiality-level=low +quarkus.vault.renew-grace-period=10 + +quarkus.log.category."io.quarkus.vault".level=DEBUG + +# CI can sometimes be slow, there is no need to fail a test if Vault doesn't respond in 1 second which is the default +quarkus.vault.read-timeout=5S diff --git a/test-framework/vault/src/main/java/io/quarkus/vault/test/VaultTestExtension.java b/test-framework/vault/src/main/java/io/quarkus/vault/test/VaultTestExtension.java index 3d496359b764f1..2f15097561de6f 100644 --- a/test-framework/vault/src/main/java/io/quarkus/vault/test/VaultTestExtension.java +++ b/test-framework/vault/src/main/java/io/quarkus/vault/test/VaultTestExtension.java @@ -381,6 +381,13 @@ private void initVault() throws InterruptedException, IOException { // TOTP execVault("vault secrets enable totp"); + + // PKI + + execVault("vault secrets enable pki"); + execVault("vault secrets enable -path=pki2 pki"); + execVault("vault secrets tune -max-lease-ttl=8760h pki"); + execVault("vault token create -policy=root -id=pkiroot"); } private String readResourceContent(String path) throws IOException {