{ config, lib, pkgs, ... }: with lib; let cfg = config.local.auth.openssh; withOath = config.local.auth.oath.enable; withPassword = config.local.auth.openssh.passwordAuthentication; port = if cfg.shiftPortNumber then 2234 else 22; restrict = cfg.restrictListen; exemptList = optionals config.local.net.fail2ban.enable config.services.fail2ban.ignoreIP; in { options.local.auth.openssh = { enable = mkEnableOption "openssh"; tunnel.enable = mkEnableOption "ssh tunnel user"; #TODO: Desfasar ecdsa, inseguro hostKeys = listToAttrs (map (name: { inherit name; value = mkOption { type = types.bool; default = false; }; }) ["ecdsa" "ed25519" "rsa"]); restrictListen = mkOption { default = null; type = with types; nullOr (submodule { options = { addresses = mkOption { type = listOf str; }; interface = mkOption { type = nullOr str; default = null; }; vsockCid = mkOption { type = nullOr ints.u32; default = null; }; }; }); }; passwordAuthentication = mkOption { type = types.bool; default = false; }; shiftPortNumber = mkOption { type = types.bool; default = true; }; withDeployKeys = mkOption { type = types.bool; default = false; }; }; config = lib.mkIf cfg.enable { assertions = [ { assertion = cfg.tunnel.enable -> withOath; message = "SSH tunnel requires oath"; } { assertion = restrict != null -> (restrict.vsockCid != null -> (restrict.interface == null && restrict.addresses == [])); message = "SSH vsock restrict requires disabling inet"; } { assertion = restrict != null -> (restrict.vsockCid != null -> config.services.openssh.startWhenNeeded); message = "SSH vsock restrict requires socket activation"; } { assertion = restrict != null -> (restrict.vsockCid != null -> config.local.virt.enable); message = "SSH vsock restrict requires nixvirt"; } { assertion = any (key: key) (attrValues cfg.hostKeys); message = "No OpenSSH host keys were enabled"; } ]; local.boot.impermanence.files = flatten (map (key: [key.path "${key.path}.pub"]) config.services.openssh.hostKeys); networking.firewall = { interfaces = optionalAttrs (restrict != null && restrict.interface != null) { ${restrict.interface}.allowedTCPPorts = [port]; }; allowedTCPPorts = optional (restrict == null || restrict.interface == null) port; }; services.openssh = { enable = true; ports = optional (restrict != null -> restrict.addresses != []) port; startWhenNeeded = mkDefault (!config.services.fail2ban.enable); extraConfig = optionalString (exemptList != []) '' PerSourcePenaltyExemptList ${concatStringsSep "," exemptList} '' + optionalString cfg.tunnel.enable '' # User 'tunnel' has no password. Use PAM OATH # and connect with -N, forward with -R. Match User tunnel AllowTcpForwarding remote AllowStreamLocalForwarding no X11Forwarding no PermitTunnel no GatewayPorts no AllowAgentForwarding no PermitOpen none PermitListen 60220 60221 60222 60223 60224 60225 60226 60227 60228 60229 Banner ${pkgs.writeText "tunnel-banner" '' This is a reverse tunnel ''} ''; hostKeys = map (name: { path = "/etc/ssh/ssh_host_${name}_key"; type = name; } // optionalAttrs (name == "rsa") { bits = 4096; }) (attrNames (filterAttrs (name: enable: enable) cfg.hostKeys)); settings = { X11Forwarding = config.local.seat.enable && config.local.seat.graphical; PermitRootLogin = "prohibit-password"; PasswordAuthentication = withOath || withPassword; # Necesario para oath, no reemplaza a oath }; listenAddresses = mkIf (restrict != null) (map (addr: {inherit addr;}) restrict.addresses); }; systemd.sockets = mkIf (restrict != null && restrict.vsockCid != null) { sshd = let kernelMod = "modprobe@${ if restrict.vsockCid == 2 then "vhost_" else "" }vsock.service"; in { after = [kernelMod]; wants = [kernelMod]; socketConfig.ListenStream = mkForce ["vsock:${toString restrict.vsockCid}:${toString port}"]; }; }; users.users = { root = mkIf cfg.withDeployKeys { openssh.authorizedKeys.keyFiles = [./ssh-key.pub]; }; tunnel = mkIf cfg.tunnel.enable { uid = 1100; group = "nogroup"; isSystemUser = true; # Requiere oath password = "tunnel"; home = "/var/empty"; shell = "${pkgs.coreutils}/bin/true"; }; }; }; }