Setup local CA for your lab

Published on Sunday, 31 December, 2023

Table of content

  1. Intro
  2. Installation
  3. Configuration
  4. Creating Root CA
    1. Enable PKI engine
    2. Configure engine
  5. Creating Intermediate CA
    1. Enable PKI engine
    2. Configure engine
    3. Sign intermediate CSR
    4. Import certificate
  6. Create server role
  7. Issue certificate
  8. Add certificate to vault
    1. Create files
    2. Update config
  9. Why is there a warning?

Intro

In modern times, not having https is not only dangerous, but inconvenient as well. Browsers will complain, some thing will just refuse to work without jumping through hoops.

So having a home lab or local setup almost dictates having a custom certificate. Which is a pain. You can create the whole thing in terminal with openssl, then you have to store it somewhere, keep track of it, renew it after a year (and probably figure it out again and deal with all the stuff that changed in last year). And the root certificate (or rather key) is one of the few things that are truly bad to have leaked, since someone with your root CA can forge almost anything.

To somewhat sort this, I'll show how to deploy HashiCorp Vault. It's not my favourite piece of software but it does help with keeping the whole Certificate Authority thing reasonable.

Installation

Like everything else, I'll deploy vault into podman container. It's not recommended, supported or advised setup by HashiCorp, but we don't care. I have an older Rocky Linux server, so installing podman is as simple as running:

# dnf install podman

But at this point it should be equally simple on any distro.

Once you have podman, create the folder structure for persistent files used by vault. I'm putting all my containers in /containers folder.

# mkdir -p /containers/vault/logs
# mkdir -p /containers/vault/file
# mkdir -p /containers/vault/config

Next thing we need is a config file, create a very basic one at /containers/vault/config/configl.hcl:

storage "file" {
  path = "/vault/file"
}

listener "tcp" {
  address     = "0.0.0.0:8200"
  tls_disable = "true"
}

api_addr = "https://0.0.0.0:8200"
ui = true  

Now a keen reader will notice that we have disabled the tls, very thing we are trying to fix. It's a catch-22 situation, we need a certificate to use vault to create the certificate. So we'll start without one and then add it later. If you are paranoid enough to think this is a big risk, you should know better than to read this guide.

Once you have all the folders and files there, you should be able to start vault with following command:

# podman run --rm -d --name vault --volume /containers/vault/logs:/vault/logs:Z --volume /containers/vault/file:/vault/file:Z --volume /containers/vault/config:/vault/config:Z --cap-add IPC_LOCK -p 8200:8200 docker.io/hashicorp/vault vault server -config=/vault/config"

This will create the container with all the folders mounted in right places, and listening on port 8200. Notice that i'm starting this pod as root.

Configuration

Now that you have the pod running, there is a few thing that we want to configure. First of, if you want to run this for any amount of time, you'll want it to autostart on boot. For that, let's create a systemd file:

# podman generate systemd --new --files --name vault

Put it in the right place and enable service:

# mv container-vault.service /etc/systemd/system/
# systemctl daemon-reload
# systemctl enable container-vault.service

Once this is done, let's login into vault. Point your browser to https://serveraddress:8200 and you should see the vault init page.

vault init

Here is the first concept that you might not have encountered before. Vault database is sealed each time you stop the server, and to get the data you have to unseal the vault each time you start it. There are multiple ways to do that automagically, but we won't bother with them, if you are interested you can read the docs.

The unseal uses multiple keys to unseal the data. You can specify the total number of keys and the number of keys you need to unseal. So if you have, say, 3 keys, lets call them A, B and C, and you choose to require 2 keys to unseal, you can use any combination of two keys: AB, BC or AC.

So let's generate multiple unseal keys and store them in a single place, we are just pretending to do security here, not actually doing it. Type your desired numbers and Initialize.

You will be presented with all your keys and a root token. This is your only chance to save them, so do that now. Once you click Continue they are gone forever and you'll have to recreate vault if you loose them. So let's continue.

Provide the unseal key in next form, and then you'll be greeted with login screen.

vault init

Until you restart the server, you won't need to enter the unseal keys any more. Login with your root token.

Creating root CA

Now vault can do a lot, and this is really only one of many things, read the docs if you are interested in more.

Enable PKI engine

First thing we need to do is enable PKI Secrets Engine, so:

  • choose Secrets Engines -> Enable new engine
  • select PKI Certificates
  • enable PKI by clicking Enable Engine

Configure engine

This will be our root CA. Select Configure PKI and choose Generate root.

I use exported as type, which will present you the key once generated, so that you can save it somewhere safe. The only reason I do this is because i want to be able to switch to different solution without recreating all the certificates and keys.

