SOPS (Secrets OPerationS) Management

This documentation covers the SOPS (Secrets OPerationS) integration used in this NixOS/Darwin configuration for secure secrets management.

Overview

SOPS is a tool for managing secrets (passwords, keys, certificates, etc.) in a secure, encrypted format. This configuration uses SOPS with age encryption to manage sensitive data across all systems.

Key Features

  • Age Encryption: Uses modern age encryption for security
  • Cross-Platform: Works on both NixOS and Darwin systems
  • Git-Safe: Encrypted secrets can be safely committed to version control
  • Flexible: Supports multiple encryption keys and formats

Architecture

Encryption Keys

The system uses age keys for encryption with platform-specific locations:

Darwin (macOS):

  • Location: /Users/roelc/.config/sops/age/keys.txt
  • Format: Standard age private key format
  • Generation: Automatically generated when missing

Linux (NixOS):

  • Location: /home/roelc/.config/sops/age/keys.txt
  • Format: Standard age private key format
  • Generation: Automatically generated when missing

SSH Keys (Linux Only)

On NixOS systems, SSH host keys provide additional security:

  • Location: /etc/ssh/ssh_host_ed25519_key (or persistence path if enabled)
  • Persistence Path: ${config.dc-tec.persistence.dataPrefix}/etc/ssh/ssh_host_ed25519_key
  • Purpose: Additional encryption layer for system-level secrets
  • Condition: Only used when config.dc-tec.isLinux is true

Secrets Configuration

Default Settings

sops = {
  defaultSopsFile = ../../../secrets/secrets.yaml;
  defaultSopsFormat = "yaml";
  
  age = {
    keyFile = 
      if config.dc-tec.isDarwin then
        "/Users/${config.dc-tec.user.name}/.config/sops/age/keys.txt"
      else
        "/home/${config.dc-tec.user.name}/.config/sops/age/keys.txt";
    
    sshKeyPaths = lib.optionals config.dc-tec.isLinux [
      (if config.dc-tec.persistence.enable then
        "${config.dc-tec.persistence.dataPrefix}/etc/ssh/ssh_host_ed25519_key"
      else
        "/etc/ssh/ssh_host_ed25519_key")
    ];
    
    generateKey = true;
  };
};

Managed Secrets

User Management Secrets

  • users/roelc: User password hash (Linux only, neededForUsers = true)
  • users/root: Root password hash (Linux only, neededForUsers = true)

SSH Access Secrets

  • authorized_keys/roelc: SSH public keys deployed to /home/roelc/.ssh/authorized_keys (Linux) or /Users/roelc/.ssh/authorized_keys (Darwin)
  • authorized_keys/root: SSH public keys deployed to /root/.ssh/authorized_keys (Linux only)

GPG Key Management

  • gpg/private_key: GPG private key deployed to ~/.gnupg/private-key.asc (mode 0600)
  • gpg/public_key: GPG public key deployed to ~/.gnupg/public-key.asc (mode 0644)
  • gpg/trust_db: GPG trust database deployed to ~/.gnupg/trust-db.txt (mode 0600)

Network Secrets

  • wireless: Wireless network credentials (Linux only)

Platform-Specific Configuration

NixOS (Linux):

  • User password hashes for system login
  • Wireless network credentials
  • Root SSH access keys
  • Full GPG key management

Darwin (macOS):

  • User SSH keys only
  • GPG key management
  • No wireless or user password management

Initial Setup

1. Generate Age Keys

Age keys are automatically generated by the NixOS/Darwin configuration when missing. The system will create them at the appropriate location based on your platform.

For manual generation (if needed):

# Darwin (macOS)
mkdir -p /Users/roelc/.config/sops/age
age-keygen -o /Users/roelc/.config/sops/age/keys.txt

# Linux (NixOS)
mkdir -p /home/roelc/.config/sops/age
age-keygen -o /home/roelc/.config/sops/age/keys.txt

2. Get Public Key

Extract your public key for adding to the repository's .sops.yaml:

