diff --git a/os/default.nix b/os/default.nix index 001e1fe..11e4021 100644 --- a/os/default.nix +++ b/os/default.nix @@ -12,6 +12,7 @@ disko.nixosModules.disko ./gaming ./geoffrey.nix + ./password ./printing ./remote-builds ./style diff --git a/os/password/default.nix b/os/password/default.nix new file mode 100644 index 0000000..67406a7 --- /dev/null +++ b/os/password/default.nix @@ -0,0 +1,163 @@ +{ pkgs, lib, config, ... }: +let + pass_subdir = ""; + readPassword = password: "pass show '${pass_subdir}/${password.path}'" + ( + if password.selector == null + then " | head -n1" + else + (if password.selector == "@" + then "" + else " | tail -n +2 | yq -r '.${password.selector}'") + ); + readPasswordFile = password: (readPassword password) + (lib.strings.optionalString (password.transform != null) "| ${password.transform}"); + passwords = builtins.attrValues config.vivarium.passwords; + passwordFiles = builtins.attrValues config.vivarium.passwordFiles; + generatePassword = password: '' + if [ ! -f "$PASSWORD_STORE_DIR/${password.path}.gpg" ] + then + ${password.generator} | pass insert -m "${password.path}" + fi + ''; + ensurePassword = password: + (lib.optionalString (password.generator != null) (generatePassword password)) + '' + ${readPassword password} > /dev/null + ''; + pipeSubstitute = k: v: " | K='${k}' V=\"$(${v})\" awk '{ gsub (ENVIRON[\"K\"], ENVIRON[\"V\"]); print }'"; + renderPasswordFile = passwordFile: "echo ${lib.strings.escapeShellArg passwordFile.text} ${lib.strings.concatStrings (map + (password: pipeSubstitute password.variable (readPasswordFile password)) + (lib.attrsets.attrValues passwordFile.passwords))}"; + installPasswordFile = passwordFile: '' + sudo mkdir -p "${builtins.dirOf passwordFile.path}" + ${renderPasswordFile passwordFile} | sudo tee ${passwordFile.path} > /dev/null + ''; + fixPermissionsPasswordFile = passwordFile: '' + sudo chown ${passwordFile.owner}:${passwordFile.group} ${passwordFile.path} + sudo chmod ${passwordFile.mode} ${passwordFile.path} + ''; +in +{ + config = { + system.extraSystemBuilderCmds = '' + ln -s ${pkgs.writeShellApplication { + name = "generate-passwords"; + text = '' + test -d "$PASSWORD_STORE_DIR" + '' + lib.strings.concatLines (builtins.map ensurePassword passwords); + }}/bin/generate-passwords $out/bin/generate-passwords + ln -s ${pkgs.writeShellApplication { + name = "install-passwords"; + text = lib.strings.concatStrings (builtins.map installPasswordFile passwordFiles); + }}/bin/install-passwords $out/bin/install-passwords + ln -s ${pkgs.writeShellApplication { + name = "fix-permissions-passwords"; + text = lib.strings.concatStrings (builtins.map fixPermissionsPasswordFile passwordFiles); + }}/bin/fix-permissions-passwords $out/bin/fix-permissions-passwords + ''; + vivarium.passwords = + let + passwordTypes = lib.lists.flatten (map (f: builtins.attrValues f.passwords) passwordFiles); + password = passwordType: { + ${passwordType.path} = { + inherit (passwordType) selector generator; + }; + }; + passwords = map password passwordTypes; + in + lib.attrsets.mergeAttrsList passwords; + }; + options = { + # Using vivarium because that's where it's from, and we don't want it in home manager's frogeye + # TODO Make this cleaner, merge the two, somehow + vivarium = + let + defaultvar = "@PASSWORD@"; + passwordTypeCommon = { + selector = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "If unset, selects the first line. If '@', select everything. If any other value, will parse the password metadata as YML and use selector (yq)."; + }; + generator = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "${lib.getExe pkgs.pwgen} -s 32"; + description = "Command to generate the password. Won't work when selector is set to read metadata."; + }; + }; + hostConfig = config; + passwordTypeFile = { name, config, ... }: { + options = passwordTypeCommon // { + variable = lib.mkOption { + type = lib.types.str; + default = name; + description = "String in the template that will be substituted by the actual password"; + }; + transform = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Shell command to transform the password with before substitution"; + }; + path = lib.mkOption { + type = lib.types.str; + description = "Path to the password store entry"; + }; + }; + }; + in + { + passwords = lib.mkOption { + default = { }; + type = lib.types.attrsOf (lib.types.submodule ({ name, ... }: { + options = passwordTypeCommon // { + path = lib.mkOption { + type = lib.types.str; + default = name; + description = "Path to the password store entry"; + }; + }; + })); + }; + passwordFiles = + lib.mkOption { + default = { }; + type = lib.types.attrsOf (lib.types.submodule ({ name, config, ... }: { + options = { + path = lib.mkOption { + type = lib.types.str; + default = name; + description = "Where to place the file."; + }; + mode = lib.mkOption { + type = lib.types.str; + default = "0400"; + description = "Unix permission"; + }; + owner = lib.mkOption { + type = lib.types.str; + default = "root"; + description = "Owner of the secret file"; + }; + group = lib.mkOption { + type = lib.types.str; + default = "root"; + description = "Group of the secret file"; + }; + text = lib.mkOption { + type = lib.types.str; + default = defaultvar; + description = "Content of the template used to make the file. Exclusive with `template`."; + }; + passwords = lib.mkOption { + default = lib.optionalAttrs (config.password != null) { ${defaultvar} = config.password; }; + type = lib.types.attrsOf (lib.types.submodule passwordTypeFile); + description = "Paths to passwords that will substitute the variables in the template. Exclusive with `password`"; + }; + password = lib.mkOption { + type = lib.types.submodule ({ ... }@args: passwordTypeFile (args // { name = defaultvar; })); + description = "Path to password that will substitute '@PASSWORD@' in the template. Exclusive with `passwords`."; + }; + }; + })); + }; + }; + }; +} diff --git a/os/rebuild.sh b/os/rebuild.sh index 7fa3243..600557f 100644 --- a/os/rebuild.sh +++ b/os/rebuild.sh @@ -39,7 +39,14 @@ then fi if [ "$verb" = "test" ] || [ "$verb" = "switch" ] || [ "$confirm" = "y" ] then + # Generate passwords first. If there's a missing one that cannot be generated, we'll know before anything is written + "$toplevel/bin/generate-passwords" + # Install the passwords to their respective directories + "$toplevel/bin/install-passwords" sudo nixos-rebuild --flake "$self#$HOSTNAME" test "${specialisationArgs[@]}" "$@" + # Fix passwords permission. After install, so it can use new users + "$toplevel/bin/fix-permissions-passwords" + # TODO Install passwords with correct permissions during activation fi # Set as boot