Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 100 additions & 58 deletions modules/home-manager/sops.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@
lib,
pkgs,
...
}:

let
}: let
cfg = config.sops;
sops-install-secrets = cfg.package;
secretType = lib.types.submodule (
{ name, ... }:
{
{name, ...}: {
options = {
name = lib.mkOption {
type = lib.types.str;
Expand All @@ -22,7 +19,10 @@ let

key = lib.mkOption {
type = lib.types.str;
default = if cfg.defaultSopsKey != null then cfg.defaultSopsKey else name;
default =
if cfg.defaultSopsKey != null
then cfg.defaultSopsKey
else name;
description = ''
Key used to lookup in the sops file.
To access nested data structures, use / as a separator.
Expand Down Expand Up @@ -73,6 +73,15 @@ let
Sops file the secret is loaded from.
'';
};

materializePath = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
Optional absolute path where this secret should be copied as a real
file during home-manager activation.
'';
};
};
}
);
Expand All @@ -85,8 +94,7 @@ let
merge = lib.mergeEqualOption;
};

manifestFor =
suffix: secrets: templates:
manifestFor = suffix: secrets: templates:
pkgs.writeTextFile {
name = "manifest${suffix}.json";
text = builtins.toJSON {
Expand All @@ -108,7 +116,9 @@ let
};
checkPhase = ''
${sops-install-secrets}/bin/sops-install-secrets -check-mode=${
if cfg.validateSopsFiles then "sopsfile" else "manifest"
if cfg.validateSopsFiles
then "sopsfile"
else "manifest"
} "$out"
'';
};
Expand All @@ -132,16 +142,15 @@ let
''
)
);
in
{
in {
imports = [
./templates.nix
];

options.sops = {
secrets = lib.mkOption {
type = lib.types.attrsOf secretType;
default = { };
default = {};
description = ''
Secrets to decrypt.
'';
Expand Down Expand Up @@ -222,7 +231,7 @@ in

environment = lib.mkOption {
type = lib.types.attrsOf (lib.types.either lib.types.str lib.types.path);
default = { };
default = {};
description = ''
Environment variables to set before calling sops-install-secrets.

Expand All @@ -232,7 +241,7 @@ in

package = lib.mkOption {
type = lib.types.package;
default = (pkgs.callPackage ../.. { }).sops-install-secrets;
default = (pkgs.callPackage ../.. {}).sops-install-secrets;
defaultText = lib.literalExpression "(pkgs.callPackage ../.. {}).sops-install-secrets";
description = ''
sops-install-secrets package to use.
Expand All @@ -251,7 +260,7 @@ in

plugins = lib.mkOption {
type = lib.types.listOf lib.types.package;
default = [ ];
default = [];
description = ''
List of plugins to use for sops decryption.
'';
Expand All @@ -269,7 +278,7 @@ in

sshKeyPaths = lib.mkOption {
type = lib.types.listOf lib.types.path;
default = [ ];
default = [];
description = ''
Paths to ssh keys added as age keys during sops description.
'';
Expand Down Expand Up @@ -310,7 +319,7 @@ in

sshKeyPaths = lib.mkOption {
type = lib.types.listOf lib.types.path;
default = [ ];
default = [];
description = ''
Path to ssh keys added as GPG keys during sops description.
This option must be explicitly unset if <literal>config.sops.gnupg.sshKeyPaths</literal> is set.
Expand All @@ -319,29 +328,32 @@ in
};
};

config = lib.mkIf (cfg.secrets != { }) {
config = lib.mkIf (cfg.secrets != {}) {
assertions = [
{
assertion =
cfg.gnupg.home != null
|| cfg.gnupg.sshKeyPaths != [ ]
cfg.gnupg.home
!= null
|| cfg.gnupg.sshKeyPaths != []
|| cfg.gnupg.qubes-split-gpg.enable == true
|| cfg.age.keyFile != null
|| cfg.age.sshKeyPaths != [ ];
|| cfg.age.sshKeyPaths != [];
message = "No key source configured for sops. Either set services.openssh.enable or set sops.age.keyFile or sops.gnupg.home or sops.gnupg.qubes-split-gpg.enable";
}
{
assertion =
!(cfg.gnupg.home != null && cfg.gnupg.sshKeyPaths != [ ])
!(cfg.gnupg.home != null && cfg.gnupg.sshKeyPaths != [])
&& !(cfg.gnupg.home != null && cfg.gnupg.qubes-split-gpg.enable == true)
&& !(cfg.gnupg.sshKeyPaths != [ ] && cfg.gnupg.qubes-split-gpg.enable == true);
&& !(cfg.gnupg.sshKeyPaths != [] && cfg.gnupg.qubes-split-gpg.enable == true);
message = "Exactly one of sops.gnupg.home, sops.gnupg.qubes-split-gpg.enable and sops.gnupg.sshKeyPaths must be set";
}
{
assertion =
cfg.gnupg.qubes-split-gpg.enable == false
cfg.gnupg.qubes-split-gpg.enable
== false
|| (
cfg.gnupg.qubes-split-gpg.enable == true
cfg.gnupg.qubes-split-gpg.enable
== true
&& cfg.gnupg.qubes-split-gpg.domain != null
&& cfg.gnupg.qubes-split-gpg.domain != ""
);
Expand All @@ -357,20 +369,19 @@ in

sops.environment = {
SOPS_GPG_EXEC = lib.mkMerge [
(lib.mkIf (cfg.gnupg.home != null || cfg.gnupg.sshKeyPaths != [ ]) (
(lib.mkIf (cfg.gnupg.home != null || cfg.gnupg.sshKeyPaths != []) (
lib.mkDefault "${cfg.gnupg.package}/bin/gpg"
))
(lib.mkIf cfg.gnupg.qubes-split-gpg.enable (
lib.mkDefault config.home.sessionVariables.SOPS_GPG_EXEC
))
];

PATH =
let
pluginPaths = lib.makeBinPath cfg.age.plugins;
systemPaths = lib.optionalString pkgs.stdenv.isDarwin "/usr/bin:/bin:/usr/sbin:/sbin";
in
lib.concatStringsSep ":" (lib.filter (p: p != "") [ pluginPaths systemPaths ]);
PATH = let
pluginPaths = lib.makeBinPath cfg.age.plugins;
systemPaths = lib.optionalString pkgs.stdenv.isDarwin "/usr/bin:/bin:/usr/sbin:/sbin";
in
lib.concatStringsSep ":" (lib.filter (p: p != "") [pluginPaths systemPaths]);

QUBES_GPG_DOMAIN = lib.mkIf cfg.gnupg.qubes-split-gpg.enable (
lib.mkDefault cfg.gnupg.qubes-split-gpg.domain
Expand All @@ -389,7 +400,9 @@ in
ExecStart = script;
};
Install.WantedBy =
if cfg.gnupg.home != null then [ "graphical-session-pre.target" ] else [ "default.target" ];
if cfg.gnupg.home != null
then ["graphical-session-pre.target"]
else ["default.target"];
};

# Darwin: load secrets once on login
Expand All @@ -406,36 +419,65 @@ in
};

# [re]load secrets on home-manager activation
home.activation =
let
darwin =
let
domain-target = "gui/$(id -u ${config.home.username})";
in
''
/bin/launchctl bootout ${domain-target}/org.nix-community.home.sops-nix && true
/bin/launchctl bootstrap ${domain-target} ${config.home.homeDirectory}/Library/LaunchAgents/org.nix-community.home.sops-nix.plist
'';
home.activation = let
darwin = let
domain-target = "gui/$(id -u ${config.home.username})";
in ''
/bin/launchctl bootout ${domain-target}/org.nix-community.home.sops-nix && true
/bin/launchctl bootstrap ${domain-target} ${config.home.homeDirectory}/Library/LaunchAgents/org.nix-community.home.sops-nix.plist
'';

linux =
let
systemctl = config.systemd.user.systemctlPath;
in
''
systemdStatus=$(${systemctl} --user is-system-running 2>&1 || true)
linux = let
systemctl = config.systemd.user.systemctlPath;
in ''
systemdStatus=$(${systemctl} --user is-system-running 2>&1 || true)

if [[ $systemdStatus == 'running' || $systemdStatus == 'degraded' ]]; then
${systemctl} restart --user sops-nix
else
echo "User systemd daemon not running. Probably executed on boot where no manual start/reload is needed."
fi
if [[ $systemdStatus == 'running' || $systemdStatus == 'degraded' ]]; then
${systemctl} restart --user sops-nix
else
echo "User systemd daemon not running. Probably executed on boot where no manual start/reload is needed."
fi

unset systemdStatus
'';
unset systemdStatus
'';

in
secretsToMaterialize = lib.filter (secret: secret.materializePath != null) (builtins.attrValues cfg.secrets);
materializeSecretFn = ''
materialize_secret() {
local src="$1"
local dst="$2"
local dstDir="$(dirname "$2")"

mkdir -p "$dstDir"

if [ -e "$dst" ] || [ -L "$dst" ]; then
src_sha="$(${pkgs.coreutils}/bin/sha256sum "$src" | cut -d ' ' -f1)"
dst_sha="$(${pkgs.coreutils}/bin/sha256sum "$dst" | cut -d ' ' -f1)"

if [ "$src_sha" != "$dst_sha" ]; then
echo "ERROR: materialized secret exists but checksum differs: $dst" >&2
exit 1
fi
else
${pkgs.coreutils}/bin/cp --no-clobber --no-preserve=mode,ownership "$src" "$dst"
${pkgs.coreutils}/bin/chmod 600 "$dst"
fi
}
'';
materializeSecret = secret: let
src = lib.escapeShellArg secret.path;
dst = lib.escapeShellArg secret.materializePath;
in "materialize_secret ${src} ${dst}";
materializeScript = materializeSecretFn + lib.concatMapStringsSep "\n\n" materializeSecret secretsToMaterialize;
in
{
sops-nix = if pkgs.stdenv.isLinux then linux else darwin;
sops-nix =
if pkgs.stdenv.isLinux
then linux
else darwin;
}
// lib.optionalAttrs (secretsToMaterialize != []) {
sops-materialize-secrets = lib.hm.dag.entryAfter ["sops-nix"] materializeScript;
};
};
}
Loading