Setting Up HashiCorp Vault with Imported Private Keys for PKI

Valente Vidal
7 min readOct 3, 2024

--

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!"

--

--

No responses yet