Vault & Vault Enterprise offer a fully ReST enabled CA, with built in expiration and leasing. You can generate a root CA, make an intermediate and get it signed, or import your own root CA. Others on this platform have written about this in general, but I'd like to look at some of the specific details. Specifically, I want to see what happens when I generate a private key for an Intermediate CA, and create a CSR for it.
If I may paraphrase the immortal Dilbert:
Here's a nickel, Kid. Get yourself a better internal CA distribution API.
--Nathan Basanese, 2019
But seriously, if you've ever thought you could do a better job than DigiCert or Symantec, Vault gives you the ability to create an internal, bespoke root CA, complete with a private key that no human can ever touch.
The guide from HashiCorp, here, shows how to do that: https://learn.hashicorp.com/vault/security/sm-pki-engine
But what if you all want something more sophisticated than that? What if you have an existing PKI system deployed all over tens of thousands of different platforms?
What about creating an Intermediate in Vault, and having another CA sign that intermediate? Vault can do that.
That way, if your organization already has a CA system in place, you all can still take advantage of Vault's API for getting signed certificates.
I've made a brief guide for how to make an Intermediate signing authority in Vault, make a CSR for that Intermediate in a way that's compatible with Micro$oft CA management tools, and create certificates using that Intermediate.
I expect that this most likely works with with all versions of HashiCorp Vault Enterprise & Open Source greater than or equal to 0.8.0.
Get Ye a Vault, Laddy!
First, download and install HashiCorp Vault from here: https://vaultproject.io/downloads.html
Extract the .zip file.
Run Ye a Vault, Laddy!
Open a Terminal, navigate to the directory to which you've downloaded Vault, using cd
and run the Vault binary there:
./vault server -dev -root-token-id=root
You now have your very own HashiCorp Vault. Congratulations.
Interact Ye with Vault, Laddy!
Open another Terminal, and run this:
export VAULT_ADDR='http://127.0.0.1:8200'
export VAULT_TOKEN='root'
Time to make a CA root.
vault write pki/root/generate/internal common_name=eff.wtf
With output:
tls$ vault write pki/root/generate/internal common_name=eff.wtf ttl=8760h
Key Value
--- -----
certificate -----BEGIN CERTIFICATE-----
MIIDqjCCApKgAwIBAgIUfBiOa+giZFHvf1jWQaP6UQ67djgwDQYJKoZIhvcNAQEL
BQAwFzEVMBMGA1UEAxMMYmFzYW5lc2UuY29tMB4XDTE5MTIyMDAzMzQyMFoXDTIw
MTIxOTAzMzQ1MFowFzEVMBMGA1UEAxMMYmFzYW5lc2UuY29tMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtPf4MKTfhIfe5NGkSa5HIrF2bWrvssu2WcOw
AZyls8zuAhouW2tvOm3dslUhbkI63RlrzlcxpDw0fGpouS3s9KHrUnRK39wFrQAw
HZNzDIvERshj2XsGADeFY9c+ztkbuqr0tBG5y8wceZi5aJB+EmUra4wtOraF5ACk
FVa6ni5YKU5RG7kSrn0+LUBLTBIKa+LU4lsrdSfqba8qttnMvlwe3fGZOhl+wA87
II53KTxEhLVARxt2+xCBTyBhFi66NTLLQD7RhG7Bkb/FpiLkD79OBxVTeUhvw3ll
VYJSrjDoaj3RIjmdwoP9b1Wr2QaIsxXhuQ85cDBX3K1yEOKezwIDAQABo4HtMIHq
MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS6/TVv
NmJ5CqGhwx9k1Knw4DsFBTAfBgNVHSMEGDAWgBS6/TVvNmJ5CqGhwx9k1Knw4DsF
BTA7BggrBgEFBQcBAQQvMC0wKwYIKwYBBQUHMAKGH2h0dHA6Ly8xMjcuMC4wLjE6
ODIwMC92MS9wa2kvY2EwFwYDVR0RBBAwDoIMYmFzYW5lc2UuY29tMDEGA1UdHwQq
MCgwJqAkoCKGIGh0dHA6Ly8xMjcuMC4wLjE6ODIwMC92MS9wa2kvY3JsMA0GCSqG
SIb3DQEBCwUAA4IBAQApG/kDT4FIPv2781IWfELZb9IZylRLIED2+LcCU4D02gSM
y8oSw3DYD1HFMTo9/Stb2K9NKby5p36MitYWXor1I/yYUwMkrW96gDytXg7zx2rT
Cu5DSVD48077urGGK73pGX7Ctlf7fgmnTGwkQAafjEQLgXeknZaILYEwWTEiqQmx
XPa/etA+8rZTUORmVO/H33ga2C21AG2KVDaTP2j2rLx16NVc4xJ6pIkfGInd8Zom
Xth53iksvAeZ/DgrPusRjMGswArYmeYk2YWr7wa1Xe0WoEXBtsiH/Hl7QMvXQq2w
/iw8nDSS16Otj0jUwtMqLv2aFOYcsp3BjkNRZWv1
-----END CERTIFICATE-----
expiration 1608348890
issuing_ca -----BEGIN CERTIFICATE-----
MIIDqjCCApKgAwIBAgIUfBiOa+giZFHvf1jWQaP6UQ67djgwDQYJKoZIhvcNAQEL
BQAwFzEVMBMGA1UEAxMMYmFzYW5lc2UuY29tMB4XDTE5MTIyMDAzMzQyMFoXDTIw
MTIxOTAzMzQ1MFowFzEVMBMGA1UEAxMMYmFzYW5lc2UuY29tMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtPf4MKTfhIfe5NGkSa5HIrF2bWrvssu2WcOw
AZyls8zuAhouW2tvOm3dslUhbkI63RlrzlcxpDw0fGpouS3s9KHrUnRK39wFrQAw
HZNzDIvERshj2XsGADeFY9c+ztkbuqr0tBG5y8wceZi5aJB+EmUra4wtOraF5ACk
FVa6ni5YKU5RG7kSrn0+LUBLTBIKa+LU4lsrdSfqba8qttnMvlwe3fGZOhl+wA87
II53KTxEhLVARxt2+xCBTyBhFi66NTLLQD7RhG7Bkb/FpiLkD79OBxVTeUhvw3ll
VYJSrjDoaj3RIjmdwoP9b1Wr2QaIsxXhuQ85cDBX3K1yEOKezwIDAQABo4HtMIHq
MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS6/TVv
NmJ5CqGhwx9k1Knw4DsFBTAfBgNVHSMEGDAWgBS6/TVvNmJ5CqGhwx9k1Knw4DsF
BTA7BggrBgEFBQcBAQQvMC0wKwYIKwYBBQUHMAKGH2h0dHA6Ly8xMjcuMC4wLjE6
ODIwMC92MS9wa2kvY2EwFwYDVR0RBBAwDoIMYmFzYW5lc2UuY29tMDEGA1UdHwQq
MCgwJqAkoCKGIGh0dHA6Ly8xMjcuMC4wLjE6ODIwMC92MS9wa2kvY3JsMA0GCSqG
SIb3DQEBCwUAA4IBAQApG/kDT4FIPv2781IWfELZb9IZylRLIED2+LcCU4D02gSM
y8oSw3DYD1HFMTo9/Stb2K9NKby5p36MitYWXor1I/yYUwMkrW96gDytXg7zx2rT
Cu5DSVD48077urGGK73pGX7Ctlf7fgmnTGwkQAafjEQLgXeknZaILYEwWTEiqQmx
XPa/etA+8rZTUORmVO/H33ga2C21AG2KVDaTP2j2rLx16NVc4xJ6pIkfGInd8Zom
Xth53iksvAeZ/DgrPusRjMGswArYmeYk2YWr7wa1Xe0WoEXBtsiH/Hl7QMvXQq2w
/iw8nDSS16Otj0jUwtMqLv2aFOYcsp3BjkNRZWv1
-----END CERTIFICATE-----
serial_number 7c:18:8e:6b:e8:22:64:51:ef:7f:58:d6:41:a3:fa:51:0e:bb:76:38
tls$
Now, let's see what our options are for making an intermediate CA.
vault path-help pki/intermediate/generate/internal
Here's the output you should, ideally, get with Vault 1.1:
tls1$ vault path-help pki/intermediate/generate/internal
Request: intermediate/generate/internal
Matching Route: ^intermediate/generate/(?P<exported>\w(([\w-.]+)?\w)?)$
Generate a new CSR and private key used for signing.
## PARAMETERS
add_basic_constraints (bool)
Whether to add a Basic Constraints
extension with CA: true. Only needed as a
workaround in some compatibility scenarios
with Active Directory Certificate Services.
alt_names (string)
The requested Subject Alternative Names, if any,
in a comma-delimited list. May contain both
DNS names and email addresses.
common_name (string)
The requested common name; if you want more than
one, specify the alternative names in the alt_names
map. If not specified when signing, the common
name will be taken from the CSR; other names
must still be specified in alt_names or ip_sans.
country (slice)
If set, Country will be set to
this value.
exclude_cn_from_sans (bool)
If true, the Common Name will not be
included in DNS or Email Subject Alternate Names.
Defaults to false (CN is included).
exported (string)
Must be "internal" or "exported". If set to
"exported", the generated private key will be
returned. This is your *only* chance to retrieve
the private key!
format (string)
Format for returned data. Can be "pem", "der",
or "pem_bundle". If "pem_bundle" any private
key and issuing cert will be appended to the
certificate pem. Defaults to "pem".
ip_sans (slice)
The requested IP SANs, if any, in a
comma-delimited list
key_bits (int)
The number of bits to use. You will almost
certainly want to change this if you adjust
the key_type.
key_type (string)
The type of key to use; defaults to RSA. "rsa"
and "ec" are the only valid values.
locality (slice)
If set, Locality will be set to
this value.
organization (slice)
If set, O (Organization) will be set to
this value.
other_sans (slice)
Requested other SANs, in an array with the format
<oid>;UTF8:<utf8 string value> for each entry.
ou (slice)
If set, OU (OrganizationalUnit) will be set to
this value.
postal_code (slice)
If set, Postal Code will be set to
this value.
private_key_format (string)
Format for the returned private key.
Generally the default will be controlled by the "format"
parameter as either base64-encoded DER or PEM-encoded DER.
However, this can be set to "pkcs8" to have the returned
private key contain base64-encoded pkcs8 or PEM-encoded
pkcs8 instead. Defaults to "der".
province (slice)
If set, Province will be set to
this value.
serial_number (string)
The requested serial number, if any. If you want
more than one, specify alternative names in
the alt_names map using OID 2.5.4.5.
street_address (slice)
If set, Street Address will be set to
this value.
ttl (duration (sec))
The requested Time To Live for the certificate;
sets the expiration date. If not specified
the role default, backend default, or system
default TTL is used, in that order. Cannot
be larger than the mount max TTL. Note:
this only has an effect when generating
a CA cert or signing a CA cert, not when
generating a CSR for an intermediate CA.
uri_sans (slice)
The requested URI SANs, if any, in a
comma-delimited list.
## DESCRIPTION
See the API documentation for more information.
tls$
The add_basic_constraints
option is in place for backwards compatibility with Micro$oft systems.
And now, we'll make an intermediate, with a basic constraint of CA: True
.
vault write pki/intermediate/generate/internal add_basic_constraints=true common_name=eff.wtf ttl=8760h -output file csr_with_catrue.asc
Here's what it looks like with output:
tls$ vault write pki/intermediate/generate/internal add_basic_constraints=true common_name=eff.wtf ttl=8760h
Key Value
--- -----
csr -----BEGIN CERTIFICATE REQUEST-----
MIICmzCCAYMCAQAwGTEXMBUGA1UEAxMObXktd2Vic2l0ZS5jb20wggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDXQGdlVA437cWAwjZjuznoBZt2KI6gcqy+
htxFIHiF7lV5zS+8MACP+ozSUtUapuPh7Bji3yMMbs/7YNgJaICWnqwjP4hKtBt5
293QLnl3SjGvb2wbKTmR0iugJ8Q96Lag1nSQ2UTOxfPvwV5ud4VgCS8sezOKJe9x
+c6KMmbgRuCeBE9MagkwhVbunMXu8rqSdO5vQPb175VnTazOYsHSQRWyfG3uHmAL
JYSOWZ0MLN4ieO75otIJmOn1CqrtYqoHsk9ukml4TL0MJdZG/W+JnlhQkDC3hDf+
hznsOAPV7wxiirX/j8H42lIj9A4Mm1F7CP3+Bs1fsO4GVIOc/drhAgMBAAGgPTA7
BgkqhkiG9w0BCQ4xLjAsMBkGA1UdEQQSMBCCDm15LXdlYnNpdGUuY29tMA8GA1Ud
EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADetBMcv8ebir2EuLDdkr8Gr
aobUDKq+/Amh/HXBE/UiqMXMTW+XGgQJHg7p0aK3tf4mmeFYpk3fmxlzujKvFPEX
2XlBR72bUbG2fJ86dU+RcPMb83kOvW6eMs3J9kPX+Z8tDS26xT4fOpjOgZxDiM4E
uPVnatFG5ajAc+Y/46zF0E9bKubynf0oIeYRsb6KDb0qiDdEQ6kcmuuI1VJqj5E8
9QmmUghoaMp6YjPYgc7BB1X5dhg2zH9ussCtJuPW39v00ez0tQdSJaW8fRTD2Fco
xOmGbGxx95jjMrKGvvtUTWym0GqQzXx25e/8u8NRu5Q61xGBjk1tPp2x3QA/DSk=
-----END CERTIFICATE REQUEST-----
tls$
To inspect the certificate request, copy the text starting with -----BEGIN CERT...
into a file like csr_with_catrue.asc
.
openssl x509 -in csr_with_catrue.asc
You should see some output like this:
Certificate Request:
Data:
Version: 1 (0x0)
Subject: CN = my-website.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:af:5d:d1:75:f3:3b:02:ce:13:1e:2f:7a:bc:88:
f9:21:b6:cb:4e:e8:56:c4:23:68:37:b8:0a:64:f5:
11:dd:0d:db:4e:2d:0a:89:65:bf:23:90:f7:b2:7d:
bc:b7:9d:9e:cd:14:92:64:43:52:b9:3d:32:29:5f:
92:cb:96:b5:e9:29:d8:c7:fa:e1:c0:c0:af:06:c6:
89:a6:c3:c9:d2:99:a0:f3:ec:6b:cc:ec:62:ab:9d:
d1:2c:15:7d:db:1d:3f:cb:44:bd:34:52:87:15:ac:
3d:7a:e8:b5:f8:cf:1a:45:e6:ec:d8:e7:9b:f0:9e:
27:7d:04:1e:8b:c7:b3:71:2a:88:0e:3d:1c:65:2a:
fa:36:c1:af:55:1c:02:bf:07:5a:3d:92:2e:e4:fd:
d2:88:82:e9:08:1f:d9:dd:63:12:5d:e7:47:69:2e:
8e:62:56:71:02:fa:90:b6:b3:2a:45:fd:21:97:8a:
3d:f3:b5:7e:9d:1a:df:bc:58:98:7c:93:2d:2c:b9:
08:45:09:b0:47:90:77:dc:33:cc:d3:62:d7:e6:c7:
c0:65:2a:af:a1:1e:b9:66:78:e5:ef:da:9b:12:24:
e9:38:0e:4d:30:57:a5:41:52:81:ac:5f:b8:6a:09:
ed:c5:ca:10:3c:7b:3b:63:0e:c2:de:52:35:92:6b:
db:21
Exponent: 65537 (0x10001)
Attributes:
Requested Extensions:
X509v3 Subject Alternative Name:
DNS:my-website.com
X509v3 Basic Constraints: critical
CA:TRUE
Signature Algorithm: sha256WithRSAEncryption
76:43:f5:e8:c8:8b:6a:63:0d:51:d9:4b:5d:0c:b6:07:cf:b1:
4e:a6:be:87:25:92:fe:75:99:57:63:7e:04:2f:f7:c3:75:a6:
a2:98:d3:03:ed:c2:3d:12:4f:b3:80:d1:10:75:e5:dc:95:1c:
a2:7a:a8:73:4e:71:5b:e8:9c:a5:b0:ed:aa:8b:67:8b:5d:5f:
cb:7f:f5:43:c7:17:47:1e:7a:e0:6b:9b:94:cf:f6:34:00:e7:
89:c6:95:0d:85:ed:81:8d:1f:33:12:11:1e:cb:2c:a0:0d:f4:
83:c8:7e:9f:95:f9:10:b5:a7:80:1f:a7:98:e6:ec:0a:8a:f1:
48:e0:92:2d:fe:44:75:96:55:9c:67:2e:13:fc:b8:3c:6b:cb:
d8:38:6a:66:da:74:d7:2d:4e:68:02:13:9e:04:a3:da:28:0e:
a1:da:13:d4:7b:a5:0e:2a:fc:0b:46:28:35:8a:a1:a3:b0:2e:
4b:78:0c:29:38:dc:fa:11:42:24:0d:1c:17:75:3e:c4:a8:5e:
39:ae:22:57:e4:29:c3:d5:72:48:18:4d:5d:7a:d2:62:46:cf:
2a:4a:14:2f:40:05:bf:91:05:23:00:8a:c6:b4:9e:e8:e9:c0:
af:f6:cb:74:12:e7:58:da:1c:e7:c1:2d:f8:aa:ae:04:5a:2d:
ae:57:dc:99
Make sure the CA: True
is in there, because Micro$oft.
Now you can get this signed by any other internal CA you may have, even a Micro$oft one.
Once it's signed, you can submit it using the /pki/intermediate/set-signed
endpoint.
I don't have my own CA at this point, so I'll skip that step. But let me know in the comments how that works out for you.
Now let's create a role and issue a certificate against the Intermediate CA whose private key we've just used Vault to generate and store.
Create a PKI Secrets Engine Role:
vault write pki/roles/eff-dot-wtf allowed_domains=eff.wtf allowed_subdomains=true max_ttl=72h
You should see some output like this:
Success! Data written to: pki/roles/eff-dot-wtf
Create a PKI Secrets Engine Role (with Output):
tls$ vault write pki/roles/eff-dot-wtf allowed_domains=eff.wtf allowed_subdomains=true max_ttl=72h
Success! Data written to: pki/roles/eff-dot-wtf
Issue a Certificate against the PKI Secrets Engine Role:
vault write pki/issue/eff-dot-wtf common_name=www.etf.wtf
Now we'll just delete that ol' root certificate.
Because what could go wrong?
tls2$ vault delete pki/root
Success! Data deleted (if it existed) at: pki/root
Further reading:
https://www.vaultproject.io/docs/commands/index.html
https://www.vaultproject.io/docs/secrets/pki/index.html
https://www.vaultproject.io/api/secret/pki/index.html
https://dev.to/fewknow/vault-at-the-center-using-a-python-cert-manager-18a1
Venafi and Vault are best pals:
https://github.com/Venafi/vault-pki-monitor-venafi
https://github.com/Venafi/vault-pki-backend-venafi
Props to Venafi for their prompt attention to industry trends and scaling needs of their enterprise customers.
Top comments (0)