Skip to content

Security Guide

This guide covers secure authentication methods for fnb, moving beyond plain-text password storage in .env files.

Overview

By default, fnb supports SSH password authentication via .env files, but this approach stores passwords in plain text. This guide presents more secure alternatives and best practices.

1. SSH Key Authentication (Most Secure)

SSH key authentication eliminates the need for password storage entirely.

Setup SSH Keys

# Generate SSH key pair (creates private/public key files)
ssh-keygen -t ed25519 -f ~/.ssh/fnb_key

# Copy public key to remote server (adds to ~/.ssh/authorized_keys)
ssh-copy-id -i ~/.ssh/fnb_key.pub user@server.com

# Configure SSH to use the key
echo "Host server.com
  IdentityFile ~/.ssh/fnb_key
  IdentitiesOnly yes" >> ~/.ssh/config

Command Explanations:

  • ssh-keygen: Creates a new SSH key pair (private key + public key)
    • -t ed25519: Uses modern Ed25519 algorithm (recommended)
    • -f ~/.ssh/fnb_key: Specifies output file path (default: id_ed25519 and id_ed25519.pub )
  • ssh-copy-id: Copies your public key to the remote server
    • -i ~/.ssh/fnb_key.pub: Specifies which public key to copy (default to ~/.ssh/id_ed25519.pub)
    • Automatically adds the key to ~/.ssh/authorized_keys on remote server

Benefits

  • No password storage - Most secure option
  • Automatic authentication - No manual intervention needed
  • Key rotation - Easy to revoke and replace keys
  • Standard practice - Widely adopted security standard

Configuration

When using SSH keys, remove password entries from your .env file:

# Remove these lines from .env
# FNB_PASSWORD_USER_SERVER_COM=mypassword
# FNB_PASSWORD_DEFAULT=defaultpassword

2. macOS Keychain Integration

On macOS, fnb can read SSH passwords directly from the Keychain instead of plain-text .env files. This is built in — no extra configuration or code changes needed, just an item in your Keychain.

fnb looks up a generic password item whose service name is fnb-{host}, where {host} is exactly the host value from your fnb.toml task (e.g. user@server.com). The lookup matches on service name only, so the account name can be anything you like.

Setup Keychain Storage

# Store a password for host "user@server.com" (the host value from fnb.toml)
security add-generic-password \
  -s "fnb-user@server.com" \
  -a "user@server.com" \
  -w "your-password"

# Verify fnb can find it
security find-generic-password \
  -s "fnb-user@server.com" \
  -w

Command Explanations:

  • security add-generic-password: Stores a password in macOS Keychain
    • -s "fnb-user@server.com": Service name — must be fnb- followed by the exact host string from your config
    • -a "user@server.com": Account name — fnb doesn't check this, so any value works, but reusing the host here keeps the entry easy to identify in Keychain Access.app
    • -w "your-password": Password to store (can also prompt interactively without this option)
  • security find-generic-password: Retrieves a password from Keychain (this is what fnb runs internally)
    • -s "fnb-user@server.com": Service name to search for
    • -w: Output only the password (without this, shows all metadata)

You can also add the item via Keychain Access.app: create a new password item, set "Keychain Item Name" to fnb-user@server.com, and set any account name and password.

Lookup Order

fnb fetch/backup/upload/sync retrieve passwords in this order:

  1. Host-specific environment variable (FNB_PASSWORD_{NORMALIZED_HOST})
  2. macOS Keychain item fnb-{host} (this section; silently skipped on non-macOS)
  3. FNB_PASSWORD_DEFAULT environment variable
  4. Interactive SSH password prompt

A Keychain item is only consulted if no host-specific environment variable is set for that host.

3. dotenvx Encryption

Use dotenvx to encrypt .env files while maintaining compatibility with existing fnb workflows.

Setup dotenvx

# Install dotenvx
npm install -g @dotenvx/dotenvx

# Or using other package managers
brew install dotenvx/brew/dotenvx
curl -fsS https://dotenvx.sh/install.sh | sh

Encrypt Existing .env File

# Encrypt your existing .env file
dotenvx encrypt

# This encrypts the .env file in-place and creates .env.keys
# The original .env content is now encrypted

File Structure After Encryption

.env           # Encrypted file (safe to commit)
.env.keys      # Decryption keys (DO NOT commit)

Usage with fnb

# Run fnb with encrypted environment
dotenvx run -- fnb fetch backup-server

# Or set the decryption key in environment
export DOTENV_KEY="dotenv://:key_1234...@dotenvx.com/vault/.env.vault?environment=production"
fnb fetch backup-server

# Decrypt for manual inspection (reference only)
dotenvx decrypt

Benefits

  • Backward compatible - Works with existing fnb implementation
  • Encrypted storage - Passwords encrypted at rest
  • Environment separation - Different keys for dev/staging/production
  • Version control safe - Encrypted .env can be safely committed

Git Configuration

