From 4fb1eef0c0858a09868384d381ff6ca926c7d812 Mon Sep 17 00:00:00 2001 From: musjj <72612857+musjj@users.noreply.github.com> Date: Tue, 22 Apr 2025 06:16:55 +0700 Subject: [PATCH] add native support for ssh keys for age --- modules/home-manager/sops.nix | 29 ++++++++++++++++++++++++++++- modules/nix-darwin/default.nix | 28 +++++++++++++++++++++++++++- modules/nix-darwin/manifest-for.nix | 1 + modules/sops/default.nix | 28 +++++++++++++++++++++++++++- modules/sops/manifest-for.nix | 1 + pkgs/sops-install-secrets/main.go | 7 ++++++- 6 files changed, 90 insertions(+), 4 deletions(-) diff --git a/modules/home-manager/sops.nix b/modules/home-manager/sops.nix index ad6fe64..892eeb6 100644 --- a/modules/home-manager/sops.nix +++ b/modules/home-manager/sops.nix @@ -98,6 +98,7 @@ let gnupgHome = cfg.gnupg.home; sshKeyPaths = cfg.gnupg.sshKeyPaths; ageKeyFile = cfg.age.keyFile; + ageSshKeyFile = cfg.age.sshKeyFile; ageSshKeyPaths = cfg.age.sshKeyPaths; placeholderBySecretName = cfg.placeholder; userMode = true; @@ -250,11 +251,23 @@ in ''; }; + sshKeyFile = lib.mkOption { + type = lib.types.nullOr pathNotInStore; + default = null; + example = "/home/someuser/.ssh/id_ed25519"; + description = '' + Path to ssh key file that will be used by age for sops decryption. + ''; + }; + sshKeyPaths = lib.mkOption { type = lib.types.listOf lib.types.path; default = [ ]; description = '' - Paths to ssh keys added as age keys during sops description. + Paths to ssh keys added as age keys during sops description. The ssh + keys will be converted into age keys manually using ssh-to-age. + + This option is deprecated and will be removed in the future. Use sops.age.sshKeyFile instead. ''; }; }; @@ -301,6 +314,7 @@ in || cfg.gnupg.sshKeyPaths != [ ] || cfg.gnupg.qubes-split-gpg.enable == true || cfg.age.keyFile != null + || cfg.age.sshKeyFile != 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"; } @@ -323,6 +337,19 @@ in } ]; + warnings = [ + (lib.mkIf + ( + cfg.age.sshKeyPaths != [ ] + && cfg.gnupg.sshKeyPaths == [ ] + && cfg.gnupg.home == null + && cfg.age.keyFile == null + && cfg.age.sshKeyFile == null + ) + "The option sops.age.sshKeyPaths has been deprecated, since age now has native SSH support. Use option sops.age.sshKeyFile instead." + ) + ]; + 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 diff --git a/modules/nix-darwin/default.nix b/modules/nix-darwin/default.nix index f169a30..15fc918 100644 --- a/modules/nix-darwin/default.nix +++ b/modules/nix-darwin/default.nix @@ -300,12 +300,24 @@ in ''; }; + sshKeyFile = lib.mkOption { + type = lib.types.nullOr pathNotInStore; + default = null; + example = "/etc/ssh/ssh_host_ed25519_key"; + description = '' + Path to ssh key file that will be used by age for sops decryption. + ''; + }; + sshKeyPaths = lib.mkOption { type = lib.types.listOf lib.types.path; default = defaultImportKeys "ed25519"; defaultText = lib.literalMD "The ed25519 keys from {option}`config.services.openssh.hostKeys`"; description = '' - Paths to ssh keys added as age keys during sops description. + Paths to ssh keys added as age keys during sops description. The ssh + keys will be converted into age keys manually using ssh-to-age. + + This option is deprecated and will be removed in the future. Use sops.age.sshKeyFile instead. ''; }; }; @@ -345,6 +357,7 @@ in cfg.gnupg.home != null || cfg.gnupg.sshKeyPaths != [ ] || cfg.age.keyFile != null + || cfg.age.sshKeyFile != 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"; } @@ -383,6 +396,19 @@ in }) { + warnings = [ + (lib.mkIf + ( + cfg.age.sshKeyPaths != [ ] + && cfg.gnupg.sshKeyPaths == [ ] + && cfg.gnupg.home == null + && cfg.age.keyFile == null + && cfg.age.sshKeyFile == null + ) + "The option sops.age.sshKeyPaths has been deprecated, since age now has native SSH support. Use option sops.age.sshKeyFile instead." + ) + ]; + sops.environment.SOPS_GPG_EXEC = lib.mkIf (cfg.gnupg.home != null || cfg.gnupg.sshKeyPaths != [ ]) ( lib.mkDefault "${pkgs.gnupg}/bin/gpg" ); diff --git a/modules/nix-darwin/manifest-for.nix b/modules/nix-darwin/manifest-for.nix index 5015659..edc6b3e 100644 --- a/modules/nix-darwin/manifest-for.nix +++ b/modules/nix-darwin/manifest-for.nix @@ -15,6 +15,7 @@ writeTextFile { gnupgHome = cfg.gnupg.home; sshKeyPaths = cfg.gnupg.sshKeyPaths; ageKeyFile = cfg.age.keyFile; + ageSshKeyFile = cfg.age.sshKeyFile; ageSshKeyPaths = cfg.age.sshKeyPaths; useTmpfs = false; placeholderBySecretName = cfg.placeholder; diff --git a/modules/sops/default.nix b/modules/sops/default.nix index b990e3e..6f38838 100644 --- a/modules/sops/default.nix +++ b/modules/sops/default.nix @@ -339,12 +339,24 @@ in ''; }; + sshKeyFile = lib.mkOption { + type = lib.types.nullOr pathNotInStore; + default = null; + example = "/etc/ssh/ssh_host_ed25519_key"; + description = '' + Path to ssh key file that will be used by age for sops decryption. + ''; + }; + sshKeyPaths = lib.mkOption { type = lib.types.listOf lib.types.path; default = defaultImportKeys "ed25519"; defaultText = lib.literalMD "The ed25519 keys from {option}`config.services.openssh.hostKeys`"; description = '' - Paths to ssh keys added as age keys during sops description. + Paths to ssh keys added as age keys during sops description. The ssh + keys will be converted into age keys manually using ssh-to-age. + + This option is deprecated and will be removed in the future. Use sops.age.sshKeyFile instead. ''; }; }; @@ -405,6 +417,7 @@ in cfg.gnupg.home != null || cfg.gnupg.sshKeyPaths != [ ] || cfg.age.keyFile != null + || cfg.age.sshKeyFile != 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"; } @@ -483,6 +496,19 @@ in }; }) { + warnings = [ + (lib.mkIf + ( + cfg.age.sshKeyPaths != [ ] + && cfg.gnupg.sshKeyPaths == [ ] + && cfg.gnupg.home == null + && cfg.age.keyFile == null + && cfg.age.sshKeyFile == null + ) + "The option sops.age.sshKeyPaths has been deprecated, since age now has native SSH support. Use option sops.age.sshKeyFile instead." + ) + ]; + system.build.sops-nix-manifest = manifest; } ]; diff --git a/modules/sops/manifest-for.nix b/modules/sops/manifest-for.nix index 1824668..dc40065 100644 --- a/modules/sops/manifest-for.nix +++ b/modules/sops/manifest-for.nix @@ -40,6 +40,7 @@ else gnupgHome = cfg.gnupg.home; sshKeyPaths = cfg.gnupg.sshKeyPaths; ageKeyFile = cfg.age.keyFile; + ageSshKeyFile = cfg.age.sshKeyFile; ageSshKeyPaths = cfg.age.sshKeyPaths; useTmpfs = cfg.useTmpfs; placeholderBySecretName = cfg.placeholder; diff --git a/pkgs/sops-install-secrets/main.go b/pkgs/sops-install-secrets/main.go index 3934039..6e3ea93 100644 --- a/pkgs/sops-install-secrets/main.go +++ b/pkgs/sops-install-secrets/main.go @@ -79,6 +79,7 @@ type manifest struct { SSHKeyPaths []string `json:"sshKeyPaths"` GnupgHome string `json:"gnupgHome"` AgeKeyFile string `json:"ageKeyFile"` + AgeSSHKeyFile string `json:"ageSshKeyFile"` AgeSSHKeyPaths []string `json:"ageSshKeyPaths"` UseTmpfs bool `json:"useTmpfs"` UserMode bool `json:"userMode"` @@ -1325,7 +1326,7 @@ func installSecrets(args []string) error { } // Import age keys - if len(manifest.AgeSSHKeyPaths) != 0 || manifest.AgeKeyFile != "" { + if (len(manifest.AgeSSHKeyPaths) != 0 || manifest.AgeKeyFile != "") && manifest.AgeSSHKeyFile == "" { keyfile := filepath.Join(manifest.SecretsMountPoint, "age-keys.txt") os.Setenv("SOPS_AGE_KEY_FILE", keyfile) // Create the keyfile @@ -1360,6 +1361,10 @@ func installSecrets(args []string) error { } } + if manifest.AgeSSHKeyFile != "" { + os.Setenv("SOPS_AGE_SSH_PRIVATE_KEY_FILE", manifest.AgeSSHKeyFile) + } + if err := decryptSecrets(manifest.Secrets); err != nil { return err }