fix restic secrets
This commit is contained in:
parent
a48b618923
commit
7c31bc5bda
4 changed files with 111 additions and 20 deletions
|
|
@ -1,4 +1,8 @@
|
||||||
{ pkgs, lib }:
|
{ pkgs, lib }:
|
||||||
|
let
|
||||||
|
inherit (builtins) isAttrs hasAttr;
|
||||||
|
inherit (lib) concatStringsSep;
|
||||||
|
in
|
||||||
rec {
|
rec {
|
||||||
# Replace secrets in a file.
|
# Replace secrets in a file.
|
||||||
# - userConfig is an attrset that will produce a config file.
|
# - userConfig is an attrset that will produce a config file.
|
||||||
|
|
@ -228,8 +232,47 @@ rec {
|
||||||
results = pkgs.lib.runTests tests;
|
results = pkgs.lib.runTests tests;
|
||||||
in
|
in
|
||||||
if results != [ ] then
|
if results != [ ] then
|
||||||
builtins.throw (builtins.concatStringsSep "\n" (map resultToString results))
|
builtins.throw (builtins.concatStringsSep "\n" (map resultToString (lib.traceValSeqN 3 results)))
|
||||||
else
|
else
|
||||||
pkgs.runCommand "nix-flake-tests-success" { } "echo > $out";
|
pkgs.runCommand "nix-flake-tests-success" { } "echo > $out";
|
||||||
|
|
||||||
|
|
||||||
|
genConfigOutOfBandSystemd = { config, configLocation, generator }:
|
||||||
|
{
|
||||||
|
loadCredentials = getLoadCredentials "source" config;
|
||||||
|
preStart = lib.mkBefore (replaceSecrets {
|
||||||
|
userConfig = updateToLoadCredentials "source" "$CREDENTIALS_DIRECTORY" config;
|
||||||
|
resultPath = configLocation;
|
||||||
|
inherit generator;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
updateToLoadCredentials = sourceField: rootDir: attrs:
|
||||||
|
let
|
||||||
|
hasPlaceholderField = v: isAttrs v && hasAttr sourceField v;
|
||||||
|
|
||||||
|
valueOrLoadCredential = path: value:
|
||||||
|
if ! (hasPlaceholderField value)
|
||||||
|
then value
|
||||||
|
else value // { ${sourceField} = rootDir + "/" + concatStringsSep "_" path; };
|
||||||
|
in
|
||||||
|
mapAttrsRecursiveCond (v: ! (hasPlaceholderField v)) valueOrLoadCredential attrs;
|
||||||
|
|
||||||
|
getLoadCredentials = sourceField: attrs:
|
||||||
|
let
|
||||||
|
hasPlaceholderField = v: isAttrs v && hasAttr sourceField v;
|
||||||
|
|
||||||
|
addPathField = path: value:
|
||||||
|
if ! (hasPlaceholderField value)
|
||||||
|
then value
|
||||||
|
else value // { inherit path; };
|
||||||
|
|
||||||
|
secretsWithPath = mapAttrsRecursiveCond (v: ! (hasPlaceholderField v)) addPathField attrs;
|
||||||
|
|
||||||
|
allSecrets = collect (v: hasPlaceholderField v) secretsWithPath;
|
||||||
|
|
||||||
|
genLoadCredentials = secret:
|
||||||
|
"${concatStringsSep "_" secret.path}:${secret.${sourceField}}";
|
||||||
|
in
|
||||||
|
map genLoadCredentials allSecrets;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ let
|
||||||
For Restic, the same user must be used for all instances.
|
For Restic, the same user must be used for all instances.
|
||||||
'';
|
'';
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
readOnly = true;
|
|
||||||
default = cfg.user;
|
default = cfg.user;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -31,7 +30,6 @@ let
|
||||||
For Restic, the same group must be used for all instances.
|
For Restic, the same group must be used for all instances.
|
||||||
'';
|
'';
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
readOnly = true;
|
|
||||||
default = cfg.group;
|
default = cfg.group;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -55,13 +53,15 @@ let
|
||||||
description = "Repository location";
|
description = "Repository location";
|
||||||
};
|
};
|
||||||
|
|
||||||
extraSecrets = lib.mkOption {
|
secrets = lib.mkOption {
|
||||||
type = lib.types.attrsOf shblib.secretFileType;
|
type = lib.types.attrsOf shblib.secretFileType;
|
||||||
default = {};
|
default = {};
|
||||||
description = ''
|
description = ''
|
||||||
Extra secrets needed to access the repository where the backups will be stored.
|
Secrets needed to access the repository where the backups will be stored.
|
||||||
|
|
||||||
|
See [s3 config](https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html#amazon-s3) for an example
|
||||||
|
and [list](https://restic.readthedocs.io/en/latest/040_backup.html#environment-variables) for the list of all secrets.
|
||||||
|
|
||||||
See [s3 config](https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html#amazon-s3) for an example.
|
|
||||||
'';
|
'';
|
||||||
example = lib.literalExpression ''
|
example = lib.literalExpression ''
|
||||||
{
|
{
|
||||||
|
|
@ -228,12 +228,6 @@ in
|
||||||
backupPrepareCommand = lib.strings.concatStringsSep "\n" instance.hooks.before_backup;
|
backupPrepareCommand = lib.strings.concatStringsSep "\n" instance.hooks.before_backup;
|
||||||
|
|
||||||
backupCleanupCommand = lib.strings.concatStringsSep "\n" instance.hooks.after_backup;
|
backupCleanupCommand = lib.strings.concatStringsSep "\n" instance.hooks.after_backup;
|
||||||
} // lib.attrsets.optionalAttrs (repository.extraSecrets != {}) {
|
|
||||||
environmentFile = shblib.replaceSecrets {
|
|
||||||
userConfig = repository.extraSecrets;
|
|
||||||
resultPath = "/var/lib/backup/${name}";
|
|
||||||
generator = name: v: pkgs.writeText "template" (lib.generators.toINIWithGlobalSection {} v);
|
|
||||||
};
|
|
||||||
} // lib.attrsets.optionalAttrs (builtins.length instance.excludePatterns > 0) {
|
} // lib.attrsets.optionalAttrs (builtins.length instance.excludePatterns > 0) {
|
||||||
exclude = instance.excludePatterns;
|
exclude = instance.excludePatterns;
|
||||||
};
|
};
|
||||||
|
|
@ -245,12 +239,42 @@ in
|
||||||
|
|
||||||
systemd.services =
|
systemd.services =
|
||||||
let
|
let
|
||||||
mkRepositorySettings = name: instance: repository: {
|
mkRepositorySettings = name: instance: repository:
|
||||||
"restic-backups-${name}_${repoSlugName repository.path}".serviceConfig = {
|
let
|
||||||
|
serviceName = "restic-backups-${name}_${repoSlugName repository.path}";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
${serviceName} = lib.mkMerge [
|
||||||
|
{
|
||||||
|
serviceConfig = {
|
||||||
Nice = cfg.performance.niceness;
|
Nice = cfg.performance.niceness;
|
||||||
IOSchedulingClass = cfg.performance.ioSchedulingClass;
|
IOSchedulingClass = cfg.performance.ioSchedulingClass;
|
||||||
IOSchedulingPriority = cfg.performance.ioPriority;
|
IOSchedulingPriority = cfg.performance.ioPriority;
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
(lib.attrsets.optionalAttrs (repository.secrets != {})
|
||||||
|
{
|
||||||
|
serviceConfig.EnvironmentFile = [
|
||||||
|
"/run/secrets/restic/${serviceName}"
|
||||||
|
];
|
||||||
|
after = [ "${serviceName}-pre.service" ];
|
||||||
|
requires = [ "${serviceName}-pre.service" ];
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
"${serviceName}-pre" = lib.mkIf (repository.secrets != {})
|
||||||
|
(let
|
||||||
|
script = shblib.genConfigOutOfBandSystemd {
|
||||||
|
config = repository.secrets;
|
||||||
|
configLocation = "/run/secrets/restic/${serviceName}";
|
||||||
|
generator = name: v: pkgs.writeText "template" (lib.generators.toINIWithGlobalSection {} { globalSection = v; });
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
script = script.preStart;
|
||||||
|
serviceConfig.Type = "oneshot";
|
||||||
|
serviceConfig.LoadCredential = script.loadCredentials;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
mkSettings = name: instance: builtins.map (mkRepositorySettings name instance) instance.repositories;
|
mkSettings = name: instance: builtins.map (mkRepositorySettings name instance) instance.repositories;
|
||||||
in
|
in
|
||||||
|
|
|
||||||
|
|
@ -76,8 +76,8 @@ This assumes you have access to such a remote S3 store, for example by using [Ba
|
||||||
};
|
};
|
||||||
|
|
||||||
+ extraSecrets = {
|
+ extraSecrets = {
|
||||||
+ AWS_ACCESS_KEY_ID="<path/to/access_key_id>";
|
+ AWS_ACCESS_KEY_ID.source="<path/to/access_key_id>";
|
||||||
+ AWS_SECRET_ACCESS_KEY="<path/to/secret_access_key>";
|
+ AWS_SECRET_ACCESS_KEY.source="<path/to/secret_access_key>";
|
||||||
+ };
|
+ };
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ let
|
||||||
pkgs' = pkgs;
|
pkgs' = pkgs;
|
||||||
|
|
||||||
testLib = pkgs.callPackage ../common.nix {};
|
testLib = pkgs.callPackage ../common.nix {};
|
||||||
|
shblib = pkgs.callPackage ../../lib {};
|
||||||
|
|
||||||
base = testLib.base [
|
base = testLib.base [
|
||||||
../../modules/blocks/restic.nix
|
../../modules/blocks/restic.nix
|
||||||
|
|
@ -38,6 +39,12 @@ in
|
||||||
OnCalendar = "00:00:00";
|
OnCalendar = "00:00:00";
|
||||||
RandomizedDelaySec = "5h";
|
RandomizedDelaySec = "5h";
|
||||||
};
|
};
|
||||||
|
# Those are not needed by the repository but are still included
|
||||||
|
# so we can test them in the hooks section.
|
||||||
|
secrets = {
|
||||||
|
A.source = pkgs.writeText "A" "secretA";
|
||||||
|
B.source = pkgs.writeText "B" "secretB";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
path = "/opt/repos/B";
|
path = "/opt/repos/B";
|
||||||
|
|
@ -47,6 +54,23 @@ in
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
hooks.before_backup = [''
|
||||||
|
echo $RUNTIME_DIRECTORY
|
||||||
|
if [ "$RUNTIME_DIRECTORY" = /run/restic-backups-testinstance_opt_repos_A ]; then
|
||||||
|
if ! [ -f /run/secrets/restic/restic-backups-testinstance_opt_repos_A ]; then
|
||||||
|
exit 10
|
||||||
|
fi
|
||||||
|
if [ -z "$A" ] || ! [ "$A" = "secretA" ]; then
|
||||||
|
echo "A:$A"
|
||||||
|
exit 11
|
||||||
|
fi
|
||||||
|
if [ -z "$B" ] || ! [ "$B" = "secretB" ]; then
|
||||||
|
echo "A:$A"
|
||||||
|
exit 12
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
''];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue