Secure SSH Access with SSH Certificates Managed by HashiCorp's Vault

Use Vault's SSH secrets engine as a certificate authority for SSH access.

SSH certificates are an effective way to secure SSH server access. They can restrict users and the commands they can run, making them especially valuable for managing access to multiple servers. By using SSH certificates, server fingerprint validation becomes unnecessary since the certificates are signed by a Certificate Authority (CA) with the CA's public key installed on the server. Vault is an excellent tool for managing SSH certificates, offering functionalities like issuing and revoking certificates, managing SSH keys, and providing audit logs.

Warning: This post describes a non-production setup of Vault. As such, it is not hardened with appropriate security measures, and it is not recommended to use this setup in production. You should use this for learning purposes on SSH CAs and Vault.

Install Vault

To quickly set up a development Vault server, use the official Docker image with the following command:

docker run --cap-add=IPC_LOCK -e 'VAULT_DEV_ROOT_TOKEN_ID=dev-token' -e 'VAULT_DEV_LISTEN_ADDRESS=0.0.0.0:8200' -p 8200:8200 hashicorp/vault server -dev

Configure SSH Certificate Authority

With Vault installed and running, configure it to issue SSH certificates using the SSH secrets engine.

First, set the Vault address and token for the dev server:

export VAULT_ADDR='http://127.0.0.1:8200'
export VAULT_TOKEN='dev-token'

Then, run the following command to enable the SSH secrets engine

vault secrets enable ssh

Next, you will need to configure the SSH CA and have it generate signing keys on your behalf to ensure that the private key stays within Vault itself.

vault write ssh/config/ca generate_signing_key=true

You can get the generated SSH public key for use later, with:

vault read -field=public_key ssh/config/ca > ssh_ca_key.pub

After enabling the SSH CA in Vault, you'll need to create a role called ops-team to issue SSH certificates. This role allows any user with access to request an SSH certificate. The example below grants broad permissions, including any option for allowed_users and port forwarding. Be sure to restrict these permissions based on your use case.

vault write ssh/roles/ops-team \
    key_type=ca \
    ttl=2h \
    max_ttl=24h \
    allow_user_certificates=true \
    allowed_users="*" \
    default_extensions='{"permit-pty":"","permit-port-forwarding":"","permit-agent-forwarding":""}'

Configure the remote server to accept the SSH certificates issued by Vault:

  1. Copy the CA public key to the remote server:
scp ssh_ca_key.pub <username>@<target_server_ip>:/tmp/ssh_ca_key.pub
  1. Add the public key to the OpenSSH configuration and restart the OpenSSH daemon:
echo "TrustedUserCAKeys /etc/ssh/user_ca.pub" | sudo tee -a /etc/ssh/sshd_config
sudo cp /tmp/ssh_ca_key.pub /etc/ssh/user_ca.pub
sudo systemctl restart sshd

Requesting SSH Certificates

To request an SSH certificate from Vault and use it to SSH into the remote server, follow these steps:

  1. Use the ops-team role to request the certificate and pass your local SSH key id_rsa.pub. Also, specify the username to use when connecting to the remote server:
vault write -field=signed_key ssh/sign/ops-team \
    public_key=@$HOME/.ssh/id_rsa.pub \
    valid_principals="<username>" > signed_id_rsa-cert.pub
  1. Use the signed_id_rsa-cert.pub file to SSH into the remote server:
ssh -i signed_id_rsa-cert.pub -i $HOME/.ssh/id_rsa <username>@<target_server_ip>

Requesting a signed certificate manually each time can be tedious. To simplify this process, create a script called vault-ssh.sh and make it executable with chmod +x vault-ssh.sh.

#!/bin/bash

# Configuration
export VAULT_ADDR="http://<vault_server_ip>:8200"
VAULT_ROLE="ops-team"
USERNAME="<username>"
PUBLIC_KEY_PATH="$HOME/.ssh/id_rsa.pub"
CERT_PATH="$HOME/.ssh/id_rsa-cert.pub"
CONFIG_FILE="/path/to/vault-creds.conf"

# Read the Vault token from the configuration file
if [ -f "$CONFIG_FILE" ]; then
  source "$CONFIG_FILE"
else
  echo "Error: Vault configuration file not found"
  exit 1
fi

# Check if the VAULT_TOKEN variable is set
if [ -z "$VAULT_TOKEN" ]; then
  echo "Error: VAULT_TOKEN is not set in the configuration file"
  exit 1
fi
export VAULT_TOKEN

# Generate a new SSH certificate
vault write -field=signed_key ssh/sign/$VAULT_ROLE \
  public_key=@$PUBLIC_KEY_PATH \
  valid_principals="$USERNAME" > $CERT_PATH

This script requires a vault-creds.conf file containing the Vault token:

VAULT_TOKEN=<vault_token>

To integrate the certificate generation process with your SSH config, use the ProxyCommand configuration option, which allows you to run a custom command (like the script) as a "proxy" for the actual SSH connection.

Add the following to your SSH config:

Host *
  IdentityFile ~/.ssh/id_rsa
  CertificateFile ~/.ssh/id_rsa-cert.pub
  ProxyCommand bash -c '/path/to/vault-ssh.sh && nc %h %p'

Keep in mind that this approach generates a new SSH certificate for every connection. Depending on the frequency of your connections and the TTL of your certificates, you might want to modify the vault-ssh.sh script to check the current certificate's validity and generate a new one only if necessary.

Conclusion

This post covered configuring Vault to issue SSH certificates, setting up a remote server to accept these certificates, and streamlining the process of requesting SSH certificates. Use the knowledge from this post to enhance your environment's security. Remember that this post describes a non-production setup of Vault and should be used for learning purposes only.

Credits: The above post was written from knowledge and experience of using Vault. The instructions for docker configuration of vault are from the official Vault docker documentation.