From Zero to Code: Automating Hashicorp Vault with Ansible to create a CA Server

From Zero to Code: Automating Hashicorp Vault with Ansible to create a CA Server

As part of an ongoing Zero to Code blog series, where I take a product I've not used before and as a Systems Administrator work through the process of Automating it using Ansible this is another post based on Hashicorp Vault.

Version  Name Date Notes
1.0 Initial Post 1 June 2021 Needs work on the code and to be accessible code not yet available.

The first post covered Building HCP Vault using Ansible, adding some passwords and then consuming them.

From Zero to Code: Storing Ansible passwords in Hashicorp Vault
VersionDateDescription1.011 May 2021Initial Post1.220 May 2021the code in theGitLab link at the end of the post is now updated to deploy using HTTPS and usethe API over HTTPS.While working on a personal project and getting to a version1 release, there was an obvious problem with my code (other …

The second post covered using AppRoles to generate RW and RO tokens for adding and consuming secrets, rather than use the Root token.

From Zero to Code: Hashicorp Vault - Using AppRole to pull secrets
In this post, I’ll be expanding on the previous instructions for installingHashicorp Vault using Ansible From Zero to Code: Storing Ansible passwords in Hashicorp VaultVersionDateDescription1.011 May 2021Initial Post1.220 May 2021the code intheGitLab link at the end of the post is now updated to…

This post will cover using the HCP Vault server as an Internal CA server for providing certificates.

As with the previous post, the process will be split into two halves, the first of which is taken straight out of the Hasicorp Tutorial and explains how to do this using the HCP vault API.

The second half of the post will go over the Ansible needed to do the same thing

Why bother doing this?

This is mainly a set of notes in a pretty format for me, it is quick and easy to generate self-signed certs in Ansible on the fly, in fact, that's what I do to have the HCP Vault server run in HTTPS mode. Having certificates in a central contained place that can act as a CRL, be revoked, removed and generated on the fly is however an important part of certificate management

The Process

The process which is explained looks like this

Disclaimer

  • This is written from the learning perspective of a Sysadmin NOT a developer, and as such, any code on this page should not be considered production ready.
  • I am happy to get feedback on the post as long as it's constructive.
  • I'm doing all of this using the root token, in a production environment you'd run this using a specific cert_create user, using AppRole or another identity provider.

Things you'll need

  • You'll need an HCP Vault server setup
  • You'll need VAULT_TOKEN and VAULT_ADDR variables setup

As an example, this is my /etc/environment

export VAULT_ADDR='https://192.168.40.99:8200'
export VAULT_TOKEN="s.udCB3uIQ0hTM9ZZpgJY8nq7W"
export VAULT_SKIP_VERIFY=1
  • Install jq to make the java outputreadable.

Using the HCP Vault API

These instructions are adapted from the excellent tutorial on the Hashicorp site and please refer to the site for reference if you're having issues here.

Build Your Own Certificate Authority (CA) | Vault - HashiCorp Learn
Learn how to provision, secure, connect, and run any infrastructure for any application.

Policies

Before the tutorial is started some policies need to be setup on the HCP Vault server

Create following the payload files

tee allmounts.json <<"EOF"
{
"policy": "# Read-only permission on 'homeserver/' path\npath "sys/mounts/" {\n  capabilities = [ "create", "read", "update", "delete", "list" ]\n}"
}
EOF
tee somemounts.json <<"EOF"
{
"policy": "# Read-only permission on 'homeserver/*' path\npath "sys/mounts" {\n  capabilities = [ "read", "list" ]\n}"
}
EOF
tee pkipayload.json <<"EOF"
{
"policy": "# Read-only permission on 'homeserver/' path\npath "pki" {\n  capabilities = [ "create", "read", "update", "delete", "list", "sudo" ]\n}"
}
EOF

Execute the following curl commands to import the templates into the HCP Vault

curl --header "X-Vault-Token: $VAULT_TOKEN" --request PUT --data @allmounts.json $VAULT_ADDR/v1/sys/policies/acl/homeserverpkirw --insecure
curl --header "X-Vault-Token: $VAULT_TOKEN" --request PUT --data @somemounts.json $VAULT_ADDR/v1/sys/policies/acl/homeserverpkiro --insecure
curl --header "X-Vault-Token: $VAULT_TOKEN" --request PUT --data @pkipayload.json $VAULT_ADDR/v1/sys/policies/acl/homeserverpki --insecure
Note I'm using --insecure at the end of the CURL lines because of the self signed cert I'm using.

Enable PKI Secret Store on Vault

Now the policies are set, the next step is to enable the PKI CA secret store in HCP Vault

Create a PKI secret

curl --header "X-Vault-Token: $VAULT_TOKEN" --request POST --data '{"type":"pki"}' $VAULT_ADDR/v1/sys/mounts/pki --insecure

Add a Cert Time to Live (TTL)

curl --header "X-Vault-Token: $VAULT_TOKEN" --request POST --data '{"max_lease_ttl":"87600h"}' $VAULT_ADDR/v1/sys/mounts/pki/tune --insecure

Set Cert details

The common name and the TTL for the CA server needs to be set, this is done again, by creating a payload.

tee commonname.json <<EOF
{
"common_name": "homeserver.lan",
"ttl": "87600h"
}
EOF

Generate the root CA, this will stay on the server in a secure location.

curl --header "X-Vault-Token: $VAULT_TOKEN" --request POST --data @commonname.json $VAULT_ADDR/v1/pki/root/generate/internal --insecure | jq -r ".data.certificate" > CA_cert.crt

Having defined the CA Certificate the Certificate revocation list URL needs to be defined. In this example, the HCP Vault server is being used. Be aware that this URL needs to be reachable from anywhere the client certs are being used. so 127.0.0.1 might not be a good URL.

tee payload-url.json <<EOF
{
"issuing_certificates": "$VAULT_ADDR/v1/pki/ca","crl_distribution_points": "$VAULT_ADDR/v1/pki/crl"
}
EOF

This can be pushed to the server

curl --header "X-Vault-Token: $VAULT_TOKEN" --request POST --data @payload-url.json $VAULT_ADDR/v1/pki/config/urls --insecure

The Certificate Authority is now setup

Enable PKI Int server

As the certificates will be generated of an intermediate CA server, this needs to be created in its own separate PKI vault store

Use CURL to create a PKI intermediate store called pki_int

curl --header "X-Vault-Token: $VAULT_TOKEN" --request POST --data '{"type":"pki"}' $VAULT_ADDR/v1/sys/mounts/pki_int --insecuree

Like the CA cert set the TTL for the Int CA cert

curl --header "X-Vault-Token: $VAULT_TOKEN" --request POST --data '{"max_lease_ttl":"43800h"}' $VAULT_ADDR/v1/sys/mounts/pki_int/tune --insecure

Again, set the common name of int cert

tee payload-int.json <<EOF
{
"common_name": "homeserver.lan Intermediate Authority"
}
EOF

Generate the  intermediate certificate CSR

curl --header "X-Vault-Token: $VAULT_TOKEN" --request POST --data @payload-int.json $VAULT_ADDR/v1/pki_int/intermediate/generate/internal --insecure | jq

This command returns the following:

{
"request_id": "4db298bf-1191-5902-5d0a-5db32947f0ad",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": {"csr": "-----BEGIN CERTIFICATE REQUEST-----\nMIICdTCCAV0CAQAwMDEuMCwGA1UEAxMlaG9tZXNlcnZlci5sYW4gSW50ZXJtZWRp\nYXRlIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOA/\ni9ZvqSlEkcNtbg0xwCvRgwK8y0O4D1HaLghXk0GKbMNeLIl8ryXVRUgSp8FGLj2g\nRN6tHwi0uwaTdLErcyZ34tM3MjQEp47zSS6CbodWskvxcpSEd84hSNnr3BZvmK0e\n8ZPf5Y4r6YjKqL0+rB7N/QZFwG9A3s6BwcgrlT3FOKFr5w7kXCaZY5KpbgIQGR2I\nAGRZqG6/HaG6Z6UkciV1ufLzxsrW0kfAaciwj77d6nbAdbDyd11fNDiZnAOOhGKo\n1b+avcrsbccGEH7i3t+ZUHIbeeNSdpvtDlmo5CKXjv5cnlH3MOEbyKdcRAmlVRPG\njkwH8yFiZYRSgJoqrc0CAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQBOKAC0utOh\nqKD1kT24wMxOFBW4eDeVv4FyMPj+QhvEjQ9zY59pUpS+aStVX38wI2J81nQZdoNE\n2ICtIqKdR514bLN4koCRdI27UNaji4SKxJttW+dz83CYQon892iOpXPBsdYIep9B\nM3KAsPj2zUzbnmMjQcdUnMbRXjg8DmNuoWG2oZzlmTA95ho09OkBd7ihuE/hAQub\nY9RUjLQTZVzAGfIkBba/hhu5KHqzUe7jLuQtnhY+lsFfd4A8TmmIv5uGADdPWhW/\nWwExDndstD2IKuDmnRl+EMo1gpDfMYRmKggvQ9Cf9Ie1hioeqNGADqwPBaG4caxM\nMF0HDoYVIiyN\n-----END CERTIFICATE REQUEST-----"},
"wrap_info": null,
"warnings": null,
"auth": null
}

The CSR is listed in the output and needs to be put into the payload for signing the Intermediate Certificate so the Root CA and the Int CA are trusted.

Sign the intermediate with the CSR by copying the CSR from the above output into the payload for generating the intermediate certificate.

tee payload-int-cert.json <<EOF
{
"csr": "-----BEGIN CERTIFICATE REQUEST-----\nMIICdTCCAV0CAQAwMDEuMCwGA1UEAxMlaG9tZXNlcnZlci5sYW4gSW50ZXJtZWRp\nYXRlIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOA/\ni9ZvqSlEkcNtbg0xwCvRgwK8y0O4D1HaLghXk0GKbMNeLIl8ryXVRUgSp8FGLj2g\nRN6tHwi0uwaTdLErcyZ34tM3MjQEp47zSS6CbodWskvxcpSEd84hSNnr3BZvmK0e\n8ZPf5Y4r6YjKqL0+rB7N/QZFwG9A3s6BwcgrlT3FOKFr5w7kXCaZY5KpbgIQGR2I\nAGRZqG6/HaG6Z6UkciV1ufLzxsrW0kfAaciwj77d6nbAdbDyd11fNDiZnAOOhGKo\n1b+avcrsbccGEH7i3t+ZUHIbeeNSdpvtDlmo5CKXjv5cnlH3MOEbyKdcRAmlVRPG\njkwH8yFiZYRSgJoqrc0CAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQBOKAC0utOh\nqKD1kT24wMxOFBW4eDeVv4FyMPj+QhvEjQ9zY59pUpS+aStVX38wI2J81nQZdoNE\n2ICtIqKdR514bLN4koCRdI27UNaji4SKxJttW+dz83CYQon892iOpXPBsdYIep9B\nM3KAsPj2zUzbnmMjQcdUnMbRXjg8DmNuoWG2oZzlmTA95ho09OkBd7ihuE/hAQub\nY9RUjLQTZVzAGfIkBba/hhu5KHqzUe7jLuQtnhY+lsFfd4A8TmmIv5uGADdPWhW/\nWwExDndstD2IKuDmnRl+EMo1gpDfMYRmKggvQ9Cf9Ie1hioeqNGADqwPBaG4caxM\nMF0HDoYVIiyN\n-----END CERTIFICATE REQUEST-----","format": "pem_bundle","ttl": "43800h"
}
EOF

use this payload to Sign the Intermediate cert

curl --header "X-Vault-Token: $VAULT_TOKEN" --request POST --data @payload-int-cert.json $VAULT_ADDR/v1/pki/root/sign-intermediate --insecure | jq

This should return the following output

{
"request_id": "f3afc412-3a13-01f0-067f-0f84409906f3",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": {"certificate": "-----BEGIN CERTIFICATE-----\nMIIDmzCCAoOgAwIBAgIUZ360AKUYbtI5nlWhGw/4azAUpKIwDQYJKoZIhvcNAQEL\nBQAwADAeFw0yMTA1MjUxNDEwMDlaFw0yNjA1MjQxNDEwMzlaMDAxLjAsBgNVBAMT\nJWhvbWVzZXJ2ZXIubGFuIEludGVybWVkaWF0ZSBBdXRob3JpdHkwggEiMA0GCSqG\nSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDgP4vWb6kpRJHDbW4NMcAr0YMCvMtDuA9R\n2i4IV5NBimzDXiyJfK8l1UVIEqfBRi49oETerR8ItLsGk3SxK3Mmd+LTNzI0BKeO\n80kugm6HVrJL8XKUhHfOIUjZ69wWb5itHvGT3+WOK+mIyqi9Pqwezf0GRcBvQN7O\ngcHIK5U9xTiha+cO5FwmmWOSqW4CEBkdiABkWahuvx2humelJHIldbny88bK1tJH\nwGnIsI++3ep2wHWw8nddXzQ4mZwDjoRiqNW/mr3K7G3HBhB+4t7fmVByG3njUnab\n7Q5ZqOQil47+XJ5R9zDhG8inXEQJpVUTxo5MB/MhYmWEUoCaKq3NAgMBAAGjgdww\ngdkwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFBJt\nxrMi/27z7WHHLZZA0YXJSJGhMB8GA1UdIwQYMBaAFIAqpIrUJYGJXbkm5kKWKwss\nprZ9MD8GCCsGAQUFBwEBBDMwMTAvBggrBgEFBQcwAoYjaHR0cHM6Ly8xOTIuMTY4\nLjg2Ljc6ODIwMC92MS9wa2kvY2EwNQYDVR0fBC4wLDAqoCigJoYkaHR0cHM6Ly8x\nOTIuMTY4Ljg2Ljc6ODIwMC92MS9wa2kvY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQAc\ni7R4E7u8JRVeSf7pTC8Gmz6xc2JNw3hXQ1A5Z+Fl/ZEBLLte/PCW1wgqIau5vhmg\nEdrNZwS8101NGJ+hqJVphQspZjbhPikCqLTmv49swEiYjfPXeYxhBFnYyH5bAFYH\neOcFj2dtF/8UiixgGuAeDcpw2GRhHnDUT3wj5hkoAG0bbLKGU70GJbpA3IAIgyCF\nVT3eZ1ZPphlSYHh9whcvbB4b9Ql0AisPApggQOZNRCrCNzuaVUJqpkxB1zMf4jEm\nEJxqEdp1R34FJi8D2tJNsiwEi0vcsADkYQctMGXmYynNn7n5LcjuWEnh4i9fGqqw\nVs0dXzvYzonunb0PeuMW\n-----END CERTIFICATE-----",
"expiration": 1779631839,
"issuing_ca": "-----BEGIN CERTIFICATE-----\nMIIC8TCCAdmgAwIBAgIUWV3Oji99wO0pfCiUiL4SECv34wswDQYJKoZIhvcNAQEL\nBQAwADAeFw0yMTA1MjUxMzUzMjRaFw0yMTA2MjYxMzUzNTRaMAAwggEiMA0GCSqG\nSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCyocMiu6/l8BHgVhHSdU071ooJP7TRk8NV\n06ODFLYVRoOaGn2EcanAaKslTxKtTkrra2FWuCgDENEuuC4Q0M+5QqXx2qaCXuWT\nSbYZ9VskDSiBBi+mdKDc9Bu1WiKXZ6zKRPCtzw55DTV2A286Q5s/qshIF2dI5KoH\nPgYHQi23ETsrCRsM+jNA9imh4DEPJtqfFqthzcY0ChteMkfkMZp/JLoSvfZaLgKl\npJgagoVUgOP4R7AED2qiUZuQOlVZJr165M/CUQtvEG076IiiqZQglBKxfUFOExR9\nf8oufl0FrpkAH0FIUvXE3eD6CbdwsLj+pQYaAEt/WXvjWkL/JdJrAgMBAAGjYzBh\nMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSAKqSK\n1CWBiV25JuZClisLLKa2fTAfBgNVHSMEGDAWgBSAKqSK1CWBiV25JuZClisLLKa2\nfTANBgkqhkiG9w0BAQsFAAOCAQEACA45b7XBVJsF9UZStNNsMghIfTHTiZNSu4aE\nSM2SQTK0JHqlBxdEbbTCqJp0rsuJ7oTBHyXsvyWb1rgHeFhhsj1iYHq7bA8EpSaa\no8INFnm+dTCzNKJSg3JTJ8lO91A5ik8Gk1BdJVF6YvtcpeNLXtdawJzJvoJN5ypo\nl97+679l/pX2nua48Zwc5oJwegXvgPT49HP4HOjiyuc7n2TmEL2uKAOFEDm8qK4O\n8YhTNwatG/uzVb1GYyQ3BURnUY9Bop+zpxrZCQ2jnkigt3S9OiF/xDlBMHEl//QV\n5yLZtgSJEg4DxlnnNeZN6pKAAgaKDShO9bjhxt4CzFLij76ynw==\n-----END CERTIFICATE-----",
"serial_number": "67:7e:b4:00:a5:18:6e:d2:39:9e:55:a1:1b:0f:f8:6b:30:14:a4:a2"},
"wrap_info": null,
"warnings": ["The expiration time for the signed certificate is after the CA's expiration time. If the new certificate is not treated as a root, validation paths with the certificate past the issuing CA's expiration time will fail."],
"auth": null
}

