Skip to content

User-Space Secret Management Pattern

This document describes the standard pattern for making SOPS secrets available as environment variables in user shell sessions.

Overview

When secrets need to be available in shell environments (SSH sessions, console logins), we use a three-tier approach:

  1. NixOS Level: SOPS template and systemd user service
  2. Home Manager Level: Environment loader script
  3. Shell Level: Source environment variables in shell configurations

Implementation Pattern

1. NixOS Module Configuration

# modules/nixos/core/app.nix
{
  config,
  lib,
  pkgs,
  ...
}: {
  options.myapp = {
    enable = lib.mkEnableOption "Enable MyApp SOPS integration";
  };

  config = lib.mkIf config.myapp.enable {
    sops = {
      # Define SOPS secrets
      secrets."myapp/apiKey" = {
        sopsFile = ../../../secrets/karl/myapp.yaml;
        owner = "karl";
        group = "users";
        mode = "0400";
      };

      # Create environment template
      templates."myapp-env-karl" = {
        content = ''
          MYAPP_API_KEY=${config.sops.placeholder."myapp/apiKey"}
        '';
        owner = "karl";
        group = "users";
        mode = "0400";
      };
    };

    # Systemd user service to copy to user-accessible location
    systemd.user.services.myapp-env = {
      description = "Copy MyApp environment to user space";
      wantedBy = ["default.target"];
      serviceConfig = {
        Type = "oneshot";
        RemainAfterExit = true;
        ExecStart = pkgs.writeShellScript "myapp-env-setup" ''
          mkdir -p "$HOME/.config/environment.d"
          cp "${config.sops.templates."myapp-env-karl".path}" "$HOME/.config/environment.d/50-myapp.conf"
          chmod 600 "$HOME/.config/environment.d/50-myapp.conf"
        '';
      };
    };

    # Enable user service lingering
    users.users.karl.linger = true;
  };
}

2. Home Manager Environment Module

# modules/home-manager/core/environment.nix (extended)
{
  config,
  lib,
  ...
}: {
  options.environment.myapp = {
    enable = lib.mkEnableOption "Enable MyApp environment loading";
  };

  config = lib.mkIf config.environment.myapp.enable {
    # Create environment loader script
    home.file.".local/bin/load-myapp-env" = {
      text = ''
        #!/bin/sh
        # Load MyApp environment variables if available
        if [ -f "$HOME/.config/environment.d/50-myapp.conf" ]; then
          set -a  # automatically export all variables
          source "$HOME/.config/environment.d/50-myapp.conf"
          set +a  # turn off automatic export
        fi
      '';
      executable = true;
    };
  };
}

3. Shell Integration

Add to each shell configuration:

# Bash
programs.bash.bashrcExtra = lib.mkIf config.environment.myapp.enable ''
  # Load MyApp environment
  if [ -x "$HOME/.local/bin/load-myapp-env" ]; then
    source "$HOME/.local/bin/load-myapp-env"
  fi
'';

# ZSH
programs.zsh.initExtra = lib.mkIf config.environment.myapp.enable ''
  # Load MyApp environment
  if [ -x "$HOME/.local/bin/load-myapp-env" ]; then
    source "$HOME/.local/bin/load-myapp-env"
  fi
'';

# Fish (native approach)
programs.fish.interactiveShellInit = ''
  ${lib.optionalString config.environment.myapp.enable ''
    if test -f "$HOME/.config/environment.d/50-myapp.conf"
      set -l env_file "$HOME/.config/environment.d/50-myapp.conf"
      for line in (cat $env_file)
        if test (string match -r '^[A-Z_]+=.*' "$line")
          set -l key_value (string split -m 1 = "$line")
          set -gx $key_value[1] $key_value[2]
        end
      end
    end
  ''}
'';

File Structure

After implementation, the pattern creates:

/run/secrets/myapp/           # NixOS SOPS secrets (system-level)
/run/secrets/rendered/        # SOPS templates (system-level)
~/.config/environment.d/      # User environment files (systemd user service)
~/.local/bin/                 # Environment loader scripts (Home Manager)

Benefits

  • User-Space: All user-accessible files in standard locations
  • Portable: Works across bash, zsh, and fish shells
  • Session-Wide: Available in SSH sessions, console, and all shell types
  • Secure: Proper file permissions and ownership
  • Maintainable: Consistent pattern for all applications
  • Standard: Follows XDG and Unix conventions

Implementation Checklist

  • Create NixOS module with SOPS secrets and template
  • Add systemd user service to copy template to user space
  • Create Home Manager environment loader script in ~/.local/bin/
  • Integrate with shell configurations (bash, zsh, fish)
  • Enable appropriate options in user profiles
  • Test environment variables are available in SSH sessions

Example

Use modules/home-manager/core/shells/environment.nix as the reference for shell-agnostic environment loading and agent bootstrap behavior.