From 2aefe24f9222cae1f74293e56adf9d9e9174d177 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. --- docs/src/main/asciidoc/vault-pki.adoc | 250 ++++ docs/src/main/asciidoc/vault.adoc | 3 + .../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/VaultPKIConfigCRLData.java | 11 + .../dto/pki/VaultPKIConfigCRLResult.java | 6 + .../dto/pki/VaultPKIConfigURLsData.java | 20 + .../dto/pki/VaultPKIConfigURLsResult.java | 6 + .../client/dto/pki/VaultPKIConstants.java | 8 + .../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 + .../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 + .../client/dto/sys/VaultEnableEngineBody.java | 27 + .../runtime/client/dto/sys/VaultTuneBody.java | 18 + .../runtime/client/dto/sys/VaultTuneData.java | 18 + .../client/dto/sys/VaultTuneResult.java | 6 + .../quarkus/vault/VaultPKISecretEngine.java | 238 ++++ .../vault/VaultPKISecretEngineFactory.java | 19 + .../vault/VaultSystemBackendEngine.java | 55 + .../io/quarkus/vault/pki/CAChainData.java | 80 ++ .../java/io/quarkus/vault/pki/CRLData.java | 79 ++ .../java/io/quarkus/vault/pki/CSRData.java | 61 + .../io/quarkus/vault/pki/CertificateData.java | 79 ++ .../pki/CertificateExtendedKeyUsage.java | 17 + .../quarkus/vault/pki/CertificateKeyType.java | 6 + .../vault/pki/CertificateKeyUsage.java | 13 + .../quarkus/vault/pki/ConfigCRLOptions.java | 27 + .../quarkus/vault/pki/ConfigURLsOptions.java | 39 + .../java/io/quarkus/vault/pki/DataFormat.java | 6 + .../vault/pki/GenerateCertificateOptions.java | 112 ++ .../pki/GenerateIntermediateCSROptions.java | 214 ++++ .../vault/pki/GenerateRootOptions.java | 242 ++++ .../vault/pki/GeneratedCertificate.java | 71 ++ .../pki/GeneratedIntermediateCSRResult.java | 41 + .../vault/pki/GeneratedRootCertificate.java | 59 + .../io/quarkus/vault/pki/PrivateKeyData.java | 114 ++ .../quarkus/vault/pki/PrivateKeyEncoding.java | 6 + .../io/quarkus/vault/pki/RoleOptions.java | 415 +++++++ .../vault/pki/SignIntermediateCAOptions.java | 220 ++++ .../quarkus/vault/pki/SignedCertificate.java | 51 + .../io/quarkus/vault/pki/TidyOptions.java | 41 + .../io/quarkus/vault/pki/X509Parsing.java | 80 ++ .../vault/runtime/VaultPKIManager.java | 684 +++++++++++ .../vault/runtime/VaultPKIManagerFactory.java | 27 + .../runtime/VaultSystemBackendManager.java | 66 ++ .../vault/runtime/client/VaultClient.java | 4 + .../runtime/client/VertxVaultClient.java | 9 + .../backend/VaultInternalSystemBackend.java | 19 + .../VaultInternalPKISecretEngine.java | 157 +++ .../vault/sys/EnableEngineOptions.java | 39 + .../quarkus/vault/sys/VaultSecretEngine.java | 21 + .../io/quarkus/vault/sys/VaultTuneInfo.java | 45 + .../java/io/quarkus/vault/VaultPKIITCase.java | 1043 +++++++++++++++++ .../java/io/quarkus/vault/VaultSysITCase.java | 43 + .../application-vault-pki.properties | 13 + .../vault/test/VaultTestExtension.java | 7 + 81 files changed, 5590 insertions(+) create mode 100644 docs/src/main/asciidoc/vault-pki.adoc 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/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/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/VaultPKIConstants.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/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/model/src/main/java/io/quarkus/vault/runtime/client/dto/sys/VaultEnableEngineBody.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/sys/VaultTuneBody.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/sys/VaultTuneData.java create mode 100644 extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/sys/VaultTuneResult.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/CAChainData.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CRLData.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CSRData.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CertificateData.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/DataFormat.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/PrivateKeyData.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/PrivateKeyEncoding.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/pki/X509Parsing.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 extensions/vault/runtime/src/main/java/io/quarkus/vault/sys/EnableEngineOptions.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/sys/VaultSecretEngine.java create mode 100644 extensions/vault/runtime/src/main/java/io/quarkus/vault/sys/VaultTuneInfo.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/docs/src/main/asciidoc/vault-pki.adoc b/docs/src/main/asciidoc/vault-pki.adoc new file mode 100644 index 0000000000000..5960ffc9a7de6 --- /dev/null +++ b/docs/src/main/asciidoc/vault-pki.adoc @@ -0,0 +1,250 @@ +//// +This guide is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc +//// += Using HashiCorp Vault's PKI Secret Engine + +include::./attributes.adoc[] +:root-token: s.5VUS8pte13RqekCB2fmMT3u2 +:client-token: s.s93BVzJPzBiIGuYJHBTkG8Uw +:config-file: application.properties +:extension-status: preview +:base-guide: link:vault[Vault guide] + +Vault's PKI Secret Engine generates dynamic X.509 certificates. It allows services to get certificates without +manually generating a private key and CSR, submitting to a CA, and waiting for signed certificate. The PKI secret +engine allows dynamically generating certificates, which has the following advantages over classic CA scenarios: + +* Generating certificates with short TTLs reduces the need for and/or size of CRLs. +* Allows for ephemeral certificates that are generated upon application startup, stored in memory and discarded on shutdown. + +In this guide we cover the following: + +* setup: configuring the engine to generate certificates +* generation: generating certificates using roles +* revocation: revoking a previously generated certificate + +See https://www.vaultproject.io/docs/secrets/pki/index.html#pki-secrets-engine[Vault PKI Secrets Engine's official documentation] + +include::./status-include.adoc[] + +== Prerequisites + +To complete this guide, you need: + +* to complete the "Starting Vault" section of the {base-guide} +* roughly 15 minutes +* an IDE +* JDK 11+ installed with `JAVA_HOME` configured appropriately +* Apache Maven {maven-version} +* Docker installed + +== Setup + +We assume there is a Vault running from the {base-guide}, and the root token is known. +The first step consists of activating the PKI Secret Engine, and configuring a CA certificate +and private key: + +[source,bash, subs=attributes+] +---- +docker exec -it dev-vault sh +export VAULT_TOKEN={root-token} + +vault secrets enable pki +# ==> Success! Enabled the pki secrets engine at: pki/ + +vault secrets tune -max-lease-ttl=8760h pki +# ==> Success! Tuned the secrets engine at: pki/ + +vault write pki/root/generate/internal \ + common_name=my-website.com \ + ttl=8760h +# ==> Success! Configured CA with self-signed root +# ==> Key Value +# ==> --- ----- +# ==> certificate -----BEGIN CERTIFICATE-----... +# ==> expiration 1536807433 +# ==> issuing_ca -----BEGIN CERTIFICATE-----... +# ==> serial_number 7c:f1:fb:2c:6e:4d:99:0e:82:1b:08:0a:81:ed:61:3e:1d:fa:f5:29 +---- + +This example configures the CA with an internal self-signed root certificate and associated key pair that is +managed by Vault. Alternatively, you can configure the CA with an existing key pair. + +NOTE: CA configuration can be done programmatically using `VaultPKISecretEngine.generateRoot(GenerateRootOptions)` + +With the CA configured we now require a role to be defined that determines what parameters are allowed when +generating certificates. + +Here we create a role `example-dot-com` that allows certificates with the common name +allowed to be any subdomain of `my-website.com`. + +[source,bash, subs=attributes+] +---- +vault write pki/roles/example-dot-com \ + allowed_domains=my-website.com \ + allow_subdomains=true \ + max_ttl=72h +# ==> Success! Data written to: pki/roles/example-dot-com +---- + +NOTE: Role configuration can be done programmatically using +`VaultPKISecretEngine.updateRole(String role, RoleOptions options)` + +== Generating Certificates + +First, let's create a simple Quarkus application with Vault and Jackson extensions: + +[source,bash, subs=attributes+] +---- +mvn io.quarkus.platform:quarkus-maven-plugin:{quarkus-version}:create \ + -DprojectGroupId=org.acme \ + -DprojectArtifactId=vault-pki-quickstart \ + -DclassName="org.acme.quickstart.GreetingResource" \ + -Dpath="/hello" \ + -Dextensions="resteasy,vault,resteasy-jackson" +cd vault-pki-quickstart +---- + +Now, configure access to Vault from the `{config-file}`: + +[source, properties] +---- +# vault url +quarkus.vault.url=http://localhost:8200 + +# vault authentication +quarkus.vault.authentication.userpass.username=bob +quarkus.vault.authentication.userpass.password=sinclair +---- + +We can then add a new endpoint that will allow us to generate a certificate using the configured CA & role: + +[source, java, subs=attributes+] +---- +@Path("/pki") +@Produces(TEXT_PLAIN) +@Consumes(TEXT_PLAIN) +public class PKIResource { + + @Inject + public VaultPKISecretEngine pkiSecretEngine; + + @POST + @Path("/generate") + public String generate(String subdomain) { + GenerateCertificateOptions options = new GenerateCertificateOptions() + .setSubjectCommonName(subdomain + ".my-website.com"); + GeneratedCertificate generated = pkiSecretEngine.generateCertificate("example-dot-com", options); + return generated.certificate.getData(); + } +} +---- + +After compiling and starting the Quarkus application, let's generate a new certificate with a generated key pair: +[source,bash, subs=attributes+] +---- +curl -X POST --data 'a-subdomain' --header "Content-Type: text/plain" http://localhost:8080/pki/generate + +# ==> -----BEGIN CERTIFICATE----- +# ==> ... +# ==> -----END CERTIFICATE----- +---- + +Alternatively we can generate a key pair and CSR locally and generate a certificate by having vault sign our CSR. + +Let's add a new method that accepts a CSR: + +[source, java, subs=attributes+] +---- +@POST +@Path("/sign") +public String sign(String csr) { + GenerateCertificateOptions options = new GenerateCertificateOptions(); + SignedCertificate signed = pkiSecretEngine.signRequest("example-dot-com", csr, options); + return signed.certificate.getData(); +} +---- + +Now we can generate a CSR (e.g. using OpenSSL) and pass it to our `/sign` endpoint to sign and generate a +certificate from the CSR: + +[source,bash, subs=attributes+] +---- +openssl req -newkey rsa:2048 -keyout example.key -out example.csr + +curl -X POST --data @example.csr --header "Content-Type: text/plain" http://localhost:8080/pki/sign + +# ==> -----BEGIN CERTIFICATE----- +# ==> ... +# ==> -----END CERTIFICATE----- +---- + +== Revoking Certificates + +Let's add another new method to our `PKIResource`: + +[source, java, subs=attributes+] +---- +@POST +@Path("/revoke") +public void revoke(String serialNumber) { + pkiSecretEngine.revokeCertificate(serialNumber); +} +---- + +And revoke a previously generated certificate: +[source,bash, subs=attributes+] +---- +curl -X POST --data '1d:2e:c6:06:45:18:60:0e:23:d6:c5:17:43:c0:fe:46:ed:d1:50:be' --header "Content-Type: text/plain" http://localhost:8080/pki/revoke +# ==> No Data +---- + +== Dynamically Mounting PKI Engines + +Quarkus's Vault PKI support includes that ability to mount & unmount PKI engines dynamically using +the `VaultPKISecretEngineFactory` & `VaultSystemBackendEngine` interfaces. + +To enable, or mount, a new PKI engine at specific mount path you can use the +`VaultSystemBackendEngine.enable` method: + +[source, java, subs=attributes+] +---- +// Obtain interfaces via injection or other standard CDI method. +VaultSystemBackendEngine systemBackendEngine = ...; +VaultPKISecretEngineFactory pkiSecretEngineFactory = ...; + +// Mount a PKI engine at a specified path. +EnableEngineOptions options = new EnableEngineOptions() + .setMaxLeaseTimeToLive("8760h"); +systemBackendEngine.enable(VaultSecretEngine.PKI, "pki-dyn", "A dynamic PKI engine", options); + +// Obtain an engine manager for the newly mounted PKI engine. +VaultPKISecretEngine dynPkiSecretEngine = pkiSecretEngineFactory.engine("pki-dyn"); + +// Use dynamically created engine as you please. +dynPkiSecretEngine.generateRoot(new GenerateRootOptions()); +---- + +To disable (aka unmount) a PKI engine at a specific path you simply use the `VaultSystemBackendEngine.disable` method: + +[source, java, subs=attributes+] +---- +systemBackendEngine.disable("pki-dyn"); +---- + +NOTE: If you want to test if a specific mount path is already in use you can use +`VaultSystemBackendEngine.isEngineMounted(String)`. + +== Conclusion + +The PKI Secret Engine is a great tool for managing CAs and their provisioned certificates. +We have seen the most obvious functions of the interface but all of the methods and modes of Vault's PKI secret +engine are supported, including: + + * Provisioning roles used to generate certificates. + * Storing the root CA externally and issuing certificates from intermediate CAs. + * Reading current CRLs for each provisioned engine instance. + +Feel free to look at the `VaultPKISecretEngine` & `VaultPKISecretEngineFactory` interfaces. diff --git a/docs/src/main/asciidoc/vault.adoc b/docs/src/main/asciidoc/vault.adoc index 358fd4215c5ed..c326f2e6cb72e 100644 --- a/docs/src/main/asciidoc/vault.adoc +++ b/docs/src/main/asciidoc/vault.adoc @@ -13,6 +13,7 @@ include::./attributes.adoc[] :extension-status: preview :vault-auth-guide: link:vault-auth[Vault Authentication guide] :vault-transit-guide: link:vault-transit[Vault Transit Secret Engine Guide] +:vault-pki-guide: link:vault-pki[Vault PKI Secret Engine Guide] :vault-datasource-guide: link:vault-datasource[Vault Datasource Guide] https://www.vaultproject.io/[HashiCorp Vault] is a multi-purpose tool aiming at protecting sensitive data, such as @@ -26,6 +27,8 @@ https://www.vaultproject.io/docs/secrets/kv/index.html[Vault kv secret engine] a * support for the https://www.vaultproject.io/docs/secrets/totp[TOTP Secret Engine] * support for the https://www.vaultproject.io/docs/secrets/transit[Transit Secret Engine] as documented in the {vault-transit-guide} +* support for the https://www.vaultproject.io/docs/secrets/pki[PKI Secret Engine] as documented +in the {vault-pki-guide} * support for several authentication methods as documented in the {vault-auth-guide} Under the hood, the Quarkus Vault extension takes care of authentication when negotiating a client Vault token plus 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 a4415fadae92f..bef92cf0ee02f 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 0000000000000..39df87ee6cf82 --- /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 0000000000000..aa8808434b5e7 --- /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 0000000000000..b2370836df0c6 --- /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 0000000000000..990a776cbfbbc --- /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 0000000000000..b79fa94f24c97 --- /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 0000000000000..9a4f24edebb32 --- /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 0000000000000..d7ec4fb79cc2a --- /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/VaultPKIConfigCRLData.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigCRLData.java new file mode 100644 index 0000000000000..2feeb76e4f5cf --- /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 0000000000000..c14b9e86d83b7 --- /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/VaultPKIConfigURLsData.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConfigURLsData.java new file mode 100644 index 0000000000000..8a68a2bdb13e0 --- /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 0000000000000..37b31f267569b --- /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/VaultPKIConstants.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConstants.java new file mode 100644 index 0000000000000..95c6a34202e66 --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIConstants.java @@ -0,0 +1,8 @@ +package io.quarkus.vault.runtime.client.dto.pki; + +public class VaultPKIConstants { + + public static final String DEFAULT_CERTIFICATE_FORMAT = "pem"; + public static final String DEFAULT_KEY_ENCODING = "pkcs8"; + +} 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 0000000000000..39abb879b6bbc --- /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; + + @JsonProperty("private_key_format") + public String privateKeyFormat; + + @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 0000000000000..b868d5da63b3a --- /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 0000000000000..c48f80a514396 --- /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 0000000000000..3600604115dd8 --- /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; + + @JsonProperty("private_key_format") + public String privateKeyFormat; + + @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 0000000000000..f4c5a3cac6755 --- /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 0000000000000..2795f6e4ff9fc --- /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 0000000000000..c623b68f82c39 --- /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; + + @JsonProperty("private_key_format") + public String privateKeyFormat; + + @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 0000000000000..56b36d44efaa7 --- /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 0000000000000..7e2ca14483744 --- /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 0000000000000..6e6645b3dea7c --- /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 0000000000000..70cd16b9c29d6 --- /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 0000000000000..bf3416ca543ae --- /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 0000000000000..e68abed641969 --- /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 0000000000000..7ef9ad4294fe0 --- /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/VaultPKIRolesListData.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/pki/VaultPKIRolesListData.java new file mode 100644 index 0000000000000..ade81f308d5fc --- /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 0000000000000..2ef8f70b247cb --- /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 0000000000000..fd6d72f1698b7 --- /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 0000000000000..89d60028cfcfa --- /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; + + @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 0000000000000..389d9d3df9a0e --- /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 0000000000000..89b4921ca399b --- /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 0000000000000..9176f440cb604 --- /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; + + @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 0000000000000..3d476b07e512b --- /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/model/src/main/java/io/quarkus/vault/runtime/client/dto/sys/VaultEnableEngineBody.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/sys/VaultEnableEngineBody.java new file mode 100644 index 0000000000000..628803957e648 --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/sys/VaultEnableEngineBody.java @@ -0,0 +1,27 @@ +package io.quarkus.vault.runtime.client.dto.sys; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class VaultEnableEngineBody { + + public static class Config { + + @JsonProperty("default_lease_ttl") + public String defaultLeaseTimeToLive; + + @JsonProperty("max_lease_ttl") + public String maxLeaseTimeToLive; + + } + + public String type; + + public String description = ""; + + public Config config; + + public Map options; + +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/sys/VaultTuneBody.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/sys/VaultTuneBody.java new file mode 100644 index 0000000000000..2258fc45fd0d3 --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/sys/VaultTuneBody.java @@ -0,0 +1,18 @@ +package io.quarkus.vault.runtime.client.dto.sys; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class VaultTuneBody { + + public String description; + + @JsonProperty("default_lease_ttl") + public Long defaultLeaseTimeToLive; + + @JsonProperty("max_lease_ttl") + public Long maxLeaseTimeToLive; + + @JsonProperty("force_no_cache") + public Boolean forceNoCache; + +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/sys/VaultTuneData.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/sys/VaultTuneData.java new file mode 100644 index 0000000000000..3c9a72b439ba2 --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/sys/VaultTuneData.java @@ -0,0 +1,18 @@ +package io.quarkus.vault.runtime.client.dto.sys; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class VaultTuneData { + + public String description; + + @JsonProperty("default_lease_ttl") + public Long defaultLeaseTimeToLive; + + @JsonProperty("max_lease_ttl") + public Long maxLeaseTimeToLive; + + @JsonProperty("force_no_cache") + public Boolean forceNoCache; + +} diff --git a/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/sys/VaultTuneResult.java b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/sys/VaultTuneResult.java new file mode 100644 index 0000000000000..386856c18f652 --- /dev/null +++ b/extensions/vault/model/src/main/java/io/quarkus/vault/runtime/client/dto/sys/VaultTuneResult.java @@ -0,0 +1,6 @@ +package io.quarkus.vault.runtime.client.dto.sys; + +import io.quarkus.vault.runtime.client.dto.AbstractVaultDTO; + +public class VaultTuneResult extends AbstractVaultDTO { +} 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 0000000000000..b2bac8783b1e3 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultPKISecretEngine.java @@ -0,0 +1,238 @@ +package io.quarkus.vault; + +import java.time.OffsetDateTime; +import java.util.List; + +import io.quarkus.vault.pki.CAChainData; +import io.quarkus.vault.pki.CRLData; +import io.quarkus.vault.pki.CertificateData; +import io.quarkus.vault.pki.ConfigCRLOptions; +import io.quarkus.vault.pki.ConfigURLsOptions; +import io.quarkus.vault.pki.DataFormat; +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 CA certificate (PEM encoded). + * + * @return Certificate authority certificate. + */ + CertificateData.PEM getCertificateAuthority(); + + /** + * Retrieves the engine's CA certificate. + * + * @param format Format of the returned certificate data. + * @return Certificate authority certificate. + */ + CertificateData getCertificateAuthority(DataFormat format); + + /** + * 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 CA chain (PEM encoded). + * + * @return Certificate authority chain. + */ + CAChainData.PEM getCertificateAuthorityChain(); + + /** + * Retrieves the engine's CRL (PEM encoded). + * + * @return Certificate revocation list. + */ + CRLData.PEM getCertificateRevocationList(); + + /** + * Retrieves the engine's CRL. + * + * @param format Format of the returned crl data. + * @return Certificate revocation list. + */ + CRLData getCertificateRevocationList(DataFormat format); + + /** + * Forces a rotation of the associated CRL. + */ + boolean rotateCertificateRevocationList(); + + /** + * List all issued certificate serial numbers. + * + * @return List of certificate serialize numbers. + */ + List getCertificates(); + + /** + * Retrieve a specific certificate (PEM encoded). + * + * @param serial Serial number of certificate. + * @return Certificate or null if no certificate exists. + */ + CertificateData.PEM 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 getRoles(); + + /** + * 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 0000000000000..d51830a9fbd62 --- /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/VaultSystemBackendEngine.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultSystemBackendEngine.java index c625500cadb6d..1218c13bce441 100644 --- a/extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultSystemBackendEngine.java +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultSystemBackendEngine.java @@ -3,10 +3,13 @@ import java.util.List; import io.quarkus.vault.runtime.config.VaultBootstrapConfig; +import io.quarkus.vault.sys.EnableEngineOptions; import io.quarkus.vault.sys.VaultHealth; import io.quarkus.vault.sys.VaultHealthStatus; import io.quarkus.vault.sys.VaultInit; import io.quarkus.vault.sys.VaultSealStatus; +import io.quarkus.vault.sys.VaultSecretEngine; +import io.quarkus.vault.sys.VaultTuneInfo; /** * This service provides access to the system backend. @@ -79,4 +82,56 @@ public interface VaultSystemBackendEngine { * @return a list of all policy names */ List getPolicies(); + + /** + * Get the tune info for a secret engine at a specific mount. + * + * @param mount Name of the mount + * @return current tune info + */ + VaultTuneInfo getTuneInfo(String mount); + + /** + * Update the tune info for a secret engine at a specific mount. + * + * @param mount Name of the mount + * @param tuneInfo Tune info with fields to update + */ + void updateTuneInfo(String mount, VaultTuneInfo tuneInfo); + + /** + * Check if an engine is mounted at a specific mount. + * + * @param mount Name of the mount + * @return True if an engine is mounted, false otherwise + */ + boolean isEngineMounted(String mount); + + /** + * Enables a secret engine at a specific mount. + * + * @param engine Type of engine to mount. + * @param mount Engine mount path. + * @param description Human friendly description of mount point. + * @param options Engine options. + */ + void enable(VaultSecretEngine engine, String mount, String description, EnableEngineOptions options); + + /** + * Enables a secret engine at a specific mount. + * + * @param engineType Type of engine to mount. + * @param mount Engine mount path. + * @param description Human friendly description of mount point. + * @param options Engine options. + */ + void enable(String engineType, String mount, String description, EnableEngineOptions options); + + /** + * Disables the engine at a specific mount. + * + * @param mount Engine mount path. + */ + void disable(String mount); + } diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CAChainData.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CAChainData.java new file mode 100644 index 0000000000000..2053f80a9b21a --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CAChainData.java @@ -0,0 +1,80 @@ +package io.quarkus.vault.pki; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.List; + +public interface CAChainData { + + /** + * Format of {@link #getData()} property. + */ + DataFormat getFormat(); + + /** + * Data in {@link DataFormat#PEM} or {@link DataFormat#DER} format. + * + * @see #getFormat() + */ + Object getData(); + + /** + * Parse and generate {@link java.security.cert.X509Certificate}s from {@link #getData()}. + */ + List getCertificates() throws CertificateException; + + /** + * {@link DataFormat#DER} implementation of {@link CAChainData} + */ + class DER implements CAChainData { + + private final byte[] derData; + + public DER(byte[] derData) { + this.derData = derData; + } + + @Override + public DataFormat getFormat() { + return DataFormat.DER; + } + + @Override + public byte[] getData() { + return derData; + } + + @Override + public List getCertificates() throws CertificateException { + return X509Parsing.parseDERCertificates(derData); + } + } + + /** + * {@link DataFormat#PEM} implementation of {@link CAChainData} + */ + class PEM implements CAChainData { + + private final String pemData; + + public PEM(String pemData) { + this.pemData = pemData; + } + + @Override + public DataFormat getFormat() { + return DataFormat.PEM; + } + + @Override + public String getData() { + return pemData; + } + + @Override + public List getCertificates() throws CertificateException { + return X509Parsing.parsePEMCertificates(pemData); + } + } + +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CRLData.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CRLData.java new file mode 100644 index 0000000000000..1e1402e3d285a --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CRLData.java @@ -0,0 +1,79 @@ +package io.quarkus.vault.pki; + +import java.security.cert.CRLException; +import java.security.cert.X509CRL; + +public interface CRLData { + + /** + * Format of {@link #getData()} property. + */ + DataFormat getFormat(); + + /** + * Data in {@link DataFormat#PEM} or {@link DataFormat#DER} format. + * + * @see #getFormat() + */ + Object getData(); + + /** + * Parse and generate {@link java.security.cert.X509CRL} from {@link #getData()}. + */ + X509CRL getCRL() throws CRLException; + + /** + * {@link DataFormat#DER} implementation of {@link CRLData} + */ + class DER implements CRLData { + + private final byte[] derData; + + public DER(byte[] derData) { + this.derData = derData; + } + + @Override + public DataFormat getFormat() { + return DataFormat.DER; + } + + @Override + public byte[] getData() { + return derData; + } + + @Override + public X509CRL getCRL() throws CRLException { + return X509Parsing.parseDERCRL(derData); + } + } + + /** + * {@link DataFormat#PEM} implementation of {@link CRLData} + */ + class PEM implements CRLData { + + private final String pemData; + + public PEM(String pemData) { + this.pemData = pemData; + } + + @Override + public DataFormat getFormat() { + return DataFormat.PEM; + } + + @Override + public String getData() { + return pemData; + } + + @Override + public X509CRL getCRL() throws CRLException { + return X509Parsing.parsePEMCRL(pemData); + } + } + +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CSRData.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CSRData.java new file mode 100644 index 0000000000000..7767f66bd7711 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CSRData.java @@ -0,0 +1,61 @@ +package io.quarkus.vault.pki; + +public interface CSRData { + + /** + * Format of {@link #getData()} property. + */ + DataFormat getFormat(); + + /** + * Data in {@link DataFormat#PEM} or {@link DataFormat#DER} format. + * + * @see #getFormat() + */ + Object getData(); + + /** + * {@link DataFormat#DER} implementation of {@link CSRData} + */ + class DER implements CSRData { + + private final byte[] derData; + + public DER(byte[] derData) { + this.derData = derData; + } + + @Override + public DataFormat getFormat() { + return DataFormat.DER; + } + + @Override + public byte[] getData() { + return derData; + } + } + + /** + * {@link DataFormat#PEM} implementation of {@link CSRData} + */ + class PEM implements CSRData { + + private final String pemData; + + public PEM(String pemData) { + this.pemData = pemData; + } + + @Override + public DataFormat getFormat() { + return DataFormat.PEM; + } + + @Override + public String getData() { + return pemData; + } + } + +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CertificateData.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CertificateData.java new file mode 100644 index 0000000000000..698a81e09540f --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/CertificateData.java @@ -0,0 +1,79 @@ +package io.quarkus.vault.pki; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +public interface CertificateData { + + /** + * Format of {@link #getData()} property. + */ + DataFormat getFormat(); + + /** + * Data in {@link DataFormat#PEM} or {@link DataFormat#DER} format. + * + * @see #getFormat() + */ + Object getData(); + + /** + * Parse and generate {@link java.security.cert.X509Certificate} from {@link #getData()}. + */ + X509Certificate getCertificate() throws CertificateException; + + /** + * {@link DataFormat#DER} implementation of {@link CertificateData} + */ + class DER implements CertificateData { + + private final byte[] derData; + + public DER(byte[] derData) { + this.derData = derData; + } + + @Override + public DataFormat getFormat() { + return DataFormat.DER; + } + + @Override + public byte[] getData() { + return derData; + } + + @Override + public X509Certificate getCertificate() throws CertificateException { + return X509Parsing.parseDERCertificate(derData); + } + } + + /** + * {@link DataFormat#PEM} implementation of {@link CertificateData} + */ + class PEM implements CertificateData { + + private final String pemData; + + public PEM(String pemData) { + this.pemData = pemData; + } + + @Override + public DataFormat getFormat() { + return DataFormat.PEM; + } + + @Override + public String getData() { + return pemData; + } + + @Override + public X509Certificate getCertificate() throws CertificateException { + return X509Parsing.parsePEMCertificate(pemData); + } + } + +} 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 0000000000000..2dfac75d6bc45 --- /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 0000000000000..cdf2dfdc6b64c --- /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 0000000000000..3fb731084375f --- /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 0000000000000..d94228dfc952f --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/ConfigCRLOptions.java @@ -0,0 +1,27 @@ +package io.quarkus.vault.pki; + +/** + * Options for configuring the CRL + */ +public class ConfigCRLOptions { + + /** + * Specifies the time until expiration. + */ + public String expiry; + + /** + * Disables or enables CRL building. + */ + public Boolean disable; + + public ConfigCRLOptions setExpiry(String expiry) { + this.expiry = expiry; + return this; + } + + public ConfigCRLOptions setDisable(Boolean disable) { + this.disable = disable; + return this; + } +} 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 0000000000000..661a5a8353471 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/ConfigURLsOptions.java @@ -0,0 +1,39 @@ +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; + + public ConfigURLsOptions setIssuingCertificates(List issuingCertificates) { + this.issuingCertificates = issuingCertificates; + return this; + } + + public ConfigURLsOptions setCrlDistributionPoints(List crlDistributionPoints) { + this.crlDistributionPoints = crlDistributionPoints; + return this; + } + + public ConfigURLsOptions setOcspServers(List ocspServers) { + this.ocspServers = ocspServers; + return this; + } +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/DataFormat.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/DataFormat.java new file mode 100644 index 0000000000000..f415962a83f59 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/DataFormat.java @@ -0,0 +1,6 @@ +package io.quarkus.vault.pki; + +public enum DataFormat { + DER, + PEM +} 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 0000000000000..2942f7478e776 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GenerateCertificateOptions.java @@ -0,0 +1,112 @@ +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; + + /** + * Specifies returned format of certificate & private key data. If unspecified it defaults + * to {@link DataFormat#PEM} + */ + public DataFormat format; + + /** + * Specifies encoding of private key data. If unspecified it defaults to {@link PrivateKeyEncoding#PKCS8}. + */ + public PrivateKeyEncoding privateKeyEncoding; + + public GenerateCertificateOptions setSubjectCommonName(String subjectCommonName) { + this.subjectCommonName = subjectCommonName; + return this; + } + + public GenerateCertificateOptions setSubjectAlternativeNames(List subjectAlternativeNames) { + this.subjectAlternativeNames = subjectAlternativeNames; + return this; + } + + public GenerateCertificateOptions setExcludeCommonNameFromSubjectAlternativeNames( + Boolean excludeCommonNameFromSubjectAlternativeNames) { + this.excludeCommonNameFromSubjectAlternativeNames = excludeCommonNameFromSubjectAlternativeNames; + return this; + } + + public GenerateCertificateOptions setIpSubjectAlternativeNames( + List ipSubjectAlternativeNames) { + this.ipSubjectAlternativeNames = ipSubjectAlternativeNames; + return this; + } + + public GenerateCertificateOptions setUriSubjectAlternativeNames( + List uriSubjectAlternativeNames) { + this.uriSubjectAlternativeNames = uriSubjectAlternativeNames; + return this; + } + + public GenerateCertificateOptions setOtherSubjectAlternativeNames( + List otherSubjectAlternativeNames) { + this.otherSubjectAlternativeNames = otherSubjectAlternativeNames; + return this; + } + + public GenerateCertificateOptions setTimeToLive(String timeToLive) { + this.timeToLive = timeToLive; + return this; + } + + public GenerateCertificateOptions setFormat(DataFormat format) { + this.format = format; + return this; + } + + public GenerateCertificateOptions setPrivateKeyEncoding(PrivateKeyEncoding privateKeyEncoding) { + this.privateKeyEncoding = privateKeyEncoding; + return this; + } +} 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 0000000000000..ee90f0ab56a56 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GenerateIntermediateCSROptions.java @@ -0,0 +1,214 @@ +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; + + /** + * Specifies returned format of CSR & private key data. If unspecified it defaults + * to {@link DataFormat#PEM} + */ + public DataFormat format; + + /** + * Specifies encoding of private key data. If unspecified it defaults to {@link PrivateKeyEncoding#PKCS8}. + */ + public PrivateKeyEncoding privateKeyEncoding; + + /** + * Flag determining if the generated private key should be exported or kept internally. + */ + public boolean exportPrivateKey = false; + + public GenerateIntermediateCSROptions setSubjectCommonName(String subjectCommonName) { + this.subjectCommonName = subjectCommonName; + return this; + } + + public GenerateIntermediateCSROptions setSubjectOrganization(String subjectOrganization) { + this.subjectOrganization = subjectOrganization; + return this; + } + + public GenerateIntermediateCSROptions setSubjectOrganizationalUnit(String subjectOrganizationalUnit) { + this.subjectOrganizationalUnit = subjectOrganizationalUnit; + return this; + } + + public GenerateIntermediateCSROptions setSubjectStreetAddress(String subjectStreetAddress) { + this.subjectStreetAddress = subjectStreetAddress; + return this; + } + + public GenerateIntermediateCSROptions setSubjectPostalCode(String subjectPostalCode) { + this.subjectPostalCode = subjectPostalCode; + return this; + } + + public GenerateIntermediateCSROptions setSubjectLocality(String subjectLocality) { + this.subjectLocality = subjectLocality; + return this; + } + + public GenerateIntermediateCSROptions setSubjectProvince(String subjectProvince) { + this.subjectProvince = subjectProvince; + return this; + } + + public GenerateIntermediateCSROptions setSubjectCountry(String subjectCountry) { + this.subjectCountry = subjectCountry; + return this; + } + + public GenerateIntermediateCSROptions setSubjectSerialNumber(String subjectSerialNumber) { + this.subjectSerialNumber = subjectSerialNumber; + return this; + } + + public GenerateIntermediateCSROptions setSubjectAlternativeNames( + List subjectAlternativeNames) { + this.subjectAlternativeNames = subjectAlternativeNames; + return this; + } + + public GenerateIntermediateCSROptions setExcludeCommonNameFromSubjectAlternativeNames( + Boolean excludeCommonNameFromSubjectAlternativeNames) { + this.excludeCommonNameFromSubjectAlternativeNames = excludeCommonNameFromSubjectAlternativeNames; + return this; + } + + public GenerateIntermediateCSROptions setIpSubjectAlternativeNames( + List ipSubjectAlternativeNames) { + this.ipSubjectAlternativeNames = ipSubjectAlternativeNames; + return this; + } + + public GenerateIntermediateCSROptions setUriSubjectAlternativeNames( + List uriSubjectAlternativeNames) { + this.uriSubjectAlternativeNames = uriSubjectAlternativeNames; + return this; + } + + public GenerateIntermediateCSROptions setOtherSubjectAlternativeNames( + List otherSubjectAlternativeNames) { + this.otherSubjectAlternativeNames = otherSubjectAlternativeNames; + return this; + } + + public GenerateIntermediateCSROptions setKeyType(CertificateKeyType keyType) { + this.keyType = keyType; + return this; + } + + public GenerateIntermediateCSROptions setKeyBits(Integer keyBits) { + this.keyBits = keyBits; + return this; + } + + public GenerateIntermediateCSROptions setExportPrivateKey(boolean exportPrivateKey) { + this.exportPrivateKey = exportPrivateKey; + return this; + } + + public GenerateIntermediateCSROptions setFormat(DataFormat format) { + this.format = format; + return this; + } + + public GenerateIntermediateCSROptions setPrivateKeyEncoding( + PrivateKeyEncoding privateKeyEncoding) { + this.privateKeyEncoding = privateKeyEncoding; + return this; + } +} 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 0000000000000..4f43518a0ff35 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GenerateRootOptions.java @@ -0,0 +1,242 @@ +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; + + /** + * Specifies returned format of certificate & private key data. If unspecified it defaults + * to {@link DataFormat#PEM} + */ + public DataFormat format; + + /** + * Specifies encoding of private key data. If unspecified it defaults to {@link PrivateKeyEncoding#PKCS8}. + */ + public PrivateKeyEncoding privateKeyEncoding; + + public GenerateRootOptions setSubjectCommonName(String subjectCommonName) { + this.subjectCommonName = subjectCommonName; + return this; + } + + public GenerateRootOptions setSubjectOrganization(String subjectOrganization) { + this.subjectOrganization = subjectOrganization; + return this; + } + + public GenerateRootOptions setSubjectOrganizationalUnit(String subjectOrganizationalUnit) { + this.subjectOrganizationalUnit = subjectOrganizationalUnit; + return this; + } + + public GenerateRootOptions setSubjectStreetAddress(String subjectStreetAddress) { + this.subjectStreetAddress = subjectStreetAddress; + return this; + } + + public GenerateRootOptions setSubjectPostalCode(String subjectPostalCode) { + this.subjectPostalCode = subjectPostalCode; + return this; + } + + public GenerateRootOptions setSubjectLocality(String subjectLocality) { + this.subjectLocality = subjectLocality; + return this; + } + + public GenerateRootOptions setSubjectProvince(String subjectProvince) { + this.subjectProvince = subjectProvince; + return this; + } + + public GenerateRootOptions setSubjectCountry(String subjectCountry) { + this.subjectCountry = subjectCountry; + return this; + } + + public GenerateRootOptions setSubjectSerialNumber(String subjectSerialNumber) { + this.subjectSerialNumber = subjectSerialNumber; + return this; + } + + public GenerateRootOptions setSubjectAlternativeNames(List subjectAlternativeNames) { + this.subjectAlternativeNames = subjectAlternativeNames; + return this; + } + + public GenerateRootOptions setExcludeCommonNameFromSubjectAlternativeNames( + Boolean excludeCommonNameFromSubjectAlternativeNames) { + this.excludeCommonNameFromSubjectAlternativeNames = excludeCommonNameFromSubjectAlternativeNames; + return this; + } + + public GenerateRootOptions setIpSubjectAlternativeNames(List ipSubjectAlternativeNames) { + this.ipSubjectAlternativeNames = ipSubjectAlternativeNames; + return this; + } + + public GenerateRootOptions setUriSubjectAlternativeNames(List uriSubjectAlternativeNames) { + this.uriSubjectAlternativeNames = uriSubjectAlternativeNames; + return this; + } + + public GenerateRootOptions setOtherSubjectAlternativeNames( + List otherSubjectAlternativeNames) { + this.otherSubjectAlternativeNames = otherSubjectAlternativeNames; + return this; + } + + public GenerateRootOptions setTimeToLive(String timeToLive) { + this.timeToLive = timeToLive; + return this; + } + + public GenerateRootOptions setKeyType(CertificateKeyType keyType) { + this.keyType = keyType; + return this; + } + + public GenerateRootOptions setKeyBits(Integer keyBits) { + this.keyBits = keyBits; + return this; + } + + public GenerateRootOptions setExportPrivateKey(boolean exportPrivateKey) { + this.exportPrivateKey = exportPrivateKey; + return this; + } + + public GenerateRootOptions setMaxPathLength(Integer maxPathLength) { + this.maxPathLength = maxPathLength; + return this; + } + + public GenerateRootOptions setPermittedDnsDomains(List permittedDnsDomains) { + this.permittedDnsDomains = permittedDnsDomains; + return this; + } + + public GenerateRootOptions setFormat(DataFormat format) { + this.format = format; + return this; + } + + public GenerateRootOptions setPrivateKeyEncoding(PrivateKeyEncoding privateKeyEncoding) { + this.privateKeyEncoding = privateKeyEncoding; + return this; + } +} 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 0000000000000..0c94de88aed8e --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GeneratedCertificate.java @@ -0,0 +1,71 @@ +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. + */ + public CertificateData certificate; + + /** + * Issuing CA of generated certificate. + */ + public CertificateData issuingCA; + + /** + * Complete CA chain of generated certificate. + */ + public List caChain; + + /** + * Type of generated private key + */ + public CertificateKeyType privateKeyType; + + /** + * Generated private Key (PEM Encoded). + */ + public PrivateKeyData privateKey; + + public GeneratedCertificate setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + return this; + } + + public GeneratedCertificate setCertificate(CertificateData certificate) { + this.certificate = certificate; + return this; + } + + public GeneratedCertificate setIssuingCA(CertificateData issuingCA) { + this.issuingCA = issuingCA; + return this; + } + + public GeneratedCertificate setCaChain(List caChain) { + this.caChain = caChain; + return this; + } + + public GeneratedCertificate setPrivateKeyType(CertificateKeyType privateKeyType) { + this.privateKeyType = privateKeyType; + return this; + } + + public GeneratedCertificate setPrivateKey(PrivateKeyData privateKey) { + this.privateKey = privateKey; + return this; + } +} 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 0000000000000..35c8a22d6e04e --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GeneratedIntermediateCSRResult.java @@ -0,0 +1,41 @@ +package io.quarkus.vault.pki; + +import io.quarkus.vault.VaultPKISecretEngine; + +/** + * Result of {@link VaultPKISecretEngine#generateIntermediateCSR(GenerateIntermediateCSROptions)}. + */ +public class GeneratedIntermediateCSRResult { + + /** + * Certificate Signing Request. + */ + public CSRData csr; + + /** + * Type of generated private key. + */ + public CertificateKeyType privateKeyType; + + /** + * Generated private key. + *