# Darwin
age-keygen -y /Users/roelc/.config/sops/age/keys.txt

# Linux
age-keygen -y /home/roelc/.config/sops/age/keys.txt

3. Add to Repository Configuration

Add your public key to the .sops.yaml file in the repository root:

keys:
  - &roelc_key age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
creation_rules:
  - path_regex: secrets/secrets.yaml$
    key_groups:
      - age:
          - *roelc_key

The secrets are stored in secrets/secrets.yaml and automatically referenced by the configuration.

Managing Secrets

Adding New Secrets

  1. Edit the secrets file:

    sops secrets/secrets.yaml
    
  2. Add your secret following the existing structure:

    # Add to existing categories or create new ones
    new_category:
      secret_name: "secret_value"
    
  3. Configure in the sops.nix module (if not already handled):

    sops.secrets."new_category/secret_name" = {
      path = "/path/to/secret/file";
      owner = config.dc-tec.user.name;
      mode = "0600";
    };
    

Updating Existing Secrets

# Edit existing secrets
sops secrets/secrets.yaml

# Re-encrypt for new keys (when adding new machines)
sops updatekeys secrets/secrets.yaml

Viewing Secrets

# View all decrypted secrets (local only)
sops -d secrets/secrets.yaml

# View specific secret categories
sops -d --extract '["users"]' secrets/secrets.yaml
sops -d --extract '["authorized_keys"]' secrets/secrets.yaml
sops -d --extract '["gpg"]' secrets/secrets.yaml

Common Workflows

Updating User Passwords

  1. Generate password hash:

    mkpasswd -m sha-512 "new_password"
    
  2. Update in secrets:

    sops secrets/secrets.yaml
    
    users:
      roelc: "$6$new_hashed_password..."
      root: "$6$new_hashed_password..."
    
  3. Apply changes:

    # NixOS
    sudo nixos-rebuild switch --flake .#hostname
    

Managing SSH Access

  1. Update SSH public keys in secrets:

    sops secrets/secrets.yaml
    
    authorized_keys:
      roelc: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5... roelc@newhost"
      root: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5... roelc@newhost"
    
  2. Keys are automatically deployed to:

    • /home/roelc/.ssh/authorized_keys (Linux/Darwin)
    • /Users/roelc/.ssh/authorized_keys (Darwin)
    • /root/.ssh/authorized_keys (Linux only)

Updating Wireless Credentials

  1. Update wireless password (NixOS only):

    sops secrets/secrets.yaml
    
    wireless: "new_wifi_password"
    
  2. Apply changes:

    sudo nixos-rebuild switch --flake .#hostname
    

Managing GPG Keys

  1. Export your current GPG keys:

    # Export private key
    gpg --export-secret-keys --armor YOUR_KEY_ID > private-key.asc
    
    # Export public key
    gpg --export --armor YOUR_KEY_ID > public-key.asc
    
    # Export trust database
    gpg --export-ownertrust > trust-db.txt
    
  2. Add to secrets:

    sops secrets/secrets.yaml
    
    gpg:
      private_key: |
        -----BEGIN PGP PRIVATE KEY BLOCK-----
        [paste private key content]
        -----END PGP PRIVATE KEY BLOCK-----
      public_key: |
        -----BEGIN PGP PUBLIC KEY BLOCK-----
        [paste public key content]
        -----END PGP PUBLIC KEY BLOCK-----
      trust_db: |
        [paste trust database content]
    
  3. Keys are automatically deployed to:

    • ~/.gnupg/private-key.asc (mode 0600)
    • ~/.gnupg/public-key.asc (mode 0644)
    • ~/.gnupg/trust-db.txt (mode 0600)

Troubleshooting

Common Issues

Cannot Decrypt Secrets

Problem: sops: error: cannot decrypt

Solutions:

  • Verify age key exists: ls -la ~/.config/sops/age/keys.txt
  • Check public key is in .sops.yaml
  • Re-encrypt secrets: sops updatekeys secrets/secrets.yaml