For common name put something that will identify it as root CA, for instance root-ca. For issuer you can put your name (or name of the company). Next is TTL and default is in seconds, so don't create a certificate valid for a few minutes (guess how I learned that). Three to five years (1095 to 1825 days) is probably a good number, but you can go with anything really.

Put your domain under Permited DNS domains. You can leave this blank, but in that case if someone get's hold of your root key they can pretend to be anyone, so don't do that unless you are doing something specific where you want to forge the certificates.

Feel free to look at other options and tweak the certificate, or just click on Done.

Depending on your choices before, you will be presented with your new certificate and key. If you choose exported as type, save the key, again, this is the only time it'll be presented.

Click Done once you are finished.

Creating Intermediate CA

While you can sign all your certificates with root CA, good practice is to have an intermediate CA to sign the keys. That way in case your intermediate CA becomes invalid, you can still keep your root. Also, you could have a single root with multiple intermediate CAs for, say, different domains.

Enable PKI engine

Just like for root CA, we'll enable the PKI Secrets Engine again:

  • choose Secrets Engines -> Enable new engine
  • select PKI Certificates
  • change name to pki-int
  • enable PKI by clicking Enable Engine

Configure engine

This will be our intermediat CA. Select Configure PKI and choose Generate intermediate CSR.

CSR stands for Certificate signing request, once generated, you can sign it by root CA and therefore creating a chain of trust.

Procedure is same as for root CA, change name to something like intermediate-ca, choose you type and click generate.

Sign intermediate CSR

Go back to your root CA (secrets -> pki), select Issuers and your only issuer in the list. Select Sign Intermediate in top menu. Paste you CSR into the appropriate field. Set Common name and TTL, which has to be shorter than the root CA TTL. And, again, add Permitted DNS domains if you want.

Once you fill all the fields, click Save.

Store your certificates somewhere safe.

Import certificate

Go back to your intermediate CA (secrets -> pki-int) and select Configue PKI again. This time let's Import a CA.

Enter the signed intermediate certificate and key (from previous step) and press Import issuer.

You should now have one Issuer in pki-int.

Create server role

We are almost there. Next thing we need to do is create a role. A role is a "model" for a certificate, so you would have server role and user role. Different roles have different key options.

Set the name as server. The TTL should be something reasonably short, but not completely impractical, say 1 year.

Select Basic constraints valid for non-CA, since we aren't creating CA any more.

You can change a lot of options here, but let's do bare minimum.

Under Domain handling you can choose if you want to allow wildcards certs, subdomains etc. You should also set the Allowed domains to something that your root CA and intermediate CA allow.

Under Key parameters you can change the key length.

You can check the rest for yourself.

Select Create when done.

Issue certificate

We are finally ready to create an actual certificate. Lets create one for vault for start.

Select server under Issue certificate in pki-int, and click Issue.

Set common name to your vault url, something like vault.mylib.tld, and under Subject Alternative Name (SAN) Options, put your vault URL under Subject Alternative Names (SANs) as well. You could also put an IP address in appropriate fields, but sticking with DNS names is probably a better idea (you do have a dns server, right?).

Click Generate and that's it, your new certificate is ready. As always, save the sensitive information, because it's only time it'll be presented to you.

Add certificate to vault

Now it's time to remedy the issue we actually started with, putting stuff behind tls.

Create files

Create new folder inside your container files:

# mkdir /containers/vault/config/certs

You have to create two files inside. First one is just the key. Create a file with key.pem as name and paste the private key of your vault certificate inside (last one issued). Next one is a certificate chain for vault. Create a new file with full-chain.pem as name and paste following certificate inside:

  • your vault certificate (last one issued)
  • your intermediate CA certificate
  • your root CA certificate

Update config

Update the config.hcl to contain:

storage "file" {
  path    = "/vault/file"
}

listener "tcp" {
  address     = "0.0.0.0:8200"
  tls_cert_file = "/vault/config/certs/full-chain.pem"
  tls_key_file  = "/vault/config/certs/key.pem"
  proxy_protocol_behavior = "use_always"
}

api_addr = "https://0.0.0.0:8200"
ui = true

And restart the vault server. You should now be able (or rather forced) to connect to your vault with https. And if you inspect your certificate, you should see something similar to this:

vault cert

Why is there a warning?

So you did all that work, yet when you open the page in browser you are greeted with something similar to:

cert warning

Why is that?

The reason is that all systems come with a collection of trusted certificates. Yours isn't there. So if you want to avoid the warning you have to add the certificate to your certificate store. This is beyond this guide and depends on what you use the certificate for. Just to give you some ideas, you can add the certificate to certificate store of your operating system, browser, or some custom application.