+ * Only valid if {@link GenerateIntermediateCSROptions#exportPrivateKey} was true. + */ + public PrivateKeyData privateKey; + + public GeneratedIntermediateCSRResult setCsr(CSRData csr) { + this.csr = csr; + return this; + } + + public GeneratedIntermediateCSRResult setPrivateKeyType(CertificateKeyType privateKeyType) { + this.privateKeyType = privateKeyType; + return this; + } + + public GeneratedIntermediateCSRResult setPrivateKey(PrivateKeyData privateKey) { + this.privateKey = privateKey; + return this; + } +} 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 0000000000000..46c0d46c78f18 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/GeneratedRootCertificate.java @@ -0,0 +1,59 @@ +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. + */ + public CertificateData certificate; + + /** + * Issuing CA of generated certificate. + */ + public CertificateData issuingCA; + + /** + * Type of generated private key + */ + public CertificateKeyType privateKeyType; + + /** + * Generated private Key (PEM Encoded). + */ + public PrivateKeyData privateKey; + + public GeneratedRootCertificate setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + return this; + } + + public GeneratedRootCertificate setCertificate(CertificateData certificate) { + this.certificate = certificate; + return this; + } + + public GeneratedRootCertificate setIssuingCA(CertificateData issuingCA) { + this.issuingCA = issuingCA; + return this; + } + + public GeneratedRootCertificate setPrivateKeyType(CertificateKeyType privateKeyType) { + this.privateKeyType = privateKeyType; + return this; + } + + public GeneratedRootCertificate setPrivateKey(PrivateKeyData privateKey) { + this.privateKey = privateKey; + return this; + } +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/PrivateKeyData.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/PrivateKeyData.java new file mode 100644 index 0000000000000..7393385256ea7 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/PrivateKeyData.java @@ -0,0 +1,114 @@ +package io.quarkus.vault.pki; + +import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; + +public interface PrivateKeyData { + + /** + * Format of {@link #getData()} property. + */ + DataFormat getFormat(); + + /** + * Data in {@link DataFormat#DER} or {@link DataFormat#PEM} format. + * + * @see #getFormat() + */ + Object getData(); + + /** + * Is {@link #getData() data} encoded in PKCS8 format? + */ + boolean isPKCS8(); + + /** + * Parse and generate {@link KeySpec} from {@link #getData()}. + * + * @implNote This currently only works with PKCS8 encoded data. + * @throws IllegalStateException When called on non-PKCS8 encoded data. + */ + KeySpec getKeySpec(); + + /** + * {@link DataFormat#DER} implementation of {@link PrivateKeyData} + */ + class DER implements PrivateKeyData { + + private final byte[] derData; + private final boolean pkcs8; + + public DER(byte[] derData, boolean pkcs8) { + this.derData = derData; + this.pkcs8 = pkcs8; + } + + @Override + public DataFormat getFormat() { + return DataFormat.DER; + } + + @Override + public byte[] getData() { + return derData; + } + + @Override + public boolean isPKCS8() { + return pkcs8; + } + + @Override + public KeySpec getKeySpec() { + if (!pkcs8) { + throw new IllegalStateException("Key must be PKCS8 encoded"); + } + return new PKCS8EncodedKeySpec(derData); + } + } + + /** + * {@link DataFormat#PEM} implementation of {@link PrivateKeyData} + */ + class PEM implements PrivateKeyData { + + private final String pemData; + private final boolean pkcs8; + + public PEM(String pemData, boolean pkcs8) { + this.pemData = pemData; + this.pkcs8 = pkcs8; + } + + @Override + public DataFormat getFormat() { + return DataFormat.PEM; + } + + @Override + public String getData() { + return pemData; + } + + @Override + public boolean isPKCS8() { + return pkcs8; + } + + @Override + public KeySpec getKeySpec() { + if (!pkcs8) { + throw new IllegalStateException("Key must be PKCS8 encoded"); + } + + String base64Data = pemData + .replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", ""); + + byte[] derData = Base64.getMimeDecoder().decode(base64Data); + + return new PKCS8EncodedKeySpec(derData); + } + } +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/PrivateKeyEncoding.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/PrivateKeyEncoding.java new file mode 100644 index 0000000000000..633bed3f13857 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/PrivateKeyEncoding.java @@ -0,0 +1,6 @@ +package io.quarkus.vault.pki; + +public enum PrivateKeyEncoding { + PKCS8, + RAW +} 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 0000000000000..3711f1f2926f0 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/RoleOptions.java @@ -0,0 +1,415 @@ +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; + + public RoleOptions setTimeToLive(String timeToLive) { + this.timeToLive = timeToLive; + return this; + } + + public RoleOptions setMaxTimeToLive(String maxTimeToLive) { + this.maxTimeToLive = maxTimeToLive; + return this; + } + + public RoleOptions setAllowLocalhost(Boolean allowLocalhost) { + this.allowLocalhost = allowLocalhost; + return this; + } + + public RoleOptions setAllowedDomains(List allowedDomains) { + this.allowedDomains = allowedDomains; + return this; + } + + public RoleOptions setAllowTemplatesInAllowedDomains(Boolean allowTemplatesInAllowedDomains) { + this.allowTemplatesInAllowedDomains = allowTemplatesInAllowedDomains; + return this; + } + + public RoleOptions setAllowBareDomains(Boolean allowBareDomains) { + this.allowBareDomains = allowBareDomains; + return this; + } + + public RoleOptions setAllowSubdomains(Boolean allowSubdomains) { + this.allowSubdomains = allowSubdomains; + return this; + } + + public RoleOptions setAllowGlobsInAllowedDomains(Boolean allowGlobsInAllowedDomains) { + this.allowGlobsInAllowedDomains = allowGlobsInAllowedDomains; + return this; + } + + public RoleOptions setAllowAnyName(Boolean allowAnyName) { + this.allowAnyName = allowAnyName; + return this; + } + + public RoleOptions setEnforceHostnames(Boolean enforceHostnames) { + this.enforceHostnames = enforceHostnames; + return this; + } + + public RoleOptions setAllowIpSubjectAlternativeNames(Boolean allowIpSubjectAlternativeNames) { + this.allowIpSubjectAlternativeNames = allowIpSubjectAlternativeNames; + return this; + } + + public RoleOptions setAllowedUriSubjectAlternativeNames( + List allowedUriSubjectAlternativeNames) { + this.allowedUriSubjectAlternativeNames = allowedUriSubjectAlternativeNames; + return this; + } + + public RoleOptions setAllowedOtherSubjectAlternativeNames( + List allowedOtherSubjectAlternativeNames) { + this.allowedOtherSubjectAlternativeNames = allowedOtherSubjectAlternativeNames; + return this; + } + + public RoleOptions setServerFlag(Boolean serverFlag) { + this.serverFlag = serverFlag; + return this; + } + + public RoleOptions setClientFlag(Boolean clientFlag) { + this.clientFlag = clientFlag; + return this; + } + + public RoleOptions setCodeSigningFlag(Boolean codeSigningFlag) { + this.codeSigningFlag = codeSigningFlag; + return this; + } + + public RoleOptions setEmailProtectionFlag(Boolean emailProtectionFlag) { + this.emailProtectionFlag = emailProtectionFlag; + return this; + } + + public RoleOptions setKeyType(CertificateKeyType keyType) { + this.keyType = keyType; + return this; + } + + public RoleOptions setKeyBits(Integer keyBits) { + this.keyBits = keyBits; + return this; + } + + public RoleOptions setKeyUsages(List keyUsages) { + this.keyUsages = keyUsages; + return this; + } + + public RoleOptions setExtendedKeyUsages( + List extendedKeyUsages) { + this.extendedKeyUsages = extendedKeyUsages; + return this; + } + + public RoleOptions setExtendedKeyUsageOIDs(List extendedKeyUsageOIDs) { + this.extendedKeyUsageOIDs = extendedKeyUsageOIDs; + return this; + } + + public RoleOptions setUseCSRCommonName(Boolean useCSRCommonName) { + this.useCSRCommonName = useCSRCommonName; + return this; + } + + public RoleOptions setUseCSRSubjectAlternativeNames(Boolean useCSRSubjectAlternativeNames) { + this.useCSRSubjectAlternativeNames = useCSRSubjectAlternativeNames; + return this; + } + + public RoleOptions setSubjectOrganization(String subjectOrganization) { + this.subjectOrganization = subjectOrganization; + return this; + } + + public RoleOptions setSubjectOrganizationalUnit(String subjectOrganizationalUnit) { + this.subjectOrganizationalUnit = subjectOrganizationalUnit; + return this; + } + + public RoleOptions setSubjectStreetAddress(String subjectStreetAddress) { + this.subjectStreetAddress = subjectStreetAddress; + return this; + } + + public RoleOptions setSubjectPostalCode(String subjectPostalCode) { + this.subjectPostalCode = subjectPostalCode; + return this; + } + + public RoleOptions setSubjectLocality(String subjectLocality) { + this.subjectLocality = subjectLocality; + return this; + } + + public RoleOptions setSubjectProvince(String subjectProvince) { + this.subjectProvince = subjectProvince; + return this; + } + + public RoleOptions setSubjectCountry(String subjectCountry) { + this.subjectCountry = subjectCountry; + return this; + } + + public RoleOptions setAllowedSubjectSerialNumbers(List allowedSubjectSerialNumbers) { + this.allowedSubjectSerialNumbers = allowedSubjectSerialNumbers; + return this; + } + + public RoleOptions setGenerateLease(Boolean generateLease) { + this.generateLease = generateLease; + return this; + } + + public RoleOptions setNoStore(Boolean noStore) { + this.noStore = noStore; + return this; + } + + public RoleOptions setRequireCommonName(Boolean requireCommonName) { + this.requireCommonName = requireCommonName; + return this; + } + + public RoleOptions setPolicyOIDs(List policyOIDs) { + this.policyOIDs = policyOIDs; + return this; + } + + public RoleOptions setBasicConstraintsValidForNonCA(Boolean basicConstraintsValidForNonCA) { + this.basicConstraintsValidForNonCA = basicConstraintsValidForNonCA; + return this; + } + + public RoleOptions setNotBeforeDuration(String notBeforeDuration) { + this.notBeforeDuration = notBeforeDuration; + return this; + } +} 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 0000000000000..2e5296a7691e4 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/SignIntermediateCAOptions.java @@ -0,0 +1,220 @@ +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; + + /** + * Specifies returned format of certificate data. If unspecified it defaults + * to {@link DataFormat#PEM} + */ + public DataFormat format; + + public SignIntermediateCAOptions setSubjectCommonName(String subjectCommonName) { + this.subjectCommonName = subjectCommonName; + return this; + } + + public SignIntermediateCAOptions setSubjectOrganization(String subjectOrganization) { + this.subjectOrganization = subjectOrganization; + return this; + } + + public SignIntermediateCAOptions setSubjectOrganizationalUnit(String subjectOrganizationalUnit) { + this.subjectOrganizationalUnit = subjectOrganizationalUnit; + return this; + } + + public SignIntermediateCAOptions setSubjectStreetAddress(String subjectStreetAddress) { + this.subjectStreetAddress = subjectStreetAddress; + return this; + } + + public SignIntermediateCAOptions setSubjectPostalCode(String subjectPostalCode) { + this.subjectPostalCode = subjectPostalCode; + return this; + } + + public SignIntermediateCAOptions setSubjectLocality(String subjectLocality) { + this.subjectLocality = subjectLocality; + return this; + } + + public SignIntermediateCAOptions setSubjectProvince(String subjectProvince) { + this.subjectProvince = subjectProvince; + return this; + } + + public SignIntermediateCAOptions setSubjectCountry(String subjectCountry) { + this.subjectCountry = subjectCountry; + return this; + } + + public SignIntermediateCAOptions setSubjectSerialNumber(String subjectSerialNumber) { + this.subjectSerialNumber = subjectSerialNumber; + return this; + } + + public SignIntermediateCAOptions setSubjectAlternativeNames(List subjectAlternativeNames) { + this.subjectAlternativeNames = subjectAlternativeNames; + return this; + } + + public SignIntermediateCAOptions setExcludeCommonNameFromSubjectAlternativeNames( + Boolean excludeCommonNameFromSubjectAlternativeNames) { + this.excludeCommonNameFromSubjectAlternativeNames = excludeCommonNameFromSubjectAlternativeNames; + return this; + } + + public SignIntermediateCAOptions setIpSubjectAlternativeNames( + List ipSubjectAlternativeNames) { + this.ipSubjectAlternativeNames = ipSubjectAlternativeNames; + return this; + } + + public SignIntermediateCAOptions setUriSubjectAlternativeNames( + List uriSubjectAlternativeNames) { + this.uriSubjectAlternativeNames = uriSubjectAlternativeNames; + return this; + } + + public SignIntermediateCAOptions setOtherSubjectAlternativeNames( + List otherSubjectAlternativeNames) { + this.otherSubjectAlternativeNames = otherSubjectAlternativeNames; + return this; + } + + public SignIntermediateCAOptions setTimeToLive(String timeToLive) { + this.timeToLive = timeToLive; + return this; + } + + public SignIntermediateCAOptions setMaxPathLength(Integer maxPathLength) { + this.maxPathLength = maxPathLength; + return this; + } + + public SignIntermediateCAOptions setUseCSRValues(Boolean useCSRValues) { + this.useCSRValues = useCSRValues; + return this; + } + + public SignIntermediateCAOptions setPermittedDnsDomains(List permittedDnsDomains) { + this.permittedDnsDomains = permittedDnsDomains; + return this; + } + + public SignIntermediateCAOptions setFormat(DataFormat format) { + this.format = format; + return this; + } +} 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 0000000000000..2acad3ec0da4c --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/SignedCertificate.java @@ -0,0 +1,51 @@ +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 CertificateData certificate; + + /** + * Issuing CA of generated certificate (PEM encoded). + */ + public CertificateData issuingCA; + + /** + * Complete CA chain of generated certificate (elements are PEM encoded). + */ + public List caChain; + + public SignedCertificate setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + return this; + } + + public SignedCertificate setCertificate(CertificateData certificate) { + this.certificate = certificate; + return this; + } + + public SignedCertificate setIssuingCA(CertificateData issuingCA) { + this.issuingCA = issuingCA; + return this; + } + + public SignedCertificate setCaChain(List caChain) { + this.caChain = caChain; + return this; + } +} 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 0000000000000..d61efef61c391 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/TidyOptions.java @@ -0,0 +1,41 @@ +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; + + public TidyOptions setTidyCertStore(Boolean tidyCertStore) { + this.tidyCertStore = tidyCertStore; + return this; + } + + public TidyOptions setTidyRevokedCerts(Boolean tidyRevokedCerts) { + this.tidyRevokedCerts = tidyRevokedCerts; + return this; + } + + public TidyOptions setSafetyBuffer(String safetyBuffer) { + this.safetyBuffer = safetyBuffer; + return this; + } +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/X509Parsing.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/X509Parsing.java new file mode 100644 index 0000000000000..b44676d1d989e --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/pki/X509Parsing.java @@ -0,0 +1,80 @@ +package io.quarkus.vault.pki; + +import java.io.ByteArrayInputStream; +import java.security.cert.CRLException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +class X509Parsing { + + private static final CertificateFactory certificateFactory; + static { + try { + certificateFactory = CertificateFactory.getInstance("X.509"); + } catch (CertificateException x) { + throw new RuntimeException(x); + } + } + + private static final Pattern PEM_CERT_REGEX = Pattern + .compile("-+\\s*BEGIN\\s*CERTIFICATE\\s*-+\\s+([A-Za-z0-9+/=\\n\\s]+)-+\\s*END\\s*CERTIFICATE\\s*-+\\s*"); + private static final Pattern PEM_CRL_REGEX = Pattern + .compile("-+\\s*BEGIN\\s+X509\\s+CRL\\s*-+\\s+([A-Za-z0-9+/=\\n\\s]+)-+\\s*END\\s+X509\\s+CRL\\s*-+\\s*"); + private static final int PEM_REGEX_CONTENT_GROUP = 1; + + private static final Base64.Decoder BASE64_DECODER = Base64.getMimeDecoder(); + + static X509Certificate parsePEMCertificate(String pem) throws CertificateException { + Matcher pemMatcher = PEM_CERT_REGEX.matcher(pem); + if (!pemMatcher.matches()) { + throw new CertificateException("Invalid PEM Certificate"); + } + byte[] certificateData = BASE64_DECODER.decode(pemMatcher.group(PEM_REGEX_CONTENT_GROUP)); + return parseDERCertificate(certificateData); + } + + static X509Certificate parseDERCertificate(byte[] certificateData) throws CertificateException { + return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certificateData)); + } + + static List parsePEMCertificates(String pem) throws CertificateException { + Matcher pemMatcher = PEM_CERT_REGEX.matcher(pem); + List certificates = new ArrayList<>(); + while (pemMatcher.find()) { + String base64CertificateData = pemMatcher.group(PEM_REGEX_CONTENT_GROUP); + byte[] certificateData = BASE64_DECODER.decode(base64CertificateData); + certificates.add(parseDERCertificate(certificateData)); + } + return certificates; + } + + static List parseDERCertificates(byte[] certificatesData) throws CertificateException { + List certificates = new ArrayList<>(); + for (Certificate certificate : certificateFactory.generateCertificates(new ByteArrayInputStream(certificatesData))) { + certificates.add((X509Certificate) certificate); + } + return certificates; + } + + static X509CRL parsePEMCRL(String pem) throws CRLException { + Matcher pemMatcher = PEM_CRL_REGEX.matcher(pem); + if (!pemMatcher.matches()) { + throw new CRLException("Invalid PEM CRL"); + } + byte[] crlData = BASE64_DECODER.decode(pemMatcher.group(PEM_REGEX_CONTENT_GROUP)); + return parseDERCRL(crlData); + } + + static X509CRL parseDERCRL(byte[] crlData) throws CRLException { + return (X509CRL) certificateFactory.generateCRL(new ByteArrayInputStream(crlData)); + } + +} 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 0000000000000..38dfd64d1daca --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/VaultPKIManager.java @@ -0,0 +1,684 @@ +package io.quarkus.vault.runtime; + +import static io.quarkus.vault.runtime.VaultPKIManagerFactory.PKI_ENGINE_NAME; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.stream.Collectors.toList; + +import java.nio.charset.StandardCharsets; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.Locale; +import java.util.function.Function; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import io.quarkus.vault.VaultException; +import io.quarkus.vault.VaultPKISecretEngine; +import io.quarkus.vault.pki.CAChainData; +import io.quarkus.vault.pki.CRLData; +import io.quarkus.vault.pki.CSRData; +import io.quarkus.vault.pki.CertificateData; +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.DataFormat; +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.PrivateKeyData; +import io.quarkus.vault.pki.PrivateKeyEncoding; +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.VaultClientException; +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.VaultPKIConfigCRLData; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIConfigCRLResult; +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.VaultPKIConstants; +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.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; +import io.vertx.mutiny.core.buffer.Buffer; + +@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_ENGINE_NAME, 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 CertificateData.PEM getCertificateAuthority() { + return (CertificateData.PEM) getCertificateAuthority(DataFormat.PEM); + } + + @Override + public CertificateData getCertificateAuthority(DataFormat format) { + String vaultFormat = format == DataFormat.PEM ? format.name().toLowerCase(Locale.ROOT) : null; + Buffer data = vaultInternalPKISecretEngine.getCertificateAuthority(getToken(), mount, vaultFormat); + + switch (format) { + case PEM: + return new CertificateData.PEM(data.toString(StandardCharsets.UTF_8)); + case DER: + return new CertificateData.DER(data.getBytes()); + default: + throw new VaultException("Unsupported Data Format"); + } + } + + @Override + public void configCertificateAuthority(String pemBundle) { + VaultPKIConfigCABody body = new VaultPKIConfigCABody(); + body.pemBundle = pemBundle; + vaultInternalPKISecretEngine.configCertificateAuthority(getToken(), mount, body); + } + + @Override + public void configURLs(ConfigURLsOptions options) { + VaultPKIConfigURLsData body = new VaultPKIConfigURLsData(); + 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); + checkDataValid(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) { + VaultPKIConfigCRLData body = new VaultPKIConfigCRLData(); + body.expiry = options.expiry; + body.disable = options.disable; + + vaultInternalPKISecretEngine.configCRL(getToken(), mount, body); + } + + @Override + public ConfigCRLOptions readCRLConfig() { + VaultPKIConfigCRLResult internalResult = vaultInternalPKISecretEngine.readCRL(getToken(), mount); + checkDataValid(internalResult); + + VaultPKIConfigCRLData internalResultData = internalResult.data; + + ConfigCRLOptions result = new ConfigCRLOptions(); + result.expiry = internalResultData.expiry; + result.disable = internalResultData.disable; + return result; + } + + @Override + public CAChainData.PEM getCertificateAuthorityChain() { + Buffer data = vaultInternalPKISecretEngine.getCertificateAuthorityChain(getToken(), mount); + return new CAChainData.PEM(data.toString(StandardCharsets.UTF_8)); + } + + @Override + public CRLData.PEM getCertificateRevocationList() { + return (CRLData.PEM) getCertificateRevocationList(DataFormat.PEM); + } + + @Override + public CRLData getCertificateRevocationList(DataFormat format) { + String vaultFormat = format == DataFormat.PEM ? format.name().toLowerCase(Locale.ROOT) : null; + Buffer data = vaultInternalPKISecretEngine.getCertificateRevocationList(getToken(), mount, vaultFormat); + + switch (format) { + case PEM: + return new CRLData.PEM(data.toString(StandardCharsets.UTF_8)); + case DER: + return new CRLData.DER(data.getBytes()); + default: + throw new VaultException("Unsupported Data Format"); + } + } + + @Override + public boolean rotateCertificateRevocationList() { + VaultPKICRLRotateResult internalResult = vaultInternalPKISecretEngine.rotateCertificateRevocationList(getToken(), + mount); + checkDataValid(internalResult); + + return internalResult.data.success; + } + + @Override + public List getCertificates() { + VaultPKICertificateListResult internalResult = vaultInternalPKISecretEngine.listCertificates(getToken(), mount); + checkDataValid(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 CertificateData.PEM getCertificate(String serial) { + VaultPKICertificateResult internalResult = vaultInternalPKISecretEngine.getCertificate(getToken(), mount, serial); + checkDataValid(internalResult); + + return new CertificateData.PEM(internalResult.data.certificate); + } + + @Override + public GeneratedCertificate generateCertificate(String role, GenerateCertificateOptions options) { + VaultPKIGenerateCertificateBody body = new VaultPKIGenerateCertificateBody(); + body.format = dataFormatToFormat(options.format); + body.privateKeyFormat = privateKeyFormat(options.format, options.privateKeyEncoding); + body.subjectCommonName = options.subjectCommonName; + body.subjectAlternativeNames = stringListToCommaString(options.subjectAlternativeNames); + body.ipSubjectAlternativeNames = stringListToCommaString(options.ipSubjectAlternativeNames); + body.uriSubjectAlternativeNames = stringListToCommaString(options.uriSubjectAlternativeNames); + body.otherSubjectAlternativeNames = options.otherSubjectAlternativeNames; + body.timeToLive = options.timeToLive; + body.excludeCommonNameFromSubjectAlternativeNames = options.excludeCommonNameFromSubjectAlternativeNames; + + VaultPKIGenerateCertificateResult internalResult = vaultInternalPKISecretEngine.generateCertificate(getToken(), mount, + role, body); + checkDataValid(internalResult); + + VaultPKIGenerateCertificateData internalResultData = internalResult.data; + + GeneratedCertificate result = new GeneratedCertificate(); + result.certificate = createCertificateData(internalResultData.certificate, body.format); + result.issuingCA = createCertificateData(internalResultData.issuingCA, body.format); + result.caChain = createCertificateDataList(internalResultData.caChain, body.format); + result.serialNumber = internalResultData.serialNumber; + result.privateKeyType = stringToCertificateKeyType(internalResultData.privateKeyType); + result.privateKey = createPrivateKeyData(internalResultData.privateKey, body.format, body.privateKeyFormat); + return result; + } + + @Override + public SignedCertificate signRequest(String role, String pemSigningRequest, GenerateCertificateOptions options) { + VaultPKISignCertificateRequestBody body = new VaultPKISignCertificateRequestBody(); + body.format = dataFormatToFormat(options.format); + body.csr = pemSigningRequest; + body.subjectCommonName = options.subjectCommonName; + body.subjectAlternativeNames = stringListToCommaString(options.subjectAlternativeNames); + body.ipSubjectAlternativeNames = stringListToCommaString(options.ipSubjectAlternativeNames); + body.uriSubjectAlternativeNames = stringListToCommaString(options.uriSubjectAlternativeNames); + body.otherSubjectAlternativeNames = options.otherSubjectAlternativeNames; + body.timeToLive = options.timeToLive; + body.excludeCommonNameFromSubjectAlternativeNames = options.excludeCommonNameFromSubjectAlternativeNames; + + VaultPKISignCertificateRequestResult internalResult = vaultInternalPKISecretEngine.signCertificate(getToken(), mount, + role, body); + checkDataValid(internalResult); + + VaultPKISignCertificateRequestData internalResultData = internalResult.data; + + SignedCertificate result = new SignedCertificate(); + result.certificate = createCertificateData(internalResultData.certificate, body.format); + result.issuingCA = createCertificateData(internalResultData.issuingCA, body.format); + result.caChain = createCertificateDataList(internalResultData.caChain, body.format); + 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); + checkDataValid(internalResult); + + return internalResult.data.revocationTime; + } + + @Override + public void updateRole(String role, RoleOptions options) { + VaultPKIRoleOptionsData body = new VaultPKIRoleOptionsData(); + 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 = certificateKeyTypeToString(options.keyType); + body.keyBits = options.keyBits; + body.keyUsages = enumListToStringList(options.keyUsages, CertificateKeyUsage::name); + body.extendedKeyUsages = enumListToStringList(options.extendedKeyUsages, CertificateExtendedKeyUsage::name); + body.extendedKeyUsageOIDs = options.extendedKeyUsageOIDs; + body.useCSRCommonName = options.useCSRCommonName; + body.useCSRSubjectAlternativeNames = options.useCSRSubjectAlternativeNames; + body.subjectOrganization = commaStringToStringList(options.subjectOrganization); + body.subjectOrganizationalUnit = commaStringToStringList(options.subjectOrganizationalUnit); + body.subjectStreetAddress = commaStringToStringList(options.subjectStreetAddress); + body.subjectPostalCode = commaStringToStringList(options.subjectPostalCode); + body.subjectLocality = commaStringToStringList(options.subjectLocality); + body.subjectProvince = commaStringToStringList(options.subjectProvince); + body.subjectCountry = commaStringToStringList(options.subjectCountry); + 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); + checkDataValid(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 = stringToCertificateKeyType(internalResultData.keyType); + result.keyBits = internalResultData.keyBits; + result.keyUsages = stringListToEnumList(internalResultData.keyUsages, CertificateKeyUsage::valueOf); + result.extendedKeyUsages = stringListToEnumList(internalResultData.extendedKeyUsages, + CertificateExtendedKeyUsage::valueOf); + result.extendedKeyUsageOIDs = internalResultData.extendedKeyUsageOIDs; + result.useCSRCommonName = internalResultData.useCSRCommonName; + result.useCSRSubjectAlternativeNames = internalResultData.useCSRSubjectAlternativeNames; + result.subjectOrganization = stringListToCommaString(internalResultData.subjectOrganization); + result.subjectOrganizationalUnit = stringListToCommaString(internalResultData.subjectOrganizationalUnit); + result.subjectStreetAddress = stringListToCommaString(internalResultData.subjectStreetAddress); + result.subjectPostalCode = stringListToCommaString(internalResultData.subjectPostalCode); + result.subjectLocality = stringListToCommaString(internalResultData.subjectLocality); + result.subjectProvince = stringListToCommaString(internalResultData.subjectProvince); + result.subjectCountry = stringListToCommaString(internalResultData.subjectCountry); + 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 getRoles() { + try { + + VaultPKIRolesListResult internalResult = vaultInternalPKISecretEngine.listRoles(getToken(), mount); + checkDataValid(internalResult); + + return internalResult.data.keys; + + } catch (VaultClientException x) { + // Translate 404 to empty list + if (x.getStatus() == 404) { + return emptyList(); + } else { + throw x; + } + } + } + + @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.format = dataFormatToFormat(options.format); + body.privateKeyFormat = privateKeyFormat(options.format, options.privateKeyEncoding); + body.subjectCommonName = options.subjectCommonName; + body.subjectAlternativeNames = stringListToCommaString(options.subjectAlternativeNames); + body.ipSubjectAlternativeNames = stringListToCommaString(options.ipSubjectAlternativeNames); + body.uriSubjectAlternativeNames = stringListToCommaString(options.uriSubjectAlternativeNames); + body.otherSubjectAlternativeNames = options.otherSubjectAlternativeNames; + body.timeToLive = options.timeToLive; + body.keyType = certificateKeyTypeToString(options.keyType); + 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); + checkDataValid(internalResult); + + VaultPKIGenerateRootData internalResultData = internalResult.data; + + GeneratedRootCertificate result = new GeneratedRootCertificate(); + result.certificate = createCertificateData(internalResultData.certificate, body.format); + result.issuingCA = createCertificateData(internalResultData.issuingCA, body.format); + result.serialNumber = internalResultData.serialNumber; + result.privateKeyType = stringToCertificateKeyType(internalResultData.privateKeyType); + result.privateKey = createPrivateKeyData(internalResultData.privateKey, body.format, body.privateKeyFormat); + return result; + } + + @Override + public void deleteRoot() { + vaultInternalPKISecretEngine.deleteRoot(getToken(), mount); + } + + @Override + public SignedCertificate signIntermediateCA(String pemSigningRequest, SignIntermediateCAOptions options) { + VaultPKISignIntermediateCABody body = new VaultPKISignIntermediateCABody(); + body.format = dataFormatToFormat(options.format); + body.csr = pemSigningRequest; + body.subjectCommonName = options.subjectCommonName; + body.subjectAlternativeNames = stringListToCommaString(options.subjectAlternativeNames); + body.ipSubjectAlternativeNames = stringListToCommaString(options.ipSubjectAlternativeNames); + body.uriSubjectAlternativeNames = stringListToCommaString(options.uriSubjectAlternativeNames); + 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); + checkDataValid(internalResult); + + VaultPKISignCertificateRequestData internalResultData = internalResult.data; + + SignedCertificate result = new SignedCertificate(); + result.certificate = createCertificateData(internalResultData.certificate, body.format); + result.issuingCA = createCertificateData(internalResultData.issuingCA, body.format); + result.caChain = createCertificateDataList(internalResultData.caChain, body.format); + result.serialNumber = internalResultData.serialNumber; + return result; + } + + @Override + public GeneratedIntermediateCSRResult generateIntermediateCSR(GenerateIntermediateCSROptions options) { + String type = options.exportPrivateKey ? "exported" : "internal"; + VaultPKIGenerateIntermediateCSRBody body = new VaultPKIGenerateIntermediateCSRBody(); + body.format = dataFormatToFormat(options.format); + body.privateKeyFormat = privateKeyFormat(options.format, options.privateKeyEncoding); + body.subjectCommonName = options.subjectCommonName; + body.subjectAlternativeNames = stringListToCommaString(options.subjectAlternativeNames); + body.ipSubjectAlternativeNames = stringListToCommaString(options.ipSubjectAlternativeNames); + body.uriSubjectAlternativeNames = stringListToCommaString(options.uriSubjectAlternativeNames); + body.otherSubjectAlternativeNames = options.otherSubjectAlternativeNames; + body.keyType = certificateKeyTypeToString(options.keyType); + 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 = createCSRData(internalResultData.csr, body.format); + result.privateKeyType = stringToCertificateKeyType(internalResultData.privateKeyType); + result.privateKey = createPrivateKeyData(internalResultData.privateKey, body.format, body.privateKeyFormat); + 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 String stringListToCommaString(List values) { + if (values == null) { + return null; + } + return String.join(",", values); + } + + private List commaStringToStringList(String value) { + if (value == null) { + return null; + } + return asList(value.split(",")); + } + + private CertificateKeyType stringToCertificateKeyType(String value) { + if (value == null) { + return null; + } + return CertificateKeyType.valueOf(value.toUpperCase()); + } + + private String certificateKeyTypeToString(CertificateKeyType value) { + if (value == null) { + return null; + } + return value.name().toLowerCase(); + } + + private > List enumListToStringList(List values, Function converter) { + if (values == null) { + return null; + } + return values.stream().map(converter).collect(toList()); + } + + private > List stringListToEnumList(List values, Function converter) { + if (values == null) { + return null; + } + return values.stream().map(converter).collect(toList()); + } + + private void checkDataValid(AbstractVaultDTO dto) { + if (dto.data != null) { + return; + } + 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"); + } + + private String dataFormatToFormat(DataFormat format) { + if (format == null) { + return VaultPKIConstants.DEFAULT_CERTIFICATE_FORMAT; + } + return format.name().toLowerCase(Locale.ROOT); + } + + private String nonNullFormat(String format) { + if (format == null) { + return VaultPKIConstants.DEFAULT_CERTIFICATE_FORMAT; + } + return format; + } + + private String privateKeyFormat(DataFormat format, PrivateKeyEncoding privateKeyEncoding) { + if (privateKeyEncoding == null) { + return VaultPKIConstants.DEFAULT_KEY_ENCODING; + } + if (privateKeyEncoding == PrivateKeyEncoding.PKCS8) { + return "pkcs8"; + } + return dataFormatToFormat(format); + } + + private CertificateData createCertificateData(String data, String format) { + if (data == null) { + return null; + } + switch (nonNullFormat(format)) { + case "der": + return new CertificateData.DER(Base64.getDecoder().decode(data)); + case "pem": + return new CertificateData.PEM(data); + default: + throw new VaultException("Unsupported certificate format"); + } + } + + private List createCertificateDataList(List datas, String format) { + if (datas == null) { + return null; + } + List result = new ArrayList<>(datas.size()); + for (String data : datas) { + result.add(createCertificateData(data, format)); + } + return result; + } + + private CSRData createCSRData(String data, String format) { + if (data == null) { + return null; + } + switch (nonNullFormat(format)) { + case "der": + return new CSRData.DER(Base64.getDecoder().decode(data)); + case "pem": + return new CSRData.PEM(data); + default: + throw new VaultException("Unsupported certification request format"); + } + } + + private PrivateKeyData createPrivateKeyData(String data, String format, String privateKeyFormat) { + if (data == null) { + return null; + } + boolean pkcs8 = "pkcs8".equals(privateKeyFormat.toLowerCase(Locale.ROOT)); + switch (nonNullFormat(format)) { + case "der": + return new PrivateKeyData.DER(Base64.getDecoder().decode(data), pkcs8); + case "pem": + return new PrivateKeyData.PEM(data, pkcs8); + default: + throw new VaultException("Unsupported private key format"); + } + } +} 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 0000000000000..6ce78e60c45e4 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/VaultPKIManagerFactory.java @@ -0,0 +1,27 @@ +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.backend.VaultInternalSystemBackend; +import io.quarkus.vault.runtime.client.secretengine.VaultInternalPKISecretEngine; + +@ApplicationScoped +public class VaultPKIManagerFactory implements VaultPKISecretEngineFactory { + + static final String PKI_ENGINE_NAME = "pki"; + + @Inject + private VaultAuthManager vaultAuthManager; + @Inject + private VaultInternalPKISecretEngine vaultInternalPKISecretEngine; + @Inject + private VaultInternalSystemBackend vaultInternalSystemBackend; + + @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/VaultSystemBackendManager.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/VaultSystemBackendManager.java index 721f88a72cd25..ba3046f87770a 100644 --- a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/VaultSystemBackendManager.java +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/VaultSystemBackendManager.java @@ -6,16 +6,23 @@ import javax.inject.Inject; import io.quarkus.vault.VaultSystemBackendEngine; +import io.quarkus.vault.runtime.client.VaultClientException; import io.quarkus.vault.runtime.client.backend.VaultInternalSystemBackend; +import io.quarkus.vault.runtime.client.dto.sys.VaultEnableEngineBody; import io.quarkus.vault.runtime.client.dto.sys.VaultHealthResult; import io.quarkus.vault.runtime.client.dto.sys.VaultInitResponse; import io.quarkus.vault.runtime.client.dto.sys.VaultPolicyBody; import io.quarkus.vault.runtime.client.dto.sys.VaultSealStatusResult; +import io.quarkus.vault.runtime.client.dto.sys.VaultTuneBody; +import io.quarkus.vault.runtime.client.dto.sys.VaultTuneResult; import io.quarkus.vault.runtime.config.VaultBuildTimeConfig; +import io.quarkus.vault.sys.EnableEngineOptions; import io.quarkus.vault.sys.VaultHealth; import io.quarkus.vault.sys.VaultHealthStatus; import io.quarkus.vault.sys.VaultInit; import io.quarkus.vault.sys.VaultSealStatus; +import io.quarkus.vault.sys.VaultSecretEngine; +import io.quarkus.vault.sys.VaultTuneInfo; @ApplicationScoped public class VaultSystemBackendManager implements VaultSystemBackendEngine { @@ -131,4 +138,63 @@ public List getPolicies() { String token = vaultAuthManager.getClientToken(); return vaultInternalSystemBackend.listPolicies(token).data.policies; } + + @Override + public VaultTuneInfo getTuneInfo(String mount) { + String token = vaultAuthManager.getClientToken(); + VaultTuneResult vaultTuneResult = vaultInternalSystemBackend.getTuneInfo(token, mount); + + VaultTuneInfo tuneInfo = new VaultTuneInfo(); + tuneInfo.setDefaultLeaseTimeToLive(vaultTuneResult.data.defaultLeaseTimeToLive); + tuneInfo.setMaxLeaseTimeToLive(vaultTuneResult.data.maxLeaseTimeToLive); + tuneInfo.setDescription(vaultTuneResult.data.description); + tuneInfo.setForceNoCache(vaultTuneResult.data.forceNoCache); + return tuneInfo; + } + + @Override + public void updateTuneInfo(String mount, VaultTuneInfo tuneInfoUpdates) { + VaultTuneBody body = new VaultTuneBody(); + body.description = tuneInfoUpdates.getDescription(); + body.defaultLeaseTimeToLive = tuneInfoUpdates.getDefaultLeaseTimeToLive(); + body.maxLeaseTimeToLive = tuneInfoUpdates.getMaxLeaseTimeToLive(); + body.forceNoCache = tuneInfoUpdates.getForceNoCache(); + + String token = vaultAuthManager.getClientToken(); + vaultInternalSystemBackend.updateTuneInfo(token, mount, body); + } + + @Override + public boolean isEngineMounted(String mount) { + try { + getTuneInfo(mount); + return true; + } catch (VaultClientException x) { + if (x.getStatus() != 400) { + throw x; + } + return false; + } + } + + public void enable(VaultSecretEngine engine, String mount, String description, EnableEngineOptions options) { + enable(engine.getType(), mount, description, options); + } + + public void enable(String engineType, String mount, String description, EnableEngineOptions options) { + VaultEnableEngineBody body = new VaultEnableEngineBody(); + body.type = engineType; + body.description = description; + body.config = new VaultEnableEngineBody.Config(); + body.config.defaultLeaseTimeToLive = options.defaultLeaseTimeToLive; + body.config.maxLeaseTimeToLive = options.maxLeaseTimeToLive; + body.options = options.options; + + vaultInternalSystemBackend.enableEngine(vaultAuthManager.getClientToken(), mount, body); + } + + @Override + public void disable(String mount) { + vaultInternalSystemBackend.disableEngine(vaultAuthManager.getClientToken(), mount); + } } diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/VaultClient.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/VaultClient.java index 5efcf0ad7d1cd..9f3d6e2468aae 100644 --- a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/VaultClient.java +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/VaultClient.java @@ -2,6 +2,8 @@ import java.util.Map; +import io.vertx.mutiny.core.buffer.Buffer; + public interface VaultClient { String X_VAULT_TOKEN = "X-Vault-Token"; @@ -30,6 +32,8 @@ public interface VaultClient { T get(String path, Map queryParams, Class resultClass); + Buffer get(String path, String token); + int head(String path); int head(String path, Map queryParams); diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/VertxVaultClient.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/VertxVaultClient.java index 9e1be387f5465..6c0d6ddf2e4e5 100644 --- a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/VertxVaultClient.java +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/VertxVaultClient.java @@ -137,6 +137,15 @@ public T get(String path, Map queryParams, Class resultCl return exec(request, resultClass); } + public Buffer get(String path, String token) { + final HttpRequest request = builder(path, token).method(HttpMethod.GET); + final HttpResponse response = request.send().await().atMost(getRequestTimeout()); + if (response.statusCode() != 200 && response.statusCode() != 204) { + throwVaultException(response); + } + return response.body(); + } + public int head(String path) { final HttpRequest request = builder(path).method(HttpMethod.HEAD); return exec(request); diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/backend/VaultInternalSystemBackend.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/backend/VaultInternalSystemBackend.java index 22f2f09330a8f..9723a65056e54 100644 --- a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/backend/VaultInternalSystemBackend.java +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/backend/VaultInternalSystemBackend.java @@ -8,6 +8,7 @@ import javax.inject.Singleton; import io.quarkus.vault.runtime.client.VaultInternalBase; +import io.quarkus.vault.runtime.client.dto.sys.VaultEnableEngineBody; import io.quarkus.vault.runtime.client.dto.sys.VaultHealthResult; import io.quarkus.vault.runtime.client.dto.sys.VaultInitBody; import io.quarkus.vault.runtime.client.dto.sys.VaultInitResponse; @@ -18,6 +19,8 @@ import io.quarkus.vault.runtime.client.dto.sys.VaultPolicyResult; import io.quarkus.vault.runtime.client.dto.sys.VaultRenewLease; import io.quarkus.vault.runtime.client.dto.sys.VaultSealStatusResult; +import io.quarkus.vault.runtime.client.dto.sys.VaultTuneBody; +import io.quarkus.vault.runtime.client.dto.sys.VaultTuneResult; import io.quarkus.vault.runtime.client.dto.sys.VaultUnwrapBody; import io.quarkus.vault.runtime.client.dto.sys.VaultWrapResult; @@ -79,6 +82,22 @@ public VaultRenewLease renewLease(String token, String leaseId) { return vaultClient.put("sys/leases/renew", token, body, VaultRenewLease.class); } + public void enableEngine(String token, String mount, VaultEnableEngineBody body) { + vaultClient.post("sys/mounts/" + mount, token, body, 204); + } + + public void disableEngine(String token, String mount) { + vaultClient.delete("sys/mounts/" + mount, token, 204); + } + + public VaultTuneResult getTuneInfo(String token, String mount) { + return vaultClient.get("sys/mounts/" + mount + "/tune", token, VaultTuneResult.class); + } + + public void updateTuneInfo(String token, String mount, VaultTuneBody body) { + vaultClient.post("sys/mounts/" + mount + "/tune", token, body, 204); + } + // --- private Map getHealthParams(boolean isStandByOk, boolean isPerfStandByOk) { 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 0000000000000..3052103c8285a --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/secretengine/VaultInternalPKISecretEngine.java @@ -0,0 +1,157 @@ +package io.quarkus.vault.runtime.client.secretengine; + +import javax.inject.Singleton; + +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.VaultPKIConfigCRLData; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIConfigCRLResult; +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.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.VaultPKIRoleOptionsData; +import io.quarkus.vault.runtime.client.dto.pki.VaultPKIRoleReadResult; +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; +import io.vertx.mutiny.core.buffer.Buffer; + +@Singleton +public class VaultInternalPKISecretEngine extends VaultInternalBase { + + private String getPath(String mount, String path) { + return mount + "/" + path; + } + + public Buffer getCertificateAuthority(String token, String mount, String format) { + return getRaw(token, mount, "ca", format); + } + + public Buffer getCertificateRevocationList(String token, String mount, String format) { + return getRaw(token, mount, "crl", format); + } + + public Buffer getCertificateAuthorityChain(String token, String mount) { + return getRaw(token, mount, "ca_chain", null); + } + + private Buffer getRaw(String token, String mount, String path, String format) { + String suffix = format != null ? "/" + format : ""; + return vaultClient.get(getPath(mount, path + suffix), token); + } + + 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, VaultPKIRoleOptionsData 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) { + return vaultClient.list(getPath(mount, "roles"), token, VaultPKIRolesListResult.class); + } + + 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, VaultPKIConfigURLsData 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, VaultPKIConfigCRLData 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/extensions/vault/runtime/src/main/java/io/quarkus/vault/sys/EnableEngineOptions.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/sys/EnableEngineOptions.java new file mode 100644 index 0000000000000..0b99505d1d4fd --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/sys/EnableEngineOptions.java @@ -0,0 +1,39 @@ +package io.quarkus.vault.sys; + +import java.util.Map; + +/** + * Options for enabling a new secret engine. + */ +public class EnableEngineOptions { + + /** + * Default lease duration. + */ + public String defaultLeaseTimeToLive; + + /** + * Max lease duration. + */ + public String maxLeaseTimeToLive; + + /** + * Engine specific mount options + */ + public Map options; + + public EnableEngineOptions setDefaultLeaseTimeToLive(String defaultLeaseTimeToLive) { + this.defaultLeaseTimeToLive = defaultLeaseTimeToLive; + return this; + } + + public EnableEngineOptions setMaxLeaseTimeToLive(String maxLeaseTimeToLive) { + this.maxLeaseTimeToLive = maxLeaseTimeToLive; + return this; + } + + public EnableEngineOptions setOptions(Map options) { + this.options = options; + return this; + } +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/sys/VaultSecretEngine.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/sys/VaultSecretEngine.java new file mode 100644 index 0000000000000..7a117c8c593d1 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/sys/VaultSecretEngine.java @@ -0,0 +1,21 @@ +package io.quarkus.vault.sys; + +public enum VaultSecretEngine { + KEY_VALUE("kv"), + KEY_VALUE_2("kv-v2"), + DATABASE("database"), + PKI("pki"), + TOTP("totp"), + TRANSIT("transit"), + ; + + private final String type; + + VaultSecretEngine(String type) { + this.type = type; + } + + public String getType() { + return type; + } +} diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/sys/VaultTuneInfo.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/sys/VaultTuneInfo.java new file mode 100644 index 0000000000000..3627d1e453ce7 --- /dev/null +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/sys/VaultTuneInfo.java @@ -0,0 +1,45 @@ +package io.quarkus.vault.sys; + +public class VaultTuneInfo { + + private String description; + private Long defaultLeaseTimeToLive; + private Long maxLeaseTimeToLive; + private Boolean forceNoCache; + + public String getDescription() { + return description; + } + + public VaultTuneInfo setDescription(String description) { + this.description = description; + return this; + } + + public Long getDefaultLeaseTimeToLive() { + return defaultLeaseTimeToLive; + } + + public VaultTuneInfo setDefaultLeaseTimeToLive(Long defaultLeaseTimeToLive) { + this.defaultLeaseTimeToLive = defaultLeaseTimeToLive; + return this; + } + + public Long getMaxLeaseTimeToLive() { + return maxLeaseTimeToLive; + } + + public VaultTuneInfo setMaxLeaseTimeToLive(Long maxLeaseTimeToLive) { + this.maxLeaseTimeToLive = maxLeaseTimeToLive; + return this; + } + + public Boolean getForceNoCache() { + return forceNoCache; + } + + public VaultTuneInfo setForceNoCache(Boolean forceNoCache) { + this.forceNoCache = forceNoCache; + return this; + } +} 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 0000000000000..7eb823e03eb60 --- /dev/null +++ b/integration-tests/vault/src/test/java/io/quarkus/vault/VaultPKIITCase.java @@ -0,0 +1,1043 @@ +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.security.cert.X509Certificate; +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.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.CAChainData; +import io.quarkus.vault.pki.CRLData; +import io.quarkus.vault.pki.CertificateData; +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.DataFormat; +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.getRoles()) { + 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.exportPrivateKey = true; + options.maxPathLength = 3; + options.permittedDnsDomains = asList("subs1.example.com", "subs2.example.com"); + + GeneratedRootCertificate result = pkiSecretEngine.generateRoot(options); + + assertEquals(DataFormat.PEM, result.certificate.getFormat()); + assertNotNull(result.certificate.getData()); + assertFalse(result.certificate.getData().toString().isEmpty()); + assertDoesNotThrow(() -> result.certificate.getCertificate()); + + X509CertificateHolder certificate = (X509CertificateHolder) new PEMParser( + new StringReader((String) result.certificate.getData())) + .readObject(); + + assertEquals(DataFormat.PEM, result.issuingCA.getFormat()); + assertNotNull(result.issuingCA.getData()); + assertFalse(result.issuingCA.getData().toString().isEmpty()); + assertDoesNotThrow(() -> result.issuingCA.getCertificate()); + + X509CertificateHolder issuingCA = (X509CertificateHolder) new PEMParser( + new StringReader((String) result.issuingCA.getData())) + .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()); + + // Check private key + assertNotNull(result.privateKey); + assertEquals(DataFormat.PEM, result.privateKey.getFormat()); + assertTrue(result.privateKey.isPKCS8()); + assertNotNull(result.privateKey.getData()); + assertFalse(result.privateKey.getData().toString().isEmpty()); + assertDoesNotThrow(() -> result.privateKey.getKeySpec()); + } + + @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; + options.exportPrivateKey = true; + + GeneratedIntermediateCSRResult result = pkiSecretEngine.generateIntermediateCSR(options); + + assertEquals(DataFormat.PEM, result.csr.getFormat()); + assertNotNull(result.csr.getData()); + assertFalse(result.csr.getData().toString().isEmpty()); + + PKCS10CertificationRequest csr = (PKCS10CertificationRequest) new PEMParser( + new StringReader((String) result.csr.getData())).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); + + // Check private key + assertNotNull(result.privateKey); + assertEquals(DataFormat.PEM, result.privateKey.getFormat()); + assertTrue(result.privateKey.isPKCS8()); + assertNotNull(result.privateKey.getData()); + assertFalse(result.privateKey.getData().toString().isEmpty()); + assertDoesNotThrow(() -> result.privateKey.getKeySpec()); + } + + @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((String) csrResult.csr.getData(), options); + + assertEquals(DataFormat.PEM, result.certificate.getFormat()); + assertNotNull(result.certificate.getData()); + assertFalse(result.certificate.getData().toString().isEmpty()); + assertDoesNotThrow(() -> result.certificate.getCertificate()); + + X509CertificateHolder certificate = (X509CertificateHolder) new PEMParser( + new StringReader((String) result.certificate.getData())) + .readObject(); + X509CertificateHolder issuingCA = (X509CertificateHolder) new PEMParser( + new StringReader((String) result.issuingCA.getData())) + .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); + assertEquals(DataFormat.PEM, csrResult.csr.getFormat()); + assertNotNull(csrResult.csr.getData()); + assertFalse(csrResult.csr.getData().toString().isEmpty()); + + // Sign the intermediate CA using "pki" + SignIntermediateCAOptions options = new SignIntermediateCAOptions(); + SignedCertificate result = pkiSecretEngine.signIntermediateCA((String) csrResult.csr.getData(), options); + + // Set signed intermediate CA into "pki2" + pkiSecretEngine2.setSignedIntermediateCA((String) result.certificate.getData()); + + // Get CA cert and check subject (PEM) + X509CertificateHolder certificate = (X509CertificateHolder) new PEMParser( + new StringReader(pkiSecretEngine2.getCertificateAuthority().getData())).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.getRoles()); + } + + @Test + public void testDeleteRole() { + RoleOptions options = new RoleOptions(); + pkiSecretEngine.updateRole("test1", options); + + assertEquals(singletonList("test1"), pkiSecretEngine.getRoles()); + + pkiSecretEngine.deleteRole("test1"); + + assertEquals(emptyList(), pkiSecretEngine.getRoles()); + } + + @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); + + assertEquals(DataFormat.PEM, result.certificate.getFormat()); + assertNotNull(result.certificate.getData()); + assertFalse(result.certificate.getData().toString().isEmpty()); + assertDoesNotThrow(() -> result.certificate.getCertificate()); + + X509CertificateHolder certificate = (X509CertificateHolder) new PEMParser( + new StringReader((String) result.certificate.getData())) + .readObject(); + + assertEquals(DataFormat.PEM, result.issuingCA.getFormat()); + assertNotNull(result.issuingCA.getData()); + assertFalse(result.issuingCA.getData().toString().isEmpty()); + assertDoesNotThrow(() -> result.issuingCA.getCertificate()); + + X509CertificateHolder issuingCA = (X509CertificateHolder) new PEMParser( + new StringReader((String) result.issuingCA.getData())) + .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 private key + assertNotNull(result.privateKey); + assertEquals(DataFormat.PEM, result.privateKey.getFormat()); + assertTrue(result.privateKey.isPKCS8()); + assertNotNull(result.privateKey.getData()); + assertFalse(result.privateKey.getData().toString().isEmpty()); + assertDoesNotThrow(() -> result.privateKey.getKeySpec()); + } + + @Test + public void testGenerateCertificateDer() 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"; + options.format = DataFormat.DER; + + GeneratedCertificate result = pkiSecretEngine.generateCertificate("test", options); + + assertEquals(DataFormat.DER, result.certificate.getFormat()); + assertNotNull(result.certificate.getData()); + assertFalse(result.certificate.getData().toString().isEmpty()); + assertDoesNotThrow(() -> result.certificate.getCertificate()); + + X509CertificateHolder certificate = new X509CertificateHolder((byte[]) result.certificate.getData()); + + assertEquals(DataFormat.DER, result.issuingCA.getFormat()); + assertNotNull(result.issuingCA.getData()); + assertFalse(result.issuingCA.getData().toString().isEmpty()); + assertDoesNotThrow(() -> result.issuingCA.getCertificate()); + + X509CertificateHolder issuingCA = new X509CertificateHolder((byte[]) result.issuingCA.getData()); + + // 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 private key + assertNotNull(result.privateKey); + assertEquals(DataFormat.DER, result.privateKey.getFormat()); + assertTrue(result.privateKey.isPKCS8()); + assertNotNull(result.privateKey.getData()); + assertFalse(result.privateKey.getData().toString().isEmpty()); + assertDoesNotThrow(() -> result.privateKey.getKeySpec()); + } + + @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); + + assertEquals(DataFormat.PEM, result.certificate.getFormat()); + assertNotNull(result.certificate.getData()); + assertFalse(result.certificate.getData().toString().isEmpty()); + assertDoesNotThrow(() -> result.certificate.getCertificate()); + + X509CertificateHolder certificate = (X509CertificateHolder) new PEMParser( + new StringReader((String) result.certificate.getData())) + .readObject(); + X509CertificateHolder issuingCA = (X509CertificateHolder) new PEMParser( + new StringReader((String) result.issuingCA.getData())) + .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 + CertificateData.PEM pemCert = pkiSecretEngine.getCertificate(certSerialNumber); + assertNotNull(pemCert); + + assertEquals(DataFormat.PEM, pemCert.getFormat()); + assertNotNull(pemCert.getData()); + assertFalse(pemCert.getData().isEmpty()); + assertDoesNotThrow(() -> new PEMParser(new StringReader(pemCert.getData())).readObject()); + assertDoesNotThrow(pemCert::getCertificate); + } + + @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.getCertificates(); + 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() { + // 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 (PEM) + CRLData.PEM pemCRL = pkiSecretEngine.getCertificateRevocationList(); + assertDoesNotThrow(pemCRL::getCRL); + + // Test CRL get (DER) + CRLData.DER derCRL = (CRLData.DER) pkiSecretEngine.getCertificateRevocationList(DataFormat.DER); + assertDoesNotThrow(derCRL::getCRL); + } + + @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((String) csrResult.csr.getData(), options); + + // Set signed intermediate CA & root CA chain into "pki2" + pkiSecretEngine2 + .setSignedIntermediateCA(result.certificate.getData() + "\n" + generatedRootCertificate.certificate.getData()); + + // Get CA chain and check subjects + CAChainData.PEM caChainData = pkiSecretEngine2.getCertificateAuthorityChain(); + List certificates = assertDoesNotThrow(caChainData::getCertificates); + + assertEquals("CN=root.example.com", certificates.get(1).getSubjectX500Principal().toString()); + assertEquals("CN=test1.example.com", certificates.get(0).getSubjectX500Principal().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); + assertEquals(DataFormat.PEM, generatedRootCertificate.certificate.getFormat()); + assertNotNull(generatedRootCertificate.certificate.getData()); + assertFalse(generatedRootCertificate.certificate.getData().toString().isEmpty()); + assertDoesNotThrow(() -> generatedRootCertificate.certificate.getCertificate()); + + assertNotNull(generatedRootCertificate.issuingCA); + assertEquals(DataFormat.PEM, generatedRootCertificate.issuingCA.getFormat()); + assertNotNull(generatedRootCertificate.issuingCA.getData()); + assertFalse(generatedRootCertificate.issuingCA.getData().toString().isEmpty()); + assertDoesNotThrow(() -> generatedRootCertificate.issuingCA.getCertificate()); + + assertNotNull(generatedRootCertificate.privateKey); + assertEquals(DataFormat.PEM, generatedRootCertificate.privateKey.getFormat()); + assertNotNull(generatedRootCertificate.privateKey.getData()); + assertFalse(generatedRootCertificate.privateKey.getData().toString().isEmpty()); + assertDoesNotThrow(() -> generatedRootCertificate.privateKey.getKeySpec()); + + // Set root CA from "pki" into "pki2" + VaultPKISecretEngine pkiSecretEngine2 = pkiSecretEngineFactory.engine("pki2"); + pkiSecretEngine2 + .configCertificateAuthority( + generatedRootCertificate.certificate.getData() + "\n" + generatedRootCertificate.privateKey.getData()); + + // Get CA cert and check subject (PEM) + CertificateData.PEM pemCAData = pkiSecretEngine2.getCertificateAuthority(); + assertEquals("CN=root.example.com", pemCAData.getCertificate().getSubjectX500Principal().toString()); + + // Get CA cert and check subject (DER) + CertificateData.DER derCAData = (CertificateData.DER) pkiSecretEngine2.getCertificateAuthority(DataFormat.DER); + assertEquals("CN=root.example.com", derCAData.getCertificate().getSubjectX500Principal().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/java/io/quarkus/vault/VaultSysITCase.java b/integration-tests/vault/src/test/java/io/quarkus/vault/VaultSysITCase.java index b0fa4d6a2858e..4e21c4594ef70 100644 --- a/integration-tests/vault/src/test/java/io/quarkus/vault/VaultSysITCase.java +++ b/integration-tests/vault/src/test/java/io/quarkus/vault/VaultSysITCase.java @@ -1,11 +1,14 @@ package io.quarkus.vault; import static org.assertj.core.api.Assertions.assertThat; +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.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; +import java.util.Random; import javax.inject.Inject; @@ -18,13 +21,18 @@ import io.quarkus.test.QuarkusUnitTest; import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.vault.sys.EnableEngineOptions; import io.quarkus.vault.sys.VaultSealStatus; +import io.quarkus.vault.sys.VaultSecretEngine; +import io.quarkus.vault.sys.VaultTuneInfo; import io.quarkus.vault.test.VaultTestLifecycleManager; @DisabledOnOs(OS.WINDOWS) // https://github.com/quarkusio/quarkus/issues/3796 @QuarkusTestResource(VaultTestLifecycleManager.class) public class VaultSysITCase { + public static final Random RANDOM = new Random(); + @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) @@ -55,4 +63,39 @@ public void policy() { policies = vaultSystemBackendEngine.getPolicies(); assertFalse(policies.contains(name)); } + + @Test + public void testTuneInfo() { + VaultTuneInfo tuneInfo = vaultSystemBackendEngine.getTuneInfo("secret"); + assertNotNull(tuneInfo.getDescription()); + assertNotNull(tuneInfo.getDefaultLeaseTimeToLive()); + assertNotNull(tuneInfo.getMaxLeaseTimeToLive()); + assertNotNull(tuneInfo.getForceNoCache()); + + VaultTuneInfo tuneInfoUpdates = new VaultTuneInfo(); + tuneInfoUpdates.setMaxLeaseTimeToLive(tuneInfo.getMaxLeaseTimeToLive() + 10); + vaultSystemBackendEngine.updateTuneInfo("secret", tuneInfoUpdates); + + VaultTuneInfo updatedTuneInfo = vaultSystemBackendEngine.getTuneInfo("secret"); + + assertEquals(tuneInfo.getMaxLeaseTimeToLive() + 10, updatedTuneInfo.getMaxLeaseTimeToLive()); + } + + @Test + public void testEnableDisable() { + String randomMount = String.format("pki-%X", RANDOM.nextInt()); + + assertFalse(vaultSystemBackendEngine.isEngineMounted(randomMount)); + + EnableEngineOptions options = new EnableEngineOptions(); + assertDoesNotThrow( + () -> vaultSystemBackendEngine.enable(VaultSecretEngine.PKI, randomMount, "Dynamic PKI engine", options)); + + assertTrue(vaultSystemBackendEngine.isEngineMounted(randomMount)); + + assertDoesNotThrow(() -> vaultSystemBackendEngine.disable(randomMount)); + + assertFalse(vaultSystemBackendEngine.isEngineMounted(randomMount)); + } + } 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 0000000000000..fdcc775e0ca40 --- /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 3d496359b764f..2f15097561de6 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 {