Skip to content

lobo235/vssh

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

4 Commits
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

vssh - Secure SSH Access via Vault-signed Certificates

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.


✨ Features

  • 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 ssh flags and arguments pass through transparently
  • Easy to customize through environment variables or .bashrc overrides

🌐 Prerequisites

  • Bash 4.0+
  • ssh, ssh-keygen, and vault CLI in your $PATH
  • VAULT_ADDR set to your Vault server URL (e.g., https://vault.example.com:8200)
  • An active Vault session β€” authenticate first via vault login or by setting VAULT_TOKEN directly
  • 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:


πŸš€ Installation

  1. 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
  1. Add to PATH

Ensure ~/bin is in your PATH (add to .bashrc if needed):

export PATH="$HOME/bin:$PATH"

Note: To make vssh system-wide, place it in /usr/local/bin instead (requires sudo).


βš–οΈ Default Configuration & Assumptions

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

πŸ”§ Usage

Basic SSH

vssh <hostname>

Run remote command

vssh <hostname> uptime

Pipe input (MOTD may display)

echo "uptime" | vssh <hostname>

Pass-through SSH flags

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 debugging

πŸ”’ Vault TLS Certificate Errors

If 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=true

For 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.


πŸͺ§ Setting Up Vault SSH Secret Engine

1. Enable the secret engine

vault secrets enable -path=ssh ssh

More info: Enable Secrets Engines

2. Generate or upload your SSH CA key

You have two options:

Option A: Let Vault generate the CA key (recommended for simplicity and security)

vault write -f ssh/config/ca

This 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.pub

You can also use the Vault UI to generate the key under the SSH Secrets Engine > Configuration > Generate CA.

More info: Vault SSH CA Config

Option B: Upload your own existing CA public key

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.

3. Create a signing role

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

πŸ” Example Vault Policies

For a specific user bob and Vault SSH role bob

path "ssh/sign/bob" {
  capabilities = ["update"]
}

For a group devops using Vault SSH role team-devops

path "ssh/sign/team-devops" {
  capabilities = ["update"]
}

Assign this policy to an identity group (e.g., devops) via Vault's Identity system.


πŸ” Configure Remote Hosts (sshd)

  1. Install the CA public key on each host
sudo cp ssh_ca.pub /etc/ssh/trusted-user-ca-keys.pem
  1. Update sshd_config
TrustedUserCAKeys /etc/ssh/trusted-user-ca-keys.pem
  1. Restart SSH
sudo systemctl restart sshd
  1. 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_keys will still work unless you disable PubkeyAuthentication. 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


🚫 What May Not Work (and Why)

Using echo "cmd" | vssh <host> suppressing output

  • This is expected: SSH with piped input does not allocate a TTY and may show MOTD. vssh doesn't try to change this default behavior to maintain compatibility with ssh.

Using aliases instead of a script

  • Aliases don’t play well with piped input or command substitution. vssh must be a real script to behave correctly in all contexts.

Using scp or sftp with Vault-signed certs

  • vssh wraps ssh only. scp and sftp are separate binaries and will not automatically use the Vault-signed certificate. Run vssh first to ensure a fresh cert is on disk, then invoke scp/sftp directly with the cert explicitly specified:
scp -i ~/.ssh/id_ed25519 -o CertificateFile=~/.ssh/id_ed25519-cert.pub myfile <hostname>:/path/

Using passphrase-protected SSH keys without ssh-agent

  • If your private key has a passphrase and ssh-agent is not running, you will be prompted for the passphrase each time vssh connects. To avoid this, add your key to the agent once per session:
ssh-add ~/.ssh/id_ed25519

Cert not renewing properly

  • Some users may find that their cert appears to renew every time they run vssh. This is often caused by inconsistent ssh-keygen output formats across different platforms (e.g., OpenSSH on macOS vs Linux).
  • The script relies on parsing the Valid: line from ssh-keygen -L -f <cert> output. If your local SSH version formats this line differently, it may fail to extract the expiration timestamp.

πŸ”§ Troubleshooting Steps:

  1. Run manually and check output:
ssh-keygen -L -f ~/.ssh/id_ed25519-cert.pub
  1. 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.

  1. To debug, set set -x in the script or echo parsed variables.

  2. Consider upgrading OpenSSH to a more recent version if output format seems unusual.


πŸͺ‘ Pro Tips

  • 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 beast

Adjust 3600 depending on the ttl configured 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>

πŸ“ƒ Logging Behavior

  • Logs are printed to stderr so stdout remains clean and usable in scripts.
  • Cert creation, renewal, and expiration status are clearly logged.

πŸ“– License

MIT License β€” see LICENSE file for details.


πŸ‘€ Author

Justin Barlow β€” @lobo235

About

No description, website, or topics provided.

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages