diff --git a/.sops.yaml b/.sops.yaml index 5d4d6e8e..33f32ed9 100644 --- a/.sops.yaml +++ b/.sops.yaml @@ -1,19 +1,9 @@ +keys: + - &joey_desktop_jafner_net age1v5wy7epv5mm8ddf3cfv8m0e9w4s693dw7djpuytz9td8ycha5f0sv2se9n + - &joey_age age1zswcq6t5wl8spr3g2wpxhxukjklngcav0vw8py0jnfkqd2jm2ypq53ga00 creation_rules: - - path_regex: .* - # The below key_group and shamir_threshold configuration - # allows any single author key to decrypt secrets, - # but hosts and deploy runners *must* use both keys - # to decrypt. This prevents a compromised host or - # runner from leaking all secrets. - shamir_threshold: 2 + - path_regex: ^.*(\.(secrets|token|passwd)|secrets.env|config.boot) key_groups: - - age: # Author keys; to be held by contributors to the repo - - 'age1zswcq6t5wl8spr3g2wpxhxukjklngcav0vw8py0jnfkqd2jm2ypq53ga00' # joey@dungeon-master - - age: # Author keys (again); hacky way to give author keys a weight of 2 shares - - 'age1zswcq6t5wl8spr3g2wpxhxukjklngcav0vw8py0jnfkqd2jm2ypq53ga00' # joey@dungeon-master - - age: # Deploy keys; to be held by the deploy environment (e.g. Gitea Actions) - - 'age193t908fjxl8ekl77p5xqnpj4xmw3y0khvyzlrw22hdzjduk6l53q05spq3' # deploy@gitea.jafner.tools - - age: # Host key; to be held by hosts to which Stacks should be deployed - - 'age13prhyye2jy3ysa6ltnjgkrqtxrxgs0035d86jyn4ltgk3wxtqgrqgav855' # fighter - - 'age1n20krynrj75jqfy2muvhrygvzd4ee8ngamljqavsrk033zwx0ses2tdtfe' # druid - - 'age1m0jpnk4t7hph5tdva3y9ap7scl8vfly9ufazr0h3cuwpcytlsulqjrt58y' # wizard + - age: + - *joey_desktop_jafner_net + - *joey_age diff --git a/dotfiles/modules/sops.nix b/dotfiles/modules/sops.nix new file mode 100644 index 00000000..1f3d007d --- /dev/null +++ b/dotfiles/modules/sops.nix @@ -0,0 +1,112 @@ +{ sys, pkgs, inputs, flake, ... }: { + imports = [ inputs.sops-nix.nixosModules.sops ]; + sops = { + age.sshKeyPaths = [ "${sys.ssh.path}/${sys.ssh.privateKey}" ]; + #age.keyFile = "/home/${sys.username}/.config/sops/age/keys.txt"; # This file is expected to be provided from outside the nix-store + age.generateKey = false; + }; + + home-manager.users.${sys.username}.home.packages = with pkgs; [ + sops + age + ssh-to-age + ( writeShellApplication { + name = "sops-nix"; + runtimeInputs = [ git ssh-to-age openssh yq ]; + text = '' + #! bash + + # shellcheck disable=SC2002 + # shellcheck disable=SC2016 + + REPO_ROOT="/home/${sys.username}/${flake.repoPath}" + # shellcheck disable=SC2034 + SOPS_AGE_KEY_FILE="/home/${sys.username}/.config/sops/age/keys.txt" + + listSecrets () { + # shellcheck disable=SC2002 + SOPS_REGEX="$(cat "$REPO_ROOT/.sops.yaml" | yq -y '.creation_rules.[].path_regex' | head -n1)" + find "$REPO_ROOT" -regextype posix-extended -regex "$SOPS_REGEX" + } + + getPubkey () { + ADDRESS="$1" + AGE_PUBKEY="$(ssh-keyscan -t ed25519 "$ADDRESS" | ssh-to-age)" + echo "$AGE_PUBKEY" + } + + addPubkey () { # Note: Does not edit any files. Prints .sops.yaml with the given key added. + AGE_PUBKEY="$1" + # shellcheck disable=SC2002,SC2016 + cat "$REPO_ROOT"/.sops.yaml |\ + yq -y --arg key "$AGE_PUBKEY" '.keys += [$key]' |\ + yq -y --arg key "$AGE_PUBKEY" '.creation_rules.[].key_groups.[].age += [$key]' + } + + updateKey () { # Note: Interactive + sops updatekeys --input-type json "$1" + } + + encryptFile () { # Note: All encrypted files are in json format + FILE="$1" + FILE_EXT="''$''\{FILE##*.}" + case "$FILE_EXT" in + "env") FILE_TYPE=dotenv ;; + "json") FILE_TYPE=json ;; + "yaml|yml") FILE_TYPE=yaml ;; + "ini") FILE_TYPE=ini ;; + *) FILE_TYPE=binary ;; + esac + sops --encrypt --config "$REPO_ROOT/.sops.yaml" --input-type "$FILE_TYPE" --output-type json "$FILE" + } + + decryptFile () { # Note: All encrypted files are in json format. + # File extension is used as the hint for decrypted file type. + FILE="$1" + FILE_EXT="''$''\{FILE##*.}" + case "$FILE_EXT" in + "env") FILE_TYPE=dotenv ;; + "json") FILE_TYPE=json ;; + "yaml|yml") FILE_TYPE=yaml ;; + "ini") FILE_TYPE=ini ;; + *) FILE_TYPE=binary ;; + esac + sops --decrypt --config "$REPO_ROOT/.sops.yaml" --input-type json --output-type "$FILE_TYPE" "$FILE" + } + + isJson () { + FILE="$1" + jq -e . >/dev/null 2>&1 <<<"$(cat "$FILE")" && return 0 || return 1; + } + + isEncrypted () { + FILE="$1" + FILE_EXT="''$''\{FILE##*.}" + case "$FILE_EXT" in + "env") FILE_TYPE=dotenv ;; + "json") FILE_TYPE=json ;; + "yaml|yml") FILE_TYPE=yaml ;; + "ini") FILE_TYPE=ini ;; + *) FILE_TYPE=binary ;; + esac + if isJson "$FILE" && [[ "$(sops filestatus "$FILE")" == '{"encrypted":true}' ]]; then echo True; else echo False; fi + } + + listSecretStatus () { + for file in $(listSecrets); do + FILE="$(realpath -s --relative-to="$REPO_ROOT" "$file")" + echo -n "$FILE: " |\ + xargs echo -n + if sops --decrypt --input-type json "$file" >/dev/null 2>&1; then + echo Decryptable + else + echo "Not decryptable" + fi + done + } + + "$@" + ''; + } ) + ]; +} \ No newline at end of file diff --git a/dotfiles/systems/desktop/configuration.nix b/dotfiles/systems/desktop/configuration.nix index 7f8f29be..f2e89ba1 100644 --- a/dotfiles/systems/desktop/configuration.nix +++ b/dotfiles/systems/desktop/configuration.nix @@ -1,12 +1,17 @@ -{ sys, pkgs, inputs, ... }: { +{ sys, pkgs, inputs, flake, ... }: { imports = [ ./hardware.nix ./services.nix ./desktop-environment.nix ./terminal-environment.nix ./theme.nix + ../../modules/sops.nix ]; + environment.sessionVariables = { + "FLAKE_DIR" = "/home/${sys.username}/${flake.repoPath}/${flake.path}/"; + }; + home-manager.backupFileExtension = "bk"; home-manager.users."${sys.username}" = { nixGL = {