diff --git a/hardening/apparmor/default.nix b/hardening/apparmor/default.nix index 887ccecb..580d7e76 100644 --- a/hardening/apparmor/default.nix +++ b/hardening/apparmor/default.nix @@ -4,56 +4,53 @@ ... }: { + # AppArmor: activate the LSM + load policies. Roll out in complain mode first + # (log violations, never block) so nothing can break; promote profiles to + # "enforce" individually once stable. services.dbus.apparmor = "enabled"; + security.apparmor = { enable = true; - # kill process that are not confined but have apparmor profiles enabled - killUnconfinedConfinables = true; - packages = with pkgs; [ - apparmor-utils - apparmor-profiles - ]; + # Do not SIGTERM running unconfined-but-confinable processes yet. + # Safe to flip to true later now that no global default-deny profile is active. + killUnconfinedConfinables = false; + + # Packages contributing to AppArmor's include path (abstractions). + # NOTE: these abstractions are FHS-oriented and reference FHS paths. nixpkgs does NOT + # provide a broad FHS->store alias layer (only /run/current-system, /usr/bin/env and + # /bin/sh are created), so some referenced paths do not resolve on NixOS and the + # resulting profiles are incomplete. They are safe only because they run in complain + # mode (log-only, no blocking); see hardening/README.md. + packages = [ pkgs.apparmor-profiles ]; - # apparmor policies policies = { + # Global default-deny scaffold. DANGEROUS to enable: `/**` matches every binary and + # the empty block allows nothing. Keep disabled until per-app profiles exist. "default_deny" = { - enforce = false; - enable = false; - profile = '' - profile default_deny /** { } - ''; + state = "disable"; + profile = "profile default_deny /** { }"; }; + # Confine sudo; complain mode so a missing rule logs instead of blocking. "sudo" = { - enforce = false; - enable = false; + state = "complain"; profile = '' - ${pkgs.sudo}/bin/sudo { + abi , + include + + profile ${pkgs.sudo}/bin/sudo { + include file /** rwlkUx, } ''; }; + # nix runs unconfined (no restriction); inert, kept disabled. "nix" = { - enforce = false; - enable = false; - profile = '' - ${config.nix.package}/bin/nix { - unconfined, - } - ''; + state = "disable"; + profile = "profile ${config.nix.package}/bin/nix { unconfined, }"; }; }; }; - - environment.systemPackages = with pkgs; [ - apparmor-bin-utils - apparmor-profiles - apparmor-parser - libapparmor - apparmor-kernel-patches - apparmor-pam - apparmor-utils - ]; } diff --git a/hosts/idols-aquamarine/monitoring/victoriametrics.nix b/hosts/idols-aquamarine/monitoring/victoriametrics.nix index 667c48e2..0ad0be2f 100644 --- a/hosts/idols-aquamarine/monitoring/victoriametrics.nix +++ b/hosts/idols-aquamarine/monitoring/victoriametrics.nix @@ -85,7 +85,8 @@ in metrics_path = "/metrics"; static_configs = [ { - targets = [ "${myvars.networking.hostsAddr.aquamarine.ipv4}:9153" ]; + # same-host exporter bound to loopback (127.0.0.1:9153) + targets = [ "127.0.0.1:9153" ]; labels.type = "app"; labels.app = "v2ray"; labels.host = "aquamarine"; @@ -116,7 +117,8 @@ in metrics_path = "/metrics"; static_configs = [ { - targets = [ "${myvars.networking.hostsAddr.aquamarine.ipv4}:9187" ]; + # same-host exporter bound to loopback (127.0.0.1:9187) + targets = [ "127.0.0.1:9187" ]; labels.type = "app"; labels.app = "postgresql"; labels.host = "aquamarine"; @@ -131,7 +133,8 @@ in metrics_path = "/metrics"; static_configs = [ { - targets = [ "${myvars.networking.hostsAddr.aquamarine.ipv4}:10000" ]; + # same-host exporter bound to loopback (127.0.0.1:10000) + targets = [ "127.0.0.1:10000" ]; labels.type = "app"; labels.app = "sftpgo"; labels.host = "aquamarine"; diff --git a/hosts/idols-aquamarine/postgresql.nix b/hosts/idols-aquamarine/postgresql.nix index 138bb97b..5c864e7d 100644 --- a/hosts/idols-aquamarine/postgresql.nix +++ b/hosts/idols-aquamarine/postgresql.nix @@ -121,7 +121,8 @@ in services.prometheus.exporters.postgres = { enable = true; - listenAddress = "0.0.0.0"; + # loopback only: scraped by VictoriaMetrics on the same host + listenAddress = "127.0.0.1"; port = 9187; user = "postgres-exporter"; group = "postgres-exporter"; diff --git a/hosts/idols-aquamarine/proxy.nix b/hosts/idols-aquamarine/proxy.nix index 67a22376..3bab4087 100644 --- a/hosts/idols-aquamarine/proxy.nix +++ b/hosts/idols-aquamarine/proxy.nix @@ -82,7 +82,8 @@ # https://github.com/wi1dcard/v2ray-exporter services.prometheus.exporters.v2ray = { enable = true; - listenAddress = "0.0.0.0"; + # loopback only: scraped by VictoriaMetrics on the same host + listenAddress = "127.0.0.1"; port = 9153; openFirewall = false; v2rayEndpoint = "127.0.0.1:54321"; diff --git a/hosts/idols-aquamarine/sftpgo.nix b/hosts/idols-aquamarine/sftpgo.nix index e7a0d4b9..da3841b1 100644 --- a/hosts/idols-aquamarine/sftpgo.nix +++ b/hosts/idols-aquamarine/sftpgo.nix @@ -95,7 +95,8 @@ in # prometheus metrics telemetry = { bind_port = 10000; - bind_address = "0.0.0.0"; + # loopback only: scraped by VictoriaMetrics on the same host + bind_address = "127.0.0.1"; # auth_user_file = ""; }; # multi-factor authentication settings diff --git a/lib/genK3sServerModule.nix b/lib/genK3sServerModule.nix index c5059b8d..2020af79 100644 --- a/lib/genK3sServerModule.nix +++ b/lib/genK3sServerModule.nix @@ -60,7 +60,7 @@ in let flagList = [ "--write-kubeconfig=${kubeconfigFile}" - "--write-kubeconfig-mode=644" + "--write-kubeconfig-mode=600" "--service-node-port-range=80-32767" "--kube-apiserver-arg='--allow-privileged=true'" # required by kubevirt "--data-dir /var/lib/rancher/k3s" diff --git a/modules/nixos/base/default.nix b/modules/nixos/base/default.nix index 049dda93..8b8274f3 100644 --- a/modules/nixos/base/default.nix +++ b/modules/nixos/base/default.nix @@ -1,4 +1,4 @@ { mylib, ... }: { - imports = mylib.scanPaths ./.; + imports = mylib.scanPaths ./. ++ [ (mylib.relativeToRoot "hardening/apparmor") ]; } diff --git a/modules/nixos/base/ssh.nix b/modules/nixos/base/ssh.nix index 1409e303..d5b0517d 100644 --- a/modules/nixos/base/ssh.nix +++ b/modules/nixos/base/ssh.nix @@ -1,12 +1,15 @@ { lib, ... }: { - # Or disable the firewall altogether. - networking.firewall.enable = lib.mkDefault false; + # Secure by default: firewall ON everywhere unless a host explicitly disables it + # (servers disable it in modules/nixos/server/{server,server-aarch64}.nix). + networking.firewall.enable = lib.mkDefault true; # Enable the OpenSSH daemon. services.openssh = { enable = true; settings = { - X11Forwarding = true; + # Secure by default: X11 forwarding off everywhere; desktops re-enable it + # in modules/nixos/desktop/ssh.nix (needed for GUI forwarding). + X11Forwarding = lib.mkDefault false; # root user is used for remote deployment, so we need to allow it PermitRootLogin = "prohibit-password"; PasswordAuthentication = false; # disable password login diff --git a/modules/nixos/desktop/ssh.nix b/modules/nixos/desktop/ssh.nix new file mode 100644 index 00000000..5e000e1d --- /dev/null +++ b/modules/nixos/desktop/ssh.nix @@ -0,0 +1,5 @@ +{ + # Desktops keep X11 forwarding (current behavior, needed for GUI forwarding); + # servers default to off (see modules/nixos/base/ssh.nix). + services.openssh.settings.X11Forwarding = true; +} diff --git a/modules/nixos/server/server-aarch64.nix b/modules/nixos/server/server-aarch64.nix index d321cbc5..7ac802cb 100644 --- a/modules/nixos/server/server-aarch64.nix +++ b/modules/nixos/server/server-aarch64.nix @@ -1,6 +1,7 @@ { lib, pkgs, + mylib, ... }: { @@ -15,8 +16,15 @@ ../base/user-group.nix ../../base + # AppArmor is wired via modules/nixos/base/default.nix for other hosts; this + # aarch64 server imports base files individually, so add it explicitly. + (mylib.relativeToRoot "hardening/apparmor") ]; + # Servers run on the trusted internal LAN (NAT'd; WAN protected at the router). + # Keep the firewall off here; the secure default is ON (see modules/nixos/base/ssh.nix). + networking.firewall.enable = false; + # Fix: jasper is marked as broken, refusing to evaluate. environment.enableAllTerminfo = lib.mkForce false; } diff --git a/modules/nixos/server/server.nix b/modules/nixos/server/server.nix index f95f803a..3e95d887 100644 --- a/modules/nixos/server/server.nix +++ b/modules/nixos/server/server.nix @@ -4,4 +4,8 @@ ../base ../../base ]; + + # Servers run on the trusted internal LAN (NAT'd; WAN protected at the router). + # Keep the firewall off here; the secure default is ON (see modules/nixos/base/ssh.nix). + networking.firewall.enable = false; } diff --git a/outputs/aarch64-linux/tests/security-apparmor/expected.nix b/outputs/aarch64-linux/tests/security-apparmor/expected.nix new file mode 100644 index 00000000..81b3fbb5 --- /dev/null +++ b/outputs/aarch64-linux/tests/security-apparmor/expected.nix @@ -0,0 +1,5 @@ +{ + lib, + outputs, +}: +lib.genAttrs (builtins.attrNames outputs.nixosConfigurations) (_: true) diff --git a/outputs/aarch64-linux/tests/security-apparmor/expr.nix b/outputs/aarch64-linux/tests/security-apparmor/expr.nix new file mode 100644 index 00000000..2b76bf01 --- /dev/null +++ b/outputs/aarch64-linux/tests/security-apparmor/expr.nix @@ -0,0 +1,7 @@ +{ + lib, + outputs, +}: +lib.genAttrs (builtins.attrNames outputs.nixosConfigurations) ( + name: outputs.nixosConfigurations.${name}.config.security.apparmor.enable +) diff --git a/outputs/aarch64-linux/tests/security-firewall/expected.nix b/outputs/aarch64-linux/tests/security-firewall/expected.nix new file mode 100644 index 00000000..6ca2c34e --- /dev/null +++ b/outputs/aarch64-linux/tests/security-firewall/expected.nix @@ -0,0 +1,7 @@ +{ + lib, + outputs, +}: +lib.genAttrs (builtins.attrNames outputs.nixosConfigurations) ( + name: name == "ai-niri" || name == "shoukei-niri" +) diff --git a/outputs/aarch64-linux/tests/security-firewall/expr.nix b/outputs/aarch64-linux/tests/security-firewall/expr.nix new file mode 100644 index 00000000..39873f63 --- /dev/null +++ b/outputs/aarch64-linux/tests/security-firewall/expr.nix @@ -0,0 +1,7 @@ +{ + lib, + outputs, +}: +lib.genAttrs (builtins.attrNames outputs.nixosConfigurations) ( + name: outputs.nixosConfigurations.${name}.config.networking.firewall.enable +) diff --git a/outputs/aarch64-linux/tests/security-k3s-kubeconfig/expected.nix b/outputs/aarch64-linux/tests/security-k3s-kubeconfig/expected.nix new file mode 100644 index 00000000..2cc5bf6e --- /dev/null +++ b/outputs/aarch64-linux/tests/security-k3s-kubeconfig/expected.nix @@ -0,0 +1,10 @@ +{ + lib, + outputs, +}: +lib.genAttrs (builtins.attrNames outputs.nixosConfigurations) (name: { + # Only the control-plane (master) nodes write the admin kubeconfig; agents + # (workers) connect to the master and have no --write-kubeconfig-* flags. + mode600 = lib.hasInfix "-master-" name; + mode644 = false; +}) diff --git a/outputs/aarch64-linux/tests/security-k3s-kubeconfig/expr.nix b/outputs/aarch64-linux/tests/security-k3s-kubeconfig/expr.nix new file mode 100644 index 00000000..7de55cf1 --- /dev/null +++ b/outputs/aarch64-linux/tests/security-k3s-kubeconfig/expr.nix @@ -0,0 +1,15 @@ +{ + lib, + outputs, +}: +lib.genAttrs (builtins.attrNames outputs.nixosConfigurations) ( + name: + let + isK3s = lib.hasPrefix "k3s" name; + flags = if isK3s then outputs.nixosConfigurations.${name}.config.services.k3s.extraFlags else ""; + in + { + mode600 = lib.hasInfix "--write-kubeconfig-mode=600" flags; + mode644 = lib.hasInfix "--write-kubeconfig-mode=644" flags; + } +) diff --git a/outputs/aarch64-linux/tests/security-ssh-x11/expected.nix b/outputs/aarch64-linux/tests/security-ssh-x11/expected.nix new file mode 100644 index 00000000..6ca2c34e --- /dev/null +++ b/outputs/aarch64-linux/tests/security-ssh-x11/expected.nix @@ -0,0 +1,7 @@ +{ + lib, + outputs, +}: +lib.genAttrs (builtins.attrNames outputs.nixosConfigurations) ( + name: name == "ai-niri" || name == "shoukei-niri" +) diff --git a/outputs/aarch64-linux/tests/security-ssh-x11/expr.nix b/outputs/aarch64-linux/tests/security-ssh-x11/expr.nix new file mode 100644 index 00000000..5905f493 --- /dev/null +++ b/outputs/aarch64-linux/tests/security-ssh-x11/expr.nix @@ -0,0 +1,7 @@ +{ + lib, + outputs, +}: +lib.genAttrs (builtins.attrNames outputs.nixosConfigurations) ( + name: outputs.nixosConfigurations.${name}.config.services.openssh.settings.X11Forwarding +) diff --git a/outputs/x86_64-linux/tests/security-apparmor/expected.nix b/outputs/x86_64-linux/tests/security-apparmor/expected.nix new file mode 100644 index 00000000..81b3fbb5 --- /dev/null +++ b/outputs/x86_64-linux/tests/security-apparmor/expected.nix @@ -0,0 +1,5 @@ +{ + lib, + outputs, +}: +lib.genAttrs (builtins.attrNames outputs.nixosConfigurations) (_: true) diff --git a/outputs/x86_64-linux/tests/security-apparmor/expr.nix b/outputs/x86_64-linux/tests/security-apparmor/expr.nix new file mode 100644 index 00000000..2b76bf01 --- /dev/null +++ b/outputs/x86_64-linux/tests/security-apparmor/expr.nix @@ -0,0 +1,7 @@ +{ + lib, + outputs, +}: +lib.genAttrs (builtins.attrNames outputs.nixosConfigurations) ( + name: outputs.nixosConfigurations.${name}.config.security.apparmor.enable +) diff --git a/outputs/x86_64-linux/tests/security-aquamarine-metrics/expected.nix b/outputs/x86_64-linux/tests/security-aquamarine-metrics/expected.nix new file mode 100644 index 00000000..c45419fd --- /dev/null +++ b/outputs/x86_64-linux/tests/security-aquamarine-metrics/expected.nix @@ -0,0 +1,9 @@ +{ + lib, + outputs, +}: +{ + v2rayExporter = "127.0.0.1"; + pgExporter = "127.0.0.1"; + sftpgoTelemetry = "127.0.0.1"; +} diff --git a/outputs/x86_64-linux/tests/security-aquamarine-metrics/expr.nix b/outputs/x86_64-linux/tests/security-aquamarine-metrics/expr.nix new file mode 100644 index 00000000..3d99fe60 --- /dev/null +++ b/outputs/x86_64-linux/tests/security-aquamarine-metrics/expr.nix @@ -0,0 +1,12 @@ +{ + lib, + outputs, +}: +let + aqua = outputs.nixosConfigurations.aquamarine.config; +in +{ + v2rayExporter = aqua.services.prometheus.exporters.v2ray.listenAddress; + pgExporter = aqua.services.prometheus.exporters.postgres.listenAddress; + sftpgoTelemetry = aqua.services.sftpgo.settings.telemetry.bind_address; +} diff --git a/outputs/x86_64-linux/tests/security-firewall/expected.nix b/outputs/x86_64-linux/tests/security-firewall/expected.nix new file mode 100644 index 00000000..6ca2c34e --- /dev/null +++ b/outputs/x86_64-linux/tests/security-firewall/expected.nix @@ -0,0 +1,7 @@ +{ + lib, + outputs, +}: +lib.genAttrs (builtins.attrNames outputs.nixosConfigurations) ( + name: name == "ai-niri" || name == "shoukei-niri" +) diff --git a/outputs/x86_64-linux/tests/security-firewall/expr.nix b/outputs/x86_64-linux/tests/security-firewall/expr.nix new file mode 100644 index 00000000..39873f63 --- /dev/null +++ b/outputs/x86_64-linux/tests/security-firewall/expr.nix @@ -0,0 +1,7 @@ +{ + lib, + outputs, +}: +lib.genAttrs (builtins.attrNames outputs.nixosConfigurations) ( + name: outputs.nixosConfigurations.${name}.config.networking.firewall.enable +) diff --git a/outputs/x86_64-linux/tests/security-k3s-kubeconfig/expected.nix b/outputs/x86_64-linux/tests/security-k3s-kubeconfig/expected.nix new file mode 100644 index 00000000..2cc5bf6e --- /dev/null +++ b/outputs/x86_64-linux/tests/security-k3s-kubeconfig/expected.nix @@ -0,0 +1,10 @@ +{ + lib, + outputs, +}: +lib.genAttrs (builtins.attrNames outputs.nixosConfigurations) (name: { + # Only the control-plane (master) nodes write the admin kubeconfig; agents + # (workers) connect to the master and have no --write-kubeconfig-* flags. + mode600 = lib.hasInfix "-master-" name; + mode644 = false; +}) diff --git a/outputs/x86_64-linux/tests/security-k3s-kubeconfig/expr.nix b/outputs/x86_64-linux/tests/security-k3s-kubeconfig/expr.nix new file mode 100644 index 00000000..7de55cf1 --- /dev/null +++ b/outputs/x86_64-linux/tests/security-k3s-kubeconfig/expr.nix @@ -0,0 +1,15 @@ +{ + lib, + outputs, +}: +lib.genAttrs (builtins.attrNames outputs.nixosConfigurations) ( + name: + let + isK3s = lib.hasPrefix "k3s" name; + flags = if isK3s then outputs.nixosConfigurations.${name}.config.services.k3s.extraFlags else ""; + in + { + mode600 = lib.hasInfix "--write-kubeconfig-mode=600" flags; + mode644 = lib.hasInfix "--write-kubeconfig-mode=644" flags; + } +) diff --git a/outputs/x86_64-linux/tests/security-ssh-x11/expected.nix b/outputs/x86_64-linux/tests/security-ssh-x11/expected.nix new file mode 100644 index 00000000..6ca2c34e --- /dev/null +++ b/outputs/x86_64-linux/tests/security-ssh-x11/expected.nix @@ -0,0 +1,7 @@ +{ + lib, + outputs, +}: +lib.genAttrs (builtins.attrNames outputs.nixosConfigurations) ( + name: name == "ai-niri" || name == "shoukei-niri" +) diff --git a/outputs/x86_64-linux/tests/security-ssh-x11/expr.nix b/outputs/x86_64-linux/tests/security-ssh-x11/expr.nix new file mode 100644 index 00000000..5905f493 --- /dev/null +++ b/outputs/x86_64-linux/tests/security-ssh-x11/expr.nix @@ -0,0 +1,7 @@ +{ + lib, + outputs, +}: +lib.genAttrs (builtins.attrNames outputs.nixosConfigurations) ( + name: outputs.nixosConfigurations.${name}.config.services.openssh.settings.X11Forwarding +)