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:
parent
b05bdb2650
commit
6b85086bcc
24 changed files with 1592 additions and 1159 deletions
18
default.nix
18
default.nix
|
@ -1,12 +1,15 @@
|
|||
{ pkgs ? import <nixpkgs> {}
|
||||
, vendorHash ? "sha256-xHScXL3i2oxJSJsvOC+KqLCA5Psu3ht7DQNrh0rB1rA="
|
||||
}: let
|
||||
{
|
||||
pkgs ? import <nixpkgs> { },
|
||||
vendorHash ? "sha256-xHScXL3i2oxJSJsvOC+KqLCA5Psu3ht7DQNrh0rB1rA=",
|
||||
}:
|
||||
let
|
||||
sops-install-secrets = pkgs.callPackage ./pkgs/sops-install-secrets {
|
||||
inherit vendorHash;
|
||||
};
|
||||
in rec {
|
||||
in
|
||||
rec {
|
||||
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;
|
||||
|
||||
sops-pgp-hook = pkgs.lib.warn ''
|
||||
|
@ -22,8 +25,9 @@ in rec {
|
|||
sops-pgp-hook-test = pkgs.callPackage ./pkgs/sops-pgp-hook-test.nix {
|
||||
inherit vendorHash;
|
||||
};
|
||||
unit-tests = pkgs.callPackage ./pkgs/unit-tests.nix {};
|
||||
} // (pkgs.lib.optionalAttrs pkgs.stdenv.isLinux {
|
||||
unit-tests = pkgs.callPackage ./pkgs/unit-tests.nix { };
|
||||
}
|
||||
// (pkgs.lib.optionalAttrs pkgs.stdenv.isLinux {
|
||||
lint = pkgs.callPackage ./pkgs/lint.nix {
|
||||
inherit sops-install-secrets;
|
||||
};
|
||||
|
|
137
flake.nix
137
flake.nix
|
@ -2,60 +2,89 @@
|
|||
description = "Integrates sops into nixos";
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
inputs.nixpkgs-stable.url = "github:NixOS/nixpkgs/release-24.05";
|
||||
nixConfig.extra-substituters = ["https://cache.thalheim.io"];
|
||||
nixConfig.extra-trusted-public-keys = ["cache.thalheim.io-1:R7msbosLEZKrxk/lKxf9BTjOOH7Ax3H0Qj0/6wiHOgc="];
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
nixpkgs-stable
|
||||
}: let
|
||||
systems = [
|
||||
"x86_64-linux"
|
||||
"x86_64-darwin"
|
||||
"aarch64-darwin"
|
||||
"aarch64-linux"
|
||||
];
|
||||
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-stable = suffix-version "-24_05";
|
||||
in {
|
||||
overlays.default = final: prev: let
|
||||
localPkgs = import ./default.nix {pkgs = final;};
|
||||
in {
|
||||
inherit (localPkgs) sops-install-secrets sops-init-gpg-key sops-pgp-hook sops-import-keys-hook sops-ssh-to-age;
|
||||
# backward compatibility
|
||||
inherit (prev) ssh-to-pgp;
|
||||
};
|
||||
nixosModules = {
|
||||
sops = ./modules/sops;
|
||||
default = self.nixosModules.sops;
|
||||
};
|
||||
homeManagerModules.sops = ./modules/home-manager/sops.nix;
|
||||
homeManagerModule = self.homeManagerModules.sops;
|
||||
darwinModules = {
|
||||
sops = ./modules/nix-darwin;
|
||||
default = self.darwinModules.sops;
|
||||
};
|
||||
packages = forAllSystems (system:
|
||||
import ./default.nix {
|
||||
pkgs = import nixpkgs {inherit system;};
|
||||
});
|
||||
checks = nixpkgs.lib.genAttrs ["x86_64-linux" "aarch64-linux"]
|
||||
(system: let
|
||||
tests = self.packages.${system}.sops-install-secrets.tests;
|
||||
packages-stable = import ./default.nix {
|
||||
pkgs = import nixpkgs-stable {inherit system;};
|
||||
nixConfig.extra-substituters = [ "https://cache.thalheim.io" ];
|
||||
nixConfig.extra-trusted-public-keys = [
|
||||
"cache.thalheim.io-1:R7msbosLEZKrxk/lKxf9BTjOOH7Ax3H0Qj0/6wiHOgc="
|
||||
];
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
nixpkgs-stable,
|
||||
}:
|
||||
let
|
||||
systems = [
|
||||
"x86_64-linux"
|
||||
"x86_64-darwin"
|
||||
"aarch64-darwin"
|
||||
"aarch64-linux"
|
||||
];
|
||||
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-stable = suffix-version "-24_05";
|
||||
in
|
||||
{
|
||||
overlays.default =
|
||||
final: prev:
|
||||
let
|
||||
localPkgs = import ./default.nix { pkgs = final; };
|
||||
in
|
||||
{
|
||||
inherit (localPkgs)
|
||||
sops-install-secrets
|
||||
sops-init-gpg-key
|
||||
sops-pgp-hook
|
||||
sops-import-keys-hook
|
||||
sops-ssh-to-age
|
||||
;
|
||||
# backward compatibility
|
||||
inherit (prev) ssh-to-pgp;
|
||||
};
|
||||
tests-stable = packages-stable.sops-install-secrets.tests;
|
||||
in tests //
|
||||
(suffix-stable tests-stable) //
|
||||
(suffix-stable packages-stable));
|
||||
nixosModules = {
|
||||
sops = ./modules/sops;
|
||||
default = self.nixosModules.sops;
|
||||
};
|
||||
homeManagerModules.sops = ./modules/home-manager/sops.nix;
|
||||
homeManagerModule = self.homeManagerModules.sops;
|
||||
darwinModules = {
|
||||
sops = ./modules/nix-darwin;
|
||||
default = self.darwinModules.sops;
|
||||
};
|
||||
packages = forAllSystems (
|
||||
system:
|
||||
import ./default.nix {
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
}
|
||||
);
|
||||
checks =
|
||||
nixpkgs.lib.genAttrs
|
||||
[
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
]
|
||||
(
|
||||
system:
|
||||
let
|
||||
tests = self.packages.${system}.sops-install-secrets.tests;
|
||||
packages-stable = import ./default.nix {
|
||||
pkgs = import nixpkgs-stable { inherit system; };
|
||||
};
|
||||
tests-stable = packages-stable.sops-install-secrets.tests;
|
||||
in
|
||||
tests // (suffix-stable tests-stable) // (suffix-stable packages-stable)
|
||||
);
|
||||
|
||||
devShells = forAllSystems (system: let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in {
|
||||
unit-tests = pkgs.callPackage ./pkgs/unit-tests.nix {};
|
||||
default = pkgs.callPackage ./shell.nix {};
|
||||
});
|
||||
};
|
||||
devShells = forAllSystems (
|
||||
system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in
|
||||
{
|
||||
unit-tests = pkgs.callPackage ./pkgs/unit-tests.nix { };
|
||||
default = pkgs.callPackage ./shell.nix { };
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,67 +1,81 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.sops;
|
||||
sops-install-secrets = (pkgs.callPackage ../.. {}).sops-install-secrets;
|
||||
secretType = lib.types.submodule ({ name, ... }: {
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = name;
|
||||
description = ''
|
||||
Name of the file used in /run/user/*/secrets
|
||||
'';
|
||||
};
|
||||
sops-install-secrets = (pkgs.callPackage ../.. { }).sops-install-secrets;
|
||||
secretType = lib.types.submodule (
|
||||
{ name, ... }:
|
||||
{
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = name;
|
||||
description = ''
|
||||
Name of the file used in /run/user/*/secrets
|
||||
'';
|
||||
};
|
||||
|
||||
key = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = if cfg.defaultSopsKey != null then cfg.defaultSopsKey else name;
|
||||
description = ''
|
||||
Key used to lookup in the sops file.
|
||||
No tested data structures are supported right now.
|
||||
This option is ignored if format is binary.
|
||||
"" means whole file.
|
||||
'';
|
||||
};
|
||||
key = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = if cfg.defaultSopsKey != null then cfg.defaultSopsKey else name;
|
||||
description = ''
|
||||
Key used to lookup in the sops file.
|
||||
No tested data structures are supported right now.
|
||||
This option is ignored if format is binary.
|
||||
"" means whole file.
|
||||
'';
|
||||
};
|
||||
|
||||
path = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "${cfg.defaultSymlinkPath}/${name}";
|
||||
description = ''
|
||||
Path where secrets are symlinked to.
|
||||
If the default is kept no other symlink is created.
|
||||
`%r` is replaced by $XDG_RUNTIME_DIR on linux or `getconf
|
||||
DARWIN_USER_TEMP_DIR` on darwin.
|
||||
'';
|
||||
};
|
||||
path = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "${cfg.defaultSymlinkPath}/${name}";
|
||||
description = ''
|
||||
Path where secrets are symlinked to.
|
||||
If the default is kept no other symlink is created.
|
||||
`%r` is replaced by $XDG_RUNTIME_DIR on linux or `getconf
|
||||
DARWIN_USER_TEMP_DIR` on darwin.
|
||||
'';
|
||||
};
|
||||
|
||||
format = lib.mkOption {
|
||||
type = lib.types.enum [ "yaml" "json" "binary" "ini" "dotenv" ];
|
||||
default = cfg.defaultSopsFormat;
|
||||
description = ''
|
||||
File format used to decrypt the sops secret.
|
||||
Binary files are written to the target file as is.
|
||||
'';
|
||||
};
|
||||
format = lib.mkOption {
|
||||
type = lib.types.enum [
|
||||
"yaml"
|
||||
"json"
|
||||
"binary"
|
||||
"ini"
|
||||
"dotenv"
|
||||
];
|
||||
default = cfg.defaultSopsFormat;
|
||||
description = ''
|
||||
File format used to decrypt the sops secret.
|
||||
Binary files are written to the target file as is.
|
||||
'';
|
||||
};
|
||||
|
||||
mode = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "0400";
|
||||
description = ''
|
||||
Permissions mode of the in octal.
|
||||
'';
|
||||
};
|
||||
mode = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "0400";
|
||||
description = ''
|
||||
Permissions mode of the in octal.
|
||||
'';
|
||||
};
|
||||
|
||||
sopsFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = cfg.defaultSopsFile;
|
||||
defaultText = lib.literalExpression "\${config.sops.defaultSopsFile}";
|
||||
description = ''
|
||||
Sops file the secret is loaded from.
|
||||
'';
|
||||
sopsFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = cfg.defaultSopsFile;
|
||||
defaultText = lib.literalExpression "\${config.sops.defaultSopsFile}";
|
||||
description = ''
|
||||
Sops file the secret is loaded from.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
pathNotInStore = lib.mkOptionType {
|
||||
name = "pathNotInStore";
|
||||
|
@ -71,44 +85,54 @@ let
|
|||
merge = lib.mergeEqualOption;
|
||||
};
|
||||
|
||||
manifestFor = suffix: secrets: templates: pkgs.writeTextFile {
|
||||
name = "manifest${suffix}.json";
|
||||
text = builtins.toJSON {
|
||||
secrets = builtins.attrValues secrets;
|
||||
templates = builtins.attrValues templates;
|
||||
secretsMountPoint = cfg.defaultSecretsMountPoint;
|
||||
symlinkPath = cfg.defaultSymlinkPath;
|
||||
keepGenerations = cfg.keepGenerations;
|
||||
gnupgHome = cfg.gnupg.home;
|
||||
sshKeyPaths = cfg.gnupg.sshKeyPaths;
|
||||
ageKeyFile = cfg.age.keyFile;
|
||||
ageSshKeyPaths = cfg.age.sshKeyPaths;
|
||||
userMode = true;
|
||||
logging = {
|
||||
keyImport = builtins.elem "keyImport" cfg.log;
|
||||
secretChanges = builtins.elem "secretChanges" cfg.log;
|
||||
manifestFor =
|
||||
suffix: secrets: templates:
|
||||
pkgs.writeTextFile {
|
||||
name = "manifest${suffix}.json";
|
||||
text = builtins.toJSON {
|
||||
secrets = builtins.attrValues secrets;
|
||||
templates = builtins.attrValues templates;
|
||||
secretsMountPoint = cfg.defaultSecretsMountPoint;
|
||||
symlinkPath = cfg.defaultSymlinkPath;
|
||||
keepGenerations = cfg.keepGenerations;
|
||||
gnupgHome = cfg.gnupg.home;
|
||||
sshKeyPaths = cfg.gnupg.sshKeyPaths;
|
||||
ageKeyFile = cfg.age.keyFile;
|
||||
ageSshKeyPaths = cfg.age.sshKeyPaths;
|
||||
userMode = true;
|
||||
logging = {
|
||||
keyImport = builtins.elem "keyImport" cfg.log;
|
||||
secretChanges = builtins.elem "secretChanges" cfg.log;
|
||||
};
|
||||
};
|
||||
checkPhase = ''
|
||||
${sops-install-secrets}/bin/sops-install-secrets -check-mode=${
|
||||
if cfg.validateSopsFiles then "sopsfile" else "manifest"
|
||||
} "$out"
|
||||
'';
|
||||
};
|
||||
checkPhase = ''
|
||||
${sops-install-secrets}/bin/sops-install-secrets -check-mode=${if cfg.validateSopsFiles then "sopsfile" else "manifest"} "$out"
|
||||
'';
|
||||
};
|
||||
|
||||
manifest = manifestFor "" cfg.secrets cfg.templates;
|
||||
|
||||
escapedAgeKeyFile = lib.escapeShellArg cfg.age.keyFile;
|
||||
|
||||
script = toString (pkgs.writeShellScript "sops-nix-user" (lib.optionalString cfg.age.generateKey ''
|
||||
if [[ ! -f ${escapedAgeKeyFile} ]]; then
|
||||
echo generating machine-specific age key...
|
||||
${pkgs.coreutils}/bin/mkdir -p $(${pkgs.coreutils}/bin/dirname ${escapedAgeKeyFile})
|
||||
# age-keygen sets 0600 by default, no need to chmod.
|
||||
${pkgs.age}/bin/age-keygen -o ${escapedAgeKeyFile}
|
||||
fi
|
||||
'' + ''
|
||||
${sops-install-secrets}/bin/sops-install-secrets -ignore-passwd ${manifest}
|
||||
''));
|
||||
in {
|
||||
script = toString (
|
||||
pkgs.writeShellScript "sops-nix-user" (
|
||||
lib.optionalString cfg.age.generateKey ''
|
||||
if [[ ! -f ${escapedAgeKeyFile} ]]; then
|
||||
echo generating machine-specific age key...
|
||||
${pkgs.coreutils}/bin/mkdir -p $(${pkgs.coreutils}/bin/dirname ${escapedAgeKeyFile})
|
||||
# age-keygen sets 0600 by default, no need to chmod.
|
||||
${pkgs.age}/bin/age-keygen -o ${escapedAgeKeyFile}
|
||||
fi
|
||||
''
|
||||
+ ''
|
||||
${sops-install-secrets}/bin/sops-install-secrets -ignore-passwd ${manifest}
|
||||
''
|
||||
)
|
||||
);
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./templates.nix
|
||||
];
|
||||
|
@ -116,7 +140,7 @@ in {
|
|||
options.sops = {
|
||||
secrets = lib.mkOption {
|
||||
type = lib.types.attrsOf secretType;
|
||||
default = {};
|
||||
default = { };
|
||||
description = ''
|
||||
Secrets to decrypt.
|
||||
'';
|
||||
|
@ -182,14 +206,22 @@ in {
|
|||
};
|
||||
|
||||
log = lib.mkOption {
|
||||
type = lib.types.listOf (lib.types.enum [ "keyImport" "secretChanges" ]);
|
||||
default = [ "keyImport" "secretChanges" ];
|
||||
type = lib.types.listOf (
|
||||
lib.types.enum [
|
||||
"keyImport"
|
||||
"secretChanges"
|
||||
]
|
||||
);
|
||||
default = [
|
||||
"keyImport"
|
||||
"secretChanges"
|
||||
];
|
||||
description = "What to log";
|
||||
};
|
||||
|
||||
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.
|
||||
|
||||
|
@ -219,7 +251,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.
|
||||
'';
|
||||
|
@ -251,7 +283,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.
|
||||
|
@ -260,37 +292,52 @@ in {
|
|||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (cfg.secrets != {}) {
|
||||
assertions = [{
|
||||
assertion =
|
||||
cfg.gnupg.home != null ||
|
||||
cfg.gnupg.sshKeyPaths != [] ||
|
||||
cfg.gnupg.qubes-split-gpg.enable == true ||
|
||||
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 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) &&
|
||||
!(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 == 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";
|
||||
}] ++ lib.optionals cfg.validateSopsFiles (
|
||||
lib.concatLists (lib.mapAttrsToList (name: secret: [{
|
||||
assertion = builtins.pathExists secret.sopsFile;
|
||||
message = "Cannot find path '${secret.sopsFile}' set in sops.secrets.${lib.strings.escapeNixIdentifier name}.sopsFile";
|
||||
} {
|
||||
assertion =
|
||||
builtins.isPath 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";
|
||||
}]) cfg.secrets)
|
||||
);
|
||||
config = lib.mkIf (cfg.secrets != { }) {
|
||||
assertions =
|
||||
[
|
||||
{
|
||||
assertion =
|
||||
cfg.gnupg.home != null
|
||||
|| cfg.gnupg.sshKeyPaths != [ ]
|
||||
|| cfg.gnupg.qubes-split-gpg.enable == true
|
||||
|| 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 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)
|
||||
&& !(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 == 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";
|
||||
}
|
||||
]
|
||||
++ lib.optionals cfg.validateSopsFiles (
|
||||
lib.concatLists (
|
||||
lib.mapAttrsToList (name: secret: [
|
||||
{
|
||||
assertion = builtins.pathExists secret.sopsFile;
|
||||
message = "Cannot find path '${secret.sopsFile}' set in sops.secrets.${lib.strings.escapeNixIdentifier name}.sopsFile";
|
||||
}
|
||||
{
|
||||
assertion =
|
||||
builtins.isPath 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";
|
||||
}
|
||||
]) cfg.secrets
|
||||
)
|
||||
);
|
||||
|
||||
home.sessionVariables = lib.mkIf cfg.gnupg.qubes-split-gpg.enable {
|
||||
# TODO: Add this package to nixpkgs and use it from the store
|
||||
|
@ -300,11 +347,17 @@ in {
|
|||
|
||||
sops.environment = {
|
||||
SOPS_GPG_EXEC = lib.mkMerge [
|
||||
(lib.mkIf (cfg.gnupg.home != null || cfg.gnupg.sshKeyPaths != []) (lib.mkDefault "${pkgs.gnupg}/bin/gpg"))
|
||||
(lib.mkIf cfg.gnupg.qubes-split-gpg.enable (lib.mkDefault config.home.sessionVariables.SOPS_GPG_EXEC))
|
||||
(lib.mkIf (cfg.gnupg.home != null || cfg.gnupg.sshKeyPaths != [ ]) (
|
||||
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 {
|
||||
|
@ -313,10 +366,13 @@ in {
|
|||
};
|
||||
Service = {
|
||||
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;
|
||||
};
|
||||
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
|
||||
|
@ -333,28 +389,36 @@ 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 {
|
||||
sops-nix = if pkgs.stdenv.isLinux then linux else darwin;
|
||||
};
|
||||
in
|
||||
{
|
||||
sops-nix = if pkgs.stdenv.isLinux then linux else darwin;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,91 +1,106 @@
|
|||
{ config, pkgs, lib, options, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
options,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib)
|
||||
mkOption
|
||||
mkDefault
|
||||
mapAttrs
|
||||
types
|
||||
;
|
||||
in {
|
||||
;
|
||||
in
|
||||
{
|
||||
options.sops = {
|
||||
templates = mkOption {
|
||||
description = "Templates for secret files";
|
||||
type = types.attrsOf (types.submodule ({ config, ... }: {
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = config._module.args.name;
|
||||
description = ''
|
||||
Name of the file used in /run/secrets/rendered
|
||||
'';
|
||||
};
|
||||
path = mkOption {
|
||||
description = "Path where the rendered file will be placed";
|
||||
type = types.singleLineStr;
|
||||
# Keep this in sync with `RenderedSubdir` in `pkgs/sops-install-secrets/main.go`
|
||||
default = "${config.xdg.configHome}/sops-nix/secrets/rendered/${config.name}";
|
||||
};
|
||||
content = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Content of the file
|
||||
'';
|
||||
};
|
||||
mode = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = "0400";
|
||||
description = ''
|
||||
Permissions mode of the rendered secret file in octal.
|
||||
'';
|
||||
};
|
||||
file = mkOption {
|
||||
type = types.path;
|
||||
default = pkgs.writeText config.name config.content;
|
||||
defaultText = lib.literalExpression ''pkgs.writeText config.name config.content'';
|
||||
example = "./configuration-template.conf";
|
||||
description = ''
|
||||
File used as the template. When this value is specified, `sops.templates.<name>.content` is ignored.
|
||||
'';
|
||||
};
|
||||
restartUnits = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
example = [ "sshd.service" ];
|
||||
description = ''
|
||||
Names of units that should be restarted when the rendered template changes.
|
||||
This works the same way as <xref linkend="opt-systemd.services._name_.restartTriggers" />.
|
||||
'';
|
||||
};
|
||||
reloadUnits = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
example = [ "sshd.service" ];
|
||||
description = ''
|
||||
Names of units that should be reloaded when the rendered template changes.
|
||||
This works the same way as <xref linkend="opt-systemd.services._name_.reloadTriggers" />.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}));
|
||||
type = types.attrsOf (
|
||||
types.submodule (
|
||||
{ config, ... }:
|
||||
{
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = config._module.args.name;
|
||||
description = ''
|
||||
Name of the file used in /run/secrets/rendered
|
||||
'';
|
||||
};
|
||||
path = mkOption {
|
||||
description = "Path where the rendered file will be placed";
|
||||
type = types.singleLineStr;
|
||||
# Keep this in sync with `RenderedSubdir` in `pkgs/sops-install-secrets/main.go`
|
||||
default = "${config.xdg.configHome}/sops-nix/secrets/rendered/${config.name}";
|
||||
};
|
||||
content = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Content of the file
|
||||
'';
|
||||
};
|
||||
mode = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = "0400";
|
||||
description = ''
|
||||
Permissions mode of the rendered secret file in octal.
|
||||
'';
|
||||
};
|
||||
file = mkOption {
|
||||
type = types.path;
|
||||
default = pkgs.writeText config.name config.content;
|
||||
defaultText = lib.literalExpression ''pkgs.writeText config.name config.content'';
|
||||
example = "./configuration-template.conf";
|
||||
description = ''
|
||||
File used as the template. When this value is specified, `sops.templates.<name>.content` is ignored.
|
||||
'';
|
||||
};
|
||||
restartUnits = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
example = [ "sshd.service" ];
|
||||
description = ''
|
||||
Names of units that should be restarted when the rendered template changes.
|
||||
This works the same way as <xref linkend="opt-systemd.services._name_.restartTriggers" />.
|
||||
'';
|
||||
};
|
||||
reloadUnits = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
example = [ "sshd.service" ];
|
||||
description = ''
|
||||
Names of units that should be reloaded when the rendered template changes.
|
||||
This works the same way as <xref linkend="opt-systemd.services._name_.reloadTriggers" />.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
default = { };
|
||||
};
|
||||
placeholder = mkOption {
|
||||
type = types.attrsOf (types.mkOptionType {
|
||||
name = "coercibleToString";
|
||||
description = "value that can be coerced to string";
|
||||
check = lib.strings.isConvertibleWithToString;
|
||||
merge = lib.mergeEqualOption;
|
||||
});
|
||||
type = types.attrsOf (
|
||||
types.mkOptionType {
|
||||
name = "coercibleToString";
|
||||
description = "value that can be coerced to string";
|
||||
check = lib.strings.isConvertibleWithToString;
|
||||
merge = lib.mergeEqualOption;
|
||||
}
|
||||
);
|
||||
default = { };
|
||||
visible = false;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.optionalAttrs (options ? sops.secrets)
|
||||
(lib.mkIf (config.sops.templates != { }) {
|
||||
sops.placeholder = mapAttrs
|
||||
(name: _: mkDefault "<SOPS:${builtins.hashString "sha256" name}:PLACEHOLDER>")
|
||||
config.sops.secrets;
|
||||
});
|
||||
config = lib.optionalAttrs (options ? sops.secrets) (
|
||||
lib.mkIf (config.sops.templates != { }) {
|
||||
sops.placeholder = mapAttrs (
|
||||
name: _: mkDefault "<SOPS:${builtins.hashString "sha256" name}:PLACEHOLDER>"
|
||||
) config.sops.secrets;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.sops;
|
||||
|
@ -7,7 +12,7 @@ let
|
|||
inherit cfg;
|
||||
inherit (pkgs) writeTextFile;
|
||||
};
|
||||
manifest = manifestFor "" regularSecrets regularTemplates {};
|
||||
manifest = manifestFor "" regularSecrets regularTemplates { };
|
||||
|
||||
# Currently, all templates are "regular" (there's no support for `neededForUsers` for templates.)
|
||||
regularTemplates = cfg.templates;
|
||||
|
@ -25,138 +30,165 @@ let
|
|||
withEnvironment = import ./with-environment.nix {
|
||||
inherit cfg lib;
|
||||
};
|
||||
secretType = lib.types.submodule ({ config, ... }: {
|
||||
config = {
|
||||
sopsFile = lib.mkOptionDefault cfg.defaultSopsFile;
|
||||
sopsFileHash = lib.mkOptionDefault (lib.optionalString cfg.validateSopsFiles "${builtins.hashFile "sha256" config.sopsFile}");
|
||||
};
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = config._module.args.name;
|
||||
description = ''
|
||||
Name of the file used in /run/secrets
|
||||
'';
|
||||
secretType = lib.types.submodule (
|
||||
{ config, ... }:
|
||||
{
|
||||
config = {
|
||||
sopsFile = lib.mkOptionDefault cfg.defaultSopsFile;
|
||||
sopsFileHash = lib.mkOptionDefault (
|
||||
lib.optionalString cfg.validateSopsFiles "${builtins.hashFile "sha256" config.sopsFile}"
|
||||
);
|
||||
};
|
||||
key = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = config._module.args.name;
|
||||
description = ''
|
||||
Key used to lookup in the sops file.
|
||||
No tested data structures are supported right now.
|
||||
This option is ignored if format is binary.
|
||||
'';
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = config._module.args.name;
|
||||
description = ''
|
||||
Name of the file used in /run/secrets
|
||||
'';
|
||||
};
|
||||
key = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = config._module.args.name;
|
||||
description = ''
|
||||
Key used to lookup in the sops file.
|
||||
No tested data structures are supported right now.
|
||||
This option is ignored if format is binary.
|
||||
'';
|
||||
};
|
||||
path = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
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.";
|
||||
description = ''
|
||||
Path where secrets are symlinked to.
|
||||
If the default is kept no symlink is created.
|
||||
'';
|
||||
};
|
||||
format = lib.mkOption {
|
||||
type = lib.types.enum [
|
||||
"yaml"
|
||||
"json"
|
||||
"binary"
|
||||
"dotenv"
|
||||
"ini"
|
||||
];
|
||||
default = cfg.defaultSopsFormat;
|
||||
description = ''
|
||||
File format used to decrypt the sops secret.
|
||||
Binary files are written to the target file as is.
|
||||
'';
|
||||
};
|
||||
mode = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "0400";
|
||||
description = ''
|
||||
Permissions mode of the in octal.
|
||||
'';
|
||||
};
|
||||
owner = lib.mkOption {
|
||||
type = with lib.types; nullOr str;
|
||||
default = "root";
|
||||
description = ''
|
||||
User of the file. Can only be set if uid is 0.
|
||||
'';
|
||||
};
|
||||
uid = lib.mkOption {
|
||||
type = with lib.types; nullOr int;
|
||||
default = 0;
|
||||
description = ''
|
||||
UID of the file, only applied when owner is null. The UID will be applied even if the corresponding user doesn't exist.
|
||||
'';
|
||||
};
|
||||
group = lib.mkOption {
|
||||
type = with lib.types; nullOr str;
|
||||
default = "staff";
|
||||
defaultText = "staff";
|
||||
description = ''
|
||||
Group of the file. Can only be set if gid is 0.
|
||||
'';
|
||||
};
|
||||
gid = lib.mkOption {
|
||||
type = with lib.types; nullOr int;
|
||||
default = 0;
|
||||
description = ''
|
||||
GID of the file, only applied when group is null. The GID will be applied even if the corresponding group doesn't exist.
|
||||
'';
|
||||
};
|
||||
sopsFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
defaultText = lib.literalExpression "\${config.sops.defaultSopsFile}";
|
||||
description = ''
|
||||
Sops file the secret is loaded from.
|
||||
'';
|
||||
};
|
||||
sopsFileHash = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
readOnly = true;
|
||||
description = ''
|
||||
Hash of the sops file.
|
||||
'';
|
||||
};
|
||||
neededForUsers = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
**Warning** This option doesn't have any effect on macOS, as nix-darwin cannot manage user passwords on macOS.
|
||||
This can be used to retrieve user's passwords from sops-nix.
|
||||
Setting this option moves the secret to /run/secrets-for-users and disallows setting owner and group to anything else than root.
|
||||
'';
|
||||
};
|
||||
};
|
||||
path = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
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.";
|
||||
description = ''
|
||||
Path where secrets are symlinked to.
|
||||
If the default is kept no symlink is created.
|
||||
'';
|
||||
};
|
||||
format = lib.mkOption {
|
||||
type = lib.types.enum ["yaml" "json" "binary" "dotenv" "ini"];
|
||||
default = cfg.defaultSopsFormat;
|
||||
description = ''
|
||||
File format used to decrypt the sops secret.
|
||||
Binary files are written to the target file as is.
|
||||
'';
|
||||
};
|
||||
mode = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "0400";
|
||||
description = ''
|
||||
Permissions mode of the in octal.
|
||||
'';
|
||||
};
|
||||
owner = lib.mkOption {
|
||||
type = with lib.types; nullOr str;
|
||||
default = "root";
|
||||
description = ''
|
||||
User of the file. Can only be set if uid is 0.
|
||||
'';
|
||||
};
|
||||
uid = lib.mkOption {
|
||||
type = with lib.types; nullOr int;
|
||||
default = 0;
|
||||
description = ''
|
||||
UID of the file, only applied when owner is null. The UID will be applied even if the corresponding user doesn't exist.
|
||||
'';
|
||||
};
|
||||
group = lib.mkOption {
|
||||
type = with lib.types; nullOr str;
|
||||
default = "staff";
|
||||
defaultText = "staff";
|
||||
description = ''
|
||||
Group of the file. Can only be set if gid is 0.
|
||||
'';
|
||||
};
|
||||
gid = lib.mkOption {
|
||||
type = with lib.types; nullOr int;
|
||||
default = 0;
|
||||
description = ''
|
||||
GID of the file, only applied when group is null. The GID will be applied even if the corresponding group doesn't exist.
|
||||
'';
|
||||
};
|
||||
sopsFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
defaultText = lib.literalExpression "\${config.sops.defaultSopsFile}";
|
||||
description = ''
|
||||
Sops file the secret is loaded from.
|
||||
'';
|
||||
};
|
||||
sopsFileHash = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
readOnly = true;
|
||||
description = ''
|
||||
Hash of the sops file.
|
||||
'';
|
||||
};
|
||||
neededForUsers = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
**Warning** This option doesn't have any effect on macOS, as nix-darwin cannot manage user passwords on macOS.
|
||||
This can be used to retrieve user's passwords from sops-nix.
|
||||
Setting this option moves the secret to /run/secrets-for-users and disallows setting owner and group to anything else than root.
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
darwinSSHKeys = [{
|
||||
type = "rsa";
|
||||
path = "/etc/ssh/ssh_host_rsa_key";
|
||||
} {
|
||||
type = "ed25519";
|
||||
path = "/etc/ssh/ssh_host_ed25519_key";
|
||||
}];
|
||||
darwinSSHKeys = [
|
||||
{
|
||||
type = "rsa";
|
||||
path = "/etc/ssh/ssh_host_rsa_key";
|
||||
}
|
||||
{
|
||||
type = "ed25519";
|
||||
path = "/etc/ssh/ssh_host_ed25519_key";
|
||||
}
|
||||
];
|
||||
|
||||
escapedKeyFile = lib.escapeShellArg cfg.age.keyFile;
|
||||
# Skip ssh keys deployed with sops to avoid a catch 22
|
||||
defaultImportKeys = algo:
|
||||
map (e: e.path) (lib.filter (e: e.type == algo && !(lib.hasPrefix "/run/secrets" e.path)) darwinSSHKeys);
|
||||
defaultImportKeys =
|
||||
algo:
|
||||
map (e: e.path) (
|
||||
lib.filter (e: e.type == algo && !(lib.hasPrefix "/run/secrets" e.path)) darwinSSHKeys
|
||||
);
|
||||
|
||||
installScript = ''
|
||||
${if cfg.age.generateKey then ''
|
||||
if [[ ! -f ${escapedKeyFile} ]]; then
|
||||
echo generating machine-specific age key...
|
||||
mkdir -p $(dirname ${escapedKeyFile})
|
||||
# age-keygen sets 0600 by default, no need to chmod.
|
||||
${pkgs.age}/bin/age-keygen -o ${escapedKeyFile}
|
||||
fi
|
||||
'' else ""}
|
||||
${
|
||||
if cfg.age.generateKey then
|
||||
''
|
||||
if [[ ! -f ${escapedKeyFile} ]]; then
|
||||
echo generating machine-specific age key...
|
||||
mkdir -p $(dirname ${escapedKeyFile})
|
||||
# age-keygen sets 0600 by default, no need to chmod.
|
||||
${pkgs.age}/bin/age-keygen -o ${escapedKeyFile}
|
||||
fi
|
||||
''
|
||||
else
|
||||
""
|
||||
}
|
||||
echo "Setting up secrets..."
|
||||
${withEnvironment "${sops-install-secrets}/bin/sops-install-secrets ${manifest}"}
|
||||
'';
|
||||
|
||||
in {
|
||||
in
|
||||
{
|
||||
options.sops = {
|
||||
secrets = lib.mkOption {
|
||||
type = lib.types.attrsOf secretType;
|
||||
default = {};
|
||||
default = { };
|
||||
description = ''
|
||||
Path where the latest secrets are mounted to.
|
||||
'';
|
||||
|
@ -195,14 +227,22 @@ in {
|
|||
};
|
||||
|
||||
log = lib.mkOption {
|
||||
type = lib.types.listOf (lib.types.enum [ "keyImport" "secretChanges" ]);
|
||||
default = [ "keyImport" "secretChanges" ];
|
||||
type = lib.types.listOf (
|
||||
lib.types.enum [
|
||||
"keyImport"
|
||||
"secretChanges"
|
||||
]
|
||||
);
|
||||
default = [
|
||||
"keyImport"
|
||||
"secretChanges"
|
||||
];
|
||||
description = "What to log";
|
||||
};
|
||||
|
||||
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.
|
||||
|
||||
|
@ -217,7 +257,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.
|
||||
|
@ -227,9 +267,10 @@ in {
|
|||
validationPackage = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
default =
|
||||
if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform
|
||||
then sops-install-secrets
|
||||
else (pkgs.pkgsBuildHost.callPackage ../.. {}).sops-install-secrets;
|
||||
if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform then
|
||||
sops-install-secrets
|
||||
else
|
||||
(pkgs.pkgsBuildHost.callPackage ../.. { }).sops-install-secrets;
|
||||
defaultText = lib.literalExpression "config.sops.package";
|
||||
|
||||
description = ''
|
||||
|
@ -296,30 +337,46 @@ in {
|
|||
];
|
||||
|
||||
config = lib.mkMerge [
|
||||
(lib.mkIf (cfg.secrets != {}) {
|
||||
assertions = [{
|
||||
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";
|
||||
} {
|
||||
assertion = !(cfg.gnupg.home != null && cfg.gnupg.sshKeyPaths != []);
|
||||
message = "Exactly one of sops.gnupg.home and sops.gnupg.sshKeyPaths must be set";
|
||||
}] ++ lib.optionals cfg.validateSopsFiles (
|
||||
lib.concatLists (lib.mapAttrsToList (name: secret: [{
|
||||
assertion = builtins.pathExists secret.sopsFile;
|
||||
message = "Cannot find path '${secret.sopsFile}' set in sops.secrets.${lib.strings.escapeNixIdentifier name}.sopsFile";
|
||||
} {
|
||||
assertion =
|
||||
builtins.isPath 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";
|
||||
} {
|
||||
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";
|
||||
} {
|
||||
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";
|
||||
}]) cfg.secrets)
|
||||
);
|
||||
(lib.mkIf (cfg.secrets != { }) {
|
||||
assertions =
|
||||
[
|
||||
{
|
||||
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";
|
||||
}
|
||||
{
|
||||
assertion = !(cfg.gnupg.home != null && cfg.gnupg.sshKeyPaths != [ ]);
|
||||
message = "Exactly one of sops.gnupg.home and sops.gnupg.sshKeyPaths must be set";
|
||||
}
|
||||
]
|
||||
++ lib.optionals cfg.validateSopsFiles (
|
||||
lib.concatLists (
|
||||
lib.mapAttrsToList (name: secret: [
|
||||
{
|
||||
assertion = builtins.pathExists secret.sopsFile;
|
||||
message = "Cannot find path '${secret.sopsFile}' set in sops.secrets.${lib.strings.escapeNixIdentifier name}.sopsFile";
|
||||
}
|
||||
{
|
||||
assertion =
|
||||
builtins.isPath 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";
|
||||
}
|
||||
{
|
||||
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";
|
||||
}
|
||||
{
|
||||
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";
|
||||
}
|
||||
]) cfg.secrets
|
||||
)
|
||||
);
|
||||
|
||||
system.build.sops-nix-manifest = manifest;
|
||||
system.activationScripts = {
|
||||
|
@ -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"
|
||||
);
|
||||
}
|
||||
];
|
||||
}
|
||||
|
|
|
@ -4,26 +4,31 @@ suffix: secrets: extraJson:
|
|||
|
||||
writeTextFile {
|
||||
name = "manifest${suffix}.json";
|
||||
text = builtins.toJSON ({
|
||||
secrets = builtins.attrValues secrets;
|
||||
# Does this need to be configurable?
|
||||
secretsMountPoint = "/run/secrets.d";
|
||||
symlinkPath = "/run/secrets";
|
||||
keepGenerations = cfg.keepGenerations;
|
||||
gnupgHome = cfg.gnupg.home;
|
||||
sshKeyPaths = cfg.gnupg.sshKeyPaths;
|
||||
ageKeyFile = cfg.age.keyFile;
|
||||
ageSshKeyPaths = cfg.age.sshKeyPaths;
|
||||
useTmpfs = false;
|
||||
templates = cfg.templates;
|
||||
placeholderBySecretName = cfg.placeholder;
|
||||
userMode = false;
|
||||
logging = {
|
||||
keyImport = builtins.elem "keyImport" cfg.log;
|
||||
secretChanges = builtins.elem "secretChanges" cfg.log;
|
||||
};
|
||||
} // extraJson);
|
||||
text = builtins.toJSON (
|
||||
{
|
||||
secrets = builtins.attrValues secrets;
|
||||
# Does this need to be configurable?
|
||||
secretsMountPoint = "/run/secrets.d";
|
||||
symlinkPath = "/run/secrets";
|
||||
keepGenerations = cfg.keepGenerations;
|
||||
gnupgHome = cfg.gnupg.home;
|
||||
sshKeyPaths = cfg.gnupg.sshKeyPaths;
|
||||
ageKeyFile = cfg.age.keyFile;
|
||||
ageSshKeyPaths = cfg.age.sshKeyPaths;
|
||||
useTmpfs = false;
|
||||
templates = cfg.templates;
|
||||
placeholderBySecretName = cfg.placeholder;
|
||||
userMode = false;
|
||||
logging = {
|
||||
keyImport = builtins.elem "keyImport" cfg.log;
|
||||
secretChanges = builtins.elem "secretChanges" cfg.log;
|
||||
};
|
||||
}
|
||||
// extraJson
|
||||
);
|
||||
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"
|
||||
'';
|
||||
}
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
{ lib, options, config, pkgs, ... }:
|
||||
{
|
||||
lib,
|
||||
options,
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.sops;
|
||||
secretsForUsers = lib.filterAttrs (_: v: v.neededForUsers) cfg.secrets;
|
||||
templatesForUsers = {}; # We do not currently support `neededForUsers` for templates.
|
||||
templatesForUsers = { }; # We do not currently support `neededForUsers` for templates.
|
||||
manifestFor = pkgs.callPackage ../manifest-for.nix {
|
||||
inherit cfg;
|
||||
inherit (pkgs) writeTextFile;
|
||||
|
@ -22,16 +28,21 @@ let
|
|||
in
|
||||
{
|
||||
|
||||
assertions = [{
|
||||
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";
|
||||
}];
|
||||
assertions = [
|
||||
{
|
||||
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";
|
||||
}
|
||||
];
|
||||
|
||||
system.activationScripts = lib.mkIf (secretsForUsers != []) {
|
||||
system.activationScripts = lib.mkIf (secretsForUsers != [ ]) {
|
||||
postActivation.text = lib.mkAfter installScript;
|
||||
};
|
||||
|
||||
launchd.daemons.sops-install-secrets-for-users = lib.mkIf (secretsForUsers != []) {
|
||||
launchd.daemons.sops-install-secrets-for-users = lib.mkIf (secretsForUsers != [ ]) {
|
||||
command = installScript;
|
||||
serviceConfig = {
|
||||
RunAtLoad = true;
|
||||
|
|
|
@ -1,87 +1,102 @@
|
|||
{ config, pkgs, lib, options, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
options,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib)
|
||||
mkOption
|
||||
mkDefault
|
||||
mapAttrs
|
||||
types
|
||||
;
|
||||
in {
|
||||
;
|
||||
in
|
||||
{
|
||||
options.sops = {
|
||||
templates = mkOption {
|
||||
description = "Templates for secret files";
|
||||
type = types.attrsOf (types.submodule ({ config, ... }: {
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = config._module.args.name;
|
||||
description = ''
|
||||
Name of the file used in /run/secrets/rendered
|
||||
'';
|
||||
};
|
||||
path = mkOption {
|
||||
description = "Path where the rendered file will be placed";
|
||||
type = types.singleLineStr;
|
||||
default = "/run/secrets/rendered/${config.name}";
|
||||
};
|
||||
content = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Content of the file
|
||||
'';
|
||||
};
|
||||
mode = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = "0400";
|
||||
description = ''
|
||||
Permissions mode of the rendered secret file in octal.
|
||||
'';
|
||||
};
|
||||
owner = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = "root";
|
||||
description = ''
|
||||
User of the file.
|
||||
'';
|
||||
};
|
||||
group = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = "staff";
|
||||
defaultText = "staff";
|
||||
description = ''
|
||||
Group of the file. Default on darwin in staff.
|
||||
'';
|
||||
};
|
||||
file = mkOption {
|
||||
type = types.path;
|
||||
default = pkgs.writeText config.name config.content;
|
||||
defaultText = lib.literalExpression ''pkgs.writeText config.name config.content'';
|
||||
example = "./configuration-template.conf";
|
||||
description = ''
|
||||
File used as the template. When this value is specified, `sops.templates.<name>.content` is ignored.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}));
|
||||
type = types.attrsOf (
|
||||
types.submodule (
|
||||
{ config, ... }:
|
||||
{
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = config._module.args.name;
|
||||
description = ''
|
||||
Name of the file used in /run/secrets/rendered
|
||||
'';
|
||||
};
|
||||
path = mkOption {
|
||||
description = "Path where the rendered file will be placed";
|
||||
type = types.singleLineStr;
|
||||
default = "/run/secrets/rendered/${config.name}";
|
||||
};
|
||||
content = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Content of the file
|
||||
'';
|
||||
};
|
||||
mode = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = "0400";
|
||||
description = ''
|
||||
Permissions mode of the rendered secret file in octal.
|
||||
'';
|
||||
};
|
||||
owner = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = "root";
|
||||
description = ''
|
||||
User of the file.
|
||||
'';
|
||||
};
|
||||
group = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = "staff";
|
||||
defaultText = "staff";
|
||||
description = ''
|
||||
Group of the file. Default on darwin in staff.
|
||||
'';
|
||||
};
|
||||
file = mkOption {
|
||||
type = types.path;
|
||||
default = pkgs.writeText config.name config.content;
|
||||
defaultText = lib.literalExpression ''pkgs.writeText config.name config.content'';
|
||||
example = "./configuration-template.conf";
|
||||
description = ''
|
||||
File used as the template. When this value is specified, `sops.templates.<name>.content` is ignored.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
default = { };
|
||||
};
|
||||
placeholder = mkOption {
|
||||
type = types.attrsOf (types.mkOptionType {
|
||||
name = "coercibleToString";
|
||||
description = "value that can be coerced to string";
|
||||
check = lib.strings.isConvertibleWithToString;
|
||||
merge = lib.mergeEqualOption;
|
||||
});
|
||||
type = types.attrsOf (
|
||||
types.mkOptionType {
|
||||
name = "coercibleToString";
|
||||
description = "value that can be coerced to string";
|
||||
check = lib.strings.isConvertibleWithToString;
|
||||
merge = lib.mergeEqualOption;
|
||||
}
|
||||
);
|
||||
default = { };
|
||||
visible = false;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.optionalAttrs (options ? sops.secrets)
|
||||
(lib.mkIf (config.sops.templates != { }) {
|
||||
sops.placeholder = mapAttrs
|
||||
(name: _: mkDefault "<SOPS:${builtins.hashString "sha256" name}:PLACEHOLDER>")
|
||||
config.sops.secrets;
|
||||
});
|
||||
config = lib.optionalAttrs (options ? sops.secrets) (
|
||||
lib.mkIf (config.sops.templates != { }) {
|
||||
sops.placeholder = mapAttrs (
|
||||
name: _: mkDefault "<SOPS:${builtins.hashString "sha256" name}:PLACEHOLDER>"
|
||||
) config.sops.secrets;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
|
||||
sopsCall:
|
||||
|
||||
if cfg.environment == {} then
|
||||
if cfg.environment == { } then
|
||||
sopsCall
|
||||
else ''
|
||||
(
|
||||
# shellcheck disable=SC2030,SC2031
|
||||
${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: " export ${n}='${v}'") cfg.environment)}
|
||||
${sopsCall}
|
||||
)
|
||||
''
|
||||
else
|
||||
''
|
||||
(
|
||||
# shellcheck disable=SC2030,SC2031
|
||||
${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: " export ${n}='${v}'") cfg.environment)}
|
||||
${sopsCall}
|
||||
)
|
||||
''
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
{ config, options, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
options,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.sops;
|
||||
|
@ -8,7 +14,7 @@ let
|
|||
inherit cfg;
|
||||
inherit (pkgs) writeTextFile;
|
||||
};
|
||||
manifest = manifestFor "" regularSecrets regularTemplates {};
|
||||
manifest = manifestFor "" regularSecrets regularTemplates { };
|
||||
|
||||
pathNotInStore = lib.mkOptionType {
|
||||
name = "pathNotInStore";
|
||||
|
@ -23,143 +29,165 @@ let
|
|||
# Currently, all templates are "regular" (there's no support for `neededForUsers` for templates.)
|
||||
regularTemplates = cfg.templates;
|
||||
|
||||
useSystemdActivation = (options.systemd ? sysusers && config.systemd.sysusers.enable) ||
|
||||
(options.services ? userborn && config.services.userborn.enable);
|
||||
useSystemdActivation =
|
||||
(options.systemd ? sysusers && config.systemd.sysusers.enable)
|
||||
|| (options.services ? userborn && config.services.userborn.enable);
|
||||
|
||||
withEnvironment = import ./with-environment.nix {
|
||||
inherit cfg lib;
|
||||
};
|
||||
secretType = lib.types.submodule ({ config, ... }: {
|
||||
config = {
|
||||
sopsFile = lib.mkOptionDefault cfg.defaultSopsFile;
|
||||
sopsFileHash = lib.mkOptionDefault (lib.optionalString cfg.validateSopsFiles "${builtins.hashFile "sha256" config.sopsFile}");
|
||||
};
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = config._module.args.name;
|
||||
description = ''
|
||||
Name of the file used in /run/secrets
|
||||
'';
|
||||
secretType = lib.types.submodule (
|
||||
{ config, ... }:
|
||||
{
|
||||
config = {
|
||||
sopsFile = lib.mkOptionDefault cfg.defaultSopsFile;
|
||||
sopsFileHash = lib.mkOptionDefault (
|
||||
lib.optionalString cfg.validateSopsFiles "${builtins.hashFile "sha256" config.sopsFile}"
|
||||
);
|
||||
};
|
||||
key = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = if cfg.defaultSopsKey != null then cfg.defaultSopsKey else config._module.args.name;
|
||||
description = ''
|
||||
Key used to lookup in the sops file.
|
||||
No tested data structures are supported right now.
|
||||
This option is ignored if format is binary.
|
||||
"" means whole file.
|
||||
'';
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = config._module.args.name;
|
||||
description = ''
|
||||
Name of the file used in /run/secrets
|
||||
'';
|
||||
};
|
||||
key = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = if cfg.defaultSopsKey != null then cfg.defaultSopsKey else config._module.args.name;
|
||||
description = ''
|
||||
Key used to lookup in the sops file.
|
||||
No tested data structures are supported right now.
|
||||
This option is ignored if format is binary.
|
||||
"" means whole file.
|
||||
'';
|
||||
};
|
||||
path = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
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.";
|
||||
description = ''
|
||||
Path where secrets are symlinked to.
|
||||
If the default is kept no symlink is created.
|
||||
'';
|
||||
};
|
||||
format = lib.mkOption {
|
||||
type = lib.types.enum [
|
||||
"yaml"
|
||||
"json"
|
||||
"binary"
|
||||
"dotenv"
|
||||
"ini"
|
||||
];
|
||||
default = cfg.defaultSopsFormat;
|
||||
description = ''
|
||||
File format used to decrypt the sops secret.
|
||||
Binary files are written to the target file as is.
|
||||
'';
|
||||
};
|
||||
mode = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "0400";
|
||||
description = ''
|
||||
Permissions mode of the in octal.
|
||||
'';
|
||||
};
|
||||
owner = lib.mkOption {
|
||||
type = with lib.types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
User of the file. Can only be set if uid is 0.
|
||||
'';
|
||||
};
|
||||
uid = lib.mkOption {
|
||||
type = with lib.types; nullOr int;
|
||||
default = 0;
|
||||
description = ''
|
||||
UID of the file, only applied when owner is null. The UID will be applied even if the corresponding user doesn't exist.
|
||||
'';
|
||||
};
|
||||
group = lib.mkOption {
|
||||
type = with lib.types; nullOr str;
|
||||
default = if config.owner != null then users.${config.owner}.group else null;
|
||||
defaultText = lib.literalMD "{option}`config.users.users.\${owner}.group`";
|
||||
description = ''
|
||||
Group of the file. Can only be set if gid is 0.
|
||||
'';
|
||||
};
|
||||
gid = lib.mkOption {
|
||||
type = with lib.types; nullOr int;
|
||||
default = 0;
|
||||
description = ''
|
||||
GID of the file, only applied when group is null. The GID will be applied even if the corresponding group doesn't exist.
|
||||
'';
|
||||
};
|
||||
sopsFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
defaultText = lib.literalExpression "\${config.sops.defaultSopsFile}";
|
||||
description = ''
|
||||
Sops file the secret is loaded from.
|
||||
'';
|
||||
};
|
||||
sopsFileHash = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
readOnly = true;
|
||||
description = ''
|
||||
Hash of the sops file, useful in <xref linkend="opt-systemd.services._name_.restartTriggers" />.
|
||||
'';
|
||||
};
|
||||
restartUnits = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
example = [ "sshd.service" ];
|
||||
description = ''
|
||||
Names of units that should be restarted when this secret changes.
|
||||
This works the same way as <xref linkend="opt-systemd.services._name_.restartTriggers" />.
|
||||
'';
|
||||
};
|
||||
reloadUnits = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
example = [ "sshd.service" ];
|
||||
description = ''
|
||||
Names of units that should be reloaded when this secret changes.
|
||||
This works the same way as <xref linkend="opt-systemd.services._name_.reloadTriggers" />.
|
||||
'';
|
||||
};
|
||||
neededForUsers = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Enabling this option causes the secret to be decrypted before users and groups are created.
|
||||
This can be used to retrieve user's passwords from sops-nix.
|
||||
Setting this option moves the secret to /run/secrets-for-users and disallows setting owner and group to anything else than root.
|
||||
'';
|
||||
};
|
||||
};
|
||||
path = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
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.";
|
||||
description = ''
|
||||
Path where secrets are symlinked to.
|
||||
If the default is kept no symlink is created.
|
||||
'';
|
||||
};
|
||||
format = lib.mkOption {
|
||||
type = lib.types.enum ["yaml" "json" "binary" "dotenv" "ini"];
|
||||
default = cfg.defaultSopsFormat;
|
||||
description = ''
|
||||
File format used to decrypt the sops secret.
|
||||
Binary files are written to the target file as is.
|
||||
'';
|
||||
};
|
||||
mode = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "0400";
|
||||
description = ''
|
||||
Permissions mode of the in octal.
|
||||
'';
|
||||
};
|
||||
owner = lib.mkOption {
|
||||
type = with lib.types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
User of the file. Can only be set if uid is 0.
|
||||
'';
|
||||
};
|
||||
uid = lib.mkOption {
|
||||
type = with lib.types; nullOr int;
|
||||
default = 0;
|
||||
description = ''
|
||||
UID of the file, only applied when owner is null. The UID will be applied even if the corresponding user doesn't exist.
|
||||
'';
|
||||
};
|
||||
group = lib.mkOption {
|
||||
type = with lib.types; nullOr str;
|
||||
default = if config.owner != null then users.${config.owner}.group else null;
|
||||
defaultText = lib.literalMD "{option}`config.users.users.\${owner}.group`";
|
||||
description = ''
|
||||
Group of the file. Can only be set if gid is 0.
|
||||
'';
|
||||
};
|
||||
gid = lib.mkOption {
|
||||
type = with lib.types; nullOr int;
|
||||
default = 0;
|
||||
description = ''
|
||||
GID of the file, only applied when group is null. The GID will be applied even if the corresponding group doesn't exist.
|
||||
'';
|
||||
};
|
||||
sopsFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
defaultText = lib.literalExpression "\${config.sops.defaultSopsFile}";
|
||||
description = ''
|
||||
Sops file the secret is loaded from.
|
||||
'';
|
||||
};
|
||||
sopsFileHash = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
readOnly = true;
|
||||
description = ''
|
||||
Hash of the sops file, useful in <xref linkend="opt-systemd.services._name_.restartTriggers" />.
|
||||
'';
|
||||
};
|
||||
restartUnits = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
example = [ "sshd.service" ];
|
||||
description = ''
|
||||
Names of units that should be restarted when this secret changes.
|
||||
This works the same way as <xref linkend="opt-systemd.services._name_.restartTriggers" />.
|
||||
'';
|
||||
};
|
||||
reloadUnits = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
example = [ "sshd.service" ];
|
||||
description = ''
|
||||
Names of units that should be reloaded when this secret changes.
|
||||
This works the same way as <xref linkend="opt-systemd.services._name_.reloadTriggers" />.
|
||||
'';
|
||||
};
|
||||
neededForUsers = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Enabling this option causes the secret to be decrypted before users and groups are created.
|
||||
This can be used to retrieve user's passwords from sops-nix.
|
||||
Setting this option moves the secret to /run/secrets-for-users and disallows setting owner and group to anything else than root.
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
# Skip ssh keys deployed with sops to avoid a catch 22
|
||||
defaultImportKeys = algo:
|
||||
defaultImportKeys =
|
||||
algo:
|
||||
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
|
||||
[];
|
||||
in {
|
||||
[ ];
|
||||
in
|
||||
{
|
||||
options.sops = {
|
||||
secrets = lib.mkOption {
|
||||
type = lib.types.attrsOf secretType;
|
||||
default = {};
|
||||
default = { };
|
||||
description = ''
|
||||
Path where the latest secrets are mounted to.
|
||||
'';
|
||||
|
@ -208,14 +236,22 @@ in {
|
|||
};
|
||||
|
||||
log = lib.mkOption {
|
||||
type = lib.types.listOf (lib.types.enum [ "keyImport" "secretChanges" ]);
|
||||
default = [ "keyImport" "secretChanges" ];
|
||||
type = lib.types.listOf (
|
||||
lib.types.enum [
|
||||
"keyImport"
|
||||
"secretChanges"
|
||||
]
|
||||
);
|
||||
default = [
|
||||
"keyImport"
|
||||
"secretChanges"
|
||||
];
|
||||
description = "What to log";
|
||||
};
|
||||
|
||||
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.
|
||||
|
||||
|
@ -230,7 +266,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.
|
||||
|
@ -240,9 +276,10 @@ in {
|
|||
validationPackage = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
default =
|
||||
if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform
|
||||
then sops-install-secrets
|
||||
else (pkgs.pkgsBuildHost.callPackage ../.. {}).sops-install-secrets;
|
||||
if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform then
|
||||
sops-install-secrets
|
||||
else
|
||||
(pkgs.pkgsBuildHost.callPackage ../.. { }).sops-install-secrets;
|
||||
defaultText = lib.literalExpression "config.sops.package";
|
||||
|
||||
description = ''
|
||||
|
@ -326,40 +363,78 @@ in {
|
|||
imports = [
|
||||
./templates
|
||||
./secrets-for-users
|
||||
(lib.mkRenamedOptionModule [ "sops" "gnupgHome" ] [ "sops" "gnupg" "home" ])
|
||||
(lib.mkRenamedOptionModule [ "sops" "sshKeyPaths" ] [ "sops" "gnupg" "sshKeyPaths" ])
|
||||
(lib.mkRenamedOptionModule
|
||||
[
|
||||
"sops"
|
||||
"gnupgHome"
|
||||
]
|
||||
[
|
||||
"sops"
|
||||
"gnupg"
|
||||
"home"
|
||||
]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[
|
||||
"sops"
|
||||
"sshKeyPaths"
|
||||
]
|
||||
[
|
||||
"sops"
|
||||
"gnupg"
|
||||
"sshKeyPaths"
|
||||
]
|
||||
)
|
||||
];
|
||||
config = lib.mkMerge [
|
||||
(lib.mkIf (cfg.secrets != {}) {
|
||||
assertions = [{
|
||||
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";
|
||||
} {
|
||||
assertion = !(cfg.gnupg.home != null && cfg.gnupg.sshKeyPaths != []);
|
||||
message = "Exactly one of sops.gnupg.home and sops.gnupg.sshKeyPaths must be set";
|
||||
}] ++ lib.optionals cfg.validateSopsFiles (
|
||||
lib.concatLists (lib.mapAttrsToList (name: secret: [{
|
||||
assertion = builtins.pathExists secret.sopsFile;
|
||||
message = "Cannot find path '${secret.sopsFile}' set in sops.secrets.${lib.strings.escapeNixIdentifier name}.sopsFile";
|
||||
} {
|
||||
assertion =
|
||||
builtins.isPath 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";
|
||||
} {
|
||||
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";
|
||||
} {
|
||||
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";
|
||||
}]) cfg.secrets)
|
||||
);
|
||||
(lib.mkIf (cfg.secrets != { }) {
|
||||
assertions =
|
||||
[
|
||||
{
|
||||
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";
|
||||
}
|
||||
{
|
||||
assertion = !(cfg.gnupg.home != null && cfg.gnupg.sshKeyPaths != [ ]);
|
||||
message = "Exactly one of sops.gnupg.home and sops.gnupg.sshKeyPaths must be set";
|
||||
}
|
||||
]
|
||||
++ lib.optionals cfg.validateSopsFiles (
|
||||
lib.concatLists (
|
||||
lib.mapAttrsToList (name: secret: [
|
||||
{
|
||||
assertion = builtins.pathExists secret.sopsFile;
|
||||
message = "Cannot find path '${secret.sopsFile}' set in sops.secrets.${lib.strings.escapeNixIdentifier name}.sopsFile";
|
||||
}
|
||||
{
|
||||
assertion =
|
||||
builtins.isPath 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";
|
||||
}
|
||||
{
|
||||
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";
|
||||
}
|
||||
{
|
||||
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";
|
||||
}
|
||||
]) 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.
|
||||
systemd.services.sops-install-secrets = lib.mkIf (regularSecrets != { } && useSystemdActivation) {
|
||||
wantedBy = [ "sysinit.target" ];
|
||||
wantedBy = [ "sysinit.target" ];
|
||||
after = [ "systemd-sysusers.service" ];
|
||||
environment = cfg.environment;
|
||||
unitConfig.DefaultDependencies = "no";
|
||||
|
@ -372,27 +447,43 @@ in {
|
|||
};
|
||||
|
||||
system.activationScripts = {
|
||||
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...
|
||||
${withEnvironment "${sops-install-secrets}/bin/sops-install-secrets ${manifest}"}
|
||||
'' // lib.optionalAttrs (config.system ? dryActivationScript) {
|
||||
supportsDryActivation = true;
|
||||
});
|
||||
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...
|
||||
${withEnvironment "${sops-install-secrets}/bin/sops-install-secrets ${manifest}"}
|
||||
''
|
||||
// lib.optionalAttrs (config.system ? dryActivationScript) {
|
||||
supportsDryActivation = true;
|
||||
}
|
||||
);
|
||||
|
||||
generate-age-key = let
|
||||
escapedKeyFile = lib.escapeShellArg cfg.age.keyFile;
|
||||
in lib.mkIf cfg.age.generateKey (lib.stringAfter [] ''
|
||||
if [[ ! -f ${escapedKeyFile} ]]; then
|
||||
echo generating machine-specific age key...
|
||||
mkdir -p $(dirname ${escapedKeyFile})
|
||||
# age-keygen sets 0600 by default, no need to chmod.
|
||||
${pkgs.age}/bin/age-keygen -o ${escapedKeyFile}
|
||||
fi
|
||||
'');
|
||||
generate-age-key =
|
||||
let
|
||||
escapedKeyFile = lib.escapeShellArg cfg.age.keyFile;
|
||||
in
|
||||
lib.mkIf cfg.age.generateKey (
|
||||
lib.stringAfter [ ] ''
|
||||
if [[ ! -f ${escapedKeyFile} ]]; then
|
||||
echo generating machine-specific age key...
|
||||
mkdir -p $(dirname ${escapedKeyFile})
|
||||
# age-keygen sets 0600 by default, no need to chmod.
|
||||
${pkgs.age}/bin/age-keygen -o ${escapedKeyFile}
|
||||
fi
|
||||
''
|
||||
);
|
||||
};
|
||||
})
|
||||
{
|
||||
system.build.sops-nix-manifest = manifest;
|
||||
system.build.sops-nix-manifest = manifest;
|
||||
}
|
||||
];
|
||||
}
|
||||
|
|
|
@ -4,26 +4,31 @@ suffix: secrets: templates: extraJson:
|
|||
|
||||
writeTextFile {
|
||||
name = "manifest${suffix}.json";
|
||||
text = builtins.toJSON ({
|
||||
secrets = builtins.attrValues secrets;
|
||||
templates = builtins.attrValues templates;
|
||||
# Does this need to be configurable?
|
||||
secretsMountPoint = "/run/secrets.d";
|
||||
symlinkPath = "/run/secrets";
|
||||
keepGenerations = cfg.keepGenerations;
|
||||
gnupgHome = cfg.gnupg.home;
|
||||
sshKeyPaths = cfg.gnupg.sshKeyPaths;
|
||||
ageKeyFile = cfg.age.keyFile;
|
||||
ageSshKeyPaths = cfg.age.sshKeyPaths;
|
||||
useTmpfs = cfg.useTmpfs;
|
||||
placeholderBySecretName = cfg.placeholder;
|
||||
userMode = false;
|
||||
logging = {
|
||||
keyImport = builtins.elem "keyImport" cfg.log;
|
||||
secretChanges = builtins.elem "secretChanges" cfg.log;
|
||||
};
|
||||
} // extraJson);
|
||||
text = builtins.toJSON (
|
||||
{
|
||||
secrets = builtins.attrValues secrets;
|
||||
templates = builtins.attrValues templates;
|
||||
# Does this need to be configurable?
|
||||
secretsMountPoint = "/run/secrets.d";
|
||||
symlinkPath = "/run/secrets";
|
||||
keepGenerations = cfg.keepGenerations;
|
||||
gnupgHome = cfg.gnupg.home;
|
||||
sshKeyPaths = cfg.gnupg.sshKeyPaths;
|
||||
ageKeyFile = cfg.age.keyFile;
|
||||
ageSshKeyPaths = cfg.age.sshKeyPaths;
|
||||
useTmpfs = cfg.useTmpfs;
|
||||
placeholderBySecretName = cfg.placeholder;
|
||||
userMode = false;
|
||||
logging = {
|
||||
keyImport = builtins.elem "keyImport" cfg.log;
|
||||
secretChanges = builtins.elem "secretChanges" cfg.log;
|
||||
};
|
||||
}
|
||||
// extraJson
|
||||
);
|
||||
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"
|
||||
'';
|
||||
}
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
{ lib, options, config, pkgs, ... }:
|
||||
{
|
||||
lib,
|
||||
options,
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.sops;
|
||||
secretsForUsers = lib.filterAttrs (_: v: v.neededForUsers) cfg.secrets;
|
||||
templatesForUsers = {}; # We do not currently support `neededForUsers` for templates.
|
||||
templatesForUsers = { }; # We do not currently support `neededForUsers` for templates.
|
||||
manifestFor = pkgs.callPackage ../manifest-for.nix {
|
||||
inherit cfg;
|
||||
inherit (pkgs) writeTextFile;
|
||||
|
@ -15,44 +21,54 @@ let
|
|||
symlinkPath = "/run/secrets-for-users";
|
||||
};
|
||||
sysusersEnabled = options.systemd ? sysusers && config.systemd.sysusers.enable;
|
||||
useSystemdActivation = sysusersEnabled ||
|
||||
(options.services ? userborn && config.services.userborn.enable);
|
||||
useSystemdActivation =
|
||||
sysusersEnabled || (options.services ? userborn && config.services.userborn.enable);
|
||||
in
|
||||
{
|
||||
systemd.services.sops-install-secrets-for-users = lib.mkIf (secretsForUsers != { } && useSystemdActivation) {
|
||||
wantedBy = [ "systemd-sysusers.service" ];
|
||||
before = [ "systemd-sysusers.service" ];
|
||||
environment = cfg.environment;
|
||||
unitConfig.DefaultDependencies = "no";
|
||||
systemd.services.sops-install-secrets-for-users =
|
||||
lib.mkIf (secretsForUsers != { } && useSystemdActivation)
|
||||
{
|
||||
wantedBy = [ "systemd-sysusers.service" ];
|
||||
before = [ "systemd-sysusers.service" ];
|
||||
environment = cfg.environment;
|
||||
unitConfig.DefaultDependencies = "no";
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = [ "${cfg.package}/bin/sops-install-secrets -ignore-passwd ${manifestForUsers}" ];
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
};
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = [ "${cfg.package}/bin/sops-install-secrets -ignore-passwd ${manifestForUsers}" ];
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
};
|
||||
|
||||
system.activationScripts = lib.mkIf (secretsForUsers != { } && !useSystemdActivation) {
|
||||
setupSecretsForUsers = lib.stringAfter ([ "specialfs" ] ++ lib.optional cfg.age.generateKey "generate-age-key") ''
|
||||
[ -e /run/current-system ] || echo setting up secrets for users...
|
||||
${withEnvironment "${cfg.package}/bin/sops-install-secrets -ignore-passwd ${manifestForUsers}"}
|
||||
'' // lib.optionalAttrs (config.system ? dryActivationScript) {
|
||||
supportsDryActivation = true;
|
||||
};
|
||||
setupSecretsForUsers =
|
||||
lib.stringAfter ([ "specialfs" ] ++ lib.optional cfg.age.generateKey "generate-age-key") ''
|
||||
[ -e /run/current-system ] || echo setting up secrets for users...
|
||||
${withEnvironment "${cfg.package}/bin/sops-install-secrets -ignore-passwd ${manifestForUsers}"}
|
||||
''
|
||||
// lib.optionalAttrs (config.system ? dryActivationScript) {
|
||||
supportsDryActivation = true;
|
||||
};
|
||||
|
||||
users.deps = [ "setupSecretsForUsers" ];
|
||||
};
|
||||
|
||||
assertions = [{
|
||||
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";
|
||||
} {
|
||||
assertion = secretsForUsers != { } && sysusersEnabled -> config.users.mutableUsers;
|
||||
message = ''
|
||||
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
|
||||
'';
|
||||
}];
|
||||
assertions = [
|
||||
{
|
||||
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";
|
||||
}
|
||||
{
|
||||
assertion = secretsForUsers != { } && sysusersEnabled -> config.users.mutableUsers;
|
||||
message = ''
|
||||
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
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
system.build.sops-nix-users-manifest = manifestForUsers;
|
||||
}
|
||||
|
|
|
@ -1,108 +1,123 @@
|
|||
{ config, pkgs, lib, options, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
options,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib)
|
||||
mkOption
|
||||
mkDefault
|
||||
mapAttrs
|
||||
types
|
||||
;
|
||||
;
|
||||
|
||||
users = config.users.users;
|
||||
in {
|
||||
in
|
||||
{
|
||||
options.sops = {
|
||||
templates = mkOption {
|
||||
description = "Templates for secret files";
|
||||
type = types.attrsOf (types.submodule ({ config, ... }: {
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = config._module.args.name;
|
||||
description = ''
|
||||
Name of the file used in /run/secrets/rendered
|
||||
'';
|
||||
};
|
||||
path = mkOption {
|
||||
description = "Path where the rendered file will be placed";
|
||||
type = types.singleLineStr;
|
||||
# Keep this in sync with `RenderedSubdir` in `pkgs/sops-install-secrets/main.go`
|
||||
default = "/run/secrets/rendered/${config.name}";
|
||||
};
|
||||
content = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Content of the file
|
||||
'';
|
||||
};
|
||||
mode = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = "0400";
|
||||
description = ''
|
||||
Permissions mode of the rendered secret file in octal.
|
||||
'';
|
||||
};
|
||||
owner = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = "root";
|
||||
description = ''
|
||||
User of the file.
|
||||
'';
|
||||
};
|
||||
group = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = users.${config.owner}.group;
|
||||
defaultText = lib.literalExpression ''config.users.users.''${cfg.owner}.group'';
|
||||
description = ''
|
||||
Group of the file.
|
||||
'';
|
||||
};
|
||||
file = mkOption {
|
||||
type = types.path;
|
||||
default = pkgs.writeText config.name config.content;
|
||||
defaultText = lib.literalExpression ''pkgs.writeText config.name config.content'';
|
||||
example = "./configuration-template.conf";
|
||||
description = ''
|
||||
File used as the template. When this value is specified, `sops.templates.<name>.content` is ignored.
|
||||
'';
|
||||
};
|
||||
restartUnits = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
example = [ "sshd.service" ];
|
||||
description = ''
|
||||
Names of units that should be restarted when the rendered template changes.
|
||||
This works the same way as <xref linkend="opt-systemd.services._name_.restartTriggers" />.
|
||||
'';
|
||||
};
|
||||
reloadUnits = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
example = [ "sshd.service" ];
|
||||
description = ''
|
||||
Names of units that should be reloaded when the rendered template changes.
|
||||
This works the same way as <xref linkend="opt-systemd.services._name_.reloadTriggers" />.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}));
|
||||
type = types.attrsOf (
|
||||
types.submodule (
|
||||
{ config, ... }:
|
||||
{
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = config._module.args.name;
|
||||
description = ''
|
||||
Name of the file used in /run/secrets/rendered
|
||||
'';
|
||||
};
|
||||
path = mkOption {
|
||||
description = "Path where the rendered file will be placed";
|
||||
type = types.singleLineStr;
|
||||
# Keep this in sync with `RenderedSubdir` in `pkgs/sops-install-secrets/main.go`
|
||||
default = "/run/secrets/rendered/${config.name}";
|
||||
};
|
||||
content = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Content of the file
|
||||
'';
|
||||
};
|
||||
mode = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = "0400";
|
||||
description = ''
|
||||
Permissions mode of the rendered secret file in octal.
|
||||
'';
|
||||
};
|
||||
owner = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = "root";
|
||||
description = ''
|
||||
User of the file.
|
||||
'';
|
||||
};
|
||||
group = mkOption {
|
||||
type = types.singleLineStr;
|
||||
default = users.${config.owner}.group;
|
||||
defaultText = lib.literalExpression ''config.users.users.''${cfg.owner}.group'';
|
||||
description = ''
|
||||
Group of the file.
|
||||
'';
|
||||
};
|
||||
file = mkOption {
|
||||
type = types.path;
|
||||
default = pkgs.writeText config.name config.content;
|
||||
defaultText = lib.literalExpression ''pkgs.writeText config.name config.content'';
|
||||
example = "./configuration-template.conf";
|
||||
description = ''
|
||||
File used as the template. When this value is specified, `sops.templates.<name>.content` is ignored.
|
||||
'';
|
||||
};
|
||||
restartUnits = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
example = [ "sshd.service" ];
|
||||
description = ''
|
||||
Names of units that should be restarted when the rendered template changes.
|
||||
This works the same way as <xref linkend="opt-systemd.services._name_.restartTriggers" />.
|
||||
'';
|
||||
};
|
||||
reloadUnits = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
example = [ "sshd.service" ];
|
||||
description = ''
|
||||
Names of units that should be reloaded when the rendered template changes.
|
||||
This works the same way as <xref linkend="opt-systemd.services._name_.reloadTriggers" />.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
default = { };
|
||||
};
|
||||
placeholder = mkOption {
|
||||
type = types.attrsOf (types.mkOptionType {
|
||||
name = "coercibleToString";
|
||||
description = "value that can be coerced to string";
|
||||
check = lib.strings.isConvertibleWithToString;
|
||||
merge = lib.mergeEqualOption;
|
||||
});
|
||||
type = types.attrsOf (
|
||||
types.mkOptionType {
|
||||
name = "coercibleToString";
|
||||
description = "value that can be coerced to string";
|
||||
check = lib.strings.isConvertibleWithToString;
|
||||
merge = lib.mergeEqualOption;
|
||||
}
|
||||
);
|
||||
default = { };
|
||||
visible = false;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.optionalAttrs (options ? sops.secrets)
|
||||
(lib.mkIf (config.sops.templates != { }) {
|
||||
sops.placeholder = mapAttrs
|
||||
(name: _: mkDefault "<SOPS:${builtins.hashString "sha256" name}:PLACEHOLDER>")
|
||||
config.sops.secrets;
|
||||
});
|
||||
config = lib.optionalAttrs (options ? sops.secrets) (
|
||||
lib.mkIf (config.sops.templates != { }) {
|
||||
sops.placeholder = mapAttrs (
|
||||
name: _: mkDefault "<SOPS:${builtins.hashString "sha256" name}:PLACEHOLDER>"
|
||||
) config.sops.secrets;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
|
||||
sopsCall:
|
||||
|
||||
if cfg.environment == {} then
|
||||
if cfg.environment == { } then
|
||||
sopsCall
|
||||
else ''
|
||||
(
|
||||
${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: " export ${n}='${v}'") cfg.environment)}
|
||||
${sopsCall}
|
||||
)
|
||||
''
|
||||
else
|
||||
''
|
||||
(
|
||||
${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: " export ${n}='${v}'") cfg.environment)}
|
||||
${sopsCall}
|
||||
)
|
||||
''
|
||||
|
|
|
@ -1,13 +1,25 @@
|
|||
{ makeSetupHook, gnupg, sops, lib }:
|
||||
{
|
||||
makeSetupHook,
|
||||
gnupg,
|
||||
sops,
|
||||
lib,
|
||||
}:
|
||||
|
||||
let
|
||||
# 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
|
||||
(makeSetupHook {
|
||||
name = "sops-import-keys-hook";
|
||||
substitutions = {
|
||||
gpg = "${gnupg}/bin/gpg";
|
||||
};
|
||||
${propagatedBuildInputs} = [ sops gnupg ];
|
||||
${propagatedBuildInputs} = [
|
||||
sops
|
||||
gnupg
|
||||
];
|
||||
} ./sops-import-keys-hook.bash)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# shell.nix
|
||||
with import <nixpkgs> {};
|
||||
with import <nixpkgs> { };
|
||||
mkShell {
|
||||
sopsPGPKeyDirs = [
|
||||
"./keys"
|
||||
|
@ -10,6 +10,6 @@ mkShell {
|
|||
];
|
||||
sopsCreateGPGHome = "1";
|
||||
nativeBuildInputs = [
|
||||
(pkgs.callPackage ../../.. {}).sops-import-keys-hook
|
||||
(pkgs.callPackage ../../.. { }).sops-import-keys-hook
|
||||
];
|
||||
}
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
{ stdenv, lib, makeWrapper, gnupg, coreutils, util-linux, unixtools }:
|
||||
{
|
||||
stdenv,
|
||||
lib,
|
||||
makeWrapper,
|
||||
gnupg,
|
||||
coreutils,
|
||||
util-linux,
|
||||
unixtools,
|
||||
}:
|
||||
|
||||
stdenv.mkDerivation {
|
||||
name = "sops-init-gpg-key";
|
||||
|
@ -11,9 +19,14 @@ stdenv.mkDerivation {
|
|||
installPhase = ''
|
||||
install -m755 -D $src $out/bin/sops-init-gpg-key
|
||||
wrapProgram $out/bin/sops-init-gpg-key \
|
||||
--prefix PATH : ${lib.makeBinPath [
|
||||
coreutils util-linux gnupg unixtools.hostname
|
||||
]}
|
||||
--prefix PATH : ${
|
||||
lib.makeBinPath [
|
||||
coreutils
|
||||
util-linux
|
||||
gnupg
|
||||
unixtools.hostname
|
||||
]
|
||||
}
|
||||
'';
|
||||
|
||||
doInstallCheck = true;
|
||||
|
|
|
@ -1,9 +1,20 @@
|
|||
{ lib, buildGoModule, stdenv, vendorHash, go, callPackages }:
|
||||
{
|
||||
lib,
|
||||
buildGoModule,
|
||||
stdenv,
|
||||
vendorHash,
|
||||
go,
|
||||
callPackages,
|
||||
}:
|
||||
buildGoModule {
|
||||
pname = "sops-install-secrets";
|
||||
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" ];
|
||||
|
||||
|
@ -12,19 +23,20 @@ buildGoModule {
|
|||
|
||||
passthru.tests = callPackages ./nixos-test.nix { };
|
||||
|
||||
outputs = [ "out" ] ++
|
||||
lib.lists.optionals (stdenv.isLinux) [ "unittest" ];
|
||||
outputs = [ "out" ] ++ lib.lists.optionals (stdenv.isLinux) [ "unittest" ];
|
||||
|
||||
postInstall = ''
|
||||
go test -c ./pkgs/sops-install-secrets
|
||||
'' + lib.optionalString (stdenv.isLinux) ''
|
||||
# *.test is only tested on linux. $unittest does not exist on darwin.
|
||||
install -D ./sops-install-secrets.test $unittest/bin/sops-install-secrets.test
|
||||
# newer versions of nixpkgs no longer require this step
|
||||
if command -v remove-references-to; then
|
||||
remove-references-to -t ${go} $unittest/bin/sops-install-secrets.test
|
||||
fi
|
||||
'';
|
||||
postInstall =
|
||||
''
|
||||
go test -c ./pkgs/sops-install-secrets
|
||||
''
|
||||
+ lib.optionalString (stdenv.isLinux) ''
|
||||
# *.test is only tested on linux. $unittest does not exist on darwin.
|
||||
install -D ./sops-install-secrets.test $unittest/bin/sops-install-secrets.test
|
||||
# newer versions of nixpkgs no longer require this step
|
||||
if command -v remove-references-to; then
|
||||
remove-references-to -t ${go} $unittest/bin/sops-install-secrets.test
|
||||
fi
|
||||
'';
|
||||
|
||||
inherit vendorHash;
|
||||
|
||||
|
|
|
@ -1,64 +1,75 @@
|
|||
{ lib, testers }:
|
||||
let
|
||||
userPasswordTest = name: extraConfig: testers.runNixOSTest {
|
||||
inherit name;
|
||||
nodes.machine = { config, lib, ... }: {
|
||||
imports = [
|
||||
../../modules/sops
|
||||
extraConfig
|
||||
];
|
||||
sops = {
|
||||
age.keyFile = "/run/age-keys.txt";
|
||||
defaultSopsFile = ./test-assets/secrets.yaml;
|
||||
secrets.test_key.neededForUsers = true;
|
||||
secrets."nested/test/file".owner = "example-user";
|
||||
};
|
||||
system.switch.enable = true;
|
||||
userPasswordTest =
|
||||
name: extraConfig:
|
||||
testers.runNixOSTest {
|
||||
inherit name;
|
||||
nodes.machine =
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
imports = [
|
||||
../../modules/sops
|
||||
extraConfig
|
||||
];
|
||||
sops = {
|
||||
age.keyFile = "/run/age-keys.txt";
|
||||
defaultSopsFile = ./test-assets/secrets.yaml;
|
||||
secrets.test_key.neededForUsers = true;
|
||||
secrets."nested/test/file".owner = "example-user";
|
||||
};
|
||||
system.switch.enable = true;
|
||||
|
||||
users.users.example-user = lib.mkMerge [
|
||||
(lib.mkIf (! config.systemd.sysusers.enable) {
|
||||
isNormalUser = true;
|
||||
hashedPasswordFile = config.sops.secrets.test_key.path;
|
||||
})
|
||||
(lib.mkIf config.systemd.sysusers.enable {
|
||||
isSystemUser = true;
|
||||
group = "users";
|
||||
hashedPasswordFile = config.sops.secrets.test_key.path;
|
||||
})
|
||||
];
|
||||
users.users.example-user = lib.mkMerge [
|
||||
(lib.mkIf (!config.systemd.sysusers.enable) {
|
||||
isNormalUser = true;
|
||||
hashedPasswordFile = config.sops.secrets.test_key.path;
|
||||
})
|
||||
(lib.mkIf config.systemd.sysusers.enable {
|
||||
isSystemUser = true;
|
||||
group = "users";
|
||||
hashedPasswordFile = config.sops.secrets.test_key.path;
|
||||
})
|
||||
];
|
||||
};
|
||||
|
||||
testScript =
|
||||
''
|
||||
start_all()
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
|
||||
machine.succeed("getent shadow example-user | grep -q :test_value:") # password was set
|
||||
machine.succeed("cat /run/secrets/nested/test/file | grep -q 'another value'") # regular secrets work...
|
||||
user = machine.succeed("stat -c%U /run/secrets/nested/test/file").strip() # ...and are owned...
|
||||
assert user == "example-user", f"Expected 'example-user', got '{user}'"
|
||||
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
|
||||
''
|
||||
+ lib.optionalString (!(extraConfig ? system.etc.overlay.enable)) ''
|
||||
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-for-users/test_key | grep -q 'test_value'") # the user password is still present after a switch
|
||||
'';
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
|
||||
machine.succeed("getent shadow example-user | grep -q :test_value:") # password was set
|
||||
machine.succeed("cat /run/secrets/nested/test/file | grep -q 'another value'") # regular secrets work...
|
||||
user = machine.succeed("stat -c%U /run/secrets/nested/test/file").strip() # ...and are owned...
|
||||
assert user == "example-user", f"Expected 'example-user', got '{user}'"
|
||||
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
|
||||
'' + lib.optionalString (!(extraConfig ? system.etc.overlay.enable)) ''
|
||||
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-for-users/test_key | grep -q 'test_value'") # the user password is still present after a switch
|
||||
'';
|
||||
};
|
||||
in {
|
||||
in
|
||||
{
|
||||
ssh-keys = testers.runNixOSTest {
|
||||
name = "sops-ssh-keys";
|
||||
nodes.server = { ... }: {
|
||||
imports = [ ../../modules/sops ];
|
||||
services.openssh.enable = true;
|
||||
services.openssh.hostKeys = [{
|
||||
type = "rsa";
|
||||
bits = 4096;
|
||||
path = ./test-assets/ssh-key;
|
||||
}];
|
||||
sops.defaultSopsFile = ./test-assets/secrets.yaml;
|
||||
sops.secrets.test_key = { };
|
||||
};
|
||||
nodes.server =
|
||||
{ ... }:
|
||||
{
|
||||
imports = [ ../../modules/sops ];
|
||||
services.openssh.enable = true;
|
||||
services.openssh.hostKeys = [
|
||||
{
|
||||
type = "rsa";
|
||||
bits = 4096;
|
||||
path = ./test-assets/ssh-key;
|
||||
}
|
||||
];
|
||||
sops.defaultSopsFile = ./test-assets/secrets.yaml;
|
||||
sops.secrets.test_key = { };
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
@ -68,24 +79,26 @@ in {
|
|||
|
||||
pruning = testers.runNixOSTest {
|
||||
name = "sops-pruning";
|
||||
nodes.machine = { lib, ... }: {
|
||||
imports = [ ../../modules/sops ];
|
||||
sops = {
|
||||
age.keyFile = "/run/age-keys.txt";
|
||||
defaultSopsFile = ./test-assets/secrets.yaml;
|
||||
secrets.test_key = { };
|
||||
keepGenerations = lib.mkDefault 0;
|
||||
nodes.machine =
|
||||
{ lib, ... }:
|
||||
{
|
||||
imports = [ ../../modules/sops ];
|
||||
sops = {
|
||||
age.keyFile = "/run/age-keys.txt";
|
||||
defaultSopsFile = ./test-assets/secrets.yaml;
|
||||
secrets.test_key = { };
|
||||
keepGenerations = lib.mkDefault 0;
|
||||
};
|
||||
|
||||
# must run before sops sets up keys
|
||||
boot.initrd.postDeviceCommands = ''
|
||||
cp -r ${./test-assets/age-keys.txt} /run/age-keys.txt
|
||||
chmod -R 700 /run/age-keys.txt
|
||||
'';
|
||||
|
||||
specialisation.pruning.configuration.sops.keepGenerations = 10;
|
||||
};
|
||||
|
||||
# must run before sops sets up keys
|
||||
boot.initrd.postDeviceCommands = ''
|
||||
cp -r ${./test-assets/age-keys.txt} /run/age-keys.txt
|
||||
chmod -R 700 /run/age-keys.txt
|
||||
'';
|
||||
|
||||
specialisation.pruning.configuration.sops.keepGenerations = 10;
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
# Force us to generation 100
|
||||
machine.succeed("mkdir /run/secrets.d/{2..99} /run/secrets.d/non-numeric")
|
||||
|
@ -112,49 +125,51 @@ in {
|
|||
|
||||
age-keys = testers.runNixOSTest {
|
||||
name = "sops-age-keys";
|
||||
nodes.machine = { config, ... }: {
|
||||
imports = [ ../../modules/sops ];
|
||||
sops = {
|
||||
age.keyFile = "/run/age-keys.txt";
|
||||
defaultSopsFile = ./test-assets/secrets.yaml;
|
||||
secrets = {
|
||||
test_key = { };
|
||||
nodes.machine =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [ ../../modules/sops ];
|
||||
sops = {
|
||||
age.keyFile = "/run/age-keys.txt";
|
||||
defaultSopsFile = ./test-assets/secrets.yaml;
|
||||
secrets = {
|
||||
test_key = { };
|
||||
|
||||
test_key_someuser_somegroup = {
|
||||
uid = config.users.users."someuser".uid;
|
||||
gid = config.users.groups."somegroup".gid;
|
||||
key = "test_key";
|
||||
};
|
||||
test_key_someuser_root = {
|
||||
uid = config.users.users."someuser".uid;
|
||||
key = "test_key";
|
||||
};
|
||||
test_key_root_root = {
|
||||
key = "test_key";
|
||||
};
|
||||
test_key_1001_1001 = {
|
||||
uid = 1001;
|
||||
gid = 1001;
|
||||
key = "test_key";
|
||||
test_key_someuser_somegroup = {
|
||||
uid = config.users.users."someuser".uid;
|
||||
gid = config.users.groups."somegroup".gid;
|
||||
key = "test_key";
|
||||
};
|
||||
test_key_someuser_root = {
|
||||
uid = config.users.users."someuser".uid;
|
||||
key = "test_key";
|
||||
};
|
||||
test_key_root_root = {
|
||||
key = "test_key";
|
||||
};
|
||||
test_key_1001_1001 = {
|
||||
uid = 1001;
|
||||
gid = 1001;
|
||||
key = "test_key";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
users.users."someuser" = {
|
||||
uid = 1000;
|
||||
group = "somegroup";
|
||||
isNormalUser = true;
|
||||
};
|
||||
users.groups."somegroup" = {
|
||||
gid = 1000;
|
||||
};
|
||||
users.users."someuser" = {
|
||||
uid = 1000;
|
||||
group = "somegroup";
|
||||
isNormalUser = true;
|
||||
};
|
||||
users.groups."somegroup" = {
|
||||
gid = 1000;
|
||||
};
|
||||
|
||||
# must run before sops sets up keys
|
||||
boot.initrd.postDeviceCommands = ''
|
||||
cp -r ${./test-assets/age-keys.txt} /run/age-keys.txt
|
||||
chmod -R 700 /run/age-keys.txt
|
||||
'';
|
||||
};
|
||||
# must run before sops sets up keys
|
||||
boot.initrd.postDeviceCommands = ''
|
||||
cp -r ${./test-assets/age-keys.txt} /run/age-keys.txt
|
||||
chmod -R 700 /run/age-keys.txt
|
||||
'';
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
@ -183,10 +198,12 @@ in {
|
|||
nodes.machine = {
|
||||
imports = [ ../../modules/sops ];
|
||||
services.openssh.enable = true;
|
||||
services.openssh.hostKeys = [{
|
||||
type = "ed25519";
|
||||
path = ./test-assets/ssh-ed25519-key;
|
||||
}];
|
||||
services.openssh.hostKeys = [
|
||||
{
|
||||
type = "ed25519";
|
||||
path = ./test-assets/ssh-ed25519-key;
|
||||
}
|
||||
];
|
||||
|
||||
sops = {
|
||||
defaultSopsFile = ./test-assets/secrets.yaml;
|
||||
|
@ -207,37 +224,39 @@ in {
|
|||
|
||||
pgp-keys = testers.runNixOSTest {
|
||||
name = "sops-pgp-keys";
|
||||
nodes.server = { lib, config, ... }: {
|
||||
imports = [ ../../modules/sops ];
|
||||
nodes.server =
|
||||
{ lib, config, ... }:
|
||||
{
|
||||
imports = [ ../../modules/sops ];
|
||||
|
||||
users.users.someuser = {
|
||||
isSystemUser = true;
|
||||
group = "nogroup";
|
||||
users.users.someuser = {
|
||||
isSystemUser = true;
|
||||
group = "nogroup";
|
||||
};
|
||||
|
||||
sops.gnupg.home = "/run/gpghome";
|
||||
sops.defaultSopsFile = ./test-assets/secrets.yaml;
|
||||
sops.secrets.test_key.owner = config.users.users.someuser.name;
|
||||
sops.secrets."nested/test/file".owner = config.users.users.someuser.name;
|
||||
sops.secrets.existing-file = {
|
||||
key = "test_key";
|
||||
path = "/run/existing-file";
|
||||
};
|
||||
# must run before sops
|
||||
system.activationScripts.gnupghome = lib.stringAfter [ "etc" ] ''
|
||||
cp -r ${./test-assets/gnupghome} /run/gpghome
|
||||
chmod -R 700 /run/gpghome
|
||||
|
||||
touch /run/existing-file
|
||||
'';
|
||||
# Useful for debugging
|
||||
#environment.systemPackages = [ pkgs.gnupg pkgs.sops ];
|
||||
#environment.variables = {
|
||||
# GNUPGHOME = "/run/gpghome";
|
||||
# SOPS_GPG_EXEC="${pkgs.gnupg}/bin/gpg";
|
||||
# SOPSFILE = "${./test-assets/secrets.yaml}";
|
||||
#};
|
||||
};
|
||||
|
||||
sops.gnupg.home = "/run/gpghome";
|
||||
sops.defaultSopsFile = ./test-assets/secrets.yaml;
|
||||
sops.secrets.test_key.owner = config.users.users.someuser.name;
|
||||
sops.secrets."nested/test/file".owner = config.users.users.someuser.name;
|
||||
sops.secrets.existing-file = {
|
||||
key = "test_key";
|
||||
path = "/run/existing-file";
|
||||
};
|
||||
# must run before sops
|
||||
system.activationScripts.gnupghome = lib.stringAfter [ "etc" ] ''
|
||||
cp -r ${./test-assets/gnupghome} /run/gpghome
|
||||
chmod -R 700 /run/gpghome
|
||||
|
||||
touch /run/existing-file
|
||||
'';
|
||||
# Useful for debugging
|
||||
#environment.systemPackages = [ pkgs.gnupg pkgs.sops ];
|
||||
#environment.variables = {
|
||||
# GNUPGHOME = "/run/gpghome";
|
||||
# SOPS_GPG_EXEC="${pkgs.gnupg}/bin/gpg";
|
||||
# SOPSFILE = "${./test-assets/secrets.yaml}";
|
||||
#};
|
||||
};
|
||||
testScript = ''
|
||||
def assertEqual(exp: str, act: str) -> None:
|
||||
if exp != act:
|
||||
|
@ -260,47 +279,49 @@ in {
|
|||
|
||||
templates = testers.runNixOSTest {
|
||||
name = "sops-templates";
|
||||
nodes.machine = { config, ... }: {
|
||||
imports = [ ../../modules/sops ];
|
||||
sops = {
|
||||
age.keyFile = "/run/age-keys.txt";
|
||||
defaultSopsFile = ./test-assets/secrets.yaml;
|
||||
secrets.test_key = { };
|
||||
nodes.machine =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [ ../../modules/sops ];
|
||||
sops = {
|
||||
age.keyFile = "/run/age-keys.txt";
|
||||
defaultSopsFile = ./test-assets/secrets.yaml;
|
||||
secrets.test_key = { };
|
||||
|
||||
# Verify that things work even with `neededForUsers` secrets. See
|
||||
# <https://github.com/Mic92/sops-nix/issues/659>.
|
||||
secrets."nested/test/file".neededForUsers = true;
|
||||
};
|
||||
# Verify that things work even with `neededForUsers` secrets. See
|
||||
# <https://github.com/Mic92/sops-nix/issues/659>.
|
||||
secrets."nested/test/file".neededForUsers = true;
|
||||
};
|
||||
|
||||
# must run before sops sets up keys
|
||||
boot.initrd.postDeviceCommands = ''
|
||||
cp -r ${./test-assets/age-keys.txt} /run/age-keys.txt
|
||||
chmod -R 700 /run/age-keys.txt
|
||||
'';
|
||||
|
||||
sops.templates.test_template = {
|
||||
content = ''
|
||||
This line is not modified.
|
||||
The next value will be replaced by ${config.sops.placeholder.test_key}
|
||||
This line is also not modified.
|
||||
# must run before sops sets up keys
|
||||
boot.initrd.postDeviceCommands = ''
|
||||
cp -r ${./test-assets/age-keys.txt} /run/age-keys.txt
|
||||
chmod -R 700 /run/age-keys.txt
|
||||
'';
|
||||
mode = "0400";
|
||||
owner = "someuser";
|
||||
group = "somegroup";
|
||||
};
|
||||
sops.templates.test_default = {
|
||||
content = ''
|
||||
Test value: ${config.sops.placeholder.test_key}
|
||||
'';
|
||||
path = "/etc/externally/linked";
|
||||
};
|
||||
|
||||
users.groups.somegroup = {};
|
||||
users.users.someuser = {
|
||||
isSystemUser = true;
|
||||
group = "somegroup";
|
||||
sops.templates.test_template = {
|
||||
content = ''
|
||||
This line is not modified.
|
||||
The next value will be replaced by ${config.sops.placeholder.test_key}
|
||||
This line is also not modified.
|
||||
'';
|
||||
mode = "0400";
|
||||
owner = "someuser";
|
||||
group = "somegroup";
|
||||
};
|
||||
sops.templates.test_default = {
|
||||
content = ''
|
||||
Test value: ${config.sops.placeholder.test_key}
|
||||
'';
|
||||
path = "/etc/externally/linked";
|
||||
};
|
||||
|
||||
users.groups.somegroup = { };
|
||||
users.users.someuser = {
|
||||
isSystemUser = true;
|
||||
group = "somegroup";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
def assertEqual(exp: str, act: str) -> None:
|
||||
|
@ -337,62 +358,72 @@ in {
|
|||
|
||||
restart-and-reload = testers.runNixOSTest {
|
||||
name = "sops-restart-and-reload";
|
||||
nodes.machine = {config, ...}: {
|
||||
imports = [ ../../modules/sops ];
|
||||
nodes.machine =
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [ ../../modules/sops ];
|
||||
|
||||
sops = {
|
||||
age.keyFile = "/run/age-keys.txt";
|
||||
defaultSopsFile = ./test-assets/secrets.yaml;
|
||||
secrets.test_key = {
|
||||
restartUnits = [ "restart-unit.service" "reload-unit.service" ];
|
||||
reloadUnits = [ "reload-trigger.service" ];
|
||||
sops = {
|
||||
age.keyFile = "/run/age-keys.txt";
|
||||
defaultSopsFile = ./test-assets/secrets.yaml;
|
||||
secrets.test_key = {
|
||||
restartUnits = [
|
||||
"restart-unit.service"
|
||||
"reload-unit.service"
|
||||
];
|
||||
reloadUnits = [ "reload-trigger.service" ];
|
||||
};
|
||||
|
||||
templates.test_template = {
|
||||
content = ''
|
||||
this is a template with
|
||||
a secret: ${config.sops.placeholder.test_key}
|
||||
'';
|
||||
restartUnits = [
|
||||
"restart-unit.service"
|
||||
"reload-unit.service"
|
||||
];
|
||||
reloadUnits = [ "reload-trigger.service" ];
|
||||
};
|
||||
};
|
||||
system.switch.enable = true;
|
||||
|
||||
# must run before sops sets up keys
|
||||
boot.initrd.postDeviceCommands = ''
|
||||
cp -r ${./test-assets/age-keys.txt} /run/age-keys.txt
|
||||
chmod -R 700 /run/age-keys.txt
|
||||
'';
|
||||
|
||||
systemd.services."restart-unit" = {
|
||||
description = "Restart unit";
|
||||
# not started on boot
|
||||
serviceConfig = {
|
||||
ExecStart = "/bin/sh -c 'echo ok > /restarted'";
|
||||
};
|
||||
};
|
||||
systemd.services."reload-unit" = {
|
||||
description = "Reload unit";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
reloadIfChanged = true;
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = "/bin/sh -c true";
|
||||
ExecReload = "/bin/sh -c 'echo ok > /reloaded'";
|
||||
};
|
||||
};
|
||||
systemd.services."reload-trigger" = {
|
||||
description = "Reload trigger unit";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = "/bin/sh -c true";
|
||||
ExecReload = "/bin/sh -c 'echo ok > /reloaded'";
|
||||
};
|
||||
};
|
||||
|
||||
templates.test_template = {
|
||||
content = ''
|
||||
this is a template with
|
||||
a secret: ${config.sops.placeholder.test_key}
|
||||
'';
|
||||
restartUnits = [ "restart-unit.service" "reload-unit.service" ];
|
||||
reloadUnits = [ "reload-trigger.service" ];
|
||||
};
|
||||
};
|
||||
system.switch.enable = true;
|
||||
|
||||
# must run before sops sets up keys
|
||||
boot.initrd.postDeviceCommands = ''
|
||||
cp -r ${./test-assets/age-keys.txt} /run/age-keys.txt
|
||||
chmod -R 700 /run/age-keys.txt
|
||||
'';
|
||||
|
||||
systemd.services."restart-unit" = {
|
||||
description = "Restart unit";
|
||||
# not started on boot
|
||||
serviceConfig = { ExecStart = "/bin/sh -c 'echo ok > /restarted'"; };
|
||||
};
|
||||
systemd.services."reload-unit" = {
|
||||
description = "Reload unit";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
reloadIfChanged = true;
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = "/bin/sh -c true";
|
||||
ExecReload = "/bin/sh -c 'echo ok > /reloaded'";
|
||||
};
|
||||
};
|
||||
systemd.services."reload-trigger" = {
|
||||
description = "Reload trigger unit";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = "/bin/sh -c true";
|
||||
ExecReload = "/bin/sh -c 'echo ok > /reloaded'";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
testScript = ''
|
||||
def assertOutput(output, *expected_lines):
|
||||
expected_lines = list(expected_lines)
|
||||
|
@ -524,32 +555,40 @@ in {
|
|||
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, ... }: {
|
||||
systemd.sysusers.enable = true;
|
||||
users.mutableUsers = true;
|
||||
system.etc.overlay.enable = true;
|
||||
boot.initrd.systemd.enable = true;
|
||||
boot.kernelPackages = pkgs.linuxPackages_latest;
|
||||
|
||||
# must run before sops sets up keys
|
||||
systemd.services."sops-install-secrets-for-users".preStart = ''
|
||||
printf '${builtins.readFile ./test-assets/age-keys.txt}' > /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, ... }: {
|
||||
services.userborn.enable = true;
|
||||
users.mutableUsers = false;
|
||||
system.etc.overlay.enable = true;
|
||||
boot.initrd.systemd.enable = true;
|
||||
boot.kernelPackages = pkgs.linuxPackages_latest;
|
||||
|
||||
# must run before sops sets up keys
|
||||
systemd.services."sops-install-secrets-for-users".preStart = ''
|
||||
printf '${builtins.readFile ./test-assets/age-keys.txt}' > /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, ... }:
|
||||
{
|
||||
systemd.sysusers.enable = true;
|
||||
users.mutableUsers = true;
|
||||
system.etc.overlay.enable = true;
|
||||
boot.initrd.systemd.enable = true;
|
||||
boot.kernelPackages = pkgs.linuxPackages_latest;
|
||||
|
||||
# must run before sops sets up keys
|
||||
systemd.services."sops-install-secrets-for-users".preStart = ''
|
||||
printf '${builtins.readFile ./test-assets/age-keys.txt}' > /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, ... }:
|
||||
{
|
||||
services.userborn.enable = true;
|
||||
users.mutableUsers = false;
|
||||
system.etc.overlay.enable = true;
|
||||
boot.initrd.systemd.enable = true;
|
||||
boot.kernelPackages = pkgs.linuxPackages_latest;
|
||||
|
||||
# must run before sops sets up keys
|
||||
systemd.services."sops-install-secrets-for-users".preStart = ''
|
||||
printf '${builtins.readFile ./test-assets/age-keys.txt}' > /run/age-keys.txt
|
||||
chmod -R 700 /run/age-keys.txt
|
||||
'';
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
{ pkgs ? import <nixpkgs> {} }:
|
||||
{
|
||||
pkgs ? import <nixpkgs> { },
|
||||
}:
|
||||
pkgs.mkShell {
|
||||
nativeBuildInputs = with pkgs; [ go delve util-linux gnupg ];
|
||||
nativeBuildInputs = with pkgs; [
|
||||
go
|
||||
delve
|
||||
util-linux
|
||||
gnupg
|
||||
];
|
||||
}
|
||||
|
|
|
@ -1,13 +1,25 @@
|
|||
{ makeSetupHook, gnupg, sops, lib }:
|
||||
{
|
||||
makeSetupHook,
|
||||
gnupg,
|
||||
sops,
|
||||
lib,
|
||||
}:
|
||||
|
||||
let
|
||||
# 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
|
||||
(makeSetupHook {
|
||||
name = "sops-pgp-hook";
|
||||
substitutions = {
|
||||
gpg = "${gnupg}/bin/gpg";
|
||||
};
|
||||
${propagatedBuildInputs} = [ sops gnupg ];
|
||||
${propagatedBuildInputs} = [
|
||||
sops
|
||||
gnupg
|
||||
];
|
||||
} ./sops-pgp-hook.bash)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# shell.nix
|
||||
with import <nixpkgs> {};
|
||||
with import <nixpkgs> { };
|
||||
mkShell {
|
||||
sopsPGPKeyDirs = [
|
||||
"./keys"
|
||||
|
@ -9,6 +9,6 @@ mkShell {
|
|||
"./non-existing-key.gpg"
|
||||
];
|
||||
nativeBuildInputs = [
|
||||
(pkgs.callPackage ../../.. {}).sops-pgp-hook
|
||||
(pkgs.callPackage ../../.. { }).sops-pgp-hook
|
||||
];
|
||||
}
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
{ pkgs ? import <nixpkgs> {}
|
||||
{
|
||||
pkgs ? import <nixpkgs> { },
|
||||
}:
|
||||
let
|
||||
sopsPkgs = import ../. { inherit pkgs; };
|
||||
in pkgs.stdenv.mkDerivation {
|
||||
in
|
||||
pkgs.stdenv.mkDerivation {
|
||||
name = "env";
|
||||
nativeBuildInputs = with pkgs; [
|
||||
bashInteractive
|
||||
gnupg
|
||||
util-linux
|
||||
nix
|
||||
sopsPkgs.sops-pgp-hook-test
|
||||
] ++ pkgs.lib.optional (pkgs.stdenv.isLinux) sopsPkgs.sops-install-secrets.unittest;
|
||||
nativeBuildInputs =
|
||||
with pkgs;
|
||||
[
|
||||
bashInteractive
|
||||
gnupg
|
||||
util-linux
|
||||
nix
|
||||
sopsPkgs.sops-pgp-hook-test
|
||||
]
|
||||
++ pkgs.lib.optional (pkgs.stdenv.isLinux) sopsPkgs.sops-install-secrets.unittest;
|
||||
# allow to prefetch shell dependencies in build phase
|
||||
dontUnpack = true;
|
||||
installPhase = ''
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
{ pkgs ? import <nixpkgs> {} }:
|
||||
{
|
||||
pkgs ? import <nixpkgs> { },
|
||||
}:
|
||||
pkgs.mkShell {
|
||||
nativeBuildInputs = with pkgs; [
|
||||
bashInteractive
|
||||
|
|
Loading…
Reference in a new issue