From 703bdb8db066b06726859e0e8049418ef16bfa5b Mon Sep 17 00:00:00 2001 From: Trolli Schmittlauch Date: Wed, 18 Mar 2026 01:54:09 +0100 Subject: [PATCH] home: introduce ssh module that enables defining multi-proxy targets also defines some good defaults for ssh. workmac config already refactored. desktop config tbd. --- home/common.nix | 2 + home/modules/ensureDirs.nix | 66 +++++++++++++++++++++ home/modules/ssh.nix | 110 ++++++++++++++++++++++++++++++++++ home/workmac.nix | 114 ++++++++++++++---------------------- 4 files changed, 222 insertions(+), 70 deletions(-) create mode 100644 home/modules/ensureDirs.nix create mode 100644 home/modules/ssh.nix diff --git a/home/common.nix b/home/common.nix index 2203fbe..d53b3ec 100644 --- a/home/common.nix +++ b/home/common.nix @@ -11,6 +11,8 @@ ./modules/vscodium.nix ./modules/fonts.nix ./modules/captive-browser.nix + ./modules/ensureDirs.nix + ./modules/ssh.nix ]; home.homeDirectory = if pkgs.stdenv.isDarwin then "/Users/${config.home.username}" else "/home/${config.home.username}"; diff --git a/home/modules/ensureDirs.nix b/home/modules/ensureDirs.nix new file mode 100644 index 0000000..6251ce9 --- /dev/null +++ b/home/modules/ensureDirs.nix @@ -0,0 +1,66 @@ +{ config, lib, ... }: + +let + cfg = config.home.ensureDirs; + + enabledDirs = lib.filterAttrs (_: opts: opts.enable) cfg; + + dirType = lib.types.submodule ( + { name, ... }: + { + options = { + enable = lib.mkEnableOption "this directory" // { + default = true; + }; + + path = lib.mkOption { + type = lib.types.str; + default = name; + description = "Directory path relative to $HOME. Defaults to the attribute name."; + }; + + mode = lib.mkOption { + type = lib.types.str; + default = "755"; + description = "Octal permissions to set on the directory."; + }; + }; + } + ); + + activationScript = builtins.concatStringsSep "\n" ( + lib.mapAttrsToList (_: opts: '' + run mkdir -p "$HOME/${opts.path}" + run chmod ${opts.mode} "$HOME/${opts.path}" + '') enabledDirs + ); + +in +{ + options.home.ensureDirs = lib.mkOption { + type = lib.types.attrsOf dirType; + default = { }; + description = '' + Set of directories (relative to $HOME) to create with specific + permissions on every home-manager activation. Works on both + Linux and Darwin. The attribute name defaults as the directory + path but can be overridden via the `path` option. + ''; + example = lib.literalExpression '' + { + ".ssh" = { mode = "700"; }; + ".gnupg" = { mode = "700"; }; + ".local/share/myapp" = { mode = "700"; }; + "projects" = {}; + "my-workspace" = { + path = "work/projects"; + mode = "750"; + }; + } + ''; + }; + + config = lib.mkIf (enabledDirs != { }) { + home.activation.ensureDirs = lib.hm.dag.entryAfter [ "writeBoundary" ] activationScript; + }; +} diff --git a/home/modules/ssh.nix b/home/modules/ssh.nix new file mode 100644 index 0000000..3301bbb --- /dev/null +++ b/home/modules/ssh.nix @@ -0,0 +1,110 @@ +{ + pkgs, + inputs, + config, + lib, + ... +}: +let + inherit (lib) types; + controlDir = "~/.ssh/controlmasters"; + + proxyTagType = types.submodule ( + { name, ... }: + { + options = { + after = lib.mkOption { + type = types.nullOr (types.listOf types.str); + default = [ ]; + description = "List of other hm.dag.entryAfter conditions the match rule needs to be placed."; + }; + # XXX extra args + connectType = lib.mkOption { + type = types.listOf ( + types.enum [ + "all" + "nomaster" + "master" + "direct" + "indirect" + ] + ); + description = '' + Select jump indirection mode: + all|nomaster|{master,direct,indirect} + using ControlMaster socket, only directly, only through SSH if direct connections fail + ''; + }; + jumpHost = lib.mkOption { + type = types.nullOr types.str; # null is also okay for achieving hyppy eyeballs + }; + noDirect = lib.mkEnableOption "Skip connecting to the remote host directly, always use the jump hosts"; + }; + + } + ); +in +{ + imports = [ + ./ensureDirs.nix + ]; + options = { + programs.ssh.multi-proxy.tags = lib.mkOption { + type = types.attrsOf proxyTagType; + default = { }; + description = "Set of ssh-multi-proxy target tag definitions."; + }; + }; + config = lib.mkIf config.programs.ssh.enable { + home.ensureDirs = { + ".ssh" = { + mode = "700"; + }; + ".ssh/controlmasters" = { + mode = "700"; + }; + }; + home.packages = [ pkgs.fc-scripts.ssh-multi-proxy ]; + programs.ssh = { + enableDefaultConfig = false; # deprecated + package = lib.mkDefault pkgs.openssh; + extraOptionOverrides = { + CanonicalizeHostname = "yes"; + CanonicalizeFallbackLocal = "yes"; + }; + matchBlocks = { + + # default, gets placed last by home-manager + "*" = { + serverAliveInterval = 10; + serverAliveCountMax = 2; # 2 strikes and you're out + forwardAgent = false; + addKeysToAgent = "no"; + compression = false; + hashKnownHosts = false; + userKnownHostsFile = "~/.ssh/known_hosts"; + controlMaster = "no"; + controlPath = "${controlDir}/%r@%n:%p"; + controlPersist = "no"; + }; + } + # ssh-multi-proxy tag definitions + // lib.mapAttrs' ( + tag: tagDef: + let + dependency = if tagDef.after != null then lib.hm.dag.entryAfter tagDef.after else lib.id; + in + lib.nameValuePair "tagged-${tag}" { + match = ''tagged="${tag}"''; + proxyCommand = "${lib.getExe pkgs.fc-scripts.ssh-multi-proxy} -v -p connect --control-path='${controlDir}'${lib.optionalString tagDef.noDirect " -n"} -i ${lib.concatStringsSep "," tagDef.connectType}${ + lib.optionalString (tagDef.jumpHost != null) " -j ${tagDef.jumpHost}" + } %h %p"; + extraOptions = { + ProxyUseFdpass = "yes"; + }; + } + ) config.programs.ssh.multi-proxy.tags; + }; + + }; +} diff --git a/home/workmac.nix b/home/workmac.nix index c8b2bcf..1646c66 100644 --- a/home/workmac.nix +++ b/home/workmac.nix @@ -37,17 +37,48 @@ in programs.ssh = { enable = true; - enableDefaultConfig = false; # deprecated - package = pkgs.openssh; - # TODO: common config for desktop as well # early catchall to enforce agent socket usage. **NOT** the place for fallback defaults. extraOptionOverrides = { IdentityAgent = "\"~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock\""; - CanonicalizeHostname = "yes"; CanonicalDomains = "fcio.net gocept.net"; - CanonicalizeFallbackLocal = "yes"; }; + multi-proxy.tags = { + # XXX genAttrs oder listToAttrs mit rzob/WHQ + + # Connect to hosts directly, or if that fails (e.g. IPv4-only network) + # use the bastion host as a jump host. + "direct-rzob" = { + after = [ "*.fcio.net *.gocept.net" ]; + jumpHost = "vpn-rzob.services.fcio.net"; + connectType = [ + "master" + "direct" + ]; + }; + + "direct-whq" = { + after = [ "*.fcio.net *.gocept.net" ]; + jumpHost = "vpn-whq.services.fcio.net"; + connectType = [ + "master" + "direct" + ]; + }; + + "proxyjump-whq" = { + after = [ "tagged-direct-whq" ]; # XXX readOnly backref option + jumpHost = "kenny12.fe.whq.fcio.net"; + connectType = [ "all" ]; + noDirect = true; + }; + "proxyjump-rzob" = { + after = [ "tagged-direct-rzob" ]; # XXX readOnly backref option + jumpHost = "kenny09.fe.whq.fcio.net"; + connectType = [ "all" ]; + noDirect = true; + }; + }; # ssh host config matchBlocks = { # --- Host blocks (order matters: specific before wildcard) -------- @@ -64,19 +95,17 @@ in # For adding more topology-related specific rules, consider moving this into secrets like SOPS or so. # Hostname wildcard for management network, also accessed via bastion host - "*.mgm.whq.fcio.net *.mgm.whq.gocept.net" = - lib.hm.dag.entryBefore [ "*.fcio.net *.gocept.net" ] { - extraOptions = { - Tag = "proxyjump-whq"; - }; + "*.mgm.whq.fcio.net *.mgm.whq.gocept.net" = lib.hm.dag.entryBefore [ "*.fcio.net *.gocept.net" ] { + extraOptions = { + Tag = "proxyjump-whq"; }; + }; - "*.mgm.rzob.fcio.net *.mgm.rzob.gocept.net" = - lib.hm.dag.entryBefore [ "*.fcio.net *.gocept.net" ] { - extraOptions = { - Tag = "proxyjump-rzob"; - }; + "*.mgm.rzob.fcio.net *.mgm.rzob.gocept.net" = lib.hm.dag.entryBefore [ "*.fcio.net *.gocept.net" ] { + extraOptions = { + Tag = "proxyjump-rzob"; }; + }; # Fallback for all other FCIO hosts "*.fcio.net *.gocept.net" = { @@ -85,61 +114,6 @@ in }; }; - # --- Match blocks (OpenSSH 9.4+ tags) ---------------------------- - - # Connect to hosts directly, or if that fails (e.g. IPv4-only network) - # use the bastion host as a jump host. - "tagged-direct-rzob" = - lib.hm.dag.entryAfter [ "*.fcio.net *.gocept.net" ] { - match = ''tagged="direct-rzob"''; - proxyCommand = "${lib.getExe pkgs.fc-scripts.ssh-multi-proxy} -p connect -i master,direct -j vpn-rzob.services.fcio.net %h %p"; - extraOptions = { - ProxyUseFdpass = "yes"; - }; - }; - - "tagged-direct-whq" = - lib.hm.dag.entryAfter [ "*.fcio.net *.gocept.net" ] { - match = ''tagged="direct-whq"''; - proxyCommand = "${lib.getExe pkgs.fc-scripts.ssh-multi-proxy} -p connect -i master,direct -j vpn-whq.services.fcio.net %h %p"; - extraOptions = { - ProxyUseFdpass = "yes"; - }; - }; - - # Don't connect directly, always use the bastion host as a jump host. - "tagged-proxyjump-whq" = - lib.hm.dag.entryAfter [ "tagged-direct" ] { - match = ''tagged="proxyjump-whq"''; - proxyCommand = "ssh-multi-proxy -p connect -ni all -j kenny12.fe.whq.fcio.net %h %p"; - extraOptions = { - ProxyUseFdpass = "yes"; - }; - }; - - "tagged-proxyjump-rzob" = - lib.hm.dag.entryAfter [ "tagged-direct" ] { - match = ''tagged="proxyjump"''; - proxyCommand = "ssh-multi-proxy -p connect -ni all -j kenny09.fe.rzob.fcio.net %h %p"; - extraOptions = { - ProxyUseFdpass = "yes"; - }; - }; - - # default, gets placed last by home-manager - "*" = { - serverAliveInterval = 10; - serverAliveCountMax = 2; # 2 strikes and you're out - forwardAgent = false; - addKeysToAgent = "no"; - compression = false; - hashKnownHosts = false; - userKnownHostsFile = "~/.ssh/known_hosts"; - controlMaster = "no"; - controlPath = "~/.ssh/master-%r@%n:%p"; - controlPersist = "no"; - }; - "hydra01" = { hostname = "hydra01.access.whq.gocept.net"; user = "os";