Secrets Management¶
fscm provides secure handling of sensitive data like passwords, API keys, and certificates.
Overview¶
fscm integrates with pass, the standard Unix password manager, for secrets storage. Secrets are:
- Encrypted at rest with GPG
- Never written to disk on remote hosts
- Passed securely through the mitogen connection
Using pass¶
Setup¶
First, install and initialize pass:
# Install pass
apt-get install pass # Debian/Ubuntu
pacman -S pass # Arch
brew install pass # macOS
# Initialize with your GPG key
pass init your-gpg-key-id
Store Secrets¶
# Add a secret
pass insert myapp/database_password
# Add a file
pass insert -m myapp/ssl_key < key.pem
# View secrets
pass myapp/database_password
Loading Secrets in fscm¶
import fscm
# Load a single secret
db_password = fscm.get_secrets("myapp/database_password")
# Load a directory of secrets
secrets = fscm.get_secrets("myapp/production")
# Returns: {"database_password": "...", "api_key": "...", ...}
Using Secrets with Remote Execution¶
from fscm.remote import Host, SSH, Sudo, executor
import fscm
# Load secrets
secrets = fscm.get_secrets("myapp/production")
host = Host(
name="webserver",
connection=SSH(hostname="10.0.0.5", username="ubuntu"),
become=Sudo(),
secrets=secrets # Attach to host
)
with executor(host) as exe:
# Use secrets in configuration
db_pass = host.secrets["database_password"]
config = f"""
DATABASE_URL=postgresql://user:{db_pass}@localhost/myapp
API_KEY={host.secrets['api_key']}
"""
exe.fscm.file("/etc/myapp/env", config, mode="0600")
RemoteCliApp with Secrets¶
from fscm.remote_cli import RemoteCliApp
app = RemoteCliApp(
description="Deploy application",
hosts_path="hosts.yaml",
secrets_path="secrets/production" # Path in pass store
)
@app.cmd
def deploy(ctx):
# Secrets are automatically loaded and available
db_pass = ctx.host.secrets.get("database_password")
# Use in configuration
config = fscm.template("config.j2", database_password=db_pass)
ctx.fscm.file("/etc/myapp/config", config, mode="0600")
Environment Variables¶
For simple cases, use environment variables:
import os
import fscm
# From environment
db_password = os.environ.get("DATABASE_PASSWORD")
api_key = os.environ.get("API_KEY")
# Use in configuration
config = f"""
DATABASE_PASSWORD={db_password}
API_KEY={api_key}
"""
fscm.file("/etc/myapp/env", config, mode="0600")
Secure File Permissions¶
Always set restrictive permissions on files containing secrets:
import fscm
# Secrets file - owner read only
fscm.file(
"/etc/myapp/secrets.env",
secret_content,
mode="0600", # Only owner can read
owner="myapp:myapp"
)
# SSL private key
fscm.file(
"/etc/ssl/private/myapp.key",
private_key_content,
mode="0600",
owner="root:root"
)
Never Log Secrets¶
Avoid printing or logging secrets:
import fscm
# BAD - Don't do this
print(f"Using password: {password}") # Exposed in logs!
# GOOD - Mask secrets
print(f"Using password: {'*' * len(password)}")
# GOOD - Don't print at all
print("Configuring database connection...")
Template Security¶
When using templates with secrets, be careful:
import fscm
# Template file (secrets.env.j2):
# DATABASE_PASSWORD={{ db_password }}
# API_KEY={{ api_key }}
config = fscm.template(
"secrets.env.j2",
db_password=secrets["database_password"],
api_key=secrets["api_key"]
)
# The rendered content contains secrets - handle carefully
fscm.file("/etc/myapp/secrets.env", config, mode="0600")
Example: Complete Secrets Workflow¶
#!/usr/bin/env python3
"""Deploy with secrets from pass."""
import fscm
from fscm.remote import Host, SSH, Sudo, executor
def deploy_with_secrets(hostname: str, env: str):
# Load environment-specific secrets
secrets = fscm.get_secrets(f"myapp/{env}")
host = Host(
name=hostname,
connection=SSH(hostname=hostname, username="deploy"),
become=Sudo(),
secrets=secrets
)
with executor(host) as exe:
# Create secrets file
env_content = f"""
# Database
DATABASE_URL=postgresql://myapp:{secrets['db_password']}@localhost/myapp
# Redis
REDIS_URL=redis://:{secrets['redis_password']}@localhost:6379
# API Keys
STRIPE_SECRET_KEY={secrets['stripe_key']}
SENDGRID_API_KEY={secrets['sendgrid_key']}
# Django
SECRET_KEY={secrets['django_secret']}
"""
exe.fscm.file("/etc/myapp/env", env_content, mode="0600", owner="myapp:myapp")
# SSL certificates
exe.fscm.file(
"/etc/ssl/private/myapp.key",
secrets["ssl_key"],
mode="0600",
owner="root:ssl-cert"
)
exe.fscm.file(
"/etc/ssl/certs/myapp.crt",
secrets["ssl_cert"],
mode="0644"
)
# Restart to pick up new secrets
exe.fscm.run("systemctl restart myapp", sudo=True)
if __name__ == "__main__":
deploy_with_secrets("web1.example.com", "production")
Best Practices¶
- Use pass for secrets — GPG-encrypted, version-controlled
- Never commit secrets — Keep them out of git
- Minimal permissions —
0600for secret files - Separate environments — Different secrets per environment
- Rotate regularly — Update secrets periodically
- Audit access — Know who can decrypt secrets
Next Steps¶
- Remote Execution — Using secrets remotely
- Examples — Real-world patterns