From 7d2fca60a0b0753baa9115a750e5c85fda20978c Mon Sep 17 00:00:00 2001 From: "Wael M. Nasreddine" Date: Fri, 8 Nov 2024 21:20:43 -0800 Subject: [PATCH 1/2] home-manager: Add support for Split GPG on Qubes OS --- README.md | 25 +++++++++++++++++ modules/home-manager/sops.nix | 51 ++++++++++++++++++++++++++++++----- 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 42d497f8..5d5769f9 100644 --- a/README.md +++ b/README.md @@ -823,6 +823,31 @@ The secrets are decrypted in a systemd user service called `sops-nix`, so other } ``` +### Qubes Split GPG support + +If you are using Qubes with the [Split GPG](https://www.qubes-os.org/doc/split-gpg), +then you can configure sops to utilize the `qubes-gpg-client-wrapper` with the `sops.gnupg.qubes-split-gpg` options. +The example above updated looks like this: +```nix +{ + sops = { + gnupg.qubes-split-gpg = { + enable = true; + domain = "vault-gpg"; + }; + defaultSopsFile = ./secrets.yaml; + secrets.test = { + # sopsFile = ./secrets.yml.enc; # optionally define per-secret files + + # %r gets replaced with a runtime directory, use %% to specify a '%' + # sign. Runtime dir is $XDG_RUNTIME_DIR on linux and $(getconf + # DARWIN_USER_TEMP_DIR) on darwin. + path = "%r/test.txt"; + }; + }; +} +``` + ## Use with GPG instead of SSH keys If you prefer having a separate GPG key, sops-nix also comes with a helper tool, `sops-init-gpg-key`: diff --git a/modules/home-manager/sops.nix b/modules/home-manager/sops.nix index 7a91abca..2d835a4e 100644 --- a/modules/home-manager/sops.nix +++ b/modules/home-manager/sops.nix @@ -231,6 +231,19 @@ in { ''; }; + qubes-split-gpg = { + enable = lib.mkEnableOption "Enable support for Qubes Split GPG feature: https://www.qubes-os.org/doc/split-gpg"; + + domain = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "vault-gpg"; + description = '' + It tells Qubes OS which secure Qube holds your GPG keys for isolated cryptographic operations. + ''; + }; + }; + sshKeyPaths = lib.mkOption { type = lib.types.listOf lib.types.path; default = []; @@ -244,11 +257,24 @@ in { config = lib.mkIf (cfg.secrets != {}) { assertions = [{ - assertion = cfg.gnupg.home != null || cfg.gnupg.sshKeyPaths != [] || cfg.age.keyFile != null || cfg.age.sshKeyPaths != []; - message = "No key source configured for sops. Either set services.openssh.enable or set sops.age.keyFile or sops.gnupg.home"; + assertion = + cfg.gnupg.home != null || + cfg.gnupg.sshKeyPaths != [] || + cfg.gnupg.qubes-split-gpg.enable == true || + cfg.age.keyFile != null || + cfg.age.sshKeyPaths != []; + message = "No key source configured for sops. Either set services.openssh.enable or set sops.age.keyFile or sops.gnupg.home or sops.gnupg.qubes-split-gpg.enable"; } { - assertion = !(cfg.gnupg.home != null && cfg.gnupg.sshKeyPaths != []); - message = "Exactly one of sops.gnupg.home and sops.gnupg.sshKeyPaths must be set"; + assertion = !(cfg.gnupg.home != null && cfg.gnupg.sshKeyPaths != []) && + !(cfg.gnupg.home != null && cfg.gnupg.qubes-split-gpg.enable == true) && + !(cfg.gnupg.sshKeyPaths != [ ] && cfg.gnupg.qubes-split-gpg.enable == true); + message = "Exactly one of sops.gnupg.home, sops.gnupg.qubes-split-gpg.enable and sops.gnupg.sshKeyPaths must be set"; + } { + assertion = cfg.gnupg.qubes-split-gpg.enable == false || + (cfg.gnupg.qubes-split-gpg.enable == true && + cfg.gnupg.qubes-split-gpg.domain != null && + cfg.gnupg.qubes-split-gpg.domain != ""); + message = "sops.gnupg.qubes-split-gpg.domain is required when sops.gnupg.qubes-split-gpg.enable is set to true"; }] ++ lib.optionals cfg.validateSopsFiles ( lib.concatLists (lib.mapAttrsToList (name: secret: [{ assertion = builtins.pathExists secret.sopsFile; @@ -256,12 +282,25 @@ in { } { assertion = builtins.isPath secret.sopsFile || - (builtins.isString secret.sopsFile && lib.hasPrefix builtins.storeDir secret.sopsFile); + (builtins.isString secret.sopsFile && lib.hasPrefix builtins.storeDir secret.sopsFile); message = "'${secret.sopsFile}' is not in the Nix store. Either add it to the Nix store or set sops.validateSopsFiles to false"; }]) cfg.secrets) ); - sops.environment.SOPS_GPG_EXEC = lib.mkIf (cfg.gnupg.home != null || cfg.gnupg.sshKeyPaths != []) (lib.mkDefault "${pkgs.gnupg}/bin/gpg"); + home.sessionVariables = lib.mkIf cfg.gnupg.qubes-split-gpg.enable { + # TODO: Add this package to nixpkgs and use it from the store + # https://github.com/QubesOS/qubes-app-linux-split-gpg + SOPS_GPG_EXEC = "/usr/bin/qubes-gpg-client-wrapper"; + }; + + sops.environment = { + SOPS_GPG_EXEC = lib.mkMerge [ + (lib.mkIf (cfg.gnupg.home != null || cfg.gnupg.sshKeyPaths != []) (lib.mkDefault "${pkgs.gnupg}/bin/gpg")) + (lib.mkIf cfg.gnupg.qubes-split-gpg.enable (lib.mkDefault config.home.sessionVariables.SOPS_GPG_EXEC)) + ]; + + QUBES_GPG_DOMAIN = lib.mkIf cfg.gnupg.qubes-split-gpg.enable (lib.mkDefault cfg.gnupg.qubes-split-gpg.domain); + }; systemd.user.services.sops-nix = lib.mkIf pkgs.stdenv.hostPlatform.isLinux { Unit = { From 1352417c3aab993f44fe01b3bf19e60e5b07007a Mon Sep 17 00:00:00 2001 From: Wael Nasreddine Date: Sat, 9 Nov 2024 18:21:55 -0800 Subject: [PATCH 2/2] Update modules/home-manager/sops.nix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jörg Thalheim --- modules/home-manager/sops.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/home-manager/sops.nix b/modules/home-manager/sops.nix index 2d835a4e..e1bb4496 100644 --- a/modules/home-manager/sops.nix +++ b/modules/home-manager/sops.nix @@ -290,7 +290,7 @@ in { home.sessionVariables = lib.mkIf cfg.gnupg.qubes-split-gpg.enable { # TODO: Add this package to nixpkgs and use it from the store # https://github.com/QubesOS/qubes-app-linux-split-gpg - SOPS_GPG_EXEC = "/usr/bin/qubes-gpg-client-wrapper"; + SOPS_GPG_EXEC = "qubes-gpg-client-wrapper"; }; sops.environment = {