# Add to .gitignore
echo ".env.keys" >> .gitignore

# Encrypted .env CAN be committed (it's encrypted)
git add .env

4. GPG Encryption

fnb can read SSH passwords from GPG-encrypted files instead of plain-text .env files. This is built in and works on any platform with the gpg command-line tool installed — no extra fnb configuration needed, just files in the right place.

fnb looks for encrypted files under ~/.config/fnb/passwords/, checking a host-specific file first and falling back to a shared default:

~/.config/fnb/passwords/
├── user_server_com.gpg    # Password for host "user@server.com"
└── default.gpg            # Fallback for any host without its own file

The filename is built the same way as the FNB_PASSWORD_* environment variable (replace @, ., - with _), but lowercase and with a .gpg extension: user@server.comuser_server_com.gpg.

Setup GPG Encryption

mkdir -p ~/.config/fnb/passwords

# Create a host-specific encrypted password file (symmetric/passphrase-based)
echo -n "your-password" | gpg --symmetric -o ~/.config/fnb/passwords/user_server_com.gpg

# Or a shared default file, used for any host without its own file
echo -n "your-password" | gpg --symmetric -o ~/.config/fnb/passwords/default.gpg

# Set restrictive permissions (fnb warns, but doesn't enforce, if this is skipped)
chmod 600 ~/.config/fnb/passwords/*.gpg

# Verify fnb can decrypt it (uses the same command fnb runs internally)
gpg --decrypt --quiet --batch ~/.config/fnb/passwords/user_server_com.gpg

Command Explanations:

  • gpg --symmetric: Encrypts data using a passphrase (no public/private key pair needed)
    • Prompts for the passphrase interactively when run without -o piping like above
    • -o <file>: Writes the encrypted output to a specific path
  • gpg --decrypt --quiet --batch: Decrypts a file (this is what fnb runs internally)
    • --batch disables interactive prompts, so decryption only succeeds if your gpg-agent already has the passphrase cached (or, for public-key encrypted files, your secret key is available) — otherwise it fails fast rather than hanging
    • Public-key encrypted files (gpg --encrypt -r you@example.com) work too, since gpg --decrypt auto-detects the encryption type

Notes

  • Both symmetric (passphrase) and public-key encrypted files work, since fnb just runs gpg --decrypt and lets GPG figure out how to decrypt it
  • If a host-specific file exists but fails to decrypt (wrong passphrase not cached, corrupted file, etc.), fnb still falls back to default.gpg if present
  • fnb logs a warning (but doesn't block) if a password file is readable by group or other — run chmod 600 on it
  • If gpg isn't installed, or no matching file exists, this source is silently skipped

5. --ask-password (One-off, No Setup Required)

For a one-off connection where setting up SSH keys, Keychain, or a GPG file isn't worth it, use --ask-password instead of --ssh-password. fnb prompts for the password interactively with input hidden, so it's never typed on the command line and never ends up in shell history.

fnb fetch backup-server --ask-password
# SSH password: [hidden input]

Avoid --ssh-password PASSWORD for anything beyond quick local experiments: the shell records the full command line — including the password — to its history file the moment you press Enter, regardless of what fnb does with the value afterward. --ssh-password and --ask-password are mutually exclusive; fnb errors out if both are given.

6. Interactive Password Input

fnb automatically falls back to interactive password input when no stored passwords are found.

Current Behavior

When no password is found in environment variables or other sources, fnb automatically falls back to interactive password input:

  1. fnb attempts to retrieve password from configured sources
  2. If no password is found (ssh_password = None)
  3. rsync executes without password automation
  4. SSH prompts user for password in terminal
  5. User enters password interactively

Benefits

  • No configuration required - Works out of the box
  • Highest security - No stored passwords
  • Standard SSH behavior - Familiar user experience
  • Fallback mechanism - Always available when other methods fail

Usage Example

# No .env file or password configuration
fnb fetch backup-server

# Output:
# Fetching backup-server from user@server:~/data/ to ./backup/
# user@server's password: [user types password]
# Fetch completed successfully: backup-server

When This Mode Activates

  • No .env file exists
  • Environment variables FNB_PASSWORD_* are not set
  • Other password sources (keychain, GPG) are not configured or fail
  • SSH key authentication is not set up
  • Neither --ssh-password nor --ask-password was given

Current .env File Security

If you must continue using .env files, follow these security practices:

File Permissions

# Set restrictive permissions
chmod 600 .env
chmod 600 ~/.config/fnb/.env

# Verify permissions
ls -la .env
# Should show: -rw------- (600)

Environment Variable Format

# Host-specific passwords (recommended)
FNB_PASSWORD_USER_EXAMPLE_COM=hostspecificpassword

# Default password (less secure)
FNB_PASSWORD_DEFAULT=defaultpassword

Git Security

# Ensure .env is in .gitignore
echo ".env" >> .gitignore
echo "*.env" >> .gitignore

# Remove .env from git history if accidentally committed
git rm --cached .env
git commit -m "Remove .env from version control"

Security Best Practices

1. Principle of Least Privilege

  • Use SSH keys with specific access scopes
  • Avoid shared or default passwords
  • Regularly rotate credentials

2. Environment Isolation

  • Use separate SSH keys for different environments
  • Maintain environment-specific .env files with restricted access
  • Never commit .env files to version control

3. Monitoring and Auditing

  • Monitor SSH key usage through server logs
  • Regular security audits of stored credentials
  • Document all authentication methods used

4. Backup Security

  • Encrypt backup destinations when possible
  • Secure backup storage locations
  • Regular backup integrity verification

Migration from Plain-text Passwords

Step 1: Audit Current Setup

# Check current .env files
ls -la .env ~/.config/fnb/.env

# Review configured hosts
fnb status

Step 2: Implement SSH Keys

# For each host in your configuration
ssh-keygen -t ed25519 -f ~/.ssh/fnb_key_hostname
ssh-copy-id -i ~/.ssh/fnb_key_hostname.pub user@hostname

Step 3: Update SSH Configuration

# Add to ~/.ssh/config
Host hostname1
  IdentityFile ~/.ssh/fnb_key_hostname1
  IdentitiesOnly yes

Host hostname2
  IdentityFile ~/.ssh/fnb_key_hostname2
  IdentitiesOnly yes

Step 4: Test and Cleanup

# Test connections
ssh user@hostname1
ssh user@hostname2

# Remove passwords from .env files
# Keep files for other environment variables if needed

Alternative: Migrating to macOS Keychain

If SSH keys aren't an option for some hosts, move their passwords to the Keychain instead of leaving them in .env (macOS only):

# 1. Read the existing password from .env, then store it in the Keychain
#    (replace user@server.com with the host value from fnb.toml)
security add-generic-password -s "fnb-user@server.com" -a "user@server.com" -w "the-password-from-env"

# 2. Verify fnb can retrieve it
security find-generic-password -s "fnb-user@server.com" -w

# 3. Remove the corresponding line from .env, then confirm fnb still works
#    (FNB_PASSWORD_USER_SERVER_COM=... in this example)
fnb fetch some-label --dry-run

Repeat for each host, then delete .env (or the now-empty FNB_PASSWORD_* lines) once every host is confirmed working.

Alternative: Migrating to GPG-Encrypted Files

Cross-platform alternative to the Keychain — works anywhere gpg is installed:

# 1. Read the existing password from .env, then encrypt it into a file
#    (replace user@server.com with the host value from fnb.toml)
mkdir -p ~/.config/fnb/passwords
echo -n "the-password-from-env" | gpg --symmetric -o ~/.config/fnb/passwords/user_server_com.gpg
chmod 600 ~/.config/fnb/passwords/user_server_com.gpg

# 2. Verify fnb can decrypt it
gpg --decrypt --quiet --batch ~/.config/fnb/passwords/user_server_com.gpg

# 3. Remove the corresponding line from .env, then confirm fnb still works
#    (FNB_PASSWORD_USER_SERVER_COM=... in this example)
fnb fetch some-label --dry-run

Repeat for each host, then delete .env (or the now-empty FNB_PASSWORD_* lines) once every host is confirmed working.

Troubleshooting

SSH Key Issues

# Check SSH agent
ssh-add -l

# Add key to agent if needed
ssh-add ~/.ssh/fnb_key

# Test connection
ssh -v user@server.com

Permission Issues

# Fix SSH directory permissions
chmod 700 ~/.ssh
chmod 600 ~/.ssh/config
chmod 600 ~/.ssh/fnb_key*
chmod 644 ~/.ssh/fnb_key*.pub

Keychain Issues (macOS)

# List all fnb-related Keychain entries
security dump-keychain | grep "fnb-"

# Update the stored password for host "user@server.com"
security delete-generic-password -s "fnb-user@server.com"
security add-generic-password -s "fnb-user@server.com" -a "user@server.com" -w "newpassword"

# Confirm fnb can retrieve it
security find-generic-password -s "fnb-user@server.com" -w

GPG Issues

# List password files fnb will check
ls -la ~/.config/fnb/passwords/

# Fix overly permissive file permissions (fnb only warns, doesn't block)
chmod 600 ~/.config/fnb/passwords/*.gpg

# Test decryption directly with the exact command fnb runs
gpg --decrypt --quiet --batch ~/.config/fnb/passwords/user_server_com.gpg
# If this hangs or fails with an agent/passphrase error, your gpg-agent
# doesn't have the passphrase cached (or the secret key isn't available
# for public-key encrypted files) — `--batch` intentionally fails fast
# instead of prompting, so fnb never hangs waiting for input

# Replace a password file
echo -n "newpassword" | gpg --symmetric -o ~/.config/fnb/passwords/user_server_com.gpg
chmod 600 ~/.config/fnb/passwords/user_server_com.gpg

See Also