mirror of
https://github.com/Mic92/sops-nix.git
synced 2024-12-14 11:57:52 +00:00
commit
b72eabc86e
4 changed files with 176 additions and 0 deletions
|
@ -294,6 +294,7 @@ in {
|
|||
};
|
||||
};
|
||||
imports = [
|
||||
./templates
|
||||
(mkRenamedOptionModule [ "sops" "gnupgHome" ] [ "sops" "gnupg" "home" ])
|
||||
(mkRenamedOptionModule [ "sops" "sshKeyPaths" ] [ "sops" "gnupg" "sshKeyPaths" ])
|
||||
];
|
||||
|
|
102
modules/sops/templates/default.nix
Normal file
102
modules/sops/templates/default.nix
Normal file
|
@ -0,0 +1,102 @@
|
|||
{ config, pkgs, lib, options, ... }:
|
||||
with lib;
|
||||
with lib.types;
|
||||
with builtins;
|
||||
let
|
||||
cfg = config.sops;
|
||||
secretsForUsers = lib.filterAttrs (_: v: v.neededForUsers) cfg.secrets;
|
||||
in {
|
||||
options.sops = {
|
||||
templates = mkOption {
|
||||
type = attrsOf (submodule ({ config, ... }: {
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = str;
|
||||
default = config._module.args.name;
|
||||
description = ''
|
||||
Name of the file used in /run/secrets-rendered
|
||||
'';
|
||||
};
|
||||
path = mkOption {
|
||||
type = str;
|
||||
default = "/run/secrets-rendered/${config.name}";
|
||||
};
|
||||
content = mkOption {
|
||||
type = lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Content of the file
|
||||
'';
|
||||
};
|
||||
mode = mkOption {
|
||||
type = str;
|
||||
default = "0400";
|
||||
description = ''
|
||||
Permissions mode of the rendered secret file in octal.
|
||||
'';
|
||||
};
|
||||
owner = mkOption {
|
||||
type = str;
|
||||
default = "root";
|
||||
description = ''
|
||||
User of the file.
|
||||
'';
|
||||
};
|
||||
group = mkOption {
|
||||
type = str;
|
||||
default = config.users.users.${config.owner}.group;
|
||||
description = ''
|
||||
Group of the file.
|
||||
'';
|
||||
};
|
||||
file = mkOption {
|
||||
type = types.path;
|
||||
default = pkgs.writeText config.name config.content;
|
||||
visible = false;
|
||||
readOnly = true;
|
||||
};
|
||||
};
|
||||
}));
|
||||
default = { };
|
||||
};
|
||||
placeholder = mkOption {
|
||||
type = attrsOf (mkOptionType {
|
||||
name = "coercibleToString";
|
||||
description = "value that can be coerced to string";
|
||||
check = strings.isCoercibleToString;
|
||||
merge = mergeEqualOption;
|
||||
});
|
||||
default = { };
|
||||
visible = false;
|
||||
};
|
||||
};
|
||||
|
||||
config = optionalAttrs (options ? sops.secrets)
|
||||
(mkIf (config.sops.templates != { }) {
|
||||
sops.placeholder = mapAttrs
|
||||
(name: _: mkDefault "<SOPS:${hashString "sha256" name}:PLACEHOLDER>")
|
||||
config.sops.secrets;
|
||||
|
||||
system.activationScripts.renderSecrets = mkIf (cfg.templates != { })
|
||||
(stringAfter ([ "setupSecrets" ]
|
||||
++ optional (secretsForUsers != { }) "setupSecretsForUsers") ''
|
||||
echo Setting up sops templates...
|
||||
${concatMapStringsSep "\n" (name:
|
||||
let
|
||||
tpl = config.sops.templates.${name};
|
||||
substitute = pkgs.writers.writePython3 "substitute" { }
|
||||
(readFile ./subs.py);
|
||||
subst-pairs = pkgs.writeText "pairs" (concatMapStringsSep "\n"
|
||||
(name:
|
||||
"${toString config.sops.placeholder.${name}} ${
|
||||
config.sops.secrets.${name}.path
|
||||
}") (attrNames config.sops.secrets));
|
||||
in ''
|
||||
mkdir -p "${dirOf tpl.path}"
|
||||
(umask 077; ${substitute} ${tpl.file} ${subst-pairs} > ${tpl.path})
|
||||
chmod "${tpl.mode}" "${tpl.path}"
|
||||
chown "${tpl.owner}:${tpl.group}" "${tpl.path}"
|
||||
'') (attrNames config.sops.templates)}
|
||||
'');
|
||||
});
|
||||
}
|
25
modules/sops/templates/subs.py
Normal file
25
modules/sops/templates/subs.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
from sys import argv
|
||||
|
||||
|
||||
def substitute(target: str, subst: str) -> str:
|
||||
with open(target) as f:
|
||||
content = f.read()
|
||||
|
||||
with open(subst) as f:
|
||||
subst_pairs = f.read().splitlines()
|
||||
|
||||
for pair in subst_pairs:
|
||||
placeholder, path = pair.split()
|
||||
with open(path) as f:
|
||||
content = content.replace(placeholder, f.read())
|
||||
|
||||
return content
|
||||
|
||||
|
||||
def main() -> None:
|
||||
target = argv[1]
|
||||
subst = argv[2]
|
||||
print(substitute(target, subst))
|
||||
|
||||
|
||||
main()
|
|
@ -202,6 +202,54 @@
|
|||
inherit (pkgs) system;
|
||||
};
|
||||
|
||||
templates = makeTest {
|
||||
name = "sops-templates";
|
||||
nodes.machine = { config, ... }: {
|
||||
imports = [ ../../modules/sops ];
|
||||
sops = {
|
||||
age.keyFile = ./test-assets/age-keys.txt;
|
||||
defaultSopsFile = ./test-assets/secrets.yaml;
|
||||
secrets.test_key = { };
|
||||
};
|
||||
|
||||
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";
|
||||
};
|
||||
|
||||
users.groups.somegroup = {};
|
||||
users.users.someuser = {
|
||||
isSystemUser = true;
|
||||
group = "somegroup";
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
machine.succeed("[ $(stat -c%U /run/secrets-rendered/test_template) = 'someuser' ]")
|
||||
machine.succeed("[ $(stat -c%G /run/secrets-rendered/test_template) = 'somegroup' ]")
|
||||
|
||||
expected = """
|
||||
This line is not modified.
|
||||
The next value will be replaced by test_value
|
||||
This line is also not modified.
|
||||
"""
|
||||
rendered = machine.succeed("cat /run/secrets-rendered/test_template")
|
||||
|
||||
if rendered.strip() != expected.strip():
|
||||
raise Exception("Template is not rendered correctly")
|
||||
'';
|
||||
} {
|
||||
inherit pkgs;
|
||||
inherit (pkgs) system;
|
||||
};
|
||||
|
||||
restart-and-reload = makeTest {
|
||||
name = "sops-restart-and-reload";
|
||||
nodes.machine = { pkgs, lib, config, ... }: {
|
||||
|
|
Loading…
Reference in a new issue