mirror of https://github.com/ryantm/agenix.git
modules/age: add support for minisign
This allows one to verify the integrity of a secret managed with agenix. As this is an initial implementation, I decided to keep it as simple as I could.
This commit is contained in:
parent
37b1d2aa3f
commit
0b7e2c2376
126
modules/age.nix
126
modules/age.nix
|
@ -1,16 +1,29 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.age;
|
||||
rage = pkgs.callPackage ../pkgs/rage.nix {};
|
||||
|
||||
rage = pkgs.callPackage ../pkgs/rage.nix { };
|
||||
ageBin = "${rage}/bin/rage";
|
||||
|
||||
minisign = pkgs.minisign;
|
||||
minisignBin = "${minisign}/bin/minisign";
|
||||
|
||||
users = config.users.users;
|
||||
|
||||
identities = builtins.concatStringsSep " " (map (path: "-i ${path}") cfg.sshKeyPaths);
|
||||
|
||||
installSecret = secretType: ''
|
||||
_invalid=0
|
||||
${verifySecret secretType}
|
||||
|
||||
if (( _invalid == 0 )); then
|
||||
${decryptSecret secretType}
|
||||
fi
|
||||
'';
|
||||
|
||||
decryptSecret = secretType: ''
|
||||
echo "decrypting ${secretType.file} to ${secretType.path}..."
|
||||
TMP_FILE="${secretType.path}.tmp"
|
||||
mkdir -p $(dirname ${secretType.path})
|
||||
|
@ -20,11 +33,39 @@ let
|
|||
mv -f "$TMP_FILE" '${secretType.path}'
|
||||
'';
|
||||
|
||||
rootOwnedSecrets = builtins.filter (st: st.owner == "root" && st.group == "root") (builtins.attrValues cfg.secrets);
|
||||
installRootOwnedSecrets = builtins.concatStringsSep "\n" (["echo '[agenix] decrypting root secrets...'"] ++ (map installSecret rootOwnedSecrets));
|
||||
verifySecret = secretType:
|
||||
let
|
||||
signature =
|
||||
if secretType.signature.pubKeyFile != null then
|
||||
"-p ${secretType.signature.pubKeyFile}"
|
||||
else if secretType.signature.pubKey != null then
|
||||
"-P ${secretType.signature.pubKey}"
|
||||
else
|
||||
throw "To verify a file's contents, either the pubKeyFile or pubKey options must be set.";
|
||||
|
||||
nonRootSecrets = builtins.filter (st: st.owner != "root" || st.group != "root") (builtins.attrValues cfg.secrets);
|
||||
installNonRootSecrets = builtins.concatStringsSep "\n" (["echo '[agenix] decrypting non-root secrets...'"] ++ (map installSecret nonRootSecrets));
|
||||
sigfile =
|
||||
if secretType.signature.file != null then
|
||||
secretType.signature.file
|
||||
else
|
||||
throw "A signature file is required in order to verify a file's contents.";
|
||||
in
|
||||
lib.optionalString (secretType.signature != null) ''
|
||||
echo "verifying ${secretType.file}..."
|
||||
${minisignBin} -Vm ${secretType.file} ${signature} -x ${sigfile}
|
||||
_invalid=$?
|
||||
'';
|
||||
|
||||
rootOwnedSecrets = builtins.filter
|
||||
(st: st.owner == "root" && st.group == "root")
|
||||
(builtins.attrValues cfg.secrets);
|
||||
installRootOwnedSecrets = builtins.concatStringsSep "\n"
|
||||
([ "echo '[agenix] decrypting root secrets...'" ] ++ (map installSecret rootOwnedSecrets));
|
||||
|
||||
nonRootSecrets = builtins.filter
|
||||
(st: st.owner != "root" || st.group != "root")
|
||||
(builtins.attrValues cfg.secrets);
|
||||
installNonRootSecrets = builtins.concatStringsSep "\n"
|
||||
([ "echo '[agenix] decrypting non-root secrets...'" ] ++ (map installSecret nonRootSecrets));
|
||||
|
||||
secretType = types.submodule ({ config, ... }: {
|
||||
options = {
|
||||
|
@ -42,12 +83,12 @@ let
|
|||
'';
|
||||
};
|
||||
path = mkOption {
|
||||
type = types.str;
|
||||
default = "/run/secrets/${config.name}";
|
||||
description = ''
|
||||
Path where the decrypted secret is installed.
|
||||
'';
|
||||
};
|
||||
type = types.str;
|
||||
default = "/run/secrets/${config.name}";
|
||||
description = ''
|
||||
Path where the decrypted secret is installed.
|
||||
'';
|
||||
};
|
||||
mode = mkOption {
|
||||
type = types.str;
|
||||
default = "0400";
|
||||
|
@ -69,30 +110,77 @@ let
|
|||
Group of the file.
|
||||
'';
|
||||
};
|
||||
signature = mkOption {
|
||||
type = types.nullOr (types.submodule ({ ... }: {
|
||||
options = {
|
||||
pubKeyFile = mkOption {
|
||||
type = with types; nullOr path;
|
||||
default = cfg.defaultPubKeyFile;
|
||||
description = ''
|
||||
Public key file used to verify the secret.
|
||||
'';
|
||||
};
|
||||
pubKey = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = cfg.defaultPubKey;
|
||||
description = ''
|
||||
Public key used to verify the secret.
|
||||
'';
|
||||
};
|
||||
file = mkOption {
|
||||
type = with types; nullOr path; # TODO: support strs as relative path
|
||||
default = null;
|
||||
description = ''
|
||||
File containing the signature of the secret.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}));
|
||||
default = null;
|
||||
description = ''
|
||||
Options for configuring signature verification of secrets.
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
in {
|
||||
in
|
||||
{
|
||||
options.age = {
|
||||
secrets = mkOption {
|
||||
type = types.attrsOf secretType;
|
||||
default = {};
|
||||
default = { };
|
||||
description = ''
|
||||
Attrset of secrets.
|
||||
'';
|
||||
};
|
||||
sshKeyPaths = mkOption {
|
||||
type = types.listOf types.path;
|
||||
default = if config.services.openssh.enable then
|
||||
map (e: e.path) (lib.filter (e: e.type == "rsa" || e.type == "ed25519") config.services.openssh.hostKeys)
|
||||
else [];
|
||||
default =
|
||||
if config.services.openssh.enable then
|
||||
map (e: e.path) (lib.filter (e: e.type == "rsa" || e.type == "ed25519") config.services.openssh.hostKeys)
|
||||
else [ ];
|
||||
description = ''
|
||||
Path to SSH keys to be used as identities in age decryption.
|
||||
'';
|
||||
};
|
||||
defaultPubKey = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
The default public key used to verify signatures.
|
||||
'';
|
||||
};
|
||||
defaultPubKeyFile = mkOption {
|
||||
type = with types; nullOr path;
|
||||
default = null;
|
||||
description = ''
|
||||
The default public key file used to verify signatures.
|
||||
'';
|
||||
};
|
||||
};
|
||||
config = mkIf (cfg.secrets != {}) {
|
||||
config = mkIf (cfg.secrets != { }) {
|
||||
assertions = [{
|
||||
assertion = cfg.sshKeyPaths != [];
|
||||
assertion = cfg.sshKeyPaths != [ ];
|
||||
message = "age.sshKeyPaths must be set.";
|
||||
}];
|
||||
|
||||
|
|
Loading…
Reference in New Issue