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:
- NixOS Level: SOPS template and systemd user service
- Home Manager Level: Environment loader script
- 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.