mTLS using Hashcorp Vault's PKI Secrets

2020-01-08

Sample code demonstrating an implementation of crypto.Signer for HashiCorp Vault where the TLS connection certificates are provided by its PKI Secrets engine.

That is, you can start a golang HTTPS server and client where the certificates are provided by Vault. The client and server will use a provided VAULT_TOKEN to acquire the secret. Vaults’ PKI secrets engine allows generates a new PKI Keypair for each authorized call

Note: there are a number of considerations you need to account for before using Vault based certificates as one end of mTLS:

  1. Max lease of a certificate: Vault based certificates are desinged to be temprorary. You can extend the lease of a cert by setting -max-lease-ttl= but in the end, ths is just a temp token..

  2. Public/Private keys are reissued and not saved. Vault does NOT save the public private keys so everytime you initialize Vault, a NEW keypair is generated and lives on the TTL cited above. Needless to say, this has significant operational ramifications on usage at scale for just TLS….But if you use this just to run a webswerver instance, that should be fine.

From the Vault documentation: This secrets engine aligns with Vault's philosophy of short-lived secrets. As such it is not expected that CRLs will grow large; the only place a private key is ever returned is to the requesting client (this secrets engine does not store generated private keys, except for CA certificates).

Anyway, this article is similar to one for Google Cloud KMS mtls

Note: if it wasn’t clear already..this repo is not supported by Google


You can find the source here


If you’re still interested in using Vault for a TLS server for any reason…

Usage

  1. Install Vault, unseal in production mode

You know the drill…this repo uses vault server running in https itself (i.,e not -dev mode). You can bootstrap with any CA but keep the public key for that handy (you’ll need it later when authenticating the clients) Here is a sample Vault server.conf. You can find a sample CA, public, private server in this repo.

    backend "file" {
      path = "filebackend"
    }

    ui = true

    listener "tcp" {
      address = "127.0.0.1:8200"
      tls_cert_file = "crt_vault.pem"
      tls_key_file = "key_vault.pem"
    }
$ vault server -config=server.conf 
  (add to /etc/hosts)
    127.0.0.1 vault.domain.com server.domain.com
# if needed:
  $ vault operator init
$ export export VAULT_ADDR='https://vault.domain.com:8200'
$ export VAULT_CACERT=`pwd`/CA_crt.pem
$ export VAULT_TOKEN=<tokenfrominit>
$ vault  operator unseal
  1. Use RootToken to enable the pki backend
vault secrets enable pki

vault write pki/config/urls \
    issuing_certificates="https://vault.domain.com:8200/v1/pki/ca" \
    crl_distribution_points="http://vault.domain.com:8200/v1/pki/crl"

The last command creates the CA and CRL urls at vault.domain.com. Since this is just a test and because i’m sure you don’t own domain.com, add the following to your /etc/hosts

127.0.0.1  vault.domain.com server.domain.com

Note vault.domain.com is the actual Vault server address…the SNI values for crt_vault.pem are bound to that…i’m just lazy and didn’t reissue the cert…

  1. Create a CA for a given domain In the following, we’re creating CA within Vault with CN domain restrions for, you know, domain.com
vault write pki/root/generate/internal  common_name=domain.com  ttl=8760h

vault write pki/config/urls issuing_certificates="https://vault.domain.com:8200/v1/pki/ca"  crl_distribution_points="https://vault.domain.com:8200/v1/pki/crl"

Once you initialize the PKI engine, download the CA Vault just generated for you (infact you should see the CA cert cain once you run the previous command)

curl  -s  --cacert CA_crt.pem   https://vault.domain.com:8200/v1/pki/ca  | openssl x509 -inform DER -outform PEM  -out Vault_CA.pem -in  -
  1. Create a Role for the domain
  vault write pki/roles/domain-dot-com \
    allowed_domains=domain.com \
    allow_subdomains=true \
    max_ttl=72h
  1. Create a Policy for the mTLS Server and Client
   vault policy write pki-policy-server pki_server.hcl
   vault policy write pki-policy-client pki_client.hcl

Where the pki_server creates a certificate with CN=server.domain.com and the client with CN=client.domain.com

You can tune/refine the poicies as needed/necessary (i.,e the pki “read” path isn’t ever used)

  1. Generate VAULT_TOKEN representing the server and client:
$ vault token create -policy=pki-policy-server
	Key                  Value
	---                  -----
	token                s.IumzeFZVsWqYcJ2IjlGaqZby
	token_accessor       ogu3k56jcqeQFOAS2dyQgT5g
	token_duration       768h
	token_renewable      true
	token_policies       ["default" "pki-policy-server"]
	identity_policies    []
	policies             ["default" "pki-policy-server"]


$ vault token create -policy=pki-policy-client
  Key                  Value
  ---                  -----
  token                s.BtpHNHEpxaWkEF1ThQKEupwL
  token_accessor       dCyZhYvuIsiWjEILcAFlyiDU
  token_duration       768h
  token_renewable      true
  token_policies       ["default" "pki-policy-client"]
  identity_policies    []
  policies             ["default" "pki-policy-client"]
  1. Start Server

Edit server/main.go and add in the VAULT_TOKEN

The root path where you run the client and server should also include both the CA for Vault and the CA that vault generated for your domain (CA_crt.pem, Vault_CA.pem)

	r, err := sal.NewVaultCrypto(&sal.Vault{
		CertCN:      "server.domain.com",
		VaultToken:  "s.IumzeFZVsWqYcJ2IjlGaqZby",
		VaultPath:   "pki/issue/domain-dot-com",
		VaultCAcert: "CA_crt.pem",
		VaultAddr:   "https://vault.domain.com:8200",
		ExtTLSConfig: &tls.Config{
			ClientCAs:   clientCaCertPool,
			ClientAuth:  tls.RequireAndVerifyClientCert,
		},
	})
  1. Start Client

Same thing as above on client/main.go but use the client’s token

	r, err := sal.NewVaultCrypto(&sal.Vault{
		CertCN:      "client.domain.com",
		VaultToken:  "s.BtpHNHEpxaWkEF1ThQKEupwL",
		VaultPath:   "pki/issue/domain-dot-com",
		VaultCAcert: "CA_crt.pem",
		VaultAddr:   "https://vault.domain.com:8200",
	})

if all goes well, you should see a lowly 200 ok indicating a successful mTLS connection

Finally, you don’t ofcourse have to use mTLS here…you can just run a webserver with one certificate in memory and be done with it.


References

API Documenation

This site supports webmentions. Send me a mention via this form.