1
0
Fork 0
mirror of https://github.com/Mic92/sops-nix.git synced 2024-12-14 11:57:52 +00:00

reformat code base with nixfmt

This commit is contained in:
Jörg Thalheim 2024-11-17 12:17:45 +01:00 committed by Jörg Thalheim
parent b05bdb2650
commit 6b85086bcc
24 changed files with 1592 additions and 1159 deletions

View file

@ -1,10 +1,13 @@
{ pkgs ? import <nixpkgs> {} {
, vendorHash ? "sha256-xHScXL3i2oxJSJsvOC+KqLCA5Psu3ht7DQNrh0rB1rA=" pkgs ? import <nixpkgs> { },
}: let vendorHash ? "sha256-xHScXL3i2oxJSJsvOC+KqLCA5Psu3ht7DQNrh0rB1rA=",
}:
let
sops-install-secrets = pkgs.callPackage ./pkgs/sops-install-secrets { sops-install-secrets = pkgs.callPackage ./pkgs/sops-install-secrets {
inherit vendorHash; inherit vendorHash;
}; };
in rec { in
rec {
inherit sops-install-secrets; inherit sops-install-secrets;
sops-init-gpg-key = pkgs.callPackage ./pkgs/sops-init-gpg-key { }; sops-init-gpg-key = pkgs.callPackage ./pkgs/sops-init-gpg-key { };
default = sops-init-gpg-key; default = sops-init-gpg-key;
@ -23,7 +26,8 @@ in rec {
inherit vendorHash; inherit vendorHash;
}; };
unit-tests = pkgs.callPackage ./pkgs/unit-tests.nix { }; unit-tests = pkgs.callPackage ./pkgs/unit-tests.nix { };
} // (pkgs.lib.optionalAttrs pkgs.stdenv.isLinux { }
// (pkgs.lib.optionalAttrs pkgs.stdenv.isLinux {
lint = pkgs.callPackage ./pkgs/lint.nix { lint = pkgs.callPackage ./pkgs/lint.nix {
inherit sops-install-secrets; inherit sops-install-secrets;
}; };

View file

@ -3,12 +3,16 @@
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
inputs.nixpkgs-stable.url = "github:NixOS/nixpkgs/release-24.05"; inputs.nixpkgs-stable.url = "github:NixOS/nixpkgs/release-24.05";
nixConfig.extra-substituters = [ "https://cache.thalheim.io" ]; nixConfig.extra-substituters = [ "https://cache.thalheim.io" ];
nixConfig.extra-trusted-public-keys = ["cache.thalheim.io-1:R7msbosLEZKrxk/lKxf9BTjOOH7Ax3H0Qj0/6wiHOgc="]; nixConfig.extra-trusted-public-keys = [
outputs = { "cache.thalheim.io-1:R7msbosLEZKrxk/lKxf9BTjOOH7Ax3H0Qj0/6wiHOgc="
];
outputs =
{
self, self,
nixpkgs, nixpkgs,
nixpkgs-stable nixpkgs-stable,
}: let }:
let
systems = [ systems = [
"x86_64-linux" "x86_64-linux"
"x86_64-darwin" "x86_64-darwin"
@ -16,13 +20,25 @@
"aarch64-linux" "aarch64-linux"
]; ];
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system); forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system);
suffix-version = version: attrs: nixpkgs.lib.mapAttrs' (name: value: nixpkgs.lib.nameValuePair (name + version) value) attrs; suffix-version =
version: attrs:
nixpkgs.lib.mapAttrs' (name: value: nixpkgs.lib.nameValuePair (name + version) value) attrs;
suffix-stable = suffix-version "-24_05"; suffix-stable = suffix-version "-24_05";
in { in
overlays.default = final: prev: let {
overlays.default =
final: prev:
let
localPkgs = import ./default.nix { pkgs = final; }; localPkgs = import ./default.nix { pkgs = final; };
in { in
inherit (localPkgs) sops-install-secrets sops-init-gpg-key sops-pgp-hook sops-import-keys-hook sops-ssh-to-age; {
inherit (localPkgs)
sops-install-secrets
sops-init-gpg-key
sops-pgp-hook
sops-import-keys-hook
sops-ssh-to-age
;
# backward compatibility # backward compatibility
inherit (prev) ssh-to-pgp; inherit (prev) ssh-to-pgp;
}; };
@ -36,26 +52,39 @@
sops = ./modules/nix-darwin; sops = ./modules/nix-darwin;
default = self.darwinModules.sops; default = self.darwinModules.sops;
}; };
packages = forAllSystems (system: packages = forAllSystems (
system:
import ./default.nix { import ./default.nix {
pkgs = import nixpkgs { inherit system; }; pkgs = import nixpkgs { inherit system; };
}); }
checks = nixpkgs.lib.genAttrs ["x86_64-linux" "aarch64-linux"] );
(system: let checks =
nixpkgs.lib.genAttrs
[
"x86_64-linux"
"aarch64-linux"
]
(
system:
let
tests = self.packages.${system}.sops-install-secrets.tests; tests = self.packages.${system}.sops-install-secrets.tests;
packages-stable = import ./default.nix { packages-stable = import ./default.nix {
pkgs = import nixpkgs-stable { inherit system; }; pkgs = import nixpkgs-stable { inherit system; };
}; };
tests-stable = packages-stable.sops-install-secrets.tests; tests-stable = packages-stable.sops-install-secrets.tests;
in tests // in
(suffix-stable tests-stable) // tests // (suffix-stable tests-stable) // (suffix-stable packages-stable)
(suffix-stable packages-stable)); );
devShells = forAllSystems (system: let devShells = forAllSystems (
system:
let
pkgs = nixpkgs.legacyPackages.${system}; pkgs = nixpkgs.legacyPackages.${system};
in { in
{
unit-tests = pkgs.callPackage ./pkgs/unit-tests.nix { }; unit-tests = pkgs.callPackage ./pkgs/unit-tests.nix { };
default = pkgs.callPackage ./shell.nix { }; default = pkgs.callPackage ./shell.nix { };
}); }
);
}; };
} }

View file

