vssh is a bash script that simplifies secure, short-lived SSH access using HashiCorp Vault's SSH Secret Engine. It automatically manages certificate signing and renewal, enabling a seamless experience for users who need strong authentication with ephemeral credentials.
Created by Justin Barlow (netlobo), this script is ideal for technical users, homelab enthusiasts, and professionals seeking to automate SSH certificate authentication.
- Automatically signs SSH public key using Vault
- Validates existing cert expiration and renews if needed
- Gracefully handles Vault TLS certificate errors with an interactive retry prompt
- Securely logs actions with optional color-coded output
- Works seamlessly for both interactive and non-interactive SSH sessions
- All standard
sshflags and arguments pass through transparently - Easy to customize through environment variables or
.bashrcoverrides
- Bash 4.0+
ssh,ssh-keygen, andvaultCLI in your$PATHVAULT_ADDRset to your Vault server URL (e.g.,https://vault.example.com:8200)- An active Vault session β authenticate first via
vault loginor by settingVAULT_TOKENdirectly - Your Vault user has permission to use the
ssh/sign/<role>endpoint - Vault's SSH Secret Engine must be enabled and configured
- Remote hosts must trust the Vault SSH CA
Useful links:
- Clone the repo or download the script
git clone https://github.com/lobo235/vssh.git
cd vssh
cp vssh ~/bin/vssh && chmod +x ~/bin/vssh- Add to PATH
Ensure ~/bin is in your PATH (add to .bashrc if needed):
export PATH="$HOME/bin:$PATH"Note: To make
vsshsystem-wide, place it in/usr/local/bininstead (requires sudo).
The script uses the following defaults, all of which can be overridden via environment variables or .bashrc:
| Variable | Default | Description |
|---|---|---|
VSSH_VAULT_ROLE |
$(whoami) |
Vault SSH role name |
VSSH_PRIVATE_KEY |
Most common default private key in ~/.ssh/ (prefers id_ed25519, then id_rsa) |
SSH private key path |
VSSH_PUBLIC_KEY |
${VSSH_PRIVATE_KEY}.pub |
SSH public key path |
VSSH_CERT_PATH |
${VSSH_PRIVATE_KEY}-cert.pub |
Output cert path |
VSSH_SIGN_PATH |
ssh/sign/${VSSH_VAULT_ROLE} |
Vault endpoint path |
VSSH_RENEW_BUFFER_SECS |
300 (5 min) |
Time before expiry to renew |
VSSH_SKIP_VERIFY |
false |
Skip TLS verification when connecting to Vault (true to enable) |
VSSH_FORCE_RENEW |
false |
Force cert renewal even if the existing cert is still valid |
vssh <hostname>vssh <hostname> uptimeecho "uptime" | vssh <hostname>Because vssh ends with exec ssh, all standard ssh flags work transparently. vssh-specific flags (e.g., --force-renew) are consumed by vssh and stripped before ssh receives the arguments.
vssh -p 2222 <hostname> # non-standard port
vssh -L 8080:localhost:80 <hostname> # local port forwarding
vssh -J jumphost <hostname> # jump host / bastion proxy
vssh -A <hostname> # agent forwarding
vssh -vvv <hostname> # verbose debuggingIf vssh encounters a TLS error when contacting Vault (e.g., expired certificate, self-signed cert, or untrusted CA), it will display the underlying error and handle it based on context:
- Interactive sessions: prompts you to retry with TLS verification disabled
- Non-interactive sessions (piped input, scripts): prints a hint and aborts
β [vssh] π Vault TLS certificate error: ...x509: certificate has expired...
β οΈ [vssh] Retry ignoring TLS verification? [y/N]
For environments with a permanently self-signed or untrusted Vault cert (e.g., a homelab), set this in your ~/.bashrc to avoid the prompt every time:
export VSSH_SKIP_VERIFY=trueFor non-interactive scripts and CI pipelines, set VSSH_SKIP_VERIFY=true in the environment before invoking vssh.
β οΈ Only disable TLS verification when you trust the network path to your Vault server. Doing so on untrusted networks exposes you to man-in-the-middle attacks.
vault secrets enable -path=ssh sshMore info: Enable Secrets Engines
You have two options:
vault write -f ssh/config/caThis will create a new key pair internally. The private key remains secured inside Vault, making this the most secure and operationally simple option. Avoids risks associated with external key reuse.
To retrieve the public CA key for distribution to your hosts:
vault read -field=public_key ssh/config/ca > ssh_ca.pubYou can also use the Vault UI to generate the key under the SSH Secrets Engine > Configuration > Generate CA.
More info: Vault SSH CA Config
vault write ssh/config/ca public_key=@/path/to/your/ssh_ca.pub
β οΈ Use this option only if absolutely necessary. Before uploading an existing CA key:
- Ensure it was never compromised or used in less-secure environments.
- Audit who or what systems had access to the corresponding private key.
- Consider rotating to a Vault-generated CA as soon as possible.
vault write ssh/roles/<role_name> \
key_type=ca \
allow_user_certificates=true \
allowed_users="*" \
default_extensions="permit-pty,permit-port-forwarding" \
ttl="60m"π Security Tip: The
ssh/roles/*paths should be protected with Vault policies that limit access to authorized users or groups only. This prevents abuse of cert signing capabilities.Refer to: Vault Policy Documentation
path "ssh/sign/bob" {
capabilities = ["update"]
}path "ssh/sign/team-devops" {
capabilities = ["update"]
}Assign this policy to an identity group (e.g., devops) via Vault's Identity system.
- Install the CA public key on each host
sudo cp ssh_ca.pub /etc/ssh/trusted-user-ca-keys.pem- Update
sshd_config
TrustedUserCAKeys /etc/ssh/trusted-user-ca-keys.pem- Restart SSH
sudo systemctl restart sshd- Disable regular pubkey access if needed
To force all SSH access through signed certs:
PubkeyAuthentication no
β οΈ Be aware: Even if you're using certificate-based authentication, any existing public keys listed in~/.ssh/authorized_keyswill still work unless you disablePubkeyAuthentication. This means users who previously authenticated via standard pubkey can still gain access. Use this setting if you want to enforce strict CA-only login.
More info: OpenSSH CA Authentication
- This is expected: SSH with piped input does not allocate a TTY and may show MOTD.
vsshdoesn't try to change this default behavior to maintain compatibility withssh.
- Aliases donβt play well with piped input or command substitution.
vsshmust be a real script to behave correctly in all contexts.
vsshwrapssshonly.scpandsftpare separate binaries and will not automatically use the Vault-signed certificate. Runvsshfirst to ensure a fresh cert is on disk, then invokescp/sftpdirectly with the cert explicitly specified:
scp -i ~/.ssh/id_ed25519 -o CertificateFile=~/.ssh/id_ed25519-cert.pub myfile <hostname>:/path/- If your private key has a passphrase and
ssh-agentis not running, you will be prompted for the passphrase each timevsshconnects. To avoid this, add your key to the agent once per session:
ssh-add ~/.ssh/id_ed25519- Some users may find that their cert appears to renew every time they run
vssh. This is often caused by inconsistentssh-keygenoutput formats across different platforms (e.g., OpenSSH on macOS vs Linux). - The script relies on parsing the
Valid:line fromssh-keygen -L -f <cert>output. If your local SSH version formats this line differently, it may fail to extract the expiration timestamp.
- Run manually and check output:
ssh-keygen -L -f ~/.ssh/id_ed25519-cert.pub- Look for the line beginning with
Valid:β if the format deviates from:
Valid: from YYYY-MM-DDTHH:MM:SS to YYYY-MM-DDTHH:MM:SS
the regex in the script may not parse it correctly.
-
To debug, set
set -xin the script or echo parsed variables. -
Consider upgrading OpenSSH to a more recent version if output format seems unusual.
- Force cert renewal without touching the cert file, useful for testing the full renewal flow (including TLS error handling):
vssh --force-renew <hostname>Or via env var (useful in scripts):
VSSH_FORCE_RENEW=true vssh <hostname>- You can also simulate expiration to test renewal logic by moving or deleting the cert:
mv ~/.ssh/id_ed25519-cert.pub ~/.ssh/id_ed25519-cert.pub.bak
vssh beast- Or force early renewal by setting the buffer high (assumes your Vault SSH role TTL is 1 hour):
export VSSH_RENEW_BUFFER_SECS=3600
vssh beastAdjust
3600depending on thettlconfigured in your Vault SSH role.
- To debug, increase verbosity:
vssh -vvv beast- For HashiCorp Cloud Platform (HCP) Vault or Enterprise Vault with namespaces, set the namespace before running
vssh:
export VAULT_NAMESPACE=admin- If your Vault token has expired, re-authenticate before running
vssh:
vault login -method=userpass username=<your-username>- Logs are printed to
stderrsostdoutremains clean and usable in scripts. - Cert creation, renewal, and expiration status are clearly logged.
MIT License β see LICENSE file for details.
Justin Barlow β @lobo235