Permission Denied

Problem: Secrets file has wrong permissions

Solutions:

  • Check file ownership in secret configuration
  • Verify user exists before secret deployment
  • Use neededForUsers = true for user password secrets

Keys Not Found

Problem: Age keys missing after system rebuild

Solutions:

  • Restore from backup
  • Regenerate and re-encrypt all secrets
  • Check key file path configuration

Debugging Commands

# Check SOPS configuration
sops -d secrets/secrets.yaml

# Verify age key (platform-specific)
# Darwin
age-keygen -y /Users/roelc/.config/sops/age/keys.txt
# Linux
age-keygen -y /home/roelc/.config/sops/age/keys.txt

# Check deployed secrets
ls -la /run/secrets/
ls -la /run/secrets/users/
ls -la /run/secrets/authorized_keys/
ls -la /run/secrets/gpg/

# Test specific secret decryption
sudo cat /run/secrets/users/roelc
sudo cat /run/secrets/wireless
sudo cat /run/secrets/gpg/private_key

# Check SSH authorized keys deployment
ls -la /home/roelc/.ssh/authorized_keys  # Linux
ls -la /Users/roelc/.ssh/authorized_keys # Darwin
ls -la /root/.ssh/authorized_keys        # Linux only

# Check GPG key deployment
ls -la ~/.gnupg/private-key.asc
ls -la ~/.gnupg/public-key.asc
ls -la ~/.gnupg/trust-db.txt

# Verify GPG key import
gpg --list-keys
gpg --list-secret-keys

Security Best Practices

Key Management

  1. Backup age keys securely
  2. Use separate keys for different environments
  3. Rotate keys periodically
  4. Never commit private keys to version control

Secret Organization

  1. Use descriptive secret names
  2. Group related secrets logically
  3. Document secret purposes
  4. Implement least-privilege access

Operational Security

  1. Audit secret access regularly
  2. Monitor for unauthorized changes
  3. Use temporary secrets when possible
  4. Implement secret rotation policies

Integration with NixOS/Darwin

Automatic Deployment

Secrets are automatically deployed during system builds to /run/secrets/:

# NixOS systems (chad, legion, ghost)
sudo nixos-rebuild switch --flake .#hostname

# Darwin systems (macOS)
darwin-rebuild switch --flake .#hostname

Service Integration

Services can reference secrets through the /run/secrets/ path:

# Example: Using wireless secret in networking configuration
networking.wireless = {
  enable = true;
  networks."YourNetworkName".pskFile = config.sops.secrets.wireless.path;
};

User Environment

GPG keys are automatically imported into the user's GPG keyring during system activation.

Advanced Topics

Adding New Machines

When adding a new machine to the configuration:

  1. Generate age key on new machine:

    # Keys are auto-generated, or manually:
    age-keygen -o ~/.config/sops/age/keys.txt
    age-keygen -y ~/.config/sops/age/keys.txt  # Get public key
    
  2. Add to .sops.yaml:

    keys:
      - &roelc_key age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
      - &new_machine_key age1yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
    creation_rules:
      - path_regex: secrets/secrets.yaml$
        key_groups:
          - age:
              - *roelc_key
              - *new_machine_key
    
  3. Re-encrypt secrets:

    sops updatekeys secrets/secrets.yaml
    

Machine-Specific Secrets

For secrets that only apply to specific machines, you can extend the configuration:

# In machine-specific configuration
sops.secrets = lib.mkMerge [
  # Standard secrets from shared/utils/sops.nix
  
  # Machine-specific secrets
  (lib.mkIf (config.networking.hostName == "specific-machine") {
    "machine-specific/secret" = {
      path = "/etc/machine-specific-secret";
      mode = "0600";
    };
  })
];

Secret Backup Strategy

  1. Backup age keys - Store securely offline
  2. Version control - All encrypted secrets are in git
  3. Key rotation - Periodically generate new age keys
  4. Documentation - Keep track of what secrets exist and their purpose

References