remove secrets module, add pass-store
This commit is contained in:
parent
aa9bf64489
commit
ca3abc45f3
77
modules/pass-store.nix
Normal file
77
modules/pass-store.nix
Normal file
@ -0,0 +1,77 @@
|
||||
{ pkgs, config, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.password-store;
|
||||
in {
|
||||
options.services.password-store = {
|
||||
enable = mkEnableOption "password-store";
|
||||
store = mkOption {
|
||||
type = types.path;
|
||||
default = "${config.home-manager.users.${config.mainuser}.xdg.dataHome}/password-store";
|
||||
};
|
||||
gnupgHome = mkOption {
|
||||
type = types.path;
|
||||
default = "${config.home-manager.users.${config.mainuser}.xdg.dataHome}/gnupg";
|
||||
};
|
||||
repo = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
sshKey = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf (cfg.enable) {
|
||||
home-manager.users.${config.mainuser} = {
|
||||
systemd.user.services.activate-secrets = {
|
||||
Service = {
|
||||
Environment = [
|
||||
"GIT_SSH_COMMAND='ssh -i ${cfg.sshKey} -o IdentitiesOnly=yes'"
|
||||
"PATH=${with pkgs; makeBinPath [ git openssh ]}"
|
||||
];
|
||||
ExecStart = pkgs.writeShellScript "activate-secrets" ''
|
||||
set -euo pipefail
|
||||
if [ -d "${cfg.store}/.git" ]; then
|
||||
git -C "${cfg.store}" pull
|
||||
else
|
||||
echo "Pulling ${escapeShellArg cfg.repo}"
|
||||
git clone ${escapeShellArg cfg.repo} "${cfg.store}"
|
||||
fi
|
||||
'';
|
||||
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=${cfg.store}"
|
||||
"GIT_SSH_COMMAND='ssh -i ${cfg.sshKey} -o IdentitiesOnly=yes'"
|
||||
"PATH=${with pkgs; makeBinPath [ pass-wayland inotify-tools ]}"
|
||||
];
|
||||
ExecStart = pkgs.writeShellScript "pass-store-sync" ''
|
||||
set -euo pipefail
|
||||
while inotifywait "$PASSWORD_STORE_DIR" -r -e move -e close_write -e create -e delete --exclude .git; do
|
||||
sleep 0.1
|
||||
pass git add --all
|
||||
pass git commit -m "$(date +%F)_$(date +%T)"
|
||||
pass git pull --rebase
|
||||
pass git push
|
||||
done
|
||||
'';
|
||||
};
|
||||
Unit = rec {
|
||||
After = [ "activate-secrets.service" ];
|
||||
Wants = After;
|
||||
};
|
||||
Install.WantedBy = [ "graphical-session-pre.target" ];
|
||||
};
|
||||
programs.password-store = {
|
||||
enable = true;
|
||||
package = pkgs.pass-wayland;
|
||||
settings.PASSWORD_STORE_DIR = cfg.store;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
{ pkgs, config, lib, inputs, ... }:
|
||||
with lib;
|
||||
with types;
|
||||
let
|
||||
envsubstSecrets = { name, ... }: {
|
||||
options = {
|
||||
directory = mkOption {
|
||||
type = nullOr str;
|
||||
default = name;
|
||||
};
|
||||
secrets = mkOption { type = listOf str; };
|
||||
template = mkOption { type = str; };
|
||||
prefix = mkOption {
|
||||
type = nullOr str;
|
||||
default = null;
|
||||
};
|
||||
substituted = mkOption {
|
||||
type = path;
|
||||
default = "/var/secrets/${name}-envsubst";
|
||||
};
|
||||
envsubst = mkOption {
|
||||
type = str;
|
||||
default = "${pkgs.envsubst}/bin/envsubst -no-unset -no-empty";
|
||||
};
|
||||
owner = mkOption {
|
||||
type = str;
|
||||
default = "root:root";
|
||||
};
|
||||
permissions = mkOption {
|
||||
type = lib.types.addCheck lib.types.str
|
||||
(perm: (builtins.match "[0-7]{3}" perm) != null);
|
||||
default = "400";
|
||||
};
|
||||
services = mkOption {
|
||||
type = listOf str;
|
||||
default = [ "${name}.service" ];
|
||||
};
|
||||
__toString = mkOption {
|
||||
readOnly = true;
|
||||
default = s: s.substituted;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
exportSecrets = name: cfg:
|
||||
let prefix = lib.optionalString (cfg.prefix != null) "${cfg.prefix}_";
|
||||
in map (secret:
|
||||
''
|
||||
export ${prefix}${secret}="$(cat ${
|
||||
config.secrets."${name}-envsubst-${secret}".decrypted
|
||||
})"'') cfg.secrets;
|
||||
|
||||
envsubst = name: cfg:
|
||||
with cfg; {
|
||||
"${name}-envsubst" = rec {
|
||||
|
||||
requires = [ "user@1000.service" ]
|
||||
++ map (secret: "${name}-envsubst-${secret}-secrets.service")
|
||||
cfg.secrets;
|
||||
after = requires;
|
||||
bindsTo = requires;
|
||||
|
||||
preStart = "mkdir -p '${builtins.dirOf substituted}'";
|
||||
|
||||
script = ''
|
||||
${builtins.concatStringsSep "\n" (exportSecrets name cfg)}
|
||||
|
||||
if cat '${
|
||||
builtins.toFile "template" template
|
||||
}' | ${cfg.envsubst} > '${substituted}.tmp'; then
|
||||
mv -f '${substituted}.tmp' '${substituted}'
|
||||
chown '${owner}' '${substituted}'
|
||||
chmod '${permissions}' '${substituted}'
|
||||
else
|
||||
echo "Failed to run the substition"
|
||||
rm '${substituted}.tmp'
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = "yes";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
addDependencies = name: cfg:
|
||||
with cfg;
|
||||
genAttrs services (service: rec {
|
||||
requires = [ "${name}-envsubst.service" ];
|
||||
after = requires;
|
||||
bindsTo = requires;
|
||||
});
|
||||
mkServices = name: cfg: [ (envsubst name cfg) (addDependencies name cfg) ];
|
||||
|
||||
mkIndividualSecrets = name: cfg:
|
||||
map (x: {
|
||||
"${name}-envsubst-${x}" = {
|
||||
encrypted = "${config.home-manager.users.${config.mainuser}.xdg.dataHome}/password-store/${
|
||||
lib.optionalString (cfg.directory != null) "${cfg.directory}/"
|
||||
}${x}.gpg";
|
||||
services = [ ];
|
||||
};
|
||||
}) cfg.secrets;
|
||||
in {
|
||||
options.secrets-envsubst = lib.mkOption {
|
||||
type = attrsOf (submodule envsubstSecrets);
|
||||
default = { };
|
||||
};
|
||||
config.systemd.services =
|
||||
mkMerge (concatLists (mapAttrsToList mkServices config.secrets-envsubst));
|
||||
config.secrets = mkMerge
|
||||
(concatLists (mapAttrsToList mkIndividualSecrets config.secrets-envsubst));
|
||||
}
|
@ -1,253 +0,0 @@
|
||||
{ pkgs, config, lib, inputs, ... }:
|
||||
with lib;
|
||||
with types;
|
||||
let
|
||||
password-store = config.secretsConfig.password-store;
|
||||
password-store-relative = removePrefix config.home-manager.users.${config.mainuser}.home.homeDirectory password-store;
|
||||
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
|
||||
export GNUPGHOME=${config.secretsConfig.gnupgHome}
|
||||
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;
|
||||
default = config.mainuser;
|
||||
};
|
||||
owner = mkOption {
|
||||
type = str;
|
||||
default = "root:root";
|
||||
};
|
||||
permissions = mkOption {
|
||||
type = lib.types.addCheck lib.types.str
|
||||
(perm: (builtins.match "[0-7]{3}" perm) != null);
|
||||
default = "400";
|
||||
};
|
||||
services = mkOption {
|
||||
type = listOf str;
|
||||
default = [ "${name}" ];
|
||||
};
|
||||
__toString = mkOption {
|
||||
readOnly = true;
|
||||
default = s: s.decrypted;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
activate-secrets = pkgs.writeShellScriptBin "activate-secrets" ''
|
||||
set -euo pipefail
|
||||
PATH="${with pkgs; lib.makeBinPath [ openssh gnupg coreutils ]}:$PATH"
|
||||
export SSH_AUTH_SOCK="$1"
|
||||
export GNUPGHOME=${config.secretsConfig.gnupgHome}
|
||||
if [ -d "${password-store}/.git" ]; then
|
||||
${pkgs.git}/bin/git -C "${password-store}" pull
|
||||
else
|
||||
echo "${lib.escapeShellArg config.secretsConfig.repo}"
|
||||
${pkgs.git}/bin/git clone ${
|
||||
lib.escapeShellArg config.secretsConfig.repo
|
||||
} "${password-store}"
|
||||
fi
|
||||
cat ${password-store}/ssh-builder.gpg | ${pkgs.gnupg}/bin/gpg --decrypt > /dev/null
|
||||
[ ! -z "${allServices}" ] && /run/wrappers/bin/sudo systemctl restart ${allServices}
|
||||
'';
|
||||
|
||||
decrypt = name: cfg:
|
||||
with cfg; let
|
||||
doas-user = "/run/wrappers/bin/doas -u ${user}";
|
||||
in {
|
||||
"${name}-secrets" = rec {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
requires = [ "user@1000.service" ];
|
||||
after = requires;
|
||||
|
||||
preStart = ''
|
||||
${doas-user} stat '${encrypted}'
|
||||
mkdir -p '${builtins.dirOf decrypted}'
|
||||
'';
|
||||
|
||||
script = ''
|
||||
if ${doas-user} cat '${encrypted}' | ${doas-user} ${cfg.decrypt} > '${decrypted}.tmp'; then
|
||||
mv -f '${decrypted}.tmp' '${decrypted}'
|
||||
chown '${owner}' '${decrypted}'
|
||||
chmod '${permissions}' '${decrypted}'
|
||||
else
|
||||
echo "Failed to decrypt the secret"
|
||||
rm '${decrypted}.tmp'
|
||||
if [[ -f '${decrypted}' ]]; then
|
||||
echo "The decrypted file exists anyways, not failing"
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
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) ];
|
||||
|
||||
allServicesMap = map (name: "${name}-envsubst.service")
|
||||
(builtins.attrNames config.secrets-envsubst)
|
||||
++ map (name: "${name}-secrets.service")
|
||||
(builtins.attrNames config.secrets);
|
||||
|
||||
allServices = toString allServicesMap;
|
||||
|
||||
# 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;
|
||||
in {
|
||||
options.secrets = lib.mkOption {
|
||||
type = attrsOf (submodule secret);
|
||||
default = { };
|
||||
};
|
||||
|
||||
options.secretsConfig = {
|
||||
password-store = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = "${config.home-manager.users.${config.mainuser}.xdg.dataHome}/password-store";
|
||||
};
|
||||
gnupgHome = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = "${config.home-manager.users.${config.mainuser}.xdg.dataHome}/gnupg";
|
||||
};
|
||||
repo = lib.mkOption {
|
||||
type = str;
|
||||
default = "gitea@code.ataraxiadev.com:AtaraxiaDev/pass.git";
|
||||
};
|
||||
};
|
||||
|
||||
config.systemd.services =
|
||||
mkMerge (concatLists (mapAttrsToList mkServices config.secrets));
|
||||
|
||||
config.security.doas.extraRules = [{
|
||||
users = [ config.mainuser ];
|
||||
noPass = true;
|
||||
keepEnv = true;
|
||||
cmd = "/run/current-system/sw/bin/systemctl";
|
||||
args = [ "restart" ] ++ allServicesMap;
|
||||
}];
|
||||
|
||||
config.security.sudo.extraRules = [{
|
||||
users = [ config.mainuser ];
|
||||
commands = [{
|
||||
command = "/run/current-system/sw/bin/systemctl";
|
||||
options = [ "SETENV" "NOPASSWD" ];
|
||||
}];
|
||||
}];
|
||||
|
||||
config.persist.derivative.directories = [ "/var/secrets" ];
|
||||
# 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";
|
||||
# }];
|
||||
|
||||
config.home-manager.users.${config.mainuser} = {
|
||||
systemd.user.services.activate-secrets = let
|
||||
ssh-agent = gpgconf "S.gpg-agent.ssh";
|
||||
in {
|
||||
Service = {
|
||||
ExecStart = "${activate-secrets}/bin/activate-secrets '${ssh-agent}'";
|
||||
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}"
|
||||
"PATH=${with pkgs; lib.makeBinPath [ pass inotify-tools gnupg ]}"
|
||||
];
|
||||
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
|
||||
sleep 0.1
|
||||
pass git add --all
|
||||
pass git commit -m "$(date +%F)_$(date +%T)"
|
||||
pass git pull --rebase
|
||||
pass git push
|
||||
done
|
||||
'');
|
||||
};
|
||||
Unit = rec {
|
||||
After = [ "activate-secrets.service" ];
|
||||
Wants = After;
|
||||
};
|
||||
Install.WantedBy = [ "graphical-session-pre.target" ];
|
||||
};
|
||||
programs.password-store = {
|
||||
enable = true;
|
||||
package = pkgs.pass-wayland;
|
||||
settings.PASSWORD_STORE_DIR = password-store;
|
||||
};
|
||||
};
|
||||
}
|
13
profiles/security/password-store.nix
Normal file
13
profiles/security/password-store.nix
Normal file
@ -0,0 +1,13 @@
|
||||
{ config, inputs, ... }: {
|
||||
sops.secrets.git-ssh-key = {
|
||||
owner = config.mainuser;
|
||||
sopsFile = inputs.self.secretsDir + /misc.yaml;
|
||||
};
|
||||
services.password-store = {
|
||||
enable = true;
|
||||
repo = "gitea@code.ataraxiadev.com:AtaraxiaDev/pass.git";
|
||||
sshKey = config.sops.secrets.git-ssh-key.path;
|
||||
};
|
||||
|
||||
persist.derivative.homeDirectories = [ ".local/share/password-store" ];
|
||||
}
|
@ -3,7 +3,7 @@ with config.deviceSpecific; {
|
||||
home-manager.users.${config.mainuser} = {
|
||||
programs.gpg = {
|
||||
enable = true;
|
||||
homedir = config.secretsConfig.gnupgHome;
|
||||
homedir = config.services.password-store.gnupgHome;
|
||||
};
|
||||
services.gpg-agent = {
|
||||
enable = true;
|
||||
@ -19,7 +19,7 @@ with config.deviceSpecific; {
|
||||
Environment = lib.mkForce [
|
||||
"GPG_TTY=/dev/tty1"
|
||||
"DISPLAY=:0"
|
||||
"GNUPGHOME=${config.secretsConfig.gnupgHome}"
|
||||
"GNUPGHOME=${config.services.password-store.gnupgHome}"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
@ -38,6 +38,7 @@
|
||||
mako
|
||||
nix-index
|
||||
pass-secret-service
|
||||
password-store
|
||||
print-scan
|
||||
proxy
|
||||
hyprland
|
||||
|
Loading…
x
Reference in New Issue
Block a user