Copy the certificate details out of the above output and included them in the signed certificate payload which will be uploaded to the HCP Vault server.

tee payload-signed.json <<EOF
{
"certificate": "-----BEGIN CERTIFICATE-----\nMIIDmzCCAoOgAwIBAgIUZ360AKUYbtI5nlWhGw/4azAUpKIwDQYJKoZIhvcNAQEL\nBQAwADAeFw0yMTA1MjUxNDEwMDlaFw0yNjA1MjQxNDEwMzlaMDAxLjAsBgNVBAMT\nJWhvbWVzZXJ2ZXIubGFuIEludGVybWVkaWF0ZSBBdXRob3JpdHkwggEiMA0GCSqG\nSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDgP4vWb6kpRJHDbW4NMcAr0YMCvMtDuA9R\n2i4IV5NBimzDXiyJfK8l1UVIEqfBRi49oETerR8ItLsGk3SxK3Mmd+LTNzI0BKeO\n80kugm6HVrJL8XKUhHfOIUjZ69wWb5itHvGT3+WOK+mIyqi9Pqwezf0GRcBvQN7O\ngcHIK5U9xTiha+cO5FwmmWOSqW4CEBkdiABkWahuvx2humelJHIldbny88bK1tJH\nwGnIsI++3ep2wHWw8nddXzQ4mZwDjoRiqNW/mr3K7G3HBhB+4t7fmVByG3njUnab\n7Q5ZqOQil47+XJ5R9zDhG8inXEQJpVUTxo5MB/MhYmWEUoCaKq3NAgMBAAGjgdww\ngdkwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFBJt\nxrMi/27z7WHHLZZA0YXJSJGhMB8GA1UdIwQYMBaAFIAqpIrUJYGJXbkm5kKWKwss\nprZ9MD8GCCsGAQUFBwEBBDMwMTAvBggrBgEFBQcwAoYjaHR0cHM6Ly8xOTIuMTY4\nLjg2Ljc6ODIwMC92MS9wa2kvY2EwNQYDVR0fBC4wLDAqoCigJoYkaHR0cHM6Ly8x\nOTIuMTY4Ljg2Ljc6ODIwMC92MS9wa2kvY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQAc\ni7R4E7u8JRVeSf7pTC8Gmz6xc2JNw3hXQ1A5Z+Fl/ZEBLLte/PCW1wgqIau5vhmg\nEdrNZwS8101NGJ+hqJVphQspZjbhPikCqLTmv49swEiYjfPXeYxhBFnYyH5bAFYH\neOcFj2dtF/8UiixgGuAeDcpw2GRhHnDUT3wj5hkoAG0bbLKGU70GJbpA3IAIgyCF\nVT3eZ1ZPphlSYHh9whcvbB4b9Ql0AisPApggQOZNRCrCNzuaVUJqpkxB1zMf4jEm\nEJxqEdp1R34FJi8D2tJNsiwEi0vcsADkYQctMGXmYynNn7n5LcjuWEnh4i9fGqqw\nVs0dXzvYzonunb0PeuMW\n-----END CERTIFICATE-----"
}
EOF
Note: when you copy the certificate from the output to include in the above payload there will be a comma at the end of -----END CERTIFICATE-----", you'll need to remove this or the next stage will fail.

