Skip to main content
SSH keys are stored in Bitwarden and automatically provisioned to ~/.ssh/ when applying dotfiles. This ensures your private keys never touch your git repository.

Overview

SSH key management in this setup:
  • Private keys stored as Bitwarden Secure Notes
  • Public keys stored as Bitwarden Custom Fields
  • Keys pulled via chezmoi templates during chezmoi apply
  • Proper file permissions set automatically

Storage in Bitwarden

Creating SSH Key Item

  1. Generate SSH key pair (if new):
    ssh-keygen -t ed25519 -C "[email protected]" -f ~/.ssh/yurgenlira
    
  2. Create Bitwarden item:
    • Item type: Secure Note
    • Name: yurgenlira ssh key
    • Notes: Paste contents of private key (~/.ssh/yurgenlira)
    • Custom field: public_key = contents of public key (~/.ssh/yurgenlira.pub)
Never commit private keys to git. Always store them in Bitwarden Secure Notes.

Chezmoi Templates

SSH keys are provisioned using Bitwarden template functions:

Private Key Template

private_dot_ssh/private_yurgenlira.tmpl:1
{{ (bitwarden "item" "yurgenlira ssh key").notes }}
Filename breakdown:
  • private_ prefix - Sets file permissions to 600 (owner read/write only)
  • dot_ssh - Creates .ssh directory
  • yurgenlira.tmpl - Target filename yurgenlira after template processing

Public Key Template

private_dot_ssh/private_yurgenlira.pub.tmpl:1-2
{{ (bitwardenFields "item" "yurgenlira ssh key").public_key.value }}
Filename breakdown:
  • private_ prefix - Sets directory permissions appropriately
  • yurgenlira.pub.tmpl - Target filename yurgenlira.pub

SSH Config (Encrypted)

The SSH config file is encrypted with age:
private_dot_ssh/encrypted_config.tmpl.age
This allows you to:
  • Store host configurations securely in git
  • Define aliases and connection settings
  • Keep infrastructure details private
Example SSH config content:
Host github.com
    User git
    IdentityFile ~/.ssh/yurgenlira
    IdentitiesOnly yes

Host work-server
    HostName server.company.com
    User yurgenlira
    IdentityFile ~/.ssh/yurgenlira
    Port 2222

Security Table

From the README:
SecretStorageHow it’s used
SSH private keyBitwarden Secure NotePulled via bitwarden template function
AWS credentialsBitwarden Custom FieldsPulled via bitwardenFields template function
age private keyBitwarden Secure NoteRetrieved during bootstrap, stored at ~/.config/chezmoi/key.txt
Encrypted filesGit repo (.age)Decrypted by chezmoi using the age identity

Applying SSH Keys

When you run chezmoi apply:
  1. Chezmoi reads Bitwarden templates
  2. Calls bw get item "yurgenlira ssh key"
  3. Extracts notes (private key) and custom fields (public key)
  4. Writes files to ~/.ssh/ with correct permissions:
    • Private key: 600 (owner read/write only)
    • Public key: 644 (owner read/write, others read)
    • Config: 600 (owner read/write only)
# Ensure Bitwarden is unlocked
bw_unlock

# Apply dotfiles (includes SSH keys)
chezmoi apply

# Verify permissions
ls -la ~/.ssh/
Expected output:
drwx------  2 user user 4096 Mar  4 10:00 .
drwxr-x--- 25 user user 4096 Mar  4 10:00 ..
-rw-------  1 user user  411 Mar  4 10:00 config
-rw-------  1 user user  464 Mar  4 10:00 yurgenlira
-rw-r--r--  1 user user  103 Mar  4 10:00 yurgenlira.pub

Adding New SSH Keys

1. Generate Key Pair

ssh-keygen -t ed25519 -C "[email protected]" -f ~/.ssh/work_key

2. Store in Bitwarden

# Unlock vault
bw_unlock

# Create secure note (manual: use Bitwarden web/desktop)
# - Name: "work ssh key"
# - Notes: [paste private key]
# - Custom field: public_key = [paste public key]

3. Create Chezmoi Templates

Private key: private_dot_ssh/private_work_key.tmpl
{{ (bitwarden "item" "work ssh key").notes }}
Public key: private_dot_ssh/private_work_key.pub.tmpl
{{ (bitwardenFields "item" "work ssh key").public_key.value }}

4. Add to Chezmoi

cd $(chezmoi source-path)
git add private_dot_ssh/private_work_key.tmpl
git add private_dot_ssh/private_work_key.pub.tmpl
git commit -m "Add work SSH key templates"
git push

5. Update SSH Config

# Edit encrypted config
chezmoi edit ~/.ssh/config
Add new host:
Host work-gitlab
    HostName gitlab.company.com
    User git
    IdentityFile ~/.ssh/work_key
    IdentitiesOnly yes

Template Functions Reference

bitwarden

Retrieves item by type and name:
{{ (bitwarden "item" "name").notes }}
{{ (bitwarden "item" "name").login.username }}
{{ (bitwarden "item" "name").login.password }}

bitwardenFields

Retrieves custom fields:
{{ (bitwardenFields "item" "name").field_name.value }}

Best Practices

  • Use separate SSH keys for personal and work
  • Store all private keys in Bitwarden, never in git
  • Use ed25519 algorithm for modern, secure keys
  • Set IdentitiesOnly yes in SSH config to prevent key leakage
  • Encrypt SSH config with age if it contains sensitive hostnames
  • Regularly rotate keys (annually recommended)

Troubleshooting

Permission Denied (publickey)

# Check key exists
ls -la ~/.ssh/

# Verify permissions
chmod 600 ~/.ssh/yurgenlira
chmod 644 ~/.ssh/yurgenlira.pub

# Test SSH connection
ssh -vT [email protected]

Key Not Found in Bitwarden

Ensure:
  1. Item name matches exactly (case-sensitive)
  2. BW_SESSION is exported: echo $BW_SESSION
  3. Vault is synced: bw sync
# Verify item exists
bw list items | jq '.[] | select(.name == "yurgenlira ssh key")'

Wrong Permissions

Chezmoi should set permissions automatically via private_ prefix, but if needed:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/config
chmod 600 ~/.ssh/yurgenlira
chmod 644 ~/.ssh/yurgenlira.pub

Template Not Processing

If files have literal {{ }} content:
# Check chezmoi status
chezmoi status

# Force re-apply
chezmoi apply --force

# Debug template
chezmoi execute-template < private_dot_ssh/private_yurgenlira.tmpl

Build docs developers (and LLMs) love