Setting Up HashiCorp Vault with Imported Private Keys for PKI
Introduction
In this guide, I will walk you through the process of setting up HashiCorp Vault on an EC2 instance. We will configure a Public Key Infrastructure (PKI) where the private keys for both the Root Certificate Authority (CA) and the Intermediate CA are generated externally and imported into Vault. This method provides additional control over your key material.
I’ll show you step by step how to install Vault on an Amazon EC2 instance and configure it to work with imported private keys.
Prerequisites
- Access to an AWS account with the ability to launch EC2 instances.
- Basic knowledge of AWS EC2, HashiCorp Vault, and PKI.
Step 1: Launch an EC2 Instance
Prerequisites
- Docker installed on your system with WSL2 (for Windows users).
- Basic knowledge of HashiCorp Vault, PKI, and TLS certificates.
Step 1: Launch an EC2 Instance
First, launch an EC2 instance using a method of your choice. You can choose any Linux distribution that supports Vault, such as Amazon Linux 2023. Ensure that the security group allows access to the necessary ports for SSH and Vault (e.g., port 8200 for Vault).
Step 2: Connect to the EC2 Instance
Once the EC2 instance is up and running, connect to it using your preferred method (e.g., SSH). After connecting, ensure that you have root or sudo privileges to install and configure software.
Step 3: Install HashiCorp Vault on Amazon Linux
Now, let’s install HashiCorp Vault on the EC2 instance:
# Update the system
sudo yum update -y
# Install Vault from the official HashiCorp repository
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo
sudo yum install -y vault
Step 4: Configure Vault
After installing Vault, configure it to use Raft storage and set up the necessary listener for external access.
# Create Vault configuration directory
sudo mkdir -p /etc/vault.d
# Create Vault configuration file
sudo tee /etc/vault.d/vault.hcl > /dev/null <<EOF
storage "raft" {
path = "/opt/vault/data"
node_id = "vault-node-1"
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_disable = 1
}
api_addr = "http://$(curl -s http://169.254.169.254/latest/meta-data/public-ipv4):8200"
cluster_addr = "http://$(curl -s http://169.254.169.254/latest/meta-data/local-ipv4):8201"
ui = true
EOF
Step 5: Set Up Vault as a Systemd Service
To ensure Vault starts on boot and can be managed easily, let’s set it up as a systemd service.
# Create Vault service user and directories
sudo useradd --system --home /etc/vault.d --shell /bin/false vault
sudo mkdir -p /opt/vault/data
sudo chown -R vault:vault /opt/vault/data /etc/vault.d
# Create systemd service for Vault
sudo tee /etc/systemd/system/vault.service > /dev/null <<EOF
[Unit]
Description="HashiCorp Vault - A tool for managing secrets"
Documentation=https://www.vaultproject.io/docs/
Requires=network-online.target
After=network-online.target
[Service]
User=vault
Group=vault
ExecStart=/usr/bin/vault server -config=/etc/vault.d/vault.hcl
ExecReload=/bin/kill -HUP \$MAINPID
KillMode=process
LimitNOFILE=65536
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
Now, enable and start Vault:
sudo systemctl enable vault
sudo systemctl start vault
At this point, Vault is up and running, ready to serve as a PKI backend.
Step 6: Initialize and Unseal Vault
Vault must be initialized and unsealed before it can be used. During initialization, Vault generates an unseal key and a root token.
# Initialize Vault and unseal it
export VAULT_ADDR="http://127.0.0.1:8200"
vault operator init -key-shares=1 -key-threshold=1 -format=json > /tmp/vault_init.json
UNSEAL_KEY=$(jq -r '.unseal_keys_b64[0]' /tmp/vault_init.json)
ROOT_TOKEN=$(jq -r '.root_token' /tmp/vault_init.json)
vault operator unseal $UNSEAL_KEY
# Authenticate with the root token
export VAULT_TOKEN=$ROOT_TOKEN
Now, we’ll import the Root CA certificate and key into Vault.
Step 7: Importing the Root CA Private Key
We will generate the private key and certificate for the Root CA externally using OpenSSL, and then import them into Vault.
# Generate the private key for the Root CA
openssl genrsa -out root_ca.key 4096
# Generate the Root CA certificate
openssl req -x509 -new -nodes -key root_ca.key -sha256 -days 3650 \
-subj "/C=US/O=Lapras Inc/OU=PKI Services/CN=Lapras Root CA R1" \
-out root_ca.crt \
-extensions v3_ca
Next, we enable the PKI secrets engine and import the Root CA into Vault:
# Enable the PKI secrets engine for the Root CA
vault secrets enable pki
# Configure max TTL for the Root CA
vault secrets tune -max-lease-ttl=87600h pki
# Combine the Root CA certificate and key
cat root_ca.crt root_ca.key > root_bundle.pem
# Configure CRL and issuing certificate URLs for the US Root CA
vault write pki/config/urls \
issuing_certificates="$VAULT_ADDR/v1/pki/ca" \
crl_distribution_points="$VAULT_ADDR/v1/pki/crl"
# Import the Root CA into Vault
vault write pki/config/ca pem_bundle=@root_bundle.pem
Step 8: Importing the Intermediate CA Private Key
Now that we have the Intermediate CA private key and certificate, we will create a bundle that includes the Intermediate CA certificate, private key, and Root CA certificate. Additionally, we will ensure the rootca.pem
file does not have a trailing newline, as it can cause issues when importing the bundle into Vault.
Step 8.1: Generate the Intermediate CA Private Key and CSR
First, generate the Intermediate CA private key and create a Certificate Signing Request (CSR).
# Generate the private key for the Intermediate CA
openssl genrsa -out intermediate_ca.key 4096
# Generate the CSR for the Intermediate CA
openssl req -new -key intermediate_ca.key -out intermediate_ca.csr \
-subj "/C=US/O=Lapras Inc/OU=PKI Services/CN=Lapras Intermediate CA R1"
Step 8.2: Sign the Intermediate CA CSR with the Root CA
The next step is to sign the Intermediate CA CSR with the Root CA certificate and private key, creating the signed Intermediate CA certificate.
Create a file called openssl_intermediate.cnf
and add the following contents:
[ ca ]
default_ca = CA_default
[ CA_default ]
default_days = 3650
default_md = sha256
preserve = no
policy = policy_anything
[ req ]
distinguished_name = req_distinguished_name
prompt = no
string_mask = utf8only
[ req_distinguished_name ]
C = US
O = Lapras Inc
OU = PKI Services
CN = Lapras Intermediate CA R1
[ v3_ca ]
basicConstraints = CA:TRUE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
keyUsage = critical, cRLSign, keyCertSign
This configuration file ensures that the Basic Constraints CA:TRUE
flag is set when creating the Intermediate CA certificate.
Use this configuration file when signing the Intermediate CA CSR with the Root CA:
# Sign the Intermediate CA CSR with the Root CA
openssl x509 -req -in intermediate_ca.csr -CA root_ca.crt -CAkey root_ca.key -CAcreateserial \
-out intermediate_ca.pem -days 3650 -sha256 -extensions v3_ca
This command generates the Intermediate CA certificate (intermediate_ca.pem
) signed by the Root CA.
Step 8.3: Create the Intermediate CA Bundle
Now that we have the signed Intermediate CA certificate, we will create the bundle required for Vault. Make sure the Root CA certificate (rootca.pem
) does not contain any trailing newlines.
Remove any trailing newlines from the rootca.pem
file if needed:
- Open
rootca.pem
in a text editor and remove any extra blank lines at the end.
Create the bundle by concatenating the Intermediate CA certificate, Intermediate CA private key, and Root CA certificate:
# Combine the Intermediate CA certificate, private key, and Root CA certificate into one bundle
cat intermediate_ca.pem intermediate_ca.key root_ca.crt > intermediate_bundle.pem
Step 8.4: Import the Intermediate CA Bundle into Vault
Now that the bundle is ready, import it into Vault.
# Enable a second PKI engine for the Intermediate CA
vault secrets enable -path=pki_int pki
# Configure max TTL for the Intermediate CA
vault secrets tune -max-lease-ttl=43800h pki_int
# Import the Intermediate CA bundle into Vault
vault write pki_int/config/ca pem_bundle=@intermediate_bundle.pem
Step 9: Issuing Certificates
Now that the Root and Intermediate CAs are set up, you can issue end-entity certificates:
# Create a role for issuing certificates
vault write pki_int/roles/lapras-role \
allowed_domains="lapras.com" \
allow_subdomains=true \
max_ttl="2h"
# Issue an end-entity certificate
vault write pki_int/issue/lapras-role \
common_name="www.lapras.com" \
ttl="1h" > /tmp/end_entity_cert.pem
Conclusion
In this guide, we set up HashiCorp Vault on an EC2 instance to manage a PKI with externally generated private keys for both the Root and Intermediate CAs. This approach offers more control over key material, allowing you to import and manage your own keys securely.
Here is the complete script that consolidates the steps for initializing Vault, importing the Root CA and Intermediate CA private keys, and setting up a role to issue certificates:
#!/bin/bash
# Step 6: Initialize and Unseal Vault
export VAULT_ADDR="http://127.0.0.1:8200"
echo "Initializing Vault..."
vault operator init -key-shares=1 -key-threshold=1 -format=json > /tmp/vault_init.json
UNSEAL_KEY=$(jq -r '.unseal_keys_b64[0]' /tmp/vault_init.json)
ROOT_TOKEN=$(jq -r '.root_token' /tmp/vault_init.json)
echo "Unsealing Vault..."
vault operator unseal $UNSEAL_KEY
echo "Authenticating with the root token..."
export VAULT_TOKEN=$ROOT_TOKEN
# Step 7: Importing the Root CA Private Key
echo "Generating Root CA private key and certificate..."
openssl genrsa -out root_ca.key 4096
openssl req -x509 -new -nodes -key root_ca.key -sha256 -days 3650 \
-subj "/C=US/O=Lapras Inc/OU=PKI Services/CN=Lapras Root CA R1" \
-out root_ca.crt \
-extensions v3_ca
echo "Enabling PKI secrets engine for Root CA..."
vault secrets enable pki
echo "Configuring max TTL for the Root CA..."
vault secrets tune -max-lease-ttl=87600h pki
echo "Combining Root CA certificate and key into a bundle..."
cat root_ca.crt root_ca.key > root_bundle.pem
echo "Configuring CRL and issuing certificate URLs for the Root CA..."
vault write pki/config/urls \
issuing_certificates="$VAULT_ADDR/v1/pki/ca" \
crl_distribution_points="$VAULT_ADDR/v1/pki/crl"
echo "Importing the Root CA bundle into Vault..."
vault write pki/config/ca pem_bundle=@root_bundle.pem
# Step 8: Importing the Intermediate CA Private Key
echo "Generating Intermediate CA private key and CSR..."
openssl genrsa -out intermediate_ca.key 4096
openssl req -new -key intermediate_ca.key -out intermediate_ca.csr \
-subj "/C=US/O=Lapras Inc/OU=PKI Services/CN=Lapras Intermediate CA R1"
echo "Signing the Intermediate CA CSR with the Root CA..."
cat <<EOF > openssl_intermediate.cnf
[ ca ]
default_ca = CA_default
[ CA_default ]
default_days = 3650
default_md = sha256
preserve = no
policy = policy_anything
[ req ]
distinguished_name = req_distinguished_name
prompt = no
string_mask = utf8only
[ req_distinguished_name ]
C = US
O = Lapras Inc
OU = PKI Services
CN = Lapras Intermediate CA R1
[ v3_ca ]
basicConstraints = CA:TRUE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
keyUsage = critical, cRLSign, keyCertSign
EOF
openssl x509 -req -in intermediate_ca.csr -CA root_ca.crt -CAkey root_ca.key -CAcreateserial \
-out intermediate_ca.pem -days 3650 -sha256 -extfile openssl_intermediate.cnf -extensions v3_ca
echo "Creating the Intermediate CA bundle..."
cat intermediate_ca.pem intermediate_ca.key root_ca.crt > intermediate_bundle.pem
# Step 8.4: Importing the Intermediate CA Bundle into Vault
echo "Enabling PKI secrets engine for the Intermediate CA..."
vault secrets enable -path=pki_int pki
echo "Configuring max TTL for the Intermediate CA..."
vault secrets tune -max-lease-ttl=43800h pki_int
echo "Importing the Intermediate CA bundle into Vault..."
vault write pki_int/config/ca pem_bundle=@intermediate_bundle.pem
# Step 9: Issuing Certificates
echo "Creating a role for issuing certificates..."
vault write pki_int/roles/lapras-role \
allowed_domains="lapras.com" \
allow_subdomains=true \
max_ttl="2h"
echo "Issuing an end-entity certificate..."
vault write pki_int/issue/lapras-role \
common_name="www.lapras.com" \
ttl="1h" > /tmp/end_entity_cert.pem
echo "Vault PKI setup complete!"