And submit the signed intermediate CA certificate back to the HCP Vault server

curl --header "X-Vault-Token: $VAULT_TOKEN" --request POST --data @payload-signed.json $VAULT_ADDR/v1/pki_int/intermediate/set-signed --insecure

Your secrets engine will now look like this with pki and pki_int setup and both having certificates underneath.

Create a role to issue certificates

A role needs to be created to allow clients to request certificates against the INT CA certificate against specific subdomains. In my example, I've used the homeserver.lan domain  

Create a payload for the certificate role

tee payload-cert-role.json <<EOF
{
"allowed_domains": "homeserver.lan",
"allow_subdomains": true,"max_ttl": "720h"
}
EOF

Push this payload to the HCP Vault server

curl --header "X-Vault-Token: $VAULT_TOKEN" --request POST --data @payload-cert-role.json $VAULT_ADDR/v1/pki_int/roles/homeserver-dot-lan --insecure
Note: In production creating the CA and INT CA certs should be done using their own identity tokens for security purposes. I'd also have separate AppRole (for example) for the Requesting of certificates.  

Request client certificates

Having created the CA and Int CA it's time to generate certificates from the HCP Vault server using the API.

curl --header "X-Vault-Token: $VAULT_TOKEN" --request POST --data '{"common_name": "dashboard.homeserver.lan", "ttl": "30d"}' $VAULT_ADDR/v1/pki_int/issue/homeserver-dot-lan --insecure | jq

There are some things to note here:

  • "common_name": "dashboard.homeserver.lan"
  • "ttl": "30d"

These can be changed accordingly

The output returned for this is

Example output

