> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/soriphoono/homelab/llms.txt
> Use this file to discover all available pages before exploring further.

# Managing Secrets

> Secure secret management with SOPS and age encryption

This guide covers managing secrets in your homelab using SOPS (Secrets OPerationS) with age encryption.

## Overview

Secrets are managed using:

* **SOPS**: For encrypting and managing secret files
* **age**: Modern encryption tool using SSH keys
* **agenix**: NixOS integration for SOPS
* **agenix-shell**: Development shell integration

## Secret Architecture

### User-Based Access

Secrets are organized by user and team access:

```nix theme={null}
users = {
  username = "ssh-ed25519 AAAA...";
};

# Per-user secrets
userSecrets = {
  api_key = [ "username" ];
};

# Team secrets
teams = {
  cloud = {
    users = ["username" "admin"];
    secrets = ["proxmox_api_secret" "cloudflare_token"];
  };
};
```

## Setting Up Secrets

<Steps>
  <Step title="Generate SSH Key (If Needed)">
    Create an ED25519 SSH key for encryption:

    ```bash theme={null}
    ssh-keygen -t ed25519 -C "your@email.com"
    ```

    This key will be used for encrypting/decrypting secrets.
  </Step>

  <Step title="Configure secrets.nix">
    Create or edit `secrets.nix` in your project root:

    ```nix secrets.nix theme={null}
    let
      secretsFunction = {lib, ...}: let
        users = {
          soriphoono = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEgxxFcqHVwYhY0TjbsqByOYpmWXqzlVyGzpKjqS8mO7";
          admin = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...";
        };

        # Per-user secrets: simple mapping of secret name -> list of users
        userSecrets = {
          github_token = [ "soriphoono" ];
          aws_credentials = [ "admin" ];
        };

        # Team secrets: for shared access across multiple users
        teams = {
          cloud = {
            users = ["soriphoono" "admin"];
            secrets = ["proxmox_api_secret" "cloudflare_token"];
          };
          
          database = {
            users = ["admin"];
            secrets = ["postgres_password" "redis_password"];
          };
        };

        # Helper: create a secret entry with public keys
        mkSecret = name: userList: {
          name = "secrets/${name}.age";
          value = {
            publicKeys = builtins.map (user: users.${user}) userList;
          };
        };

        # Collect secrets from teams
        teamSecretsList = builtins.concatLists (
          builtins.map (team: 
            builtins.map (secret: mkSecret secret team.users) team.secrets
          ) (builtins.attrValues teams)
        );

        # Collect per-user secrets
        userSecretsList = builtins.attrValues (
          lib.mapAttrs' (secret: userList: mkSecret secret userList) userSecrets
        );

        # Shell secrets for agenix-shell
        envUser = builtins.getEnv "USER";
        currentUser = if envUser == "" then "soriphoono" else envUser;
        userTeams = builtins.filter (team: builtins.elem currentUser team.users) (builtins.attrValues teams);
        userOwnSecrets = builtins.attrNames (lib.filterAttrs (_: userList: builtins.elem currentUser userList) userSecrets);

        agenix-shell-secrets = lib.listToAttrs (builtins.concatLists [
          # Secrets from teams the user belongs to
          (builtins.concatLists (builtins.map (
              team:
                builtins.map (secret: {
                  name = lib.toUpper secret;
                  value.file = ./secrets/${secret}.age;
                })
                team.secrets
            )
            userTeams))
          # Per-user secrets the user has access to
          (builtins.map (secret: {
              name = lib.toUpper secret;
              value.file = ./secrets/${secret}.age;
            })
            userOwnSecrets)
        ]);
      in
        {
          inherit agenix-shell-secrets;
        }
        // builtins.listToAttrs (teamSecretsList ++ userSecretsList);

      # Minimal lib for the default call
      libMinimal = {
        inherit (builtins) listToAttrs concatLists;
        mapAttrs' = f: set:
          builtins.listToAttrs (builtins.map (n: f n set.${n}) (builtins.attrNames set));
        filterAttrs = f: set:
          builtins.listToAttrs (builtins.map (n: {
            name = n;
            value = set.${n};
          }) (builtins.filter (n: f n set.${n}) (builtins.attrNames set)));
        toUpper = s: s;
      };
    in
      (secretsFunction {lib = libMinimal;})
      // {
        __functor = _: secretsFunction;
      }
    ```
  </Step>

  <Step title="Create secrets Directory">
    ```bash theme={null}
    mkdir -p secrets
    ```
  </Step>

  <Step title="Add User to secrets.nix">
    Add your SSH public key to the `users` attribute:

    ```nix theme={null}
    users = {
      yourname = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...";
    };
    ```
  </Step>

  <Step title="Create a Secret File">
    Use agenix to create and encrypt a secret:

    ```bash theme={null}
    # Enter dev shell with agenix
    nix develop

    # Create/edit secret
    agenix -e secrets/proxmox_api_secret.age
    ```

    This opens your `$EDITOR` to input the secret value.
  </Step>

  <Step title="Enable Secrets Module in System">
    In your system configuration:

    ```nix systems/yoursystem/default.nix theme={null}
    core.secrets = {
      enable = true;
      defaultSopsFile = ./secrets.yaml;  # If using YAML
    };
    ```
  </Step>

  <Step title="Commit Encrypted Secrets">
    ```bash theme={null}
    git add secrets/*.age secrets.nix
    git commit -m "feat: add secret management configuration"
    ```

    <Warning>
      Only commit `.age` encrypted files, never plain text secrets!
    </Warning>
  </Step>
