diff --git a/modules/features/ssh/config.nix b/modules/features/ssh/config.nix new file mode 100644 index 0000000..3a01934 --- /dev/null +++ b/modules/features/ssh/config.nix @@ -0,0 +1,68 @@ +{ lib, ... }: { + options.optionModules.ssh-certs = lib.mkOption { + type = lib.types.deferredModule; + description = "SSH certificate options"; + }; + + config.optionModules.ssh-certs = { config, lib, pkgs, ... }: { + # Needed for some kind of de-duping when this module is used more than once? + key = "ssh-cert-config"; + _file = "modules/features/ssh/config.nix"; + + options.ssh-new = { + user = { + keyFile = lib.mkOption { + type = lib.types.str; + default = "id_ed25519"; + }; + }; + host = { + configDir = lib.mkOption { + type = lib.types.str; + default = "/etc/ssh"; + }; + keyFile = lib.mkOption { + description = "String path to the host private key file"; + type = lib.types.str; + default = "ssh_host_ed25519_key"; + }; + keyType = lib.mkOption { + description = "OpenSSH host key type for ssh.hostKey."; + type = lib.types.enum [ "ed25519" "rsa" "ecdsa" ]; + default = "ed25519"; + }; + extraSettings = lib.mkOption { + description = "Extra settings to merge"; + type = lib.types.attrs; + default = { }; + }; + enable-scripts = lib.mkEnableOption "Enable SSH host cert management scripts"; + }; + certificates = { + provisioner = lib.mkOption { + type = lib.types.nullOr lib.types.str; + }; + host = { + enable = lib.mkEnableOption "Enable SSH host certs"; + autoRenew = lib.mkEnableOption "Auto-renew the SSH host certs with a systemd service/timer"; + extraPrincipals = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + }; + }; + user = { + enable = lib.mkEnableOption "Enable SSH user certs"; + CAFile = lib.mkOption { + description = "Filename of the SSH user CA with the config directory"; + type = lib.types.str; + default = "ssh_user_ca_key.pub"; + }; + extraPrincipals = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + }; + }; + }; + }; + }; +} \ No newline at end of file diff --git a/modules/features/ssh/default.nix b/modules/features/ssh/default.nix index ab39b8f..2f0ab9c 100644 --- a/modules/features/ssh/default.nix +++ b/modules/features/ssh/default.nix @@ -1,115 +1,79 @@ # New attempt at consolidating SSH config that wraps the step client, SSH certs, and all that in a top-level module -{ self, inputs, ... }: +{ self, inputs, lib, config, ... }: let - caPatterns = [ "*.john-stream.com" "192.168.1.*" "fded:fb16:653e:25da:be24:11ff:*" ]; sshHostCAPath = ../../hosts/janus/public/ssh_host_ca_key.pub; + caPatterns = [ "*.john-stream.com" "192.168.1.*" "fded:fb16:653e:25da:be24:11ff:*" ]; + wrappers = inputs.self.wrappers; + sshCertConfig = config.optionModules.ssh-certs; + + mkHostScripts = { cfg, pkgs, provisionerPasswordFile ? null }: let + hostKeyFile = "${cfg.host.configDir}/${cfg.host.keyFile}"; + hostCertFile = "${hostKeyFile}-cert.pub"; + in { + inherit hostKeyFile hostCertFile; + sign = (wrappers.signHostWrapper.apply { + inherit pkgs provisionerPasswordFile; + inherit (cfg.certificates) provisioner; + inherit (cfg.certificates.host) extraPrincipals; + overwrite = true; + }).wrapper; + renew = (wrappers.renewHostWrapper.apply { + inherit pkgs; + sshHostKeyFile = hostKeyFile; + overwrite = true; + }).wrapper; + check = (wrappers.hostCheckWrapper.apply { + inherit pkgs; + certPath = hostCertFile; + }).wrapper; + renewalCheck = (wrappers.renewalCheck.apply { + inherit pkgs; + certPath = hostCertFile; + expires-in = "4h"; + }).wrapper; + }; + + mkUserScripts = { cfg, pkgs, provisionerPasswordFile ? null }: { + sign = (wrappers.signUserWrapper.apply { + inherit pkgs provisionerPasswordFile; + inherit (cfg.certificates) provisioner; + validUsers = [ "root" "john" "appdaemon" ]; + overwrite = true; + }).wrapper; + check = (wrappers.userCheckWrapper.apply { + inherit pkgs; + certPath = "${cfg.user.keyFile}-cert.pub"; + }).wrapper; + }; in { flake.modules.nixos.ssh-new = { config, pkgs, lib, ... }: let cfg = config.ssh-new; - hostKeyFile = "${cfg.host.configDir}/${cfg.host.keyFile}"; - hostCertFile = "${hostKeyFile}-cert.pub"; - userCAFile = "${cfg.host.configDir}/${cfg.certificates.user.CAFile}"; + hostScripts = mkHostScripts { + inherit cfg pkgs; + provisionerPasswordFile = if cfg.certificates.host.autoRenew + then config.sops.secrets."janus/admin_jwk".path + else null; + }; + userScripts = mkUserScripts { + inherit cfg pkgs; + provisionerPasswordFile = config.sops.secrets."janus/admin_jwk".path; + }; CAknownHosts = (lib.genAttrs caPatterns (_: { - certAuthority = true; - publicKey = lib.removeSuffix "\n" (builtins.readFile sshHostCAPath); + certAuthority = true; + publicKey = lib.removeSuffix "\n" (builtins.readFile sshHostCAPath); })); - - wrappers = inputs.self.wrappers; - provisionerPasswordFile = config.sops.secrets."janus/admin_jwk".path; - sshHostCertSign = (wrappers.signHostWrapper.apply { - inherit pkgs provisionerPasswordFile; - inherit (cfg.certificates) provisioner; - inherit (cfg.certificates.host) extraPrincipals; - }).wrapper; - sshHostCertRenew = (wrappers.renewHostWrapper.apply { - inherit pkgs; - sshHostKeyFile = hostKeyFile; - overwrite = true; - }).wrapper; - sshHostCertCheck = (wrappers.hostCheckWrapper.apply { - inherit pkgs; - certPath = hostCertFile; - }).wrapper; - sshHostRenewalCheck = (wrappers.renewalCheck.apply { - inherit pkgs; - certPath = hostCertFile; - expires-in = "4h"; - }).wrapper; - sshUserCertSign = (wrappers.signUserWrapper.apply { - inherit pkgs provisionerPasswordFile; - inherit (cfg.certificates) provisioner; - validUsers = [ "root" "john" "appdaemon" ]; - }).wrapper; - sshUserCertCheck = (wrappers.userCheckWrapper.apply { - inherit pkgs; - certPath = "${cfg.user.keyFile}-cert.pub"; - }).wrapper; in { - options.ssh-new = { - user = { - keyFile = lib.mkOption { - type = lib.types.str; - default = "$HOME/.ssh/id_ed25519"; - }; - }; - host = { - configDir = lib.mkOption { - type = lib.types.str; - default = "/etc/ssh"; - }; - keyFile = lib.mkOption { - description = "String path to the host private key file"; - type = lib.types.str; - default = "ssh_host_ed25519_key"; - }; - keyType = lib.mkOption { - description = "OpenSSH host key type for ssh.hostKey."; - type = lib.types.enum [ "ed25519" "rsa" "ecdsa" ]; - default = "ed25519"; - }; - extraSettings = lib.mkOption { - description = "Extra settings to merge"; - type = lib.types.attrs; - default = { }; - }; - }; - certificates = { - provisioner = lib.mkOption { - type = lib.types.nullOr lib.types.str; - }; - host = { - enable = lib.mkEnableOption "Enable SSH host certs"; - autoRenew = lib.mkEnableOption "Auto-renew the SSH host certs with a systemd service/timer"; - extraPrincipals = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ ]; - }; - }; - user = { - enable = lib.mkEnableOption "Enable SSH user certs"; - CAFile = lib.mkOption { - description = "Filename of the SSH user CA with the config directory"; - type = lib.types.str; - default = "ssh_user_ca_key.pub"; - }; - extraPrincipals = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ ]; - }; - }; - }; - }; - + imports = [ sshCertConfig ]; config = { services.openssh = { enable = true; openFirewall = true; hostKeys = [ { - path = hostKeyFile; + path = hostScripts.hostKeyFile; type = cfg.host.keyType; } ]; @@ -120,7 +84,7 @@ in } cfg.host.extraSettings (lib.mkIf cfg.certificates.host.enable { - HostCertificate = "${hostKeyFile}-cert.pub"; + HostCertificate = hostScripts.hostCertFile; }) (lib.mkIf cfg.certificates.user.enable { TrustedUserCAKeys = "${cfg.host.configDir}/${cfg.certificates.user.CAFile}"; @@ -134,10 +98,10 @@ in environment.systemPackages = ( lib.optionals cfg.certificates.host.enable [ - sshHostCertSign - sshHostCertRenew - sshHostRenewalCheck - sshHostCertCheck + hostScripts.sign + hostScripts.renew + hostScripts.renewalCheck + hostScripts.check ] ); @@ -161,8 +125,8 @@ in Type = "oneshot"; User = "root"; Group = "root"; - ExecCondition = lib.getExe sshHostRenewalCheck; - ExecStart = lib.getExe sshHostCertRenew; + ExecCondition = lib.getExe hostScripts.renewalCheck; + ExecStart = lib.getExe hostScripts.renew; }; }; timers.ssh-certs-renew = { @@ -183,14 +147,12 @@ in home-manager.users.root = lib.mkIf cfg.certificates.user.enable { home.stateVersion = lib.mkDefault config.system.stateVersion; - imports = with inputs.self.modules.homeManager; [ - ssh-new - ]; + imports = [ inputs.self.modules.homeManager.ssh-new ]; home.packages = [ - sshUserCertSign - sshUserCertCheck + userScripts.sign + userScripts.check ]; - ssh-new.certificates.enable = true; + ssh-new.certificates.user.enable = true; }; }; }; @@ -198,26 +160,24 @@ in flake.modules.homeManager.ssh-new = { config, pkgs, lib, ... }: let cfg = config.ssh-new; + hostScripts = mkHostScripts { inherit cfg pkgs; }; sshHostCAContent = lib.removeSuffix "\n" (builtins.readFile sshHostCAPath); knownHostsText = lib.concatMapStrings (pattern: "@cert-authority ${pattern} ${sshHostCAContent}\n") caPatterns; in { - options.ssh-new = { - keyFile = lib.mkOption { - type = lib.types.str; - default = "${config.home.homeDirectory}/.ssh/id_ed25519"; - }; - certificates = { - enable = lib.mkEnableOption "Enable SSH client certificates"; - }; - }; - + imports = [ sshCertConfig ]; config = { - home.file.".ssh/known_hosts" = lib.mkIf cfg.certificates.enable { + home.file.".ssh/known_hosts" = lib.mkIf cfg.certificates.user.enable { text = knownHostsText; }; + home.packages = lib.optionals cfg.host.enable-scripts [ + hostScripts.sign + hostScripts.renew + hostScripts.renewalCheck + hostScripts.check + ]; programs.ssh = { enable = true; enableDefaultConfig = false; @@ -237,7 +197,7 @@ in PasswordAuthentication = "no"; PreferredAuthentications = "publickey"; IdentitiesOnly = true; - IdentityFile = cfg.keyFile; + IdentityFile = cfg.user.keyFile; StrictHostKeyChecking = "accept-new"; UserKnownHostsFile = "${config.home.homeDirectory}/.ssh/known_hosts"; @@ -251,13 +211,16 @@ in } ] # SSH certificate settings - ++ lib.optionals cfg.certificates.enable [ + ++ lib.optionals cfg.certificates.user.enable [ { - CertificateFile = "${cfg.keyFile}-cert.pub"; + CertificateFile = "${cfg.user.keyFile}-cert.pub"; } ] ); - + "john-pc" = { + HostName = "192.168.1.85"; + User = "john"; + }; "gitea" = { HostName = "192.168.1.104"; User = "john"; diff --git a/modules/features/ssh/ssh-wrappers.nix b/modules/features/ssh/ssh-wrappers.nix index f37977e..d2a1898 100644 --- a/modules/features/ssh/ssh-wrappers.nix +++ b/modules/features/ssh/ssh-wrappers.nix @@ -1,25 +1,12 @@ -{ self, inputs, ... }: +{ self, inputs, config, ... }: let + sshCertConfig = config.optionModules.ssh-certs; mkPrincipalArgs = principals: builtins.concatLists (map (principal: [ "--principal" principal ]) principals); in { flake.wrappers.signHostWrapper = inputs.wrappers.lib.wrapModule ({config, lib, wlib, ... }: { - options = { - provisioner = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = "admin"; - }; - provisionerPasswordFile = lib.mkOption { - type = lib.types.nullOr lib.types.str; - }; - extraPrincipals = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ ]; - }; - overwrite = lib.mkEnableOption "Overwrite existing cert file?"; - }; - + imports = [ sshCertConfig ]; config = { binName = "ssh-host-cert-sign"; package = config.pkgs.step-cli; @@ -53,14 +40,7 @@ in }); flake.wrappers.renewHostWrapper = inputs.wrappers.lib.wrapModule ({config, lib, wlib, ... }: { - options = { - sshHostKeyFile = lib.mkOption { - type = lib.types.str; - default = "/etc/ssh/ssh_host_ed25519_key"; - }; - overwrite = lib.mkEnableOption "Overwrite existing cert file?"; - }; - + imports = [ sshCertConfig ]; config = { binName = "ssh-host-cert-renew"; package = config.pkgs.step-cli; @@ -72,13 +52,7 @@ in }); flake.wrappers.hostCheckWrapper = inputs.wrappers.lib.wrapModule ({config, lib, wlib, ... }: { - options = { - certPath = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = "/etc/ssh/ssh_host_ed25519_key-cert.pub"; - }; - }; - + imports = [ sshCertConfig ]; config = { binName = "ssh-host-cert-check"; package = config.pkgs.openssh; @@ -88,22 +62,7 @@ in }); flake.wrappers.signUserWrapper = inputs.wrappers.lib.wrapModule ({config, lib, wlib, ... }: { - options = { - provisioner = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = "admin"; - }; - provisionerPasswordFile = lib.mkOption { - type = lib.types.nullOr lib.types.str; - }; - validUsers = lib.mkOption { - description = "A list of the user names that this cert will be valid for"; - type = lib.types.listOf lib.types.str; - default = [ ]; - }; - overwrite = lib.mkEnableOption "Overwrite existing cert file?"; - }; - + imports = [ sshCertConfig ]; config = { binName = "ssh-user-cert-sign"; package = config.pkgs.step-cli; @@ -118,13 +77,7 @@ in }); flake.wrappers.userCheckWrapper = inputs.wrappers.lib.wrapModule ({config, lib, wlib, ... }: { - options = { - certPath = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = "$HOME/.ssh/id_ed25519-cert.pub"; - }; - }; - + imports = [ sshCertConfig ]; config = { binName = "ssh-user-cert-check"; package = config.pkgs.openssh; @@ -134,16 +87,7 @@ in }); flake.wrappers.renewalCheck = inputs.wrappers.lib.wrapModule ({config, lib, wlib, ... }: { - options = { - certPath = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = "$HOME/.ssh/id_ed25519-cert.pub"; - }; - expires-in = lib.mkOption { - type = lib.types.str; - default = "4h"; - }; - }; + imports = [ sshCertConfig ]; config = { binName = "ssh-renewal-check"; package = config.pkgs.step-cli; diff --git a/modules/hosts/john-pc/default.nix b/modules/hosts/john-pc/default.nix index ff3c89b..289c897 100644 --- a/modules/hosts/john-pc/default.nix +++ b/modules/hosts/john-pc/default.nix @@ -17,8 +17,8 @@ in imports = with inputs.self.modules.homeManager; [ rebuild john - mtls - restic + # mtls + # restic docker desktop step-client @@ -33,6 +33,7 @@ in targets.genericLinux.enable = true; + homeManagerFlakeDir = flakeDir; home.username = "${username}"; home.homeDirectory = "/home/${username}"; home.packages = with pkgs; [ @@ -45,11 +46,11 @@ in }).wrapper ]; - homeManagerFlakeDir = flakeDir; docker.enable = true; - ssh-new = { - certificates.enable = true; - }; + # ssh-new = { + # certificates.enable = true; + # host-scripts.enable = true; + # }; # ssh = { # matchSets = { # certs = true; @@ -71,18 +72,18 @@ in mode = "0400"; sopsFile = ./secrets.yaml; }; - restic = { - passwordFile = resticPasswordFile; - OnCalendar = "*:0/15"; - paths = [ "${config.xdg.userDirs.documents}" "/conf" ]; - exclude = [ - "/home/*/Pictures" - "/home/*/Videos" - "/home/*/go" - "/home/*/snap" - "/home/john/john-nas" - ]; - }; + # restic = { + # passwordFile = resticPasswordFile; + # OnCalendar = "*:0/15"; + # paths = [ "${config.xdg.userDirs.documents}" "/conf" ]; + # exclude = [ + # "/home/*/Pictures" + # "/home/*/Videos" + # "/home/*/go" + # "/home/*/snap" + # "/home/john/john-nas" + # ]; + # }; # mtls = { # enable = true; # subject = hostname; diff --git a/modules/programs/sops.nix b/modules/programs/sops.nix index 44dcae4..16c2d29 100644 --- a/modules/programs/sops.nix +++ b/modules/programs/sops.nix @@ -35,7 +35,7 @@ in config = let - identityFile = config.ssh-new.keyFile; + identityFile = "${config.home.homeDirectory}/.ssh/${config.ssh-new.user.keyFile}"; my-sops = (inputs.self.wrappers.mySops.apply { inherit pkgs; sshKey = identityFile;