{
"request_id": "4c9af43d-0c9d-c9ba-1c89-79b85a00a7df",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": {
"ca_chain": ["-----BEGIN CERTIFICATE-----\nMIIDmzCCAoOgAwIBAgIUZ360AKUYbtI5nlWhGw/4azAUpKIwDQYJKoZIhvcNAQEL\nBQAwADAeFw0yMTA1MjUxNDEwMDlaFw0yNjA1MjQxNDEwMzlaMDAxLjAsBgNVBAMT\nJWhvbWVzZXJ2ZXIubGFuIEludGVybWVkaWF0ZSBBdXRob3JpdHkwggEiMA0GCSqG\nSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDgP4vWb6kpRJHDbW4NMcAr0YMCvMtDuA9R\n2i4IV5NBimzDXiyJfK8l1UVIEqfBRi49oETerR8ItLsGk3SxK3Mmd+LTNzI0BKeO\n80kugm6HVrJL8XKUhHfOIUjZ69wWb5itHvGT3+WOK+mIyqi9Pqwezf0GRcBvQN7O\ngcHIK5U9xTiha+cO5FwmmWOSqW4CEBkdiABkWahuvx2humelJHIldbny88bK1tJH\nwGnIsI++3ep2wHWw8nddXzQ4mZwDjoRiqNW/mr3K7G3HBhB+4t7fmVByG3njUnab\n7Q5ZqOQil47+XJ5R9zDhG8inXEQJpVUTxo5MB/MhYmWEUoCaKq3NAgMBAAGjgdww\ngdkwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFBJt\nxrMi/27z7WHHLZZA0YXJSJGhMB8GA1UdIwQYMBaAFIAqpIrUJYGJXbkm5kKWKwss\nprZ9MD8GCCsGAQUFBwEBBDMwMTAvBggrBgEFBQcwAoYjaHR0cHM6Ly8xOTIuMTY4\nLjg2Ljc6ODIwMC92MS9wa2kvY2EwNQYDVR0fBC4wLDAqoCigJoYkaHR0cHM6Ly8x\nOTIuMTY4Ljg2Ljc6ODIwMC92MS9wa2kvY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQAc\ni7R4E7u8JRVeSf7pTC8Gmz6xc2JNw3hXQ1A5Z+Fl/ZEBLLte/PCW1wgqIau5vhmg\nEdrNZwS8101NGJ+hqJVphQspZjbhPikCqLTmv49swEiYjfPXeYxhBFnYyH5bAFYH\neOcFj2dtF/8UiixgGuAeDcpw2GRhHnDUT3wj5hkoAG0bbLKGU70GJbpA3IAIgyCF\nVT3eZ1ZPphlSYHh9whcvbB4b9Ql0AisPApggQOZNRCrCNzuaVUJqpkxB1zMf4jEm\nEJxqEdp1R34FJi8D2tJNsiwEi0vcsADkYQctMGXmYynNn7n5LcjuWEnh4i9fGqqw\nVs0dXzvYzonunb0PeuMW\n-----END CERTIFICATE-----"],

"certificate": "-----BEGIN CERTIFICATE-----\nMIIDeTCCAmGgAwIBAgIUdfFGkvjR8NBgihzsnvmiP4kWQX8wDQYJKoZIhvcNAQEL\nBQAwMDEuMCwGA1UEAxMlaG9tZXNlcnZlci5sYW4gSW50ZXJtZWRpYXRlIEF1dGhv\ncml0eTAeFw0yMTA1MjUxNDI5MDRaFw0yMTA1MjYxNDI5MzNaMCMxITAfBgNVBAMT\nGGRhc2hib2FyZC5ob21lc2VydmVyLmxhbjCCASIwDQYJKoZIhvcNAQEBBQADggEP\nADCCAQoCggEBANg1WOU1EL7ifQ+KthWJTZOMrIoT+OBro+dFZwlL56Y7F8znwnFF\nw1yy/yD6R7FmzE2oNnVnaHBmJBA91dm8m9wQ5z98M9d7Ir/nABechUzrGiiI0O5t\n5DqMzJxiUqyklvXsX0xnAxbH3X5DQ0PcKRUVMBHARDeV05efm2VIEzdVn9O4f5RG\n1L2FfBO258CtGOQYqoW73WggxVwdsLAg6OGm6BrO0kgDXugsQyeEVq+jd71ou6NV\nRac2fqFkv5B+MDcQ64y1+1YIdju7JlrWWjI7v38KkYUxKeuTP7+eCoEDQLivLDIm\nfmdtol8TihiWhH8p+Hb6+OnjxJt+dEK2NJECAwEAAaOBlzCBlDAOBgNVHQ8BAf8E\nBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBQk\nEh5MLrSOsG951cJzyZCWtdxHIjAfBgNVHSMEGDAWgBQSbcazIv9u8+1hxy2WQNGF\nyUiRoTAjBgNVHREEHDAaghhkYXNoYm9hcmQuaG9tZXNlcnZlci5sYW4wDQYJKoZI\nhvcNAQELBQADggEBALHvdhRIpIyWmdlaw6zsLQSTu3kMoJ7HBoM87JbYGtsVaiDA\n3f4Sc//yoKKpHWPsnePjVit81hBoiyTz0mwlFWjFKRBaCsctb16oynox2J5YUcBL\nzChbZ0BEAVmpcgVKRs43g70nCy12Nq0LiyOhYx918OvVyxsUV3sBgg6H7Lms02o9\nEMde3/O8rbnbRU90SV0bYPXRvh6gzErOtJGdK/tc3Djle6SUuhQV/zT+Zg4V1mAf\nwMUPr4GZku5wA7cgDAjUCQGyl8A0OtwTYUnks8d/I7osN8nV2kVhD/rMLp5saqGy\nLtaGIB2bBiedsw7BP+n3SCdMm6ePik+052kjFxg=\n-----END CERTIFICATE-----",
"expiration": 1622039373,

"issuing_ca": "-----BEGIN CERTIFICATE-----\nMIIDmzCCAoOgAwIBAgIUZ360AKUYbtI5nlWhGw/4azAUpKIwDQYJKoZIhvcNAQEL\nBQAwADAeFw0yMTA1MjUxNDEwMDlaFw0yNjA1MjQxNDEwMzlaMDAxLjAsBgNVBAMT\nJWhvbWVzZXJ2ZXIubGFuIEludGVybWVkaWF0ZSBBdXRob3JpdHkwggEiMA0GCSqG\nSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDgP4vWb6kpRJHDbW4NMcAr0YMCvMtDuA9R\n2i4IV5NBimzDXiyJfK8l1UVIEqfBRi49oETerR8ItLsGk3SxK3Mmd+LTNzI0BKeO\n80kugm6HVrJL8XKUhHfOIUjZ69wWb5itHvGT3+WOK+mIyqi9Pqwezf0GRcBvQN7O\ngcHIK5U9xTiha+cO5FwmmWOSqW4CEBkdiABkWahuvx2humelJHIldbny88bK1tJH\nwGnIsI++3ep2wHWw8nddXzQ4mZwDjoRiqNW/mr3K7G3HBhB+4t7fmVByG3njUnab\n7Q5ZqOQil47+XJ5R9zDhG8inXEQJpVUTxo5MB/MhYmWEUoCaKq3NAgMBAAGjgdww\ngdkwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFBJt\nxrMi/27z7WHHLZZA0YXJSJGhMB8GA1UdIwQYMBaAFIAqpIrUJYGJXbkm5kKWKwss\nprZ9MD8GCCsGAQUFBwEBBDMwMTAvBggrBgEFBQcwAoYjaHR0cHM6Ly8xOTIuMTY4\nLjg2Ljc6ODIwMC92MS9wa2kvY2EwNQYDVR0fBC4wLDAqoCigJoYkaHR0cHM6Ly8x\nOTIuMTY4Ljg2Ljc6ODIwMC92MS9wa2kvY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQAc\ni7R4E7u8JRVeSf7pTC8Gmz6xc2JNw3hXQ1A5Z+Fl/ZEBLLte/PCW1wgqIau5vhmg\nEdrNZwS8101NGJ+hqJVphQspZjbhPikCqLTmv49swEiYjfPXeYxhBFnYyH5bAFYH\neOcFj2dtF/8UiixgGuAeDcpw2GRhHnDUT3wj5hkoAG0bbLKGU70GJbpA3IAIgyCF\nVT3eZ1ZPphlSYHh9whcvbB4b9Ql0AisPApggQOZNRCrCNzuaVUJqpkxB1zMf4jEm\nEJxqEdp1R34FJi8D2tJNsiwEi0vcsADkYQctMGXmYynNn7n5LcjuWEnh4i9fGqqw\nVs0dXzvYzonunb0PeuMW\n-----END CERTIFICATE-----",

"private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA2DVY5TUQvuJ9D4q2FYlNk4ysihP44Guj50VnCUvnpjsXzOfC\ncUXDXLL/IPpHsWbMTag2dWdocGYkED3V2byb3BDnP3wz13siv+cAF5yFTOsaKIjQ\n7m3kOozMnGJSrKSW9exfTGcDFsfdfkNDQ9wpFRUwEcBEN5XTl5+bZUgTN1Wf07h/\nlEbUvYV8E7bnwK0Y5BiqhbvdaCDFXB2wsCDo4aboGs7SSANe6CxDJ4RWr6N3vWi7\no1VFpzZ+oWS/kH4wNxDrjLX7Vgh2O7smWtZaMju/fwqRhTEp65M/v54KgQNAuK8s\nMiZ+Z22iXxOKGJaEfyn4dvr46ePEm350QrY0kQIDAQABAoIBADNm83yC0jlfpXX1\nd3bFTvE+Z6LoPqo0TSJlyKpYJnnJ4M2xZ/QALwMx9yADANp3YykvTcs5y4W1cut9\nmAMNKUz3o9LfF4AqYUeYhtgWOUbhOjXa2TlmXPVilh6z7Y3oD4/mI34Jm51l5Q3o\ntexDQm0lvWjq+gzxDP4mTw6URSVJQNa1L2rF0dQoDzl9nkTZJTj/YW7giawNAmLv\nyhsKKflPkj/XImk73kqEEkWLmV3/TFSQktcEeO1Zfljg8MuHQe7P3ulI8iYWiAQj\nBX4RM6wGnH+nXWa3htnz9OiTAYVdITPNm/7991syF7sPkCOW3D7kN2CjI3GGyOGS\n19gWK9UCgYEA/6AdFj9oROxWYWYziFfi53Tsq3dURr5pUvCJxUJdGHLLCcIPKA0d\n97xMHxM5jFK1P/3Uo+iine0iivDhxllZpZQ3m02Z42NPxp+9CN9K21Zjf+0ZWRdM\n633vhMO3wovOnFwdhml2r9lRpbt+GkAdZiHzHPgxyL8okmamYEnaQAcCgYEA2IZy\nti0z7G9X9cQwhNA6XIMXhGI3RkbDJ5CXodck6razEB+lr0BtsyvnB1lZxr0RzONd\nfP7pRTKN3pZAVnMQJN2sJrVPugxwp2qFPmhLLOJphCEVwqwU0Z6Bn5o5FovDDOAp\ne6WXodh/igCj05dcJ55R4XpwmEZMJ1cSpSJ6EKcCgYEAuLX9zqGquoL8OA0dl5vJ\n/e3jRlNHtobInIHrS3qUwqHQTRDI2uv/h4+sgZfmsZriFYdZK8diGjPMDhHZUvYl\nbRwYwkPkuwZ8Es5CTjLraGqYI0w0UMghcNjjRlAWbKGRfjKhswpqFM83zEYa7OT/\nWVmWzowZjTF0I7XA6zryVekCgYEAhTi77bEESI39Xb63Z5BCyFb0KkTP45J4UqiZ\nUz9vfGaq59nA9II8vMffXtsv7KK6CAlApT6mQignt/NUZJxpK3WkjTBzfHJZAfj9\nQHelAVnRODWvENcV/B99e7jFNUUK3qoxe91X3YG6fyuDoRV44vt7P7M5AcgG5RGi\n7C25UvMCgYAJ/PXfgQ5+2+GujAz0KLgYqdxEvMsY8XaHx+zmnuYb30HK6CxQ3kG1\nHzt6Itd+sjNasuVBWLrnTEku4RB66u7rv0WEJ1LDo8CaLE3ztIfTMW0dqCu+OOnd\n23vhE0aW3spEPiBzDgU94oZ+jN+cGhN6+AhL7SRPL69zj1O/Qhn5fQ==\n-----END RSA PRIVATE KEY-----",

"private_key_type": "rsa",
"serial_number": "75:f1:46:92:f8:d1:f0:d0:60:8a:1c:ec:9e:f9:a2:3f:89:16:41:7f"},
"wrap_info": null,
"warnings": null,
"auth": null
}

Dump each of these certificates into files

  • cachain.pem
  • issuing.pem
  • certificate.pem
  • key.pem

Additional Features

As well as creating certificates you'll need to revoke and remove certs if they are compromised at the start of this a CRL link back to the HCP Vault server was added to the CA and as such any cert created which can be traced back to that CA cert will know where to look for revoked certificates.

Revoke Certs

curl --header "X-Vault-Token: $VAULT_TOKEN" --request POST --data '{"serial_number": "3f:03:a9:10:e0:28:df:dc:d8:a8:f9:50:7b:cd:d4:8c:01:f5:47:5e"}' $VAULT_ADDR/v1/pki_int/revoke --insecure

Remove Expired certs

curl --header "X-Vault-Token: $VAULT_TOKEN" --request POST --data '{"tidy_cert_store": true, "tidy_revoked_certs": true}' $VAULT_ADDR/v1/pki_int/tidy --insecure

Summary

This is how to set up a CA server using the API on HCP Vault, the next step is to automate this using Ansible. There are lots more features that can be used here, this is a quick high-level overview.

Using Ansible

The idea here is to make the API calls above and use them in Ansible using the URi builtin to create an automated deployment of a CA server in HCP Vault.

Ansible Code

The Ansible code for this up until 1 point was pretty simple and ended up looking like this.

Warning: formatting WILL be off on this if you copy and paste it
##Playbook to deploy aCA Server on Vault
##It needs vault setting up with RW app roles first
---
- hosts: localhost
  become_user: root

  vars:
    vault_url: "https://{{ ansible_host}}:8200"
    mydomain: homeserver.lan

  roles:
    - getrwtoken
    - gettoken


  tasks:

