Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

home-manager: Add support for Split GPG on Qubes OS #657

Merged
merged 2 commits into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`:
Expand Down
51 changes: 45 additions & 6 deletions modules/home-manager/sops.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [];
Expand All @@ -244,24 +257,50 @@ 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;
message = "Cannot find path '${secret.sopsFile}' set in sops.secrets.${lib.strings.escapeNixIdentifier name}.sopsFile";
} {
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 = "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 = {
Expand Down