</Steps>

## Using Secrets

### In NixOS Configuration

Reference secrets in your system configuration:

```nix theme={null}
sops.secrets."proxmox_api_secret" = {
  sopsFile = ./secrets/proxmox_api_secret.age;
  owner = "root";
  group = "root";
  mode = "0400";
};

# Use in service configuration
services.myservice = {
  enable = true;
  apiKeyFile = config.sops.secrets."proxmox_api_secret".path;
};
```

### In Development Shell

Secrets are automatically loaded as environment variables in the dev shell:

```bash theme={null}
nix develop

# Access secrets as environment variables
echo $PROXMOX_API_SECRET
```

### In Home Manager

For user-level secrets:

```nix theme={null}
core.secrets.enable = true;

# Creates ~/.config/sops/age/keys.txt
```

## Secret Management Workflows

<AccordionGroup>
  <Accordion title="Adding a New Secret">
    1. Update `secrets.nix` with user/team access
    2. Create the secret file:
       ```bash theme={null}
       agenix -e secrets/newsecret.age
       ```
    3. Commit the encrypted file:
       ```bash theme={null}
       git add secrets/newsecret.age
       git commit -m "feat: add new secret for service X"
       ```
  </Accordion>

  <Accordion title="Rotating a Secret">
    1. Edit the existing secret:
       ```bash theme={null}
       agenix -e secrets/existing.age
       ```
    2. Update the value and save
    3. Commit the change:
       ```bash theme={null}
       git add secrets/existing.age
       git commit -m "chore: rotate existing secret"
       ```
    4. Deploy to systems:
       ```bash theme={null}
       nixos-rebuild switch --flake .#system
       ```
  </Accordion>

  <Accordion title="Granting Access to a User">
    1. Get user's SSH public key
    2. Add to `secrets.nix`:
       ```nix theme={null}
       users.newuser = "ssh-ed25519 AAAAC3...";
       ```
    3. Add user to team or grant individual access:
       ```nix theme={null}
       teams.cloud.users = ["existinguser" "newuser"];
       ```
    4. Re-encrypt all secrets:
       ```bash theme={null}
       agenix -r
       ```
    5. Commit changes:
       ```bash theme={null}
       git add secrets.nix secrets/*.age
       git commit -m "chore: grant secret access to newuser"
       ```
  </Accordion>

  <Accordion title="Revoking Access">
    1. Remove user from `secrets.nix`
    2. Re-encrypt secrets:
       ```bash theme={null}
       agenix -r
       ```
    3. Commit and deploy immediately
  </Accordion>
</AccordionGroup>

## Secret Organization

### Directory Structure

```
.
├── secrets.nix           # Secret access configuration
├── secrets/
│   ├── proxmox_api_secret.age
│   ├── cloudflare_token.age
│   ├── github_token.age
│   └── postgres_password.age
└── systems/
    └── myserver/
        └── secrets.yaml  # System-specific secrets (optional)
```

### Naming Conventions

* Use lowercase with underscores: `api_key.age`
* Be descriptive: `cloudflare_api_token.age` not `cf.age`
* Group by service: `postgres_password.age`, `postgres_user.age`

## Templates Integration

The empty template includes secrets configuration:

```bash theme={null}
nix flake init -t .#empty
```

This creates:

* `secrets.nix` with base configuration
* `shell.nix` with agenix integration
* Pre-configured flake inputs for agenix

## Security Best Practices

<Warning>
  * Never commit unencrypted secrets
  * Always use `.gitignore` for plain text secret files
  * Rotate secrets after removing user access
  * Use team-based access for shared secrets
  * Keep SSH private keys secure
</Warning>

<Tip>
  * Use different secrets for dev/staging/prod
  * Document secret purpose in `secrets.nix` comments
  * Regular secret rotation improves security
  * Use read-only permissions (0400) where possible
</Tip>

## Troubleshooting

### Cannot Decrypt Secret

```bash theme={null}
# Verify your SSH key is loaded
ssh-add -l

# Check secrets.nix contains your public key
grep "your-key" secrets.nix
```

### Secret Not Available in Shell

```bash theme={null}
# Ensure agenix-shell is configured in flake.nix
# Exit and re-enter dev shell
exit
nix develop
```

### Re-encrypting All Secrets

```bash theme={null}
# After updating secrets.nix
agenix -r
```

## Next Steps

* [Create modules that use secrets](/guides/creating-modules)
* [Add secrets to your systems](/guides/adding-new-system)
* [Contribute securely](/guides/contributing)
