Skip to content

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

  1. Use pass for secrets — GPG-encrypted, version-controlled
  2. Never commit secrets — Keep them out of git
  3. Minimal permissions0600 for secret files
  4. Separate environments — Different secrets per environment
  5. Rotate regularly — Update secrets periodically
  6. Audit access — Know who can decrypt secrets

Next Steps