## Create Policy Paylod files from Templates
    - name: (PKI Server) Create All Mounts Policy
      template:
        dest: /root/caallmounts.json
        src: ca/allmounts.json.j2
        owner: vault
        group: vault
        mode: '0600'

    - name: (PKI Server) Create Some Mounts Policy
      template:
        dest: /root/casomemounts.json
        src: ca/somemounts.json.j2
        owner: vault
        group: vault
        mode: '0600'

    - name: (PKI Server) Create PKI Policy Policy
      template:
        dest: /root/capkipayload.json
        src: ca/pkipayload.json.j2
        owner: vault
        group: vault
        mode: '0600'

 ## Import JSON policy Files into HCP Vault
    - name: (PKI Server) Import All Mounts Policy
      uri:
        body_format: json
        validate_certs: false
        headers:
          X-Vault-Token: "{{ vault_token }}"
        url: "{{ vault_url }}/v1/sys/policies/acl/{{ item }}"
        method: POST
        src: "/root/{{ item }}.json"
        remote_src: yes
        status_code: "204"
      with_items:
        - caallmounts
        - casomemounts
    - capkipayload

## Create the PKI secret
    - name: (PKI server) Create PKI CA Store
      uri:
        body_format: json
        validate_certs: false
        headers:
          X-Vault-Token: "{{ vault_token }}"
        url: "{{ vault_url }}/v1/sys/mounts/pki"
        method: POST
        body: 
          type: "pki"
        status_code: "204"

    - name: (PKI server) Create PKI CA Store TTL
      uri:
        body_format: json
        validate_certs: false
        headers:
          X-Vault-Token: "{{ vault_token }}"
        url: "{{ vault_url }}/v1/sys/mounts/pki/tune"
        method: POST
        body: 
          max_lease_ttl: "87600h"  
        status_code: "204"

    - name: (PKI Server) Create PKI Common Name Policy
      template:
        dest: /root/commonname.json
        src: ca/commonname.json.j2
        owner: vault
        group: vault
        mode: '0600'

## Generate CA cert

- name: (PKI Server) Generate CA cert
  uri:
    body_format: json
    validate_certs: false
    headers:
      X-Vault-Token: "{{ vault_token }}"
    url: "{{ vault_url }}/v1/pki/root/generate/internal"
    method: POST
    src: "/root/commonname.json"
    remote_src: yes
    status_code: "200"
  register: certdata


- name: set_fact some paramater
  set_fact:
    cacert: "{{ certdata.json.data['certificate'] }}"

- debug: msg="{{ cacert }}"

##set CRL

- name: (PKI Server) Create CRL Policy File
  template:
    dest: /root/payload-url.json
    src: ca/payload-url.json.j2
    owner: vault
    group: vault
    mode: '0600'

- name: (PKI Server) Import CRL Policy
  uri:
    body_format: json
    validate_certs: false
    headers:
      X-Vault-Token: "{{ vault_token }}"
    url: "{{ vault_url }}/v1/pki/config/urls"
    method: POST
    src: "/root/{{ item }}.json"
    remote_src: yes
    status_code: "204"
  with_items:
    - payload-url

##CREATE PKI INIT SERVER

- name: (PKI server) Create PKI Int CA Store
  uri:
    body_format: json
    validate_certs: false
    headers:
      X-Vault-Token: "{{ vault_token }}"
    url: "{{ vault_url }}/v1/sys/mounts/pki_int"
    method: POST
    body: 
      type: "pki"
    status_code: "204"

- name: (PKI server) Create PKI Int CA Store TTL
  uri:
    body_format: json
    validate_certs: false
    headers:
      X-Vault-Token: "{{ vault_token }}"
    url: "{{ vault_url }}/v1/sys/mounts/pki_int/tune"
    method: POST
    body: 
      max_lease_ttl: "43800h"  
    status_code: "204"

- name: (PKI Server) Create PKI Int Common Name Policy File
  template:
    dest: /root/payload-int.json
    src: ca/payload-int.json.j2
    owner: vault
    group: vault
    mode: '0600'


##create CSR
- name: (PKI Server) Generate PKI Int CSR 
  uri:
    body_format: json
    validate_certs: false
    headers:
      X-Vault-Token: "{{ vault_token }}"
    url: "{{ vault_url }}/v1/pki_int/intermediate/generate/internal"
    method: POST
    src: "/root/payload-int.json"
    remote_src: yes
    status_code: "200"
  register: intcsrdata

- debug: msg="{{ intcsrdata }}"

- name: Pull out just the CSR as a fact
  set_fact:
    csrcert: "{{ intcsrdata.json.data['csr'] | trim }}"

- debug: msg="{{ csrcert }}"

## Sign Int CA Cert with CSR

- name: Ansible PKI Int Cert CSR Payload
  copy:
    dest: "/root/payload-csr-cert.json"
    content: |
      {
        "csr": "{{ csrcert }}",
        "format": "pem_bundle",
        "ttl": "43800h"
      }
###############################################
### Need to add newline \n to the end of the first line
### the beginning and end of the last line
### https://groups.google.com/g/vault-tool/c/vaBtI-S6f14/m/G5blRvx3AQAJ
###############################################


- name: add newline at start of cert
  ansible.builtin.replace:
    path: /root/payload-csr-cert.json
    regexp: '-----BEGIN CERTIFICATE REQUEST-----'
    replace: '-----BEGIN CERTIFICATE REQUEST-----\\n'

- name: add 2 newlines at the end of the cert
  ansible.builtin.replace:
    path: /root/payload-csr-cert.json
    regexp: '-----END CERTIFICATE REQUEST-----'
    replace: '\\n-----END CERTIFICATE REQUEST-----\\n'

- name: Change file ownership, group and permissions
  ansible.builtin.file:
    path: /root/payload-csr-cert.json
    owner: vault
    group: vault
    mode: '0644'

