Working secrets implementation
This commit is contained in:
@@ -32,6 +32,7 @@ with lib; {
|
||||
./fudo/password.nix
|
||||
./fudo/postgres.nix
|
||||
./fudo/prometheus.nix
|
||||
./fudo/secrets.nix
|
||||
./fudo/secure-dns-proxy.nix
|
||||
./fudo/sites.nix
|
||||
./fudo/slynk.nix
|
||||
|
||||
+25
-15
@@ -4,6 +4,11 @@ with lib;
|
||||
let
|
||||
cfg = config.fudo.client.dns;
|
||||
|
||||
ssh-key-files =
|
||||
map (host-key: host-key.path) config.services.openssh.hostKeys;
|
||||
|
||||
ssh-key-args = concatStringsSep " " (map (file: "-f ${file}") ssh-key-files);
|
||||
|
||||
in {
|
||||
options.fudo.client.dns = {
|
||||
enable = mkEnableOption "Enable Fudo DynDNS Client.";
|
||||
@@ -46,23 +51,24 @@ in {
|
||||
|
||||
frequency = mkOption {
|
||||
type = types.str;
|
||||
description = "Frequency at which to report the local IP(s) to backplane.";
|
||||
description =
|
||||
"Frequency at which to report the local IP(s) to backplane.";
|
||||
default = "*:0/15";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
description = "User as which to run the client script (must have access to password file).";
|
||||
description =
|
||||
"User as which to run the client script (must have access to password file).";
|
||||
default = "backplane-dns-client";
|
||||
};
|
||||
|
||||
external-interface = mkOption {
|
||||
type = with types; nullOr str;
|
||||
description = "Interface with which this host communicates with the larger internet.";
|
||||
description =
|
||||
"Interface with which this host communicates with the larger internet.";
|
||||
default = null;
|
||||
};
|
||||
|
||||
# FIXME: take the relevant SSH package
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
@@ -81,20 +87,16 @@ in {
|
||||
partOf = [ "backplane-dns-client.service" ];
|
||||
wantedBy = [ "timers.target" ];
|
||||
requires = [ "network-online.target" ];
|
||||
timerConfig = {
|
||||
OnCalendar = cfg.frequency;
|
||||
};
|
||||
timerConfig = { OnCalendar = cfg.frequency; };
|
||||
};
|
||||
|
||||
services.backplane-dns-client-pw-file = {
|
||||
enable = true;
|
||||
requiredBy = [ "backplane-dns-client.services" ];
|
||||
reloadIfChanged = true;
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
};
|
||||
serviceConfig = { Type = "oneshot"; };
|
||||
script = ''
|
||||
chmod 600 ${cfg.password-file}
|
||||
chmod 400 ${cfg.password-file}
|
||||
chown ${cfg.user} ${cfg.password-file}
|
||||
'';
|
||||
};
|
||||
@@ -105,12 +107,20 @@ in {
|
||||
Type = "oneshot";
|
||||
StandardOutput = "journal";
|
||||
User = cfg.user;
|
||||
ExecStart = pkgs.writeShellScript "start-backplane-dns-client.sh" ''
|
||||
${pkgs.backplane-dns-client}/bin/backplane-dns-client ${
|
||||
optionalString cfg.ipv4 "-4"
|
||||
} ${optionalString cfg.ipv6 "-6"} ${
|
||||
optionalString cfg.sshfp ssh-key-args
|
||||
} ${
|
||||
optionalString (cfg.external-interface != null)
|
||||
"--interface=${cfg.external-interface}"
|
||||
} --domain=${cfg.domain} --server=${cfg.server} --password-file=${cfg.password-file}
|
||||
'';
|
||||
};
|
||||
# Needed to generate SSH fingerprinst
|
||||
path = [ pkgs.openssh ];
|
||||
reloadIfChanged = true;
|
||||
script = ''
|
||||
${pkgs.backplane-dns-client}/bin/backplane-dns-client ${optionalString cfg.ipv4 "-4"} ${optionalString cfg.ipv6 "-6"} ${optionalString cfg.sshfp "-f"} ${optionalString (cfg.external-interface != null) "--interface=${cfg.external-interface}"} --domain=${cfg.domain} --server=${cfg.server} --password-file=${cfg.password-file}
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
all-secrets = config.fudo.secrets;
|
||||
|
||||
encrypt-on-disk = name:
|
||||
{ target-host, source-file }:
|
||||
pkgs.stdenv.mkDerivation {
|
||||
name = "${name}-secret";
|
||||
phases = "installPhase";
|
||||
buildInputs = [ pkgs.age ];
|
||||
installPhase = let key = config.fudo.hosts.${target-host}.ssh-pubkey;
|
||||
in ''
|
||||
age -a -r "${key}" -o $out ${source-file}
|
||||
'';
|
||||
};
|
||||
|
||||
decrypt-script = name:
|
||||
{ source-file, target-host, target-file, decrypt-key, user, group
|
||||
, permissions }:
|
||||
pkgs.writeShellScript "decrypt-fudo-secret-${name}.sh" ''
|
||||
rm -rf ${target-file}
|
||||
age -d -i ${decrypt-key} -o ${target-file} ${
|
||||
encrypt-on-disk name { inherit source-file target-host; }
|
||||
}
|
||||
chown ${user}:${group} ${target-file}
|
||||
chmod ${permissions} ${target-file}
|
||||
'';
|
||||
|
||||
secret-service = name:
|
||||
{ source-file, target-host, target-file, user, group, permissions
|
||||
, key-type ? "ed25519" }: {
|
||||
description = "decrypt secret ${name} for ${target-host}.";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStartPre = pkgs.writeShellScript "prepare-secrets-dir.sh" ''
|
||||
TARGET_DIR=$(dirname ${target-file})
|
||||
if [[ ! -d "$TARGET_DIR" ]]; then
|
||||
mkdir -p "$TARGET_DIR"
|
||||
fi
|
||||
'';
|
||||
ExecStart = let
|
||||
decrypt-keys =
|
||||
filter (key: key.type == key-type) config.services.openssh.hostKeys;
|
||||
decrypt-key = head (map (key: key.path) decrypt-keys);
|
||||
in decrypt-script name {
|
||||
inherit source-file target-host target-file decrypt-key user group
|
||||
permissions;
|
||||
};
|
||||
};
|
||||
path = [ pkgs.age ];
|
||||
};
|
||||
|
||||
secretOpts = { ... }: {
|
||||
options = with types; {
|
||||
source-file = mkOption {
|
||||
type = path;
|
||||
description = "File from which to load the secret.";
|
||||
};
|
||||
|
||||
target-host = mkOption {
|
||||
type = str;
|
||||
description =
|
||||
"Host to which the secret belongs (determins SSH key to encrypt).";
|
||||
};
|
||||
|
||||
target-file = mkOption {
|
||||
type = str;
|
||||
description =
|
||||
"Target file on the host; the secret will be decrypted to this file.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = str;
|
||||
description = "User (on target host) to which the file will belong.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = str;
|
||||
description = "Group (on target host) to which the file will belong.";
|
||||
default = "nogroup";
|
||||
};
|
||||
|
||||
permissions = mkOption {
|
||||
type = str;
|
||||
description = "Permissions to set on the target file.";
|
||||
default = "0400";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
in {
|
||||
options.fudo.secrets = with types;
|
||||
mkOption {
|
||||
type = attrsOf (submodule secretOpts);
|
||||
description = "Map of secrets to secret config.";
|
||||
default = { };
|
||||
};
|
||||
|
||||
config = {
|
||||
systemd.services = let
|
||||
hostname = config.instance.hostname;
|
||||
host-secrets =
|
||||
filterAttrs (secret: secretOpts: secretOpts.target-host == hostname)
|
||||
all-secrets;
|
||||
in mapAttrs' (secret: secretOpts:
|
||||
(nameValuePair "fudo-secret-${secret}"
|
||||
(secret-service secret secretOpts))) host-secrets;
|
||||
};
|
||||
}
|
||||
@@ -196,6 +196,7 @@ in {
|
||||
openssh.authorizedKeys.keys =
|
||||
concatMap (hostOpts: hostOpts.build-pubkeys)
|
||||
(attrValues site-hosts);
|
||||
shell = pkgs.bash;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user