diff --git a/configuration.nix b/configuration.nix index cfde9f4..7eb07fd 100644 --- a/configuration.nix +++ b/configuration.nix @@ -57,8 +57,42 @@ in boot.loader.grub.device = "/dev/vda"; # Install to MBR # Network configuration - networking.useDHCP = false; - networking.interfaces.ens3.useDHCP = true; + networking = { + useDHCP = false; + interfaces.ens3.useDHCP = true; + + # Firewall + firewall = { + enable = true; + allowedTCPPorts = [ 22 80 443 ]; # SSH, HTTP, HTTPS + allowedUDPPortRanges = [ { from = 60000; to = 60010; } ]; # mosh + + # Egress controls for regular users + # UID range: 1000 (first regular user) to 65534 (nobody - excluded from controls) + # This covers all dev accounts while excluding system services + extraCommands = '' + # Rate limit new outbound connections (150/min sustained, burst 300) + # High enough for npm install, low enough to prevent abuse + # DROP instead of REJECT so apps back off naturally + iptables -A OUTPUT -m state --state NEW -m owner --uid-owner 1000:65534 \ + -m limit --limit 150/min --limit-burst 300 -j ACCEPT + iptables -A OUTPUT -m state --state NEW -m owner --uid-owner 1000:65534 \ + -j LOG --log-prefix "EGRESS-LIMIT: " --log-level warning + iptables -A OUTPUT -m state --state NEW -m owner --uid-owner 1000:65534 \ + -j DROP + ''; + + # Clean up on stop + extraStopCommands = '' + iptables -D OUTPUT -m state --state NEW -m owner --uid-owner 1000:65534 \ + -m limit --limit 150/min --limit-burst 300 -j ACCEPT 2>/dev/null || true + iptables -D OUTPUT -m state --state NEW -m owner --uid-owner 1000:65534 \ + -j LOG --log-prefix "EGRESS-LIMIT: " --log-level warning 2>/dev/null || true + iptables -D OUTPUT -m state --state NEW -m owner --uid-owner 1000:65534 \ + -j DROP 2>/dev/null || true + ''; + }; + }; # Time zone time.timeZone = "UTC"; @@ -66,50 +100,52 @@ in # Internationalization i18n.defaultLocale = "en_US.UTF-8"; - # System packages - environment.systemPackages = with pkgs; [ - vim - git - htop - curl - tmux - # Dev environment - python3 - uv - direnv - zig - # AI coding tools (via flake inputs) - opencode # AI coding agent (opencode CLI) - # For JS-based AI tools (gemini-cli, claude-cli): users run bun/npm install - nodejs_22 - bun - # Terminfo for modern terminals (ghostty, kitty, etc.) - pkgs-unstable.ghostty.terminfo - kitty.terminfo - # Classic Unix social tools - bsd-finger - ytalk - fortune - # Mobile shell - resilient SSH for spotty connections - mosh - # Admin scripts (declarative deployment) - dev-add - dev-remove - egress-status - ]; + # Environment configuration + environment = { + systemPackages = with pkgs; [ + vim + git + htop + curl + tmux + # Dev environment + python3 + uv + direnv + zig + # AI coding tools (via flake inputs) + opencode # AI coding agent (opencode CLI) + # For JS-based AI tools (gemini-cli, claude-cli): users run bun/npm install + nodejs_22 + bun + # Terminfo for modern terminals (ghostty, kitty, etc.) + pkgs-unstable.ghostty.terminfo + kitty.terminfo + # Classic Unix social tools + bsd-finger + ytalk + fortune + # Mobile shell - resilient SSH for spotty connections + mosh + # Admin scripts (declarative deployment) + dev-add + dev-remove + egress-status + ]; - # Add ~/.local/bin and /usr/local/bin to PATH for manually installed tools - environment.localBinInPath = true; - environment.shellInit = '' - export PATH="/usr/local/bin:$PATH" + # Add ~/.local/bin and /usr/local/bin to PATH for manually installed tools + localBinInPath = true; + shellInit = '' + export PATH="/usr/local/bin:$PATH" - # Set COLORTERM for truecolor terminals (SSH doesn't forward this) - case "$TERM" in - xterm-ghostty|xterm-kitty|alacritty|xterm-256color) - export COLORTERM=truecolor - ;; - esac - ''; + # Set COLORTERM for truecolor terminals (SSH doesn't forward this) + case "$TERM" in + xterm-ghostty|xterm-kitty|alacritty|xterm-256color) + export COLORTERM=truecolor + ;; + esac + ''; + }; # Fortune on interactive shell login programs.bash.interactiveShellInit = '' @@ -138,38 +174,6 @@ in # Devs group for shared resources (Slack tokens, etc.) users.groups.devs = {}; - # Firewall (will be configured for Matrix services) - networking.firewall = { - enable = true; - allowedTCPPorts = [ 22 80 443 ]; # SSH, HTTP, HTTPS - allowedUDPPortRanges = [ { from = 60000; to = 60010; } ]; # mosh - - # Egress controls for regular users - # UID range: 1000 (first regular user) to 65534 (nobody - excluded from controls) - # This covers all dev accounts while excluding system services - extraCommands = '' - # Rate limit new outbound connections (150/min sustained, burst 300) - # High enough for npm install, low enough to prevent abuse - # DROP instead of REJECT so apps back off naturally - iptables -A OUTPUT -m state --state NEW -m owner --uid-owner 1000:65534 \ - -m limit --limit 150/min --limit-burst 300 -j ACCEPT - iptables -A OUTPUT -m state --state NEW -m owner --uid-owner 1000:65534 \ - -j LOG --log-prefix "EGRESS-LIMIT: " --log-level warning - iptables -A OUTPUT -m state --state NEW -m owner --uid-owner 1000:65534 \ - -j DROP - ''; - - # Clean up on stop - extraStopCommands = '' - iptables -D OUTPUT -m state --state NEW -m owner --uid-owner 1000:65534 \ - -m limit --limit 150/min --limit-burst 300 -j ACCEPT 2>/dev/null || true - iptables -D OUTPUT -m state --state NEW -m owner --uid-owner 1000:65534 \ - -j LOG --log-prefix "EGRESS-LIMIT: " --log-level warning 2>/dev/null || true - iptables -D OUTPUT -m state --state NEW -m owner --uid-owner 1000:65534 \ - -j DROP 2>/dev/null || true - ''; - }; - # ACME for Let's Encrypt certificates security.acme = { acceptTerms = true; @@ -182,11 +186,54 @@ in "olm-3.2.16" ]; - # Resource limits for user slices (prevent one user from crashing server) - systemd.slices."user".sliceConfig = { - MemoryMax = "80%"; # Users collectively can't exceed 80% RAM - TasksMax = 500; # Max 500 total processes across all users - CPUWeight = 100; # Fair sharing when contended, bursts allowed + # Systemd configuration - resource limits and watchdogs + systemd = { + # Resource limits for user slices (prevent one user from crashing server) + slices."user".sliceConfig = { + MemoryMax = "80%"; # Users collectively can't exceed 80% RAM + TasksMax = 500; # Max 500 total processes across all users + CPUWeight = 100; # Fair sharing when contended, bursts allowed + }; + + services = { + # CPU watchdog - detects sustained abuse, kills offending user + cpu-watchdog = { + description = "CPU abuse watchdog"; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${cpu-watchdog}/bin/cpu-watchdog"; + }; + }; + + # Egress watchdog - detects users hitting rate limits, kills after 3 strikes + egress-watchdog = { + description = "Egress abuse watchdog"; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${egress-watchdog}/bin/egress-watchdog"; + }; + }; + }; + + timers = { + cpu-watchdog = { + description = "Run CPU watchdog every minute"; + wantedBy = [ "timers.target" ]; + timerConfig = { + OnBootSec = "1min"; + OnUnitActiveSec = "1min"; + }; + }; + + egress-watchdog = { + description = "Run egress watchdog every minute"; + wantedBy = [ "timers.target" ]; + timerConfig = { + OnBootSec = "1min"; + OnUnitActiveSec = "1min"; + }; + }; + }; }; # Per-user limits - create drop-in on activation @@ -201,42 +248,6 @@ in EOF ''; - # CPU watchdog - detects sustained abuse, kills offending user - systemd.services.cpu-watchdog = { - description = "CPU abuse watchdog"; - serviceConfig = { - Type = "oneshot"; - ExecStart = "${cpu-watchdog}/bin/cpu-watchdog"; - }; - }; - - systemd.timers.cpu-watchdog = { - description = "Run CPU watchdog every minute"; - wantedBy = [ "timers.target" ]; - timerConfig = { - OnBootSec = "1min"; - OnUnitActiveSec = "1min"; - }; - }; - - # Egress watchdog - detects users hitting rate limits, kills after 3 strikes - systemd.services.egress-watchdog = { - description = "Egress abuse watchdog"; - serviceConfig = { - Type = "oneshot"; - ExecStart = "${egress-watchdog}/bin/egress-watchdog"; - }; - }; - - systemd.timers.egress-watchdog = { - description = "Run egress watchdog every minute"; - wantedBy = [ "timers.target" ]; - timerConfig = { - OnBootSec = "1min"; - OnUnitActiveSec = "1min"; - }; - }; - # This value determines the NixOS release compatibility system.stateVersion = "24.05"; }