nixos-config/modules/secrets.nix

253 lines
7.8 KiB
Nix
Raw Normal View History

2021-06-16 23:42:44 +03:00
{ pkgs, config, lib, inputs, ... }:
2019-08-27 23:41:02 +04:00
with lib;
with types;
let
2022-08-18 18:57:47 +03:00
password-store = config.secretsConfig.password-store;
2023-01-26 00:27:05 +03:00
password-store-relative = removePrefix config.home-manager.users.${config.mainuser}.home.homeDirectory password-store;
2021-06-16 23:42:44 +03:00
secret = { name, ... }: {
options = {
encrypted = mkOption {
type = path;
default = "${password-store}/${name}.gpg";
};
decrypted = mkOption {
type = path;
default = "/var/secrets/${name}";
};
decrypt = mkOption {
default = pkgs.writeShellScript "gpg-decrypt" ''
set -euo pipefail
2022-08-18 18:57:47 +03:00
export GNUPGHOME=${config.secretsConfig.gnupgHome}
2021-06-16 23:42:44 +03:00
export GPG_TTY="$(tty)"
${pkgs.gnupg}/bin/gpg-connect-agent updatestartuptty /bye 1>&2
${pkgs.gnupg}/bin/gpg --batch --no-tty --decrypt
'';
};
user = mkOption {
type = str;
2022-12-10 22:34:39 +03:00
default = config.mainuser;
2021-06-16 23:42:44 +03:00
};
owner = mkOption {
type = str;
default = "root:root";
};
permissions = mkOption {
type = lib.types.addCheck lib.types.str
2023-01-26 02:12:00 +03:00
(perm: (builtins.match "[0-7]{3}" perm) != null);
2021-06-16 23:42:44 +03:00
default = "400";
};
services = mkOption {
type = listOf str;
default = [ "${name}" ];
};
__toString = mkOption {
readOnly = true;
default = s: s.decrypted;
};
2020-08-04 01:44:50 +04:00
};
2021-06-16 23:42:44 +03:00
};
2022-01-29 00:41:41 +03:00
activate-secrets = pkgs.writeShellScriptBin "activate-secrets" ''
set -euo pipefail
2023-01-26 00:27:05 +03:00
PATH="${with pkgs; lib.makeBinPath [ openssh gnupg coreutils ]}:$PATH"
export SSH_AUTH_SOCK="$1"
2022-10-21 13:57:17 +03:00
export GNUPGHOME=${config.secretsConfig.gnupgHome}
2022-01-29 00:41:41 +03:00
if [ -d "${password-store}/.git" ]; then
2023-01-26 00:27:05 +03:00
${pkgs.git}/bin/git -C "${password-store}" pull
2022-01-29 00:41:41 +03:00
else
echo "${lib.escapeShellArg config.secretsConfig.repo}"
2023-01-26 00:27:05 +03:00
${pkgs.git}/bin/git clone ${
2022-01-29 00:41:41 +03:00
lib.escapeShellArg config.secretsConfig.repo
} "${password-store}"
fi
2022-12-14 23:46:25 +03:00
cat ${password-store}/ssh-builder.gpg | ${pkgs.gnupg}/bin/gpg --decrypt > /dev/null
2023-01-26 00:27:05 +03:00
[ ! -z "${allServices}" ] && /run/wrappers/bin/sudo systemctl restart ${allServices}
2022-01-29 00:41:41 +03:00
'';
2021-06-16 23:42:44 +03:00
decrypt = name: cfg:
2023-01-26 00:41:28 +03:00
with cfg; let
doas-user = "/run/wrappers/bin/doas -u ${user}";
in {
2021-06-16 23:42:44 +03:00
"${name}-secrets" = rec {
2022-08-18 18:00:14 +03:00
wantedBy = [ "multi-user.target" ];
2021-06-16 23:42:44 +03:00
requires = [ "user@1000.service" ];
after = requires;
preStart = ''
2023-01-26 00:41:28 +03:00
${doas-user} stat '${encrypted}'
2021-06-16 23:42:44 +03:00
mkdir -p '${builtins.dirOf decrypted}'
'';
script = ''
2023-01-26 00:41:28 +03:00
if ${doas-user} cat '${encrypted}' | ${doas-user} ${cfg.decrypt} > '${decrypted}.tmp'; then
2021-06-16 23:42:44 +03:00
mv -f '${decrypted}.tmp' '${decrypted}'
chown '${owner}' '${decrypted}'
chmod '${permissions}' '${decrypted}'
else
echo "Failed to decrypt the secret"
rm '${decrypted}.tmp'
2021-09-15 23:17:00 +03:00
if [[ -f '${decrypted}' ]]; then
echo "The decrypted file exists anyways, not failing"
exit 0
else
exit 1
fi
2021-06-16 23:42:44 +03:00
fi
'';
serviceConfig = {
Type = "oneshot";
RemainAfterExit = "yes";
};
};
};
addDependencies = name: cfg:
with cfg;
genAttrs services (service: rec {
requires = [ "${name}-secrets.service" ];
after = requires;
bindsTo = requires;
});
mkServices = name: cfg: [ (decrypt name cfg) (addDependencies name cfg) ];
2023-01-26 02:12:00 +03:00
allServicesMap = map (name: "${name}-envsubst.service")
2021-06-16 23:42:44 +03:00
(builtins.attrNames config.secrets-envsubst)
++ map (name: "${name}-secrets.service")
2023-01-26 02:12:00 +03:00
(builtins.attrNames config.secrets);
2022-12-07 22:13:34 +03:00
allServices = toString allServicesMap;
2023-01-26 00:27:05 +03:00
# https://github.com/nix-community/home-manager/blob/a993eac1065c6ce63a8d724b7bccf624d0e91ca2/modules/services/gpg-agent.nix#L22
home-conf = config.home-manager.users.${config.mainuser};
homedir = home-conf.programs.gpg.homedir;
gpgconf = dir: let
hash = substring 0 24 (hexStringToBase32 (builtins.hashString "sha1" homedir));
in if homedir == "${home-conf.home.homeDirectory}/.gnupg" then
"%t/gnupg/${dir}"
else
"%t/gnupg/d.${hash}/${dir}";
hexStringToBase32 = with lib; let
mod = a: b: a - a / b * b;
pow2 = elemAt [ 1 2 4 8 16 32 64 128 256 ];
splitChars = s: init (tail (splitString "" s));
base32Alphabet = splitChars "ybndrfg8ejkmcpqxot1uwisza345h769";
hexToIntTable = listToAttrs (genList (x: {
name = toLower (toHexString x);
value = x;
}) 16);
initState = {
ret = "";
buf = 0;
bufBits = 0;
};
go = { ret, buf, bufBits }:
hex:
let
buf' = buf * pow2 4 + hexToIntTable.${hex};
bufBits' = bufBits + 4;
extraBits = bufBits' - 5;
in if bufBits >= 5 then {
ret = ret + elemAt base32Alphabet (buf' / pow2 extraBits);
buf = mod buf' (pow2 extraBits);
bufBits = bufBits' - 5;
} else {
ret = ret;
buf = buf';
bufBits = bufBits';
};
in hexString: (foldl' go initState (splitChars hexString)).ret;
2021-06-16 23:42:44 +03:00
in {
options.secrets = lib.mkOption {
type = attrsOf (submodule secret);
default = { };
};
options.secretsConfig = {
2022-08-18 18:57:47 +03:00
password-store = lib.mkOption {
type = lib.types.path;
2022-12-10 22:34:39 +03:00
default = "${config.home-manager.users.${config.mainuser}.xdg.dataHome}/password-store";
2022-08-18 18:57:47 +03:00
};
gnupgHome = lib.mkOption {
type = lib.types.path;
2022-12-10 22:34:39 +03:00
default = "${config.home-manager.users.${config.mainuser}.xdg.dataHome}/gnupg";
2022-08-18 18:57:47 +03:00
};
2021-06-16 23:42:44 +03:00
repo = lib.mkOption {
type = str;
2022-03-10 19:56:27 +03:00
default = "gitea@code.ataraxiadev.com:AtaraxiaDev/pass.git";
2021-06-16 23:42:44 +03:00
};
};
config.systemd.services =
mkMerge (concatLists (mapAttrsToList mkServices config.secrets));
2022-12-07 22:13:34 +03:00
config.security.doas.extraRules = [{
2022-12-10 22:34:39 +03:00
users = [ config.mainuser ];
2022-12-07 22:13:34 +03:00
noPass = true;
keepEnv = true;
2023-01-26 00:27:05 +03:00
cmd = "/run/current-system/sw/bin/systemctl";
2022-12-07 22:13:34 +03:00
args = [ "restart" ] ++ allServicesMap;
2021-06-16 23:42:44 +03:00
}];
2023-01-26 00:27:05 +03:00
config.security.sudo.extraRules = [{
users = [ config.mainuser ];
commands = [{
command = "/run/current-system/sw/bin/systemctl";
options = [ "SETENV" "NOPASSWD" ];
}];
}];
2022-12-14 23:46:25 +03:00
config.persist.derivative.directories = [ "/var/secrets" ];
2023-01-26 00:41:28 +03:00
# config.persist.derivative.homeDirectories = [ password-store-relative ];
config.persist.derivative.homeDirectories = [ ".local/share/password-store" ];
# config.persist.derivative.homeDirectories = [{
# directory = password-store-relative;
# method = "symlink";
# }];
2022-12-14 23:46:25 +03:00
2022-12-10 22:34:39 +03:00
config.home-manager.users.${config.mainuser} = {
2023-01-26 00:27:05 +03:00
systemd.user.services.activate-secrets = let
ssh-agent = gpgconf "S.gpg-agent.ssh";
in {
2022-01-29 00:41:41 +03:00
Service = {
2023-01-26 00:27:05 +03:00
ExecStart = "${activate-secrets}/bin/activate-secrets '${ssh-agent}'";
2022-01-29 00:41:41 +03:00
Type = "oneshot";
};
Unit = {
PartOf = [ "graphical-session-pre.target" ];
};
Install.WantedBy = [ "graphical-session-pre.target" ];
};
systemd.user.services.pass-store-sync = {
Service = {
Environment = [
"PASSWORD_STORE_DIR=${password-store}"
2022-08-18 18:57:47 +03:00
"PATH=${with pkgs; lib.makeBinPath [ pass inotify-tools gnupg ]}"
2022-01-29 00:41:41 +03:00
];
ExecStart = toString (pkgs.writeShellScript "pass-store-sync" ''
export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)"
while inotifywait "$PASSWORD_STORE_DIR" -r -e move -e close_write -e create -e delete --exclude .git; do
2022-08-18 18:57:47 +03:00
sleep 0.1
2022-01-29 00:41:41 +03:00
pass git add --all
2022-02-01 05:17:22 +03:00
pass git commit -m "$(date +%F)_$(date +%T)"
2022-01-29 00:41:41 +03:00
pass git pull --rebase
pass git push
done
'');
};
Unit = rec {
After = [ "activate-secrets.service" ];
Wants = After;
};
Install.WantedBy = [ "graphical-session-pre.target" ];
2020-08-07 23:27:49 +04:00
};
2021-06-16 23:42:44 +03:00
programs.password-store = {
enable = true;
2022-01-29 00:41:41 +03:00
package = pkgs.pass-wayland;
2021-06-16 23:42:44 +03:00
settings.PASSWORD_STORE_DIR = password-store;
2019-08-27 23:41:02 +04:00
};
};
2022-01-29 00:41:41 +03:00
}