@ -1,9 +1,16 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.sops; cfg = config.sops;
sops-install-secrets = (pkgs.callPackage ../.. { }).sops-install-secrets; sops-install-secrets = (pkgs.callPackage ../.. { }).sops-install-secrets;
secretType = lib.types.submodule ({ name, ... }: { secretType = lib.types.submodule (
{ name, ... }:
{
options = { options = {
name = lib.mkOption { name = lib.mkOption {
type = lib.types.str; type = lib.types.str;
@ -36,7 +43,13 @@ let
}; };
format = lib.mkOption { format = lib.mkOption {
type = lib.types.enum [ "yaml" "json" "binary" "ini" "dotenv" ]; type = lib.types.enum [
"yaml"
"json"
"binary"
"ini"
"dotenv"
];
default = cfg.defaultSopsFormat; default = cfg.defaultSopsFormat;
description = '' description = ''
File format used to decrypt the sops secret. File format used to decrypt the sops secret.
@ -61,7 +74,8 @@ let
''; '';
}; };
}; };
}); }
);
pathNotInStore = lib.mkOptionType { pathNotInStore = lib.mkOptionType {
name = "pathNotInStore"; name = "pathNotInStore";
@ -71,7 +85,9 @@ let
merge = lib.mergeEqualOption; merge = lib.mergeEqualOption;
}; };
manifestFor = suffix: secrets: templates: pkgs.writeTextFile { manifestFor =
suffix: secrets: templates:
pkgs.writeTextFile {
name = "manifest${suffix}.json"; name = "manifest${suffix}.json";
text = builtins.toJSON { text = builtins.toJSON {
secrets = builtins.attrValues secrets; secrets = builtins.attrValues secrets;
@ -90,7 +106,9 @@ let
}; };
}; };
checkPhase = '' checkPhase = ''
${sops-install-secrets}/bin/sops-install-secrets -check-mode=${if cfg.validateSopsFiles then "sopsfile" else "manifest"} "$out" ${sops-install-secrets}/bin/sops-install-secrets -check-mode=${
if cfg.validateSopsFiles then "sopsfile" else "manifest"
} "$out"
''; '';
}; };
@ -98,17 +116,23 @@ let
escapedAgeKeyFile = lib.escapeShellArg cfg.age.keyFile; escapedAgeKeyFile = lib.escapeShellArg cfg.age.keyFile;
script = toString (pkgs.writeShellScript "sops-nix-user" (lib.optionalString cfg.age.generateKey '' script = toString (
pkgs.writeShellScript "sops-nix-user" (
lib.optionalString cfg.age.generateKey ''
if [[ ! -f ${escapedAgeKeyFile} ]]; then if [[ ! -f ${escapedAgeKeyFile} ]]; then
echo generating machine-specific age key... echo generating machine-specific age key...
${pkgs.coreutils}/bin/mkdir -p $(${pkgs.coreutils}/bin/dirname ${escapedAgeKeyFile}) ${pkgs.coreutils}/bin/mkdir -p $(${pkgs.coreutils}/bin/dirname ${escapedAgeKeyFile})
# age-keygen sets 0600 by default, no need to chmod. # age-keygen sets 0600 by default, no need to chmod.
${pkgs.age}/bin/age-keygen -o ${escapedAgeKeyFile} ${pkgs.age}/bin/age-keygen -o ${escapedAgeKeyFile}
fi fi
'' + '' ''
+ ''
${sops-install-secrets}/bin/sops-install-secrets -ignore-passwd ${manifest} ${sops-install-secrets}/bin/sops-install-secrets -ignore-passwd ${manifest}
'')); ''
in { )
);
in
{
imports = [ imports = [
./templates.nix ./templates.nix
]; ];
@ -182,8 +206,16 @@ in {
}; };
log = lib.mkOption { log = lib.mkOption {
type = lib.types.listOf (lib.types.enum [ "keyImport" "secretChanges" ]); type = lib.types.listOf (
default = [ "keyImport" "secretChanges" ]; lib.types.enum [
"keyImport"
"secretChanges"
]
);
default = [
"keyImport"
"secretChanges"
];
description = "What to log"; description = "What to log";
}; };
@ -261,35 +293,50 @@ in {
}; };
config = lib.mkIf (cfg.secrets != { }) { config = lib.mkIf (cfg.secrets != { }) {
assertions = [{ assertions =
[
{
assertion = assertion =
cfg.gnupg.home != null || cfg.gnupg.home != null
cfg.gnupg.sshKeyPaths != [] || || cfg.gnupg.sshKeyPaths != [ ]
cfg.gnupg.qubes-split-gpg.enable == true || || cfg.gnupg.qubes-split-gpg.enable == true
cfg.age.keyFile != null || || 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"; 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.qubes-split-gpg.enable == true) && assertion =
!(cfg.gnupg.sshKeyPaths != [ ] && cfg.gnupg.qubes-split-gpg.enable == true); !(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);
message = "Exactly one of sops.gnupg.home, sops.gnupg.qubes-split-gpg.enable and sops.gnupg.sshKeyPaths must be set"; 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 == true && assertion =
cfg.gnupg.qubes-split-gpg.domain != null && cfg.gnupg.qubes-split-gpg.enable == false
cfg.gnupg.qubes-split-gpg.domain != ""); || (
cfg.gnupg.qubes-split-gpg.enable == true
&& cfg.gnupg.qubes-split-gpg.domain != null
&& cfg.gnupg.qubes-split-gpg.domain != ""
);
message = "sops.gnupg.qubes-split-gpg.domain is required when sops.gnupg.qubes-split-gpg.enable is set to true"; message = "sops.gnupg.qubes-split-gpg.domain is required when sops.gnupg.qubes-split-gpg.enable is set to true";
}] ++ lib.optionals cfg.validateSopsFiles ( }
lib.concatLists (lib.mapAttrsToList (name: secret: [{ ]
++ lib.optionals cfg.validateSopsFiles (
lib.concatLists (
lib.mapAttrsToList (name: secret: [
{
assertion = builtins.pathExists secret.sopsFile; assertion = builtins.pathExists secret.sopsFile;
message = "Cannot find path '${secret.sopsFile}' set in sops.secrets.${lib.strings.escapeNixIdentifier name}.sopsFile"; message = "Cannot find path '${secret.sopsFile}' set in sops.secrets.${lib.strings.escapeNixIdentifier name}.sopsFile";
} { }
{
assertion = assertion =
builtins.isPath secret.sopsFile || builtins.isPath secret.sopsFile
(builtins.isString secret.sopsFile && lib.hasPrefix builtins.storeDir secret.sopsFile); || (builtins.isString secret.sopsFile && lib.hasPrefix builtins.storeDir secret.sopsFile);
message = "'${secret.sopsFile}' is not in the Nix store. Either add it to the Nix store or set sops.validateSopsFiles to false"; message = "'${secret.sopsFile}' is not in the Nix store. Either add it to the Nix store or set sops.validateSopsFiles to false";
}]) cfg.secrets) }
]) cfg.secrets
)
); );
home.sessionVariables = lib.mkIf cfg.gnupg.qubes-split-gpg.enable { home.sessionVariables = lib.mkIf cfg.gnupg.qubes-split-gpg.enable {
@ -300,11 +347,17 @@ in {
sops.environment = { sops.environment = {
SOPS_GPG_EXEC = lib.mkMerge [ SOPS_GPG_EXEC = lib.mkMerge [
(lib.mkIf (cfg.gnupg.home != null || cfg.gnupg.sshKeyPaths != []) (lib.mkDefault "${pkgs.gnupg}/bin/gpg")) (lib.mkIf (cfg.gnupg.home != null || cfg.gnupg.sshKeyPaths != [ ]) (
(lib.mkIf cfg.gnupg.qubes-split-gpg.enable (lib.mkDefault config.home.sessionVariables.SOPS_GPG_EXEC)) lib.mkDefault "${pkgs.gnupg}/bin/gpg"
))
(lib.mkIf cfg.gnupg.qubes-split-gpg.enable (
lib.mkDefault config.home.sessionVariables.SOPS_GPG_EXEC
))
]; ];
QUBES_GPG_DOMAIN = lib.mkIf cfg.gnupg.qubes-split-gpg.enable (lib.mkDefault cfg.gnupg.qubes-split-gpg.domain); QUBES_GPG_DOMAIN = lib.mkIf cfg.gnupg.qubes-split-gpg.enable (
lib.mkDefault cfg.gnupg.qubes-split-gpg.domain
);
}; };
systemd.user.services.sops-nix = lib.mkIf pkgs.stdenv.hostPlatform.isLinux { systemd.user.services.sops-nix = lib.mkIf pkgs.stdenv.hostPlatform.isLinux {
@ -313,10 +366,13 @@ in {
}; };
Service = { Service = {
Type = "oneshot"; Type = "oneshot";
Environment = builtins.concatStringsSep " " (lib.mapAttrsToList (name: value: "'${name}=${value}'") cfg.environment); Environment = builtins.concatStringsSep " " (
lib.mapAttrsToList (name: value: "'${name}=${value}'") cfg.environment
);
ExecStart = script; ExecStart = script;
}; };
Install.WantedBy = if cfg.gnupg.home != null then [ "graphical-session-pre.target" ] else [ "default.target" ]; Install.WantedBy =
if cfg.gnupg.home != null then [ "graphical-session-pre.target" ] else [ "default.target" ];
}; };
# Darwin: load secrets once on login # Darwin: load secrets once on login
@ -333,15 +389,22 @@ in {
}; };
# [re]load secrets on home-manager activation # [re]load secrets on home-manager activation
home.activation = let home.activation =
darwin = let let
darwin =
let
domain-target = "gui/$(id -u ${config.home.username})"; domain-target = "gui/$(id -u ${config.home.username})";
in '' in
''
/bin/launchctl bootout ${domain-target}/org.nix-community.home.sops-nix && true /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 /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 '' linux =
let
systemctl = config.systemd.user.systemctlPath;
in
''
systemdStatus=$(${systemctl} --user is-system-running 2>&1 || true) systemdStatus=$(${systemctl} --user is-system-running 2>&1 || true)
if [[ $systemdStatus == 'running' || $systemdStatus == 'degraded' ]]; then if [[ $systemdStatus == 'running' || $systemdStatus == 'degraded' ]]; then
@ -353,7 +416,8 @@ in {
unset systemdStatus unset systemdStatus
''; '';
in { in
{
sops-nix = if pkgs.stdenv.isLinux then linux else darwin; sops-nix = if pkgs.stdenv.isLinux then linux else darwin;
}; };
}; };

View file

@ -1,4 +1,10 @@
{ config, pkgs, lib, options, ... }: {
config,
pkgs,
lib,
options,
...
}:
let let
inherit (lib) inherit (lib)
mkOption mkOption
@ -6,11 +12,15 @@ let
mapAttrs mapAttrs
types types
; ;
in { in
{
options.sops = { options.sops = {
templates = mkOption { templates = mkOption {
description = "Templates for secret files"; description = "Templates for secret files";
type = types.attrsOf (types.submodule ({ config, ... }: { type = types.attrsOf (
types.submodule (
{ config, ... }:
{
options = { options = {
name = mkOption { name = mkOption {
type = types.singleLineStr; type = types.singleLineStr;
@ -67,25 +77,30 @@ in {
''; '';
}; };
}; };
})); }
)
);
default = { }; default = { };
}; };
placeholder = mkOption { placeholder = mkOption {
type = types.attrsOf (types.mkOptionType { type = types.attrsOf (
types.mkOptionType {
name = "coercibleToString"; name = "coercibleToString";
description = "value that can be coerced to string"; description = "value that can be coerced to string";
check = lib.strings.isConvertibleWithToString; check = lib.strings.isConvertibleWithToString;
merge = lib.mergeEqualOption; merge = lib.mergeEqualOption;
}); }
);
default = { }; default = { };
visible = false; visible = false;
}; };
}; };
config = lib.optionalAttrs (options ? sops.secrets) config = lib.optionalAttrs (options ? sops.secrets) (
(lib.mkIf (config.sops.templates != { }) { lib.mkIf (config.sops.templates != { }) {
sops.placeholder = mapAttrs sops.placeholder = mapAttrs (
(name: _: mkDefault "<SOPS:${builtins.hashString "sha256" name}:PLACEHOLDER>") name: _: mkDefault "<SOPS:${builtins.hashString "sha256" name}:PLACEHOLDER>"
config.sops.secrets; ) config.sops.secrets;
}); }
);
} }

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.sops; cfg = config.sops;
@ -25,10 +30,14 @@ let
withEnvironment = import ./with-environment.nix { withEnvironment = import ./with-environment.nix {
inherit cfg lib; inherit cfg lib;
}; };
secretType = lib.types.submodule ({ config, ... }: { secretType = lib.types.submodule (
{ config, ... }:
{
config = { config = {
sopsFile = lib.mkOptionDefault cfg.defaultSopsFile; sopsFile = lib.mkOptionDefault cfg.defaultSopsFile;
sopsFileHash = lib.mkOptionDefault (lib.optionalString cfg.validateSopsFiles "${builtins.hashFile "sha256" config.sopsFile}"); sopsFileHash = lib.mkOptionDefault (
lib.optionalString cfg.validateSopsFiles "${builtins.hashFile "sha256" config.sopsFile}"
);
}; };
options = { options = {
name = lib.mkOption { name = lib.mkOption {
@ -49,7 +58,11 @@ let
}; };
path = lib.mkOption { path = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = if config.neededForUsers then "/run/secrets-for-users/${config.name}" else "/run/secrets/${config.name}"; default =
if config.neededForUsers then
"/run/secrets-for-users/${config.name}"
else
"/run/secrets/${config.name}";
defaultText = "/run/secrets-for-users/$name when neededForUsers is set, /run/secrets/$name when otherwise."; defaultText = "/run/secrets-for-users/$name when neededForUsers is set, /run/secrets/$name when otherwise.";
description = '' description = ''
Path where secrets are symlinked to. Path where secrets are symlinked to.
@ -57,7 +70,13 @@ let
''; '';
}; };
format = lib.mkOption { format = lib.mkOption {
type = lib.types.enum ["yaml" "json" "binary" "dotenv" "ini"]; type = lib.types.enum [
"yaml"
"json"
"binary"
"dotenv"
"ini"
];
default = cfg.defaultSopsFormat; default = cfg.defaultSopsFormat;
description = '' description = ''
File format used to decrypt the sops secret. File format used to decrypt the sops secret.
@ -124,35 +143,48 @@ let
''; '';
}; };
}; };
}); }
);
darwinSSHKeys = [{ darwinSSHKeys = [
{
type = "rsa"; type = "rsa";
path = "/etc/ssh/ssh_host_rsa_key"; path = "/etc/ssh/ssh_host_rsa_key";
} { }
{
type = "ed25519"; type = "ed25519";
path = "/etc/ssh/ssh_host_ed25519_key"; path = "/etc/ssh/ssh_host_ed25519_key";
}]; }
];
escapedKeyFile = lib.escapeShellArg cfg.age.keyFile; escapedKeyFile = lib.escapeShellArg cfg.age.keyFile;
# Skip ssh keys deployed with sops to avoid a catch 22 # Skip ssh keys deployed with sops to avoid a catch 22
defaultImportKeys = algo: defaultImportKeys =
map (e: e.path) (lib.filter (e: e.type == algo && !(lib.hasPrefix "/run/secrets" e.path)) darwinSSHKeys); algo:
map (e: e.path) (
lib.filter (e: e.type == algo && !(lib.hasPrefix "/run/secrets" e.path)) darwinSSHKeys
);
installScript = '' installScript = ''
${if cfg.age.generateKey then '' ${
if cfg.age.generateKey then
''
if [[ ! -f ${escapedKeyFile} ]]; then if [[ ! -f ${escapedKeyFile} ]]; then
echo generating machine-specific age key... echo generating machine-specific age key...
mkdir -p $(dirname ${escapedKeyFile}) mkdir -p $(dirname ${escapedKeyFile})
# age-keygen sets 0600 by default, no need to chmod. # age-keygen sets 0600 by default, no need to chmod.
${pkgs.age}/bin/age-keygen -o ${escapedKeyFile} ${pkgs.age}/bin/age-keygen -o ${escapedKeyFile}
fi fi
'' else ""} ''
else
""
}
echo "Setting up secrets..." echo "Setting up secrets..."
${withEnvironment "${sops-install-secrets}/bin/sops-install-secrets ${manifest}"} ${withEnvironment "${sops-install-secrets}/bin/sops-install-secrets ${manifest}"}
''; '';
in { in
{
options.sops = { options.sops = {
secrets = lib.mkOption { secrets = lib.mkOption {
type = lib.types.attrsOf secretType; type = lib.types.attrsOf secretType;
@ -195,8 +227,16 @@ in {
}; };
log = lib.mkOption { log = lib.mkOption {
type = lib.types.listOf (lib.types.enum [ "keyImport" "secretChanges" ]); type = lib.types.listOf (
default = [ "keyImport" "secretChanges" ]; lib.types.enum [
"keyImport"
"secretChanges"
]
);
default = [
"keyImport"
"secretChanges"
];
description = "What to log"; description = "What to log";
}; };
@ -227,9 +267,10 @@ in {
validationPackage = lib.mkOption { validationPackage = lib.mkOption {
type = lib.types.package; type = lib.types.package;
default = default =
if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform then
then sops-install-secrets sops-install-secrets
else (pkgs.pkgsBuildHost.callPackage ../.. {}).sops-install-secrets; else
(pkgs.pkgsBuildHost.callPackage ../.. { }).sops-install-secrets;
defaultText = lib.literalExpression "config.sops.package"; defaultText = lib.literalExpression "config.sops.package";
description = '' description = ''
@ -297,28 +338,44 @@ in {
config = lib.mkMerge [ config = lib.mkMerge [
(lib.mkIf (cfg.secrets != { }) { (lib.mkIf (cfg.secrets != { }) {
assertions = [{ assertions =
assertion = cfg.gnupg.home != null || cfg.gnupg.sshKeyPaths != [] || cfg.age.keyFile != null || cfg.age.sshKeyPaths != []; [
{
assertion =
cfg.gnupg.home != null
|| cfg.gnupg.sshKeyPaths != [ ]
|| cfg.age.keyFile != null
|| cfg.age.sshKeyPaths != [ ];
message = "No key source configured for sops. Either set services.openssh.enable or set sops.age.keyFile or sops.gnupg.home"; message = "No key source configured for sops. Either set services.openssh.enable or set sops.age.keyFile or sops.gnupg.home";
} { }
{
assertion = !(cfg.gnupg.home != null && cfg.gnupg.sshKeyPaths != [ ]); assertion = !(cfg.gnupg.home != null && cfg.gnupg.sshKeyPaths != [ ]);
message = "Exactly one of sops.gnupg.home and sops.gnupg.sshKeyPaths must be set"; message = "Exactly one of sops.gnupg.home and sops.gnupg.sshKeyPaths must be set";
}] ++ lib.optionals cfg.validateSopsFiles ( }
lib.concatLists (lib.mapAttrsToList (name: secret: [{ ]
++ lib.optionals cfg.validateSopsFiles (
lib.concatLists (
lib.mapAttrsToList (name: secret: [
{
assertion = builtins.pathExists secret.sopsFile; assertion = builtins.pathExists secret.sopsFile;
message = "Cannot find path '${secret.sopsFile}' set in sops.secrets.${lib.strings.escapeNixIdentifier name}.sopsFile"; message = "Cannot find path '${secret.sopsFile}' set in sops.secrets.${lib.strings.escapeNixIdentifier name}.sopsFile";
} { }
{
assertion = assertion =
builtins.isPath secret.sopsFile || builtins.isPath secret.sopsFile
(builtins.isString secret.sopsFile && lib.hasPrefix builtins.storeDir secret.sopsFile); || (builtins.isString secret.sopsFile && lib.hasPrefix builtins.storeDir secret.sopsFile);
message = "'${secret.sopsFile}' is not in the Nix store. Either add it to the Nix store or set sops.validateSopsFiles to false"; message = "'${secret.sopsFile}' is not in the Nix store. Either add it to the Nix store or set sops.validateSopsFiles to false";
} { }
{
assertion = secret.uid != null && secret.uid != 0 -> secret.owner == null; assertion = secret.uid != null && secret.uid != 0 -> secret.owner == null;
message = "In ${secret.name} exactly one of sops.owner and sops.uid must be set"; message = "In ${secret.name} exactly one of sops.owner and sops.uid must be set";
} { }
{
assertion = secret.gid != null && secret.gid != 0 -> secret.group == null; assertion = secret.gid != null && secret.gid != 0 -> secret.group == null;
message = "In ${secret.name} exactly one of sops.group and sops.gid must be set"; message = "In ${secret.name} exactly one of sops.group and sops.gid must be set";
}]) cfg.secrets) }
]) cfg.secrets
)
); );
system.build.sops-nix-manifest = manifest; system.build.sops-nix-manifest = manifest;
@ -336,7 +393,9 @@ in {
}) })
{ {
sops.environment.SOPS_GPG_EXEC = lib.mkIf (cfg.gnupg.home != null || cfg.gnupg.sshKeyPaths != []) (lib.mkDefault "${pkgs.gnupg}/bin/gpg"); sops.environment.SOPS_GPG_EXEC = lib.mkIf (cfg.gnupg.home != null || cfg.gnupg.sshKeyPaths != [ ]) (
lib.mkDefault "${pkgs.gnupg}/bin/gpg"
);
} }
]; ];
} }

View file

@ -4,7 +4,8 @@ suffix: secrets: extraJson:
writeTextFile { writeTextFile {
name = "manifest${suffix}.json"; name = "manifest${suffix}.json";
text = builtins.toJSON ({ text = builtins.toJSON (
{
secrets = builtins.attrValues secrets; secrets = builtins.attrValues secrets;
# Does this need to be configurable? # Does this need to be configurable?
secretsMountPoint = "/run/secrets.d"; secretsMountPoint = "/run/secrets.d";
@ -22,8 +23,12 @@ writeTextFile {
keyImport = builtins.elem "keyImport" cfg.log; keyImport = builtins.elem "keyImport" cfg.log;
secretChanges = builtins.elem "secretChanges" cfg.log; secretChanges = builtins.elem "secretChanges" cfg.log;
}; };
} // extraJson); }
// extraJson
);
checkPhase = '' checkPhase = ''
${cfg.validationPackage}/bin/sops-install-secrets -check-mode=${if cfg.validateSopsFiles then "sopsfile" else "manifest"} "$out" ${cfg.validationPackage}/bin/sops-install-secrets -check-mode=${
if cfg.validateSopsFiles then "sopsfile" else "manifest"
} "$out"
''; '';
} }

View file

@ -1,4 +1,10 @@
{ lib, options, config, pkgs, ... }: {
lib,
options,
config,
pkgs,
...
}:
let let
cfg = config.sops; cfg = config.sops;
secretsForUsers = lib.filterAttrs (_: v: v.neededForUsers) cfg.secrets; secretsForUsers = lib.filterAttrs (_: v: v.neededForUsers) cfg.secrets;
@ -22,10 +28,15 @@ let
in in
{ {
assertions = [{ assertions = [
assertion = (lib.filterAttrs (_: v: (v.uid != 0 && v.owner != "root") || (v.gid != 0 && v.group != "root")) secretsForUsers) == { }; {
assertion =
(lib.filterAttrs (
_: v: (v.uid != 0 && v.owner != "root") || (v.gid != 0 && v.group != "root")
) secretsForUsers) == { };
message = "neededForUsers cannot be used for secrets that are not root-owned"; message = "neededForUsers cannot be used for secrets that are not root-owned";
}]; }
];
system.activationScripts = lib.mkIf (secretsForUsers != [ ]) { system.activationScripts = lib.mkIf (secretsForUsers != [ ]) {
postActivation.text = lib.mkAfter installScript; postActivation.text = lib.mkAfter installScript;

View file

@ -1,4 +1,10 @@
{ config, pkgs, lib, options, ... }: {
config,
pkgs,
lib,
options,
...
}:
let let
inherit (lib) inherit (lib)
mkOption mkOption
@ -6,11 +12,15 @@ let
mapAttrs mapAttrs
types types
; ;
in { in
{
options.sops = { options.sops = {
templates = mkOption { templates = mkOption {
description = "Templates for secret files"; description = "Templates for secret files";
type = types.attrsOf (types.submodule ({ config, ... }: { type = types.attrsOf (
types.submodule (
{ config, ... }:
{
options = { options = {
name = mkOption { name = mkOption {
type = types.singleLineStr; type = types.singleLineStr;
@ -63,25 +73,30 @@ in {
''; '';
}; };
}; };
})); }
)
);
default = { }; default = { };
}; };
placeholder = mkOption { placeholder = mkOption {
type = types.attrsOf (types.mkOptionType { type = types.attrsOf (
types.mkOptionType {
name = "coercibleToString"; name = "coercibleToString";
description = "value that can be coerced to string"; description = "value that can be coerced to string";
check = lib.strings.isConvertibleWithToString; check = lib.strings.isConvertibleWithToString;
merge = lib.mergeEqualOption; merge = lib.mergeEqualOption;
}); }
);
default = { }; default = { };
visible = false; visible = false;
}; };
}; };
config = lib.optionalAttrs (options ? sops.secrets) config = lib.optionalAttrs (options ? sops.secrets) (
(lib.mkIf (config.sops.templates != { }) { lib.mkIf (config.sops.templates != { }) {
sops.placeholder = mapAttrs sops.placeholder = mapAttrs (
(name: _: mkDefault "<SOPS:${builtins.hashString "sha256" name}:PLACEHOLDER>") name: _: mkDefault "<SOPS:${builtins.hashString "sha256" name}:PLACEHOLDER>"
config.sops.secrets; ) config.sops.secrets;
}); }
);
} }

View file

@ -4,7 +4,8 @@ sopsCall:
if cfg.environment == { } then if cfg.environment == { } then
sopsCall sopsCall
else '' else
''
( (
# shellcheck disable=SC2030,SC2031 # shellcheck disable=SC2030,SC2031
${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: " export ${n}='${v}'") cfg.environment)} ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: " export ${n}='${v}'") cfg.environment)}

View file

@ -1,4 +1,10 @@
{ config, options, lib, pkgs, ... }: {
config,
options,
lib,
pkgs,
...
}:
let let
cfg = config.sops; cfg = config.sops;
@ -23,16 +29,21 @@ let
# Currently, all templates are "regular" (there's no support for `neededForUsers` for templates.) # Currently, all templates are "regular" (there's no support for `neededForUsers` for templates.)
regularTemplates = cfg.templates; regularTemplates = cfg.templates;
useSystemdActivation = (options.systemd ? sysusers && config.systemd.sysusers.enable) || useSystemdActivation =
(options.services ? userborn && config.services.userborn.enable); (options.systemd ? sysusers && config.systemd.sysusers.enable)
|| (options.services ? userborn && config.services.userborn.enable);
withEnvironment = import ./with-environment.nix { withEnvironment = import ./with-environment.nix {
inherit cfg lib; inherit cfg lib;
}; };
secretType = lib.types.submodule ({ config, ... }: { secretType = lib.types.submodule (
{ config, ... }:
{
config = { config = {
sopsFile = lib.mkOptionDefault cfg.defaultSopsFile; sopsFile = lib.mkOptionDefault cfg.defaultSopsFile;
sopsFileHash = lib.mkOptionDefault (lib.optionalString cfg.validateSopsFiles "${builtins.hashFile "sha256" config.sopsFile}"); sopsFileHash = lib.mkOptionDefault (
lib.optionalString cfg.validateSopsFiles "${builtins.hashFile "sha256" config.sopsFile}"
);
}; };
options = { options = {
name = lib.mkOption { name = lib.mkOption {
@ -54,7 +65,11 @@ let
}; };
path = lib.mkOption { path = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = if config.neededForUsers then "/run/secrets-for-users/${config.name}" else "/run/secrets/${config.name}"; default =
if config.neededForUsers then
"/run/secrets-for-users/${config.name}"
else
"/run/secrets/${config.name}";
defaultText = "/run/secrets-for-users/$name when neededForUsers is set, /run/secrets/$name when otherwise."; defaultText = "/run/secrets-for-users/$name when neededForUsers is set, /run/secrets/$name when otherwise.";
description = '' description = ''
Path where secrets are symlinked to. Path where secrets are symlinked to.
@ -62,7 +77,13 @@ let
''; '';
}; };
format = lib.mkOption { format = lib.mkOption {
type = lib.types.enum ["yaml" "json" "binary" "dotenv" "ini"]; type = lib.types.enum [
"yaml"
"json"
"binary"
"dotenv"
"ini"
];
default = cfg.defaultSopsFormat; default = cfg.defaultSopsFormat;
description = '' description = ''
File format used to decrypt the sops secret. File format used to decrypt the sops secret.
@ -147,15 +168,22 @@ let
''; '';
}; };
}; };
}); }
);
# Skip ssh keys deployed with sops to avoid a catch 22 # Skip ssh keys deployed with sops to avoid a catch 22
defaultImportKeys = algo: defaultImportKeys =
algo:
if config.services.openssh.enable then if config.services.openssh.enable then
map (e: e.path) (lib.filter (e: e.type == algo && !(lib.hasPrefix "/run/secrets" e.path)) config.services.openssh.hostKeys) map (e: e.path) (
lib.filter (
e: e.type == algo && !(lib.hasPrefix "/run/secrets" e.path)
) config.services.openssh.hostKeys
)
else else
[ ]; [ ];
in { in
{
options.sops = { options.sops = {
secrets = lib.mkOption { secrets = lib.mkOption {
type = lib.types.attrsOf secretType; type = lib.types.attrsOf secretType;
@ -208,8 +236,16 @@ in {
}; };
log = lib.mkOption { log = lib.mkOption {
type = lib.types.listOf (lib.types.enum [ "keyImport" "secretChanges" ]); type = lib.types.listOf (
default = [ "keyImport" "secretChanges" ]; lib.types.enum [
"keyImport"
"secretChanges"
]
);
default = [
"keyImport"
"secretChanges"
];
description = "What to log"; description = "What to log";
}; };
@ -240,9 +276,10 @@ in {
validationPackage = lib.mkOption { validationPackage = lib.mkOption {
type = lib.types.package; type = lib.types.package;
default = default =
if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform then
then sops-install-secrets sops-install-secrets
else (pkgs.pkgsBuildHost.callPackage ../.. {}).sops-install-secrets; else
(pkgs.pkgsBuildHost.callPackage ../.. { }).sops-install-secrets;
defaultText = lib.literalExpression "config.sops.package"; defaultText = lib.literalExpression "config.sops.package";
description = '' description = ''
@ -326,36 +363,74 @@ in {
imports = [ imports = [
./templates ./templates
./secrets-for-users ./secrets-for-users
(lib.mkRenamedOptionModule [ "sops" "gnupgHome" ] [ "sops" "gnupg" "home" ]) (lib.mkRenamedOptionModule
(lib.mkRenamedOptionModule [ "sops" "sshKeyPaths" ] [ "sops" "gnupg" "sshKeyPaths" ]) [
"sops"
"gnupgHome"
]
[
"sops"
"gnupg"
"home"
]
)
(lib.mkRenamedOptionModule
[
"sops"
"sshKeyPaths"
]
[
"sops"
"gnupg"
"sshKeyPaths"
]
)
]; ];
config = lib.mkMerge [ config = lib.mkMerge [
(lib.mkIf (cfg.secrets != { }) { (lib.mkIf (cfg.secrets != { }) {
assertions = [{ assertions =
assertion = cfg.gnupg.home != null || cfg.gnupg.sshKeyPaths != [] || cfg.age.keyFile != null || cfg.age.sshKeyPaths != []; [
{
assertion =
cfg.gnupg.home != null
|| cfg.gnupg.sshKeyPaths != [ ]
|| cfg.age.keyFile != null
|| cfg.age.sshKeyPaths != [ ];
message = "No key source configured for sops. Either set services.openssh.enable or set sops.age.keyFile or sops.gnupg.home"; message = "No key source configured for sops. Either set services.openssh.enable or set sops.age.keyFile or sops.gnupg.home";
} { }
{
assertion = !(cfg.gnupg.home != null && cfg.gnupg.sshKeyPaths != [ ]); assertion = !(cfg.gnupg.home != null && cfg.gnupg.sshKeyPaths != [ ]);
message = "Exactly one of sops.gnupg.home and sops.gnupg.sshKeyPaths must be set"; message = "Exactly one of sops.gnupg.home and sops.gnupg.sshKeyPaths must be set";
}] ++ lib.optionals cfg.validateSopsFiles ( }
lib.concatLists (lib.mapAttrsToList (name: secret: [{ ]
++ lib.optionals cfg.validateSopsFiles (
lib.concatLists (
lib.mapAttrsToList (name: secret: [
{
assertion = builtins.pathExists secret.sopsFile; assertion = builtins.pathExists secret.sopsFile;
message = "Cannot find path '${secret.sopsFile}' set in sops.secrets.${lib.strings.escapeNixIdentifier name}.sopsFile"; message = "Cannot find path '${secret.sopsFile}' set in sops.secrets.${lib.strings.escapeNixIdentifier name}.sopsFile";
} { }
{
assertion = assertion =
builtins.isPath secret.sopsFile || builtins.isPath secret.sopsFile
(builtins.isString secret.sopsFile && lib.hasPrefix builtins.storeDir secret.sopsFile); || (builtins.isString secret.sopsFile && lib.hasPrefix builtins.storeDir secret.sopsFile);
message = "'${secret.sopsFile}' is not in the Nix store. Either add it to the Nix store or set sops.validateSopsFiles to false"; message = "'${secret.sopsFile}' is not in the Nix store. Either add it to the Nix store or set sops.validateSopsFiles to false";
} { }
{
assertion = secret.uid != null && secret.uid != 0 -> secret.owner == null; assertion = secret.uid != null && secret.uid != 0 -> secret.owner == null;
message = "In ${secret.name} exactly one of sops.owner and sops.uid must be set"; message = "In ${secret.name} exactly one of sops.owner and sops.uid must be set";
} { }
{
assertion = secret.gid != null && secret.gid != 0 -> secret.group == null; assertion = secret.gid != null && secret.gid != 0 -> secret.group == null;
message = "In ${secret.name} exactly one of sops.group and sops.gid must be set"; message = "In ${secret.name} exactly one of sops.group and sops.gid must be set";
}]) cfg.secrets) }
]) cfg.secrets
)
); );
sops.environment.SOPS_GPG_EXEC = lib.mkIf (cfg.gnupg.home != null || cfg.gnupg.sshKeyPaths != []) (lib.mkDefault "${pkgs.gnupg}/bin/gpg"); sops.environment.SOPS_GPG_EXEC = lib.mkIf (cfg.gnupg.home != null || cfg.gnupg.sshKeyPaths != [ ]) (
lib.mkDefault "${pkgs.gnupg}/bin/gpg"
);
# When using sysusers we no longer are started as an activation script because those are started in initrd while sysusers is started later. # When using sysusers we no longer are started as an activation script because those are started in initrd while sysusers is started later.
systemd.services.sops-install-secrets = lib.mkIf (regularSecrets != { } && useSystemdActivation) { systemd.services.sops-install-secrets = lib.mkIf (regularSecrets != { } && useSystemdActivation) {
@ -372,23 +447,39 @@ in {
}; };
system.activationScripts = { system.activationScripts = {
setupSecrets = lib.mkIf (regularSecrets != {} && !useSystemdActivation) (lib.stringAfter ([ "specialfs" "users" "groups" ] ++ lib.optional cfg.age.generateKey "generate-age-key") '' setupSecrets = lib.mkIf (regularSecrets != { } && !useSystemdActivation) (
lib.stringAfter
(
[
"specialfs"
"users"
"groups"
]
++ lib.optional cfg.age.generateKey "generate-age-key"
)
''
[ -e /run/current-system ] || echo setting up secrets... [ -e /run/current-system ] || echo setting up secrets...
${withEnvironment "${sops-install-secrets}/bin/sops-install-secrets ${manifest}"} ${withEnvironment "${sops-install-secrets}/bin/sops-install-secrets ${manifest}"}
'' // lib.optionalAttrs (config.system ? dryActivationScript) { ''
// lib.optionalAttrs (config.system ? dryActivationScript) {
supportsDryActivation = true; supportsDryActivation = true;
}); }
);
generate-age-key = let generate-age-key =
let
escapedKeyFile = lib.escapeShellArg cfg.age.keyFile; escapedKeyFile = lib.escapeShellArg cfg.age.keyFile;
in lib.mkIf cfg.age.generateKey (lib.stringAfter [] '' in
lib.mkIf cfg.age.generateKey (
lib.stringAfter [ ] ''
if [[ ! -f ${escapedKeyFile} ]]; then if [[ ! -f ${escapedKeyFile} ]]; then
echo generating machine-specific age key... echo generating machine-specific age key...
mkdir -p $(dirname ${escapedKeyFile}) mkdir -p $(dirname ${escapedKeyFile})
# age-keygen sets 0600 by default, no need to chmod. # age-keygen sets 0600 by default, no need to chmod.
${pkgs.age}/bin/age-keygen -o ${escapedKeyFile} ${pkgs.age}/bin/age-keygen -o ${escapedKeyFile}
fi fi
''); ''
);
}; };
}) })
{ {

View file

@ -4,7 +4,8 @@ suffix: secrets: templates: extraJson:
writeTextFile { writeTextFile {
name = "manifest${suffix}.json"; name = "manifest${suffix}.json";
text = builtins.toJSON ({ text = builtins.toJSON (
{
secrets = builtins.attrValues secrets; secrets = builtins.attrValues secrets;
templates = builtins.attrValues templates; templates = builtins.attrValues templates;
# Does this need to be configurable? # Does this need to be configurable?
@ -22,8 +23,12 @@ writeTextFile {
keyImport = builtins.elem "keyImport" cfg.log; keyImport = builtins.elem "keyImport" cfg.log;
secretChanges = builtins.elem "secretChanges" cfg.log; secretChanges = builtins.elem "secretChanges" cfg.log;
}; };
} // extraJson); }
// extraJson
);
checkPhase = '' checkPhase = ''
${cfg.validationPackage}/bin/sops-install-secrets -check-mode=${if cfg.validateSopsFiles then "sopsfile" else "manifest"} "$out" ${cfg.validationPackage}/bin/sops-install-secrets -check-mode=${
if cfg.validateSopsFiles then "sopsfile" else "manifest"
} "$out"
''; '';
} }

View file

@ -1,4 +1,10 @@
{ lib, options, config, pkgs, ... }: {
lib,
options,
config,
pkgs,
...
}:
let let
cfg = config.sops; cfg = config.sops;
secretsForUsers = lib.filterAttrs (_: v: v.neededForUsers) cfg.secrets; secretsForUsers = lib.filterAttrs (_: v: v.neededForUsers) cfg.secrets;
@ -15,11 +21,13 @@ let
symlinkPath = "/run/secrets-for-users"; symlinkPath = "/run/secrets-for-users";
}; };
sysusersEnabled = options.systemd ? sysusers && config.systemd.sysusers.enable; sysusersEnabled = options.systemd ? sysusers && config.systemd.sysusers.enable;
useSystemdActivation = sysusersEnabled || useSystemdActivation =
(options.services ? userborn && config.services.userborn.enable); sysusersEnabled || (options.services ? userborn && config.services.userborn.enable);
in in
{ {
systemd.services.sops-install-secrets-for-users = lib.mkIf (secretsForUsers != { } && useSystemdActivation) { systemd.services.sops-install-secrets-for-users =
lib.mkIf (secretsForUsers != { } && useSystemdActivation)
{
wantedBy = [ "systemd-sysusers.service" ]; wantedBy = [ "systemd-sysusers.service" ];
before = [ "systemd-sysusers.service" ]; before = [ "systemd-sysusers.service" ];
environment = cfg.environment; environment = cfg.environment;
@ -33,26 +41,34 @@ in
}; };
system.activationScripts = lib.mkIf (secretsForUsers != { } && !useSystemdActivation) { system.activationScripts = lib.mkIf (secretsForUsers != { } && !useSystemdActivation) {
setupSecretsForUsers = lib.stringAfter ([ "specialfs" ] ++ lib.optional cfg.age.generateKey "generate-age-key") '' setupSecretsForUsers =
lib.stringAfter ([ "specialfs" ] ++ lib.optional cfg.age.generateKey "generate-age-key") ''
[ -e /run/current-system ] || echo setting up secrets for users... [ -e /run/current-system ] || echo setting up secrets for users...
${withEnvironment "${cfg.package}/bin/sops-install-secrets -ignore-passwd ${manifestForUsers}"} ${withEnvironment "${cfg.package}/bin/sops-install-secrets -ignore-passwd ${manifestForUsers}"}
'' // lib.optionalAttrs (config.system ? dryActivationScript) { ''
// lib.optionalAttrs (config.system ? dryActivationScript) {
supportsDryActivation = true; supportsDryActivation = true;
}; };
users.deps = [ "setupSecretsForUsers" ]; users.deps = [ "setupSecretsForUsers" ];
}; };
assertions = [{ assertions = [
assertion = (lib.filterAttrs (_: v: (v.uid != 0 && v.owner != "root") || (v.gid != 0 && v.group != "root")) secretsForUsers) == { }; {
assertion =
(lib.filterAttrs (
_: v: (v.uid != 0 && v.owner != "root") || (v.gid != 0 && v.group != "root")
) secretsForUsers) == { };
message = "neededForUsers cannot be used for secrets that are not root-owned"; message = "neededForUsers cannot be used for secrets that are not root-owned";
} { }
{
assertion = secretsForUsers != { } && sysusersEnabled -> config.users.mutableUsers; assertion = secretsForUsers != { } && sysusersEnabled -> config.users.mutableUsers;
message = '' message = ''
systemd.sysusers.enable in combination with sops.secrets.<name>.neededForUsers can only work with config.users.mutableUsers enabled. systemd.sysusers.enable in combination with sops.secrets.<name>.neededForUsers can only work with config.users.mutableUsers enabled.
See https://github.com/Mic92/sops-nix/issues/475 See https://github.com/Mic92/sops-nix/issues/475
''; '';
}]; }
];
system.build.sops-nix-users-manifest = manifestForUsers; system.build.sops-nix-users-manifest = manifestForUsers;
} }

View file

@ -1,4 +1,10 @@
{ config, pkgs, lib, options, ... }: {
config,
pkgs,
lib,
options,
...
}:
let let
inherit (lib) inherit (lib)
mkOption mkOption
@ -8,11 +14,15 @@ let
; ;
users = config.users.users; users = config.users.users;
in { in
{
options.sops = { options.sops = {
templates = mkOption { templates = mkOption {
description = "Templates for secret files"; description = "Templates for secret files";
type = types.attrsOf (types.submodule ({ config, ... }: { type = types.attrsOf (
types.submodule (
{ config, ... }:
{
options = { options = {
name = mkOption { name = mkOption {
type = types.singleLineStr; type = types.singleLineStr;
@ -84,25 +94,30 @@ in {
''; '';
}; };
}; };
})); }
)
);
default = { }; default = { };
}; };
placeholder = mkOption { placeholder = mkOption {
type = types.attrsOf (types.mkOptionType { type = types.attrsOf (
types.mkOptionType {
name = "coercibleToString"; name = "coercibleToString";
description = "value that can be coerced to string"; description = "value that can be coerced to string";
check = lib.strings.isConvertibleWithToString; check = lib.strings.isConvertibleWithToString;
merge = lib.mergeEqualOption; merge = lib.mergeEqualOption;
}); }
);
default = { }; default = { };
visible = false; visible = false;
}; };
}; };
config = lib.optionalAttrs (options ? sops.secrets) config = lib.optionalAttrs (options ? sops.secrets) (
(lib.mkIf (config.sops.templates != { }) { lib.mkIf (config.sops.templates != { }) {
sops.placeholder = mapAttrs sops.placeholder = mapAttrs (
(name: _: mkDefault "<SOPS:${builtins.hashString "sha256" name}:PLACEHOLDER>") name: _: mkDefault "<SOPS:${builtins.hashString "sha256" name}:PLACEHOLDER>"
config.sops.secrets; ) config.sops.secrets;
}); }
);
} }

View file

@ -4,7 +4,8 @@ sopsCall:
if cfg.environment == { } then if cfg.environment == { } then
sopsCall sopsCall
else '' else
''
( (
${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: " export ${n}='${v}'") cfg.environment)} ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: " export ${n}='${v}'") cfg.environment)}
${sopsCall} ${sopsCall}

View file

@ -1,13 +1,25 @@
{ makeSetupHook, gnupg, sops, lib }: {
makeSetupHook,
gnupg,
sops,
lib,
}:
let let
# FIXME: drop after 23.05 # FIXME: drop after 23.05
propagatedBuildInputs = if (lib.versionOlder (lib.versions.majorMinor lib.version) "23.05") then "deps" else "propagatedBuildInputs"; propagatedBuildInputs =
if (lib.versionOlder (lib.versions.majorMinor lib.version) "23.05") then
"deps"
else
"propagatedBuildInputs";
in in
(makeSetupHook { (makeSetupHook {
name = "sops-import-keys-hook"; name = "sops-import-keys-hook";
substitutions = { substitutions = {
gpg = "${gnupg}/bin/gpg"; gpg = "${gnupg}/bin/gpg";
}; };
${propagatedBuildInputs} = [ sops gnupg ]; ${propagatedBuildInputs} = [
sops
gnupg
];
} ./sops-import-keys-hook.bash) } ./sops-import-keys-hook.bash)

View file

@ -1,4 +1,12 @@
{ stdenv, lib, makeWrapper, gnupg, coreutils, util-linux, unixtools }: {
stdenv,
lib,
makeWrapper,
gnupg,
coreutils,
util-linux,
unixtools,
}:
stdenv.mkDerivation { stdenv.mkDerivation {
name = "sops-init-gpg-key"; name = "sops-init-gpg-key";
@ -11,9 +19,14 @@ stdenv.mkDerivation {
installPhase = '' installPhase = ''
install -m755 -D $src $out/bin/sops-init-gpg-key install -m755 -D $src $out/bin/sops-init-gpg-key
wrapProgram $out/bin/sops-init-gpg-key \ wrapProgram $out/bin/sops-init-gpg-key \
--prefix PATH : ${lib.makeBinPath [ --prefix PATH : ${
coreutils util-linux gnupg unixtools.hostname lib.makeBinPath [
]} coreutils
util-linux
gnupg
unixtools.hostname
]
}
''; '';
doInstallCheck = true; doInstallCheck = true;

View file

@ -1,9 +1,20 @@
{ lib, buildGoModule, stdenv, vendorHash, go, callPackages }: {
lib,
buildGoModule,
stdenv,
vendorHash,
go,
callPackages,
}:
buildGoModule { buildGoModule {
pname = "sops-install-secrets"; pname = "sops-install-secrets";
version = "0.0.1"; version = "0.0.1";
src = lib.sourceByRegex ../.. [ "go\.(mod|sum)" "pkgs" "pkgs/sops-install-secrets.*" ]; src = lib.sourceByRegex ../.. [
"go\.(mod|sum)"
"pkgs"
"pkgs/sops-install-secrets.*"
];
subPackages = [ "pkgs/sops-install-secrets" ]; subPackages = [ "pkgs/sops-install-secrets" ];
@ -12,12 +23,13 @@ buildGoModule {
passthru.tests = callPackages ./nixos-test.nix { }; passthru.tests = callPackages ./nixos-test.nix { };
outputs = [ "out" ] ++ outputs = [ "out" ] ++ lib.lists.optionals (stdenv.isLinux) [ "unittest" ];
lib.lists.optionals (stdenv.isLinux) [ "unittest" ];
postInstall = '' postInstall =
''
go test -c ./pkgs/sops-install-secrets go test -c ./pkgs/sops-install-secrets
'' + lib.optionalString (stdenv.isLinux) '' ''
+ lib.optionalString (stdenv.isLinux) ''
# *.test is only tested on linux. $unittest does not exist on darwin. # *.test is only tested on linux. $unittest does not exist on darwin.
install -D ./sops-install-secrets.test $unittest/bin/sops-install-secrets.test install -D ./sops-install-secrets.test $unittest/bin/sops-install-secrets.test
# newer versions of nixpkgs no longer require this step # newer versions of nixpkgs no longer require this step

View file

@ -1,8 +1,12 @@
{ lib, testers }: { lib, testers }:
let let
userPasswordTest = name: extraConfig: testers.runNixOSTest { userPasswordTest =
name: extraConfig:
testers.runNixOSTest {
inherit name; inherit name;
nodes.machine = { config, lib, ... }: { nodes.machine =
{ config, lib, ... }:
{
imports = [ imports = [
../../modules/sops ../../modules/sops
extraConfig extraConfig
@ -28,7 +32,8 @@ let
]; ];
}; };
testScript = '' testScript =
''
start_all() start_all()
machine.wait_for_unit("multi-user.target") machine.wait_for_unit("multi-user.target")
@ -39,23 +44,29 @@ let
machine.succeed("cat /run/secrets-for-users/test_key | grep -q 'test_value'") # the user password still exists machine.succeed("cat /run/secrets-for-users/test_key | grep -q 'test_value'") # the user password still exists
# BUG in nixos's overlayfs... systemd crashes on switch-to-configuration test # BUG in nixos's overlayfs... systemd crashes on switch-to-configuration test
'' + lib.optionalString (!(extraConfig ? system.etc.overlay.enable)) '' ''
+ lib.optionalString (!(extraConfig ? system.etc.overlay.enable)) ''
machine.succeed("/run/current-system/bin/switch-to-configuration test") machine.succeed("/run/current-system/bin/switch-to-configuration test")
machine.succeed("cat /run/secrets/nested/test/file | grep -q 'another value'") # the regular secrets still work after a switch machine.succeed("cat /run/secrets/nested/test/file | grep -q 'another value'") # the regular secrets still work after a switch
machine.succeed("cat /run/secrets-for-users/test_key | grep -q 'test_value'") # the user password is still present after a switch machine.succeed("cat /run/secrets-for-users/test_key | grep -q 'test_value'") # the user password is still present after a switch
''; '';
}; };
in { in
{
ssh-keys = testers.runNixOSTest { ssh-keys = testers.runNixOSTest {
name = "sops-ssh-keys"; name = "sops-ssh-keys";
nodes.server = { ... }: { nodes.server =
{ ... }:
{
imports = [ ../../modules/sops ]; imports = [ ../../modules/sops ];
services.openssh.enable = true; services.openssh.enable = true;
services.openssh.hostKeys = [{ services.openssh.hostKeys = [
{
type = "rsa"; type = "rsa";
bits = 4096; bits = 4096;
path = ./test-assets/ssh-key; path = ./test-assets/ssh-key;
}]; }
];
sops.defaultSopsFile = ./test-assets/secrets.yaml; sops.defaultSopsFile = ./test-assets/secrets.yaml;
sops.secrets.test_key = { }; sops.secrets.test_key = { };
}; };
@ -68,7 +79,9 @@ in {
pruning = testers.runNixOSTest { pruning = testers.runNixOSTest {
name = "sops-pruning"; name = "sops-pruning";
nodes.machine = { lib, ... }: { nodes.machine =
{ lib, ... }:
{
imports = [ ../../modules/sops ]; imports = [ ../../modules/sops ];
sops = { sops = {
age.keyFile = "/run/age-keys.txt"; age.keyFile = "/run/age-keys.txt";
@ -112,7 +125,9 @@ in {
age-keys = testers.runNixOSTest { age-keys = testers.runNixOSTest {
name = "sops-age-keys"; name = "sops-age-keys";
nodes.machine = { config, ... }: { nodes.machine =
{ config, ... }:
{
imports = [ ../../modules/sops ]; imports = [ ../../modules/sops ];
sops = { sops = {
age.keyFile = "/run/age-keys.txt"; age.keyFile = "/run/age-keys.txt";
@ -183,10 +198,12 @@ in {
nodes.machine = { nodes.machine = {
imports = [ ../../modules/sops ]; imports = [ ../../modules/sops ];
services.openssh.enable = true; services.openssh.enable = true;
services.openssh.hostKeys = [{ services.openssh.hostKeys = [
{
type = "ed25519"; type = "ed25519";
path = ./test-assets/ssh-ed25519-key; path = ./test-assets/ssh-ed25519-key;
}]; }
];
sops = { sops = {
defaultSopsFile = ./test-assets/secrets.yaml; defaultSopsFile = ./test-assets/secrets.yaml;
@ -207,7 +224,9 @@ in {
pgp-keys = testers.runNixOSTest { pgp-keys = testers.runNixOSTest {
name = "sops-pgp-keys"; name = "sops-pgp-keys";
nodes.server = { lib, config, ... }: { nodes.server =
{ lib, config, ... }:
{
imports = [ ../../modules/sops ]; imports = [ ../../modules/sops ];
users.users.someuser = { users.users.someuser = {
@ -260,7 +279,9 @@ in {
templates = testers.runNixOSTest { templates = testers.runNixOSTest {
name = "sops-templates"; name = "sops-templates";
nodes.machine = { config, ... }: { nodes.machine =
{ config, ... }:
{
imports = [ ../../modules/sops ]; imports = [ ../../modules/sops ];
sops = { sops = {
age.keyFile = "/run/age-keys.txt"; age.keyFile = "/run/age-keys.txt";
@ -337,14 +358,19 @@ in {
restart-and-reload = testers.runNixOSTest { restart-and-reload = testers.runNixOSTest {
name = "sops-restart-and-reload"; name = "sops-restart-and-reload";
nodes.machine = {config, ...}: { nodes.machine =
{ config, ... }:
{
imports = [ ../../modules/sops ]; imports = [ ../../modules/sops ];
sops = { sops = {
age.keyFile = "/run/age-keys.txt"; age.keyFile = "/run/age-keys.txt";
defaultSopsFile = ./test-assets/secrets.yaml; defaultSopsFile = ./test-assets/secrets.yaml;
secrets.test_key = { secrets.test_key = {
restartUnits = [ "restart-unit.service" "reload-unit.service" ]; restartUnits = [
"restart-unit.service"
"reload-unit.service"
];
reloadUnits = [ "reload-trigger.service" ]; reloadUnits = [ "reload-trigger.service" ];
}; };
@ -353,7 +379,10 @@ in {
this is a template with this is a template with
a secret: ${config.sops.placeholder.test_key} a secret: ${config.sops.placeholder.test_key}
''; '';
restartUnits = [ "restart-unit.service" "reload-unit.service" ]; restartUnits = [
"restart-unit.service"
"reload-unit.service"
];
reloadUnits = [ "reload-trigger.service" ]; reloadUnits = [ "reload-trigger.service" ];
}; };
}; };
@ -368,7 +397,9 @@ in {
systemd.services."restart-unit" = { systemd.services."restart-unit" = {
description = "Restart unit"; description = "Restart unit";
# not started on boot # not started on boot
serviceConfig = { ExecStart = "/bin/sh -c 'echo ok > /restarted'"; }; serviceConfig = {
ExecStart = "/bin/sh -c 'echo ok > /restarted'";
};
}; };
systemd.services."reload-unit" = { systemd.services."reload-unit" = {
description = "Reload unit"; description = "Reload unit";
@ -524,8 +555,11 @@ in {
chmod -R 700 /run/age-keys.txt chmod -R 700 /run/age-keys.txt
''; '';
}; };
} // lib.optionalAttrs (lib.versionAtLeast (lib.versions.majorMinor lib.version) "24.05") { }
user-passwords-sysusers = userPasswordTest "sops-user-passwords-sysusers" ({ pkgs, ... }: { // lib.optionalAttrs (lib.versionAtLeast (lib.versions.majorMinor lib.version) "24.05") {
user-passwords-sysusers = userPasswordTest "sops-user-passwords-sysusers" (
{ pkgs, ... }:
{
systemd.sysusers.enable = true; systemd.sysusers.enable = true;
users.mutableUsers = true; users.mutableUsers = true;
system.etc.overlay.enable = true; system.etc.overlay.enable = true;
@ -537,9 +571,13 @@ in {
printf '${builtins.readFile ./test-assets/age-keys.txt}' > /run/age-keys.txt printf '${builtins.readFile ./test-assets/age-keys.txt}' > /run/age-keys.txt
chmod -R 700 /run/age-keys.txt chmod -R 700 /run/age-keys.txt
''; '';
}); }
} // lib.optionalAttrs (lib.versionAtLeast (lib.versions.majorMinor lib.version) "24.11") { );
user-passwords-userborn = userPasswordTest "sops-user-passwords-userborn" ({ pkgs, ... }: { }
// lib.optionalAttrs (lib.versionAtLeast (lib.versions.majorMinor lib.version) "24.11") {
user-passwords-userborn = userPasswordTest "sops-user-passwords-userborn" (
{ pkgs, ... }:
{
services.userborn.enable = true; services.userborn.enable = true;
users.mutableUsers = false; users.mutableUsers = false;
system.etc.overlay.enable = true; system.etc.overlay.enable = true;
@ -551,5 +589,6 @@ in {
printf '${builtins.readFile ./test-assets/age-keys.txt}' > /run/age-keys.txt printf '${builtins.readFile ./test-assets/age-keys.txt}' > /run/age-keys.txt
chmod -R 700 /run/age-keys.txt chmod -R 700 /run/age-keys.txt
''; '';
}); }
);
} }

View file

@ -1,4 +1,11 @@
{ pkgs ? import <nixpkgs> {} }: {
pkgs ? import <nixpkgs> { },
}:
pkgs.mkShell { pkgs.mkShell {
nativeBuildInputs = with pkgs; [ go delve util-linux gnupg ]; nativeBuildInputs = with pkgs; [
go
delve
util-linux
gnupg
];
} }

View file

@ -1,13 +1,25 @@
{ makeSetupHook, gnupg, sops, lib }: {
makeSetupHook,
gnupg,
sops,
lib,
}:
let let
# FIXME: drop after 23.05 # FIXME: drop after 23.05
propagatedBuildInputs = if (lib.versionOlder (lib.versions.majorMinor lib.version) "23.05") then "deps" else "propagatedBuildInputs"; propagatedBuildInputs =
if (lib.versionOlder (lib.versions.majorMinor lib.version) "23.05") then
"deps"
else
"propagatedBuildInputs";
in in
(makeSetupHook { (makeSetupHook {
name = "sops-pgp-hook"; name = "sops-pgp-hook";
substitutions = { substitutions = {
gpg = "${gnupg}/bin/gpg"; gpg = "${gnupg}/bin/gpg";
}; };
${propagatedBuildInputs} = [ sops gnupg ]; ${propagatedBuildInputs} = [
sops
gnupg
];
} ./sops-pgp-hook.bash) } ./sops-pgp-hook.bash)

View file

@ -1,16 +1,21 @@
{ pkgs ? import <nixpkgs> {} {
pkgs ? import <nixpkgs> { },
}: }:
let let
sopsPkgs = import ../. { inherit pkgs; }; sopsPkgs = import ../. { inherit pkgs; };
in pkgs.stdenv.mkDerivation { in
pkgs.stdenv.mkDerivation {
name = "env"; name = "env";
nativeBuildInputs = with pkgs; [ nativeBuildInputs =
with pkgs;
[
bashInteractive bashInteractive
gnupg gnupg
util-linux util-linux
nix nix
sopsPkgs.sops-pgp-hook-test sopsPkgs.sops-pgp-hook-test
] ++ pkgs.lib.optional (pkgs.stdenv.isLinux) sopsPkgs.sops-install-secrets.unittest; ]
++ pkgs.lib.optional (pkgs.stdenv.isLinux) sopsPkgs.sops-install-secrets.unittest;
# allow to prefetch shell dependencies in build phase # allow to prefetch shell dependencies in build phase
dontUnpack = true; dontUnpack = true;
installPhase = '' installPhase = ''

View file

@ -1,4 +1,6 @@
{ pkgs ? import <nixpkgs> {} }: {
pkgs ? import <nixpkgs> { },
}:
pkgs.mkShell { pkgs.mkShell {
nativeBuildInputs = with pkgs; [ nativeBuildInputs = with pkgs; [
bashInteractive bashInteractive