This almost (as I'll explain below in "the final bit" ) ran the whole process as a playbook to setup a CA server on HCP Vault.

Code Breakdown

Some quick notes

  • I use validate_certs: false in the URL because the Vault was set up using a self-signed cert
  • The YAML formatting might be off off
  • I need to add several creates to this code to make it immutable.

Setting files, ownership and rights

There are several files I'veincluded as j2 templates to allow them to be expanded as I improve this code and streamline it, which I've moved into the /root folder. Set the permissions so they are readable by Vault.  

## Create Policy Paylod files from Templates
    - name: (PKI Server) Create All Mounts Policy
      template:
        dest: /root/caallmounts.json
        src: ca/allmounts.json.j2
        owner: vault
        group: vault
        mode: '0600'

    - name: (PKI Server) Create Some Mounts Policy
      template:
        dest: /root/casomemounts.json
        src: ca/somemounts.json.j2
        owner: vault
        group: vault
        mode: '0600'

    - name: (PKI Server) Create PKI Policy Policy
      template:
        dest: /root/capkipayload.json
        src: ca/pkipayload.json.j2
        owner: vault
        group: vault
        mode: '0600'
      
    - name: (PKI Server) Create PKI Common Name Policy
      template:
        dest: /root/commonname.json
        src: ca/commonname.json.j2
        owner: vault
        group: vault
        mode: '0600'

   - name: (PKI Server) Create CRL Policy File
      template:
        dest: /root/payload-url.json
        src: ca/payload-url.json.j2
        owner: vault
        group: vault
        mode: '0600'

These files are called by the various URi POST and GET calls

caallmounts.json

{
"policy": "# Read-only permission on 'homeserver/' path\npath \"sys/mounts/*\" {\n  capabilities = [ \"create\", \"read\", \"update\", \"delete\", \"list\" ]\n}"
}

casomemounts.json

{
"policy": "# Read-only permission on 'homeserver/*' path\npath \"sys/mounts\" {\n  capabilities = [ \"read\", \"list\" ]\n}"
}

capkipayload.json

{
"policy": "# Read-only permission on 'homeserver/*' path\npath \"pki*\" {\n  capabilities = [ \"create\", \"read\", \"update\", \"delete\", \"list\", \"sudo\" ]\n}"
}

commonname.json

{
"common_name": "homeserver.lan",
"ttl": "87600h"
}

payload-url.json

{
  "issuing_certificates": "https://127.0.0.1:8200/v1/pki/ca",
  "crl_distribution_points": "https://127.0.0.1:8200/v1/pki/crl"
}

A quick note on this last one, for the CRL to work, this needs to be a URL accessible by any endpoint using certs generated by the Certificate Authority

It should look something like this if the public URL of your Vault server was 192.168.99.7

{
  "issuing_certificates": "https://192.168.99.7:8200/v1/pki/ca",
  "crl_distribution_points": "https://192.168.99.7:8200/v1/pki/crl"
}

Create a HCP Vault Policy

 ## Import JSON policy Files into HCP Vault
    - name: (PKI Server) Import All Mounts Policy
      uri:
        body_format: json
        validate_certs: false
        headers:
          X-Vault-Token: "{{ vault_token }}"
        url: "{{ vault_url }}/v1/sys/policies/acl/{{ item }}"
        method: POST
        src: "/root/{{ item }}.json"
        remote_src: yes
        status_code: "204"
      with_items:
        - caallmounts
        - casomemounts
        - capkipayload

This play loops around the above 3 policy files and imports them into Vault

Setup the PKI Space

As we have set up Secrets before and needed to enable the KV Engine the PKI engine needs to be enabled.

## Create the PKI secret
    - name: (PKI server) Create PKI CA Store
      uri:
        body_format: json
        validate_certs: false
        headers:
          X-Vault-Token: "{{ vault_token }}"
        url: "{{ vault_url }}/v1/sys/mounts/pki"
        method: POST
        body: 
          type: "pki"
        status_code: "204"

    - name: (PKI server) Create PKI CA Store TTL
      uri:
        body_format: json
        validate_certs: false
        headers:
          X-Vault-Token: "{{ vault_token }}"
        url: "{{ vault_url }}/v1/sys/mounts/pki/tune"
        method: POST
        body: 
          max_lease_ttl: "87600h"  
        status_code: "204"

The core root CA store is also created.

Generate the Certificate Authority (CA) Root Cert

Once the PKI CA Engine is running, the commonname.json file is used to create a root certificate.

 ## Generate CA cert

- name: (PKI Server) Generate CA cert
  uri:
    body_format: json
    validate_certs: false
    headers:
      X-Vault-Token: "{{ vault_token }}"
    url: "{{ vault_url }}/v1/pki/root/generate/internal"
    method: POST
    src: "/root/commonname.json"
    remote_src: yes
    status_code: "200"
  register: certdata


- name: set_fact some paramater
  set_fact:
    cacert: "{{ certdata.json.data['certificate'] }}"

- debug: msg="{{ cacert }}"

Ansible JSON formatting is used to extract only the certificate details from the output and store this as a variable cacert.

Create CRL Policy

With the CA server running, the payload-url.json file is used to ensure certificates can be revoked and checked to see if they are still valid.

   - name: (PKI Server) Import CRL Policy
     uri:
       body_format: json
       validate_certs: false
       headers:
         X-Vault-Token: "{{ vault_token }}"
         url: "{{ vault_url }}/v1/pki/config/urls"
       method: POST
       src: "/root/{{ item }}.json"
       remote_src: yes
       status_code: "204"
     with_items:
       - payload-url

This can be checked and edited by navigating to:

Secrets -> PKI -> Configuration -> Configure -> URLs

Which will display

Create the Int-CA Server on HCP Vault

Because the Root CA should be kept separate with strict policies around access, an Intermediate CA is created as a separate PKI Engine.

##CREATE PKI INIT SERVER

- name: (PKI server) Create PKI Int CA Store
  uri:
    body_format: json
    validate_certs: false
    headers:
      X-Vault-Token: "{{ vault_token }}"
    url: "{{ vault_url }}/v1/sys/mounts/pki_int"
    method: POST
    body: 
      type: "pki"
    status_code: "204"

- name: (PKI server) Create PKI Int CA Store TTL
  uri:
    body_format: json
    validate_certs: false
    headers:
      X-Vault-Token: "{{ vault_token }}"
    url: "{{ vault_url }}/v1/sys/mounts/pki_int/tune"
    method: POST
    body: 
      max_lease_ttl: "43800h"  
    status_code: "204"

This is set up in much the same way as the Rook CA PKI Engine

Create CSR

A certificate signing request needs to be created to have the Int-CA cert signed by the CA cert for authenticity.

##create CSR
- name: (PKI Server) Generate PKI Int CSR 
  uri:
    body_format: json
    validate_certs: false
    headers:
      X-Vault-Token: "{{ vault_token }}"
    url: "{{ vault_url }}/v1/pki_int/intermediate/generate/internal"
    method: POST
    src: "/root/payload-int.json"
    remote_src: yes
    status_code: "200"
  register: intcsrdata

- debug: msg="{{ intcsrdata }}"

- name: Pull out just the CSR as a fact
  set_fact:
    csrcert: "{{ intcsrdata.json.data['csr'] | trim }}"

- debug: msg="{{ csrcert }}"

This will use payload-int.json which will define which domain this intermediate server will sign certificates for.

{
  "common_name": "homeserver.lan Intermediate Authority"
}

The resulting output is filtered using a JSON Query to pull out only the CSR information needed into a variable csrcert

Create CSR Payload File

The CSR needs to output to a file so the URi can ingress it into a file (more on this after this section)

## Sign Int CA Cert with CSR

- name: Ansible PKI Int Cert CSR Payload
  copy:
    dest: "/root/payload-csr-cert.json"
    content: |
      {
        "csr": "{{ csrcert }}",
        "format": "pem_bundle",
        "ttl": "43800h"
      }
###############################################
### Need to add newline \n to the end of the first line
### the beginning and end of the last line
### https://groups.google.com/g/vault-tool/c/vaBtI-S6f14/m/G5blRvx3AQAJ
###############################################


- name: add newline at start of cert
  ansible.builtin.replace:
    path: /root/payload-csr-cert.json
    regexp: '-----BEGIN CERTIFICATE REQUEST-----'
    replace: '-----BEGIN CERTIFICATE REQUEST-----\\n'

- name: add 2 newlines at the end of the cert
  ansible.builtin.replace:
    path: /root/payload-csr-cert.json
    regexp: '-----END CERTIFICATE REQUEST-----'
    replace: '\\n-----END CERTIFICATE REQUEST-----\\n'

- name: Change file ownership, group and permissions
  ansible.builtin.file:
    path: /root/payload-csr-cert.json
    owner: vault
    group: vault
    mode: '0644'

Problems

At this point, I started having issues with Ansible and a URi Post. No matter how i formatted the resulting JSON file it would not import using ansible but would using the curl command from the command line.

The resulting file looked like this

{
  "csr": "-----BEGIN CERTIFICATE REQUEST-----\n
MIICdTCCAV0CAQAwMDEuMCwGA1UEAxMlaG9tZXNlcnZlci5sYW4gSW50ZXJtZWRp
YXRlIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANi0
QEL0hw4jlf+iCo3Oe/AHP3h/Nd06cLbJ+DYCWpmrLTRT1Tp/e+k7S8cp3keN5j4p
Jzw53Io8JoUDVJF5vtNDfNLBt4cMn6kHv1cpYj+8mPOg8ZdxjVaYz3kKaVIH3Myn
a68tD7FJwekvs081SWQ+s6dog0jXefipzx+0V2aCu7Pe+RzYxfIM9XXqe8C4/5Eo
Rvpropiw0ewRC9QSfD9CbnkTs1VzAx4q3on7db4Rvv2ZPoXOdcYf+AyYGb239C3w
p8CTeQAT+wcELbUev1aRpXF55ZCrwIx+kQvMNUQDIGWDsWDUXFILgIKc+P+WGWji
qB1OGg0uuCM2NaGT/10CAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQCuIDGTc7XU
Vqlj7mS0HYJXuX5WFWknsBTwcRolcN/4rLB57AmF+a1Hj4nc7tvwuQBXP9N+B6hT
V8IgxRb2nduTS34Ap5jaFa8lzQo6cbug61LlUJ/j956IH/2i/Y1kgLAvhkhkoy6c
hREUE4UTOLNZsWbzXOYovrHNpJqIvFstanHeruImbUj5Ec/DWMsCB8DHGw5WQ07Y
8v5Bk3yDdsCIJUb+kaadwchQmu5q4e4G90f7+e9wC4n2q27YTbXNduQda4mBlWVw
3uCiTbhZZU05OP3wdMf9AOms4uq1nqvFsRBNBm1LmEqqZCXPBn8gq9LW/42YlErQ
bMONY7Gq3cYt
\n-----END CERTIFICATE REQUEST-----\n",
"format": "pem_bundle",
"ttl": "43800h"
}

The Ansible replace above added the \\n which were needed.

Running this works

curl --header "X-Vault-Token: $VAULT_TOKEN" --request POST --data @/root/payload-csr-cert.json $VAULT_ADDR/v1/pki/root/sign-intermediate --insecure

No matter what combination of user with src: </file/name/here.json> or body: I kept getting 400 errors, which I think are because the spacing in the file

The final bit

Because I'm running Jenkins, i ended up running a  stage which runs a simple bash script

curl --header "X-Vault-Token: $VAULT_TOKEN" --request POST --data @/root/payload-csr-cert.json $VAULT_ADDR/v1/pki/root/sign-intermediate --insecure | jq '.data | { certificate }' > /root/intermediatecert.pem

Which outputs (this too has CR's at the end of each line, i removed them to make the output format better.)

{
  "certificate": "-----BEGIN CERTIFICATE-----\nMIIDrjCCApagAwIBAgIUAwj4T6jnamhqZvY/Myv+glS6QZowDQYJKoZIhvcNAQEL\nBQAwGTEXMBUGA1UEAxMOaG9tZXNlcnZlci5sYW4wHhcNMjEwNjAxMTQxNDExWhcN\nMjYwNTMxMTQxNDQxWjAwMS4wLAYDVQQDEyVob21lc2VydmVyLmxhbiBJbnRlcm1l\nZGlhdGUgQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\n2LRAQvSHDiOV/6IKjc578Ac/eH813Tpwtsn4NgJamastNFPVOn976TtLxyneR43m\nPiknPDncijwmhQNUkXm+00N80sG3hwyfqQe/VyliP7yY86Dxl3GNVpjPeQppUgfc\nzKdrry0PsUnB6S+zTzVJZD6zp2iDSNd5+KnPH7RXZoK7s975HNjF8gz1dep7wLj/\nkShG+muimLDR7BEL1BJ8P0JueROzVXMDHireift1vhG+/Zk+hc51xh/4DJgZvbf0\nLfCnwJN5ABP7BwQttR6/VpGlcXnlkKvAjH6RC8w1RAMgZYOxYNRcUguAgpz4/5YZ\naOKoHU4aDS64IzY1oZP/XQIDAQABo4HWMIHTMA4GA1UdDwEB/wQEAwIBBjAPBgNV\nHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSo2uBJ1wVIYAgcMLYBvgHF89e7AjAfBgNV\nHSMEGDAWgBS6zDHZbTtdDT0LPP6Gkwl0Tg1e9DA8BggrBgEFBQcBAQQwMC4wLAYI\nKwYBBQUHMAKGIGh0dHBzOi8vMTI3LjAuMC4xOjgyMDAvdjEvcGtpL2NhMDIGA1Ud\nHwQrMCkwJ6AloCOGIWh0dHBzOi8vMTI3LjAuMC4xOjgyMDAvdjEvcGtpL2NybDAN\nBgkqhkiG9w0BAQsFAAOCAQEAplFWmfX1L/gIn4N4myzKvcnV48AMhTDj3sxIXXSw\nPS92Ngk67ZxAiTxT2GhdOTcbI8Kh5GyJOmTumMvXntLj3bgjzEqHc4de23113Bf/\nSiT1FdG4Jdq7u/gVpSqelUU8+B+qsVgikvD3UiNhDCHMP13NUJArpxX35fXrLoi1\nMzjCVN+7XRh0LHbtyA+ZlrNwImHgCNcRoap9zpEMw6yYxKmZ0DAYSar2Egd1iS1i\nD4KO+UUxGKoZpA1Yv1EBo/TcG8+F3Bf9O6qjrKDcBm/TFhUDKlYGJPhdmuFN2Sx+\n1MjAhr+NPIe7qdd7eYUgMrTQ4hqk44d2Nt32EatSZqfUMA==\n-----END CERTIFICATE-----"
}

Why not run this as shell: or command in Ansible? Simple, special characters are a pig and lifes to short to find the right escaping to get past a colon or a doublequote.  

A final set of Ansible is run

    - name: (PKI Server) Submit Signed Intermediate Certificate to Vault 
      uri:
        body_format: json
        validate_certs: false
        headers:
          X-Vault-Token: "{{ vault_token }}"
        url: "{{ vault_url }}/v1/pki_int/intermediate/set-signed"
        method: POST
        src: "/root/intermediatecert.pem"
        remote_src: yes
        status_code: "204"

Which silently imports the intermediate certificate into Vault

Create a Vault Role to pull the endpoint certs down

Create a payload file

payload-role.json

  - name: Creating a file with content
    copy:
      dest: "/your path"
      content: |
        {
          "allowed_domains": "homeserver.com",
          "allow_subdomains": true,
          "max_ttl": "720h"
        }

Run this

    - name: (PKI Server) Create Endpoint Role
      uri:
        body_format: json
        validate_certs: false
        headers:
          X-Vault-Token: "{{ vault_token }}"
        url: "{{ vault_url }}/v1/pki_int/roles/homeserver-dot-com"
        method: POST
        src: "/root/payload-role.json"
        remote_src: yes
        status_code: "204"

Note the URL

url: "{{ vault_url }}/v1/pki_int/roles/homeserver-dot-com"

This can be changed and will be used by the Ansible role to pull the endpoint certs down.

At this point, the CA Server with a Root and Intermediate CA has been set up.

An Ansible Role

To consume the server an Ansible role is needed to run the following

$ curl --header "X-Vault-Token: $VAULT_TOKEN" \
       --request POST \
       --data '{"common_name": "test.example.com", "ttl": "24h"}' \
       $VAULT_ADDR/v1/pki_int/issue/example-dot-com | jq

And extract the JSON formatted output as a list

{
  "request_id": "7544fb94-49aa-8a11-1396-67938b69c3f2",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": {
    "ca_chain": [
      "-----BEGIN CERTIFICATE-----\nMIIDqDCCApCgAwIBAgIUfg7l07KVEnht4ETStjHLAA3xvp4wDQYJKoZIhvcNAQEL\nBQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMjEwNDE5MTYzNTM1WhcNMjYw\nNDE4MTYzNjA1WjAtMSswKQYDVQQDEyJleGFtcGxlLmNvbSBJbnRlcm1lZGlhdGUg\nQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApranvA5f\ncP+OySUE+uKNxzR3IkWI59Z0mMYlHYBtAY47V8avXcmwsaFDJiGSq6OXJs8SXKiF\nrXONaQSudWxTO28U8AovcwoV3/C5KsldqbhLMOyor+XDWYQP7GVXGuKYhn9C+kaF\nbiyaPE0SsAb6Yl3emVl1UCq9kBbQm1XmFyih7rdREB4XJZW2Mtp5KdzlHf3zGLfB\nEcK/BF0CsC1kI5UkkJVWxLB3hKx2pr3Bl3olufQDDdweKqlg7YtiPPnmxLvuEq5n\nG5h18++mbxywIvU7iehGYDj+EBFV1zrsOmZcqfSC34xRbJLVg8wFhuQ59Zy/k+mh\nEUo63NOrT1lzTQIDAQABo4HWMIHTMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E\nBTADAQH/MB0GA1UdDgQWBBQoQnqjxjJ8ExU3SDHRG9M67KNlLDAfBgNVHSMEGDAW\ngBSnG76ILbfk2VdLrRd6m6bVYK3qRjA8BggrBgEFBQcBAQQwMC4wLAYIKwYBBQUH\nMAKGIGh0dHBzOi8vMTI3LjAuMC4xOjgyMDAvdjEvcGtpL2NhMDIGA1UdHwQrMCkw\nJ6AloCOGIWh0dHBzOi8vMTI3LjAuMC4xOjgyMDAvdjEvcGtpL2NybDANBgkqhkiG\n9w0BAQsFAAOCAQEAegVUIb5SFR/bXo33xqjuocINJLfec22dgjgBO0lMOGp32GFB\nIp4clgfNU1Dw8yGQEoRQHEfWDjZeGdPuCYbgu/ucLW1pLrBiQqO/XgVeFQhwnJp6\nza0NLGnmaDd5k8z1+VJ79tJa0Wb2WRxWxYqu6lsqtYPAj9jD4DM73tfGR76/rCiK\nRB9DRwlr9UTm9h0cw84UpPwaX33kfiIKBeyY9/hUe3pBzGuNGuDgjOiaMph0v+9z\nwiyNCzcR0AN0y1qDVvsqABH9GJ0BpaZ48GbZTZm91Q/kg+HdNlPkgwqpY8+V8aMa\n1CyvvvJeega8q9Evg1v68MajkFFT37YI5XESCw==\n-----END CERTIFICATE-----"
    ],
    "certificate": "-----BEGIN CERTIFICATE-----\nMIIDZjCCAk6gAwIBAgIUPwOpEOAo39zYqPlQe83UjAH1R14wDQYJKoZIhvcNAQEL\nBQAwLTErMCkGA1UEAxMiZXhhbXBsZS5jb20gSW50ZXJtZWRpYXRlIEF1dGhvcml0\neTAeFw0yMTA0MTkxNjM5MjVaFw0yMTA0MjAxNjM5NTVaMBsxGTAXBgNVBAMTEHRl\nc3QuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDP\nfe4nVV+AFnRUgW33qXyCOC4y7ruzSm9P6SiEirTFT+MmNtHEGPBGImGFBLYrSqMo\nePiO3GolpHWezY73cvRzFWM/ZLTmfjqsFNsQmP4tS1bbKa7Bn2ZcypbJNENr+/d1\nBkv9mIJuJwI1sQLI2yQe8KZT3HLrtr4tbEGXnY/tZD3rm+kgZNvCq0GpTARATCkZ\n3qUgOT10O5dMYe8Y2GGCZisiviKeGP1S42EL15QsoLhvIui2sZws7sa6LLeWMNYM\nIsWRAYsTkDffFRWEx/yK1axr0Heo9TrRVBuNfRkU8X/p1Ls3kXUB/HMF9RsW53uZ\nDPJbXOHDSJVhkubPoDxXAgMBAAGjgY8wgYwwDgYDVR0PAQH/BAQDAgOoMB0GA1Ud\nJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQU0UKGmwpRpQsi+wTo\nviMS6w6iEkowHwYDVR0jBBgwFoAUKEJ6o8YyfBMVN0gx0RvTOuyjZSwwGwYDVR0R\nBBQwEoIQdGVzdC5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAhRcjJf6T\nve9hrVsYSVZLGEOmfYoVJ9J2u7fxOfIMnBYJ0EEm047my6Qj5wmu00HSefhheBK1\n46XK5iBlpdSVrV28IjE8PDnWotWVyYhy9vq+LdVbrhHV3JWbRtM21AXkL/73dB3h\nBC3o+lRjmw+ySSEFlfugP+a9ULqwWtuQH+uFWq86F2Ar4y5Sl4TnIiZ9i22Yx+i4\nKcwPRPFsEyCvwRh1Ih/r/UTsrF0lqE7iQ77Yo9I9MuNKMc6y3+fI3vZWTED0QIfz\nVlb+A5Q9AfGhfaygc2fzJ9w/gnZ+D6wp/mQWQz2xNxs3iaDQf8xHpXywYNGbkVXX\ny/cTj/D8a8iAJA==\n-----END CERTIFICATE-----",
    "expiration": 1618936795,
    "issuing_ca": "-----BEGIN CERTIFICATE-----\nMIIDqDCCApCgAwIBAgIUfg7l07KVEnht4ETStjHLAA3xvp4wDQYJKoZIhvcNAQEL\nBQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMjEwNDE5MTYzNTM1WhcNMjYw\nNDE4MTYzNjA1WjAtMSswKQYDVQQDEyJleGFtcGxlLmNvbSBJbnRlcm1lZGlhdGUg\nQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApranvA5f\ncP+OySUE+uKNxzR3IkWI59Z0mMYlHYBtAY47V8avXcmwsaFDJiGSq6OXJs8SXKiF\nrXONaQSudWxTO28U8AovcwoV3/C5KsldqbhLMOyor+XDWYQP7GVXGuKYhn9C+kaF\nbiyaPE0SsAb6Yl3emVl1UCq9kBbQm1XmFyih7rdREB4XJZW2Mtp5KdzlHf3zGLfB\nEcK/BF0CsC1kI5UkkJVWxLB3hKx2pr3Bl3olufQDDdweKqlg7YtiPPnmxLvuEq5n\nG5h18++mbxywIvU7iehGYDj+EBFV1zrsOmZcqfSC34xRbJLVg8wFhuQ59Zy/k+mh\nEUo63NOrT1lzTQIDAQABo4HWMIHTMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E\nBTADAQH/MB0GA1UdDgQWBBQoQnqjxjJ8ExU3SDHRG9M67KNlLDAfBgNVHSMEGDAW\ngBSnG76ILbfk2VdLrRd6m6bVYK3qRjA8BggrBgEFBQcBAQQwMC4wLAYIKwYBBQUH\nMAKGIGh0dHBzOi8vMTI3LjAuMC4xOjgyMDAvdjEvcGtpL2NhMDIGA1UdHwQrMCkw\nJ6AloCOGIWh0dHBzOi8vMTI3LjAuMC4xOjgyMDAvdjEvcGtpL2NybDANBgkqhkiG\n9w0BAQsFAAOCAQEAegVUIb5SFR/bXo33xqjuocINJLfec22dgjgBO0lMOGp32GFB\nIp4clgfNU1Dw8yGQEoRQHEfWDjZeGdPuCYbgu/ucLW1pLrBiQqO/XgVeFQhwnJp6\nza0NLGnmaDd5k8z1+VJ79tJa0Wb2WRxWxYqu6lsqtYPAj9jD4DM73tfGR76/rCiK\nRB9DRwlr9UTm9h0cw84UpPwaX33kfiIKBeyY9/hUe3pBzGuNGuDgjOiaMph0v+9z\nwiyNCzcR0AN0y1qDVvsqABH9GJ0BpaZ48GbZTZm91Q/kg+HdNlPkgwqpY8+V8aMa\n1CyvvvJeega8q9Evg1v68MajkFFT37YI5XESCw==\n-----END CERTIFICATE-----",
    "private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAz33uJ1VfgBZ0VIFt96l8gjguMu67s0pvT+kohIq0xU/jJjbR\nxBjwRiJhhQS2K0qjKHj4jtxqJaR1ns2O93L0cxVjP2S05n46rBTbEJj+LUtW2ymu\nwZ9mXMqWyTRDa/v3dQZL/ZiCbicCNbECyNskHvCmU9xy67a+LWxBl52P7WQ965vp\nIGTbwqtBqUwEQEwpGd6lIDk9dDuXTGHvGNhhgmYrIr4inhj9UuNhC9eULKC4byLo\ntrGcLO7Guiy3ljDWDCLFkQGLE5A33xUVhMf8itWsa9B3qPU60VQbjX0ZFPF/6dS7\nN5F1AfxzBfUbFud7mQzyW1zhw0iVYZLmz6A8VwIDAQABAoIBADMwNhiuDyliYMCY\nTbDTt0vI4FzgWJ4atutX8g8AySgEVV2QGJ/wJxamVLikOOzlNOs/LNLRvb4bnIjY\n3XRef8AEfr+c8KQMcB0T6BdoJwy1kW/wEJTj5jTuJdTtd9SkDKBqNUUS4tqZ9QmZ\n6b3zki2v4Ni/gfp00uYR1vy4elFt/yjTmA0lhY4onPNPqXNWCF1LY38xNegBQZg4\nrYkSFvIOr7bl7Rk2JljWCxZZef5kEIFGdoqus5KekzW8Li9s2XVVXWEfoY/r/NI1\nHgAwHugwe3+8ve3YKLIIKJqGvYk+8Lp8rXPsf9GRZxpxx/yER4NX8+83DndBnfQO\n7G/BOwECgYEA/0ZAU6h+1cW+PxQ+CuSdbClXhKgqTN4pRwCbrGUQ3y2KGsGUzZW/\nUuO/XIN/hkx0SkKz10TvUV5o4SXh5nY7s+3OCsnc7fBx3Gv4vHCWQ4t9JNxeH6vP\nzNKjQeceVzlniyxKlW9OvVDXZU6nv27LBRH1fIYp9QRbc7yHzUQXLCcCgYEA0BTp\nD3CXELJYoV0/LFvRQqZ+P1Xo26qa9xjRMI+Owr2nNuzqcMl4dBvZWNNCpDwqrKn6\n6iiWXYhwxvvKkJe9QgE3LCvkmluGiBDpK4U2rVbB/LoBAtNgGdnxaP9t9OfhtQC3\nPjhVJvBVbk3mFWG950Ua5E1cgl6n4xtGoHgxHFECgYBaXOTieE/FnoUU0TaRJpIv\nOoc3d0vZ//5+mtGAeho51mX/yKzDBZI/Zk1UE1xuDtxPeUMuHcHVfOUFZiKMMSg7\nLh/0o7ZoJ+g2TaY0FmqqqFL5XGSZM3mQmLOf3Y9Y8wIbOud/9HHcBCTrQKeS1UZa\nmhvbI6bwi8VPt9oeqE7HmwKBgHqOVlbJsbAb2yfvi+3MhowDFAipyOTYrz0qWMuJ\nQkRg/8PR9qNHhrKcVH+ErpOc/GWGGEsibK3aVtJcKwrO1KGzpZNWpuZjUfGCRFNl\nuraNiuQXidDoPon7W7zD9Tdx+/Zn3YXAGCc/FpJJP2MIlplIknY1Om9u4ONahVau\nc/6BAoGBAIfW6PPFjXUUn3LvcswvQAahnclYIW+Ml6peerfMr5kq0TTWykrMN4nc\nLl9oGG2ifigSUo/mz2cfemExO+hgXJP+UOOKi2LkUUpV9x1fzJFHYutiIyV4mBPd\nTFPCpLJjz3T4Nn18nzGmrQiQT6ymgt4KDx6cW+xbOLTuNsNg+0ib\n-----END RSA PRIVATE KEY-----",
    "private_key_type": "rsa",
    "serial_number": "3f:03:a9:10:e0:28:df:dc:d8:a8:f9:50:7b:cd:d4:8c:01:f5:47:5e"
  },
  "wrap_info": null,
  "warnings": null,
  "auth": null
}

While I've not done this yet, the Ansible task for the role would look something like this to extract the certificate:

- name: (PKI Server) Submit Signed Intermediate Certificate to Vault 
  uri:
    body_format: json
    validate_certs: false
    headers:
      X-Vault-Token: "{{ vault_token }}"
    url: "{{ vault_url }}/v1/pki_int/issue/homeserver-dot-com
    method: POST
    body:
      common_name: "{{ servername}}.{{domainname}}" 
      ttl: "15d"
    remote_src: yes
    status_code: "204"

This will be output as JSON and using similar filter and lists the certs can be extracted and written out to file for the applications as needed.

Thoughts

This is run once code, it needs work to make it immutable, which can be done quickly using creates: and checks in the code.

I'd love to know why the JSON output ansible creates for the CSR doesn't work in Ansible.

This is the play failed output

TASK [(PKI Server)Sign Intermediate Certificate with CSR] **********************
fatal: [localhost]: FAILED! => changed=false 
  cache_control: no-store
  connection: close
  content: |-
    {"errors":["error parsing JSON"]}
  content_length: '34'
  content_type: application/json
  date: Fri, 28 May 2021 14:38:30 GMT
  elapsed: 0
  json:
    errors:
    - error parsing JSON
  msg: 'Status code was 400 and not [200]: HTTP Error 400: Bad Request'
  redirected: false
  status: 400
  url: https://127.0.0.1:8200/v1/pki/root/sign-intermediate

Using Jenkins it would be quicker to run it as a set of Bash scripts to this point.

Further Reading

Build Your Own Certificate Authority (CA) | Vault - HashiCorp Learn
Learn how to provision, secure, connect, and run any infrastructure for any application.

Share Tweet Send
0 Comments
Loading...
You've successfully subscribed to Tech Blog Posts - David Field
Great! Next, complete checkout for full access to Tech Blog Posts - David Field
Welcome back! You've successfully signed in
Success! Your account is fully activated, you now have access to all content.