mirror of
https://github.com/ryan4yin/nix-config.git
synced 2026-11-13 12:13:46 +01:00
fix(k3s): deny credential-bearing host ports to pod sources (#399)
* fix(k3s): deny credential-bearing host ports to pod sources * test: assert the k3s pod firewall drops sensitive ports * docs(security): record the pod firewall boundary
This commit is contained in:
1 parent
b79c5807ae
commit
43f39e8e9f
5 files changed
+61
-6
No files matched your search
@@ -198,6 +198,11 @@ checks remain necessary after deployment.
|
||||
`sec=sys,no_root_squash`. File mode 0755 does not contain an allowed client with root privileges;
|
||||
such a client can act as server root within this export. The network allowlist and client trust
|
||||
are therefore material boundaries, not merely filesystem permissions.
|
||||
- **Pod-to-host firewall:** pod CIDR sources (`10.0.0.0/8`, `fd00::/104`) are accepted on the k3s
|
||||
nodes, but the credential-bearing host ports are denied first (SSH, NFS, Postgres, restic, VNC,
|
||||
Immich, and the exporter/monitoring ports). The broad accept stays so Cilium and in-cluster
|
||||
service traffic keep working; a full port allowlist is future work. Ports 80/443 are deliberately
|
||||
not denied, so in-cluster calls through the ingress are not broken.
|
||||
|
||||
Only selected pod security fields, RBAC rules/binding metadata, listeners and export metadata were
|
||||
queried. Secret contents and raw kubeconfigs were not inspected. No live access policy, workload,
|
||||
|
||||
@@ -25,10 +25,16 @@ in
|
||||
enableIPv6 = true;
|
||||
};
|
||||
|
||||
# Cilium pods reach host services (e.g. the API server) from the pod CIDR.
|
||||
# `mkAfter` keeps this after the shared base rules (incl. the node_exporter
|
||||
# drop), so pods cannot scrape node_exporter either.
|
||||
# Pods may reach the host services they need, but they are not part of the
|
||||
# host's trusted LAN. Deny the credential-bearing host ports before the broad
|
||||
# pod-network accept below, so a compromised pod cannot reach NFS, the
|
||||
# database, the backup server, the VNC consoles, or an exporter. `mkAfter`
|
||||
# keeps this after the shared base rules (incl. the node_exporter drop).
|
||||
# Ports 80/443 stay reachable so in-cluster calls through the ingress are not
|
||||
# broken; the ingress is the authentication boundary there.
|
||||
networking.firewall.extraInputRules = pkgs.lib.mkAfter ''
|
||||
ip saddr 10.0.0.0/8 tcp dport { 22, 2049, 2283, 5432, 5900, 5901, 5902, 5903, 8000, 8081, 8082, 9090, 9093, 9100, 9633, 9835 } drop
|
||||
ip6 saddr fd00::/104 tcp dport { 22, 2049, 2283, 5432, 5900, 5901, 5902, 5903, 8000, 8081, 8082, 9090, 9093, 9100, 9633, 9835 } drop
|
||||
ip saddr 10.0.0.0/8 accept
|
||||
ip6 saddr fd00::/104 accept
|
||||
'';
|
||||
|
||||
@@ -52,10 +52,16 @@ in
|
||||
enableIPv6 = true;
|
||||
};
|
||||
|
||||
# Cilium pods reach host services (e.g. the API server) from the pod CIDR.
|
||||
# `mkAfter` keeps this after the shared base rules (incl. the node_exporter
|
||||
# drop), so pods cannot scrape node_exporter either.
|
||||
# Pods may reach the host services they need, but they are not part of the
|
||||
# host's trusted LAN. Deny the credential-bearing host ports before the broad
|
||||
# pod-network accept below, so a compromised pod cannot reach NFS, the
|
||||
# database, the backup server, the VNC consoles, or an exporter. `mkAfter`
|
||||
# keeps this after the shared base rules (incl. the node_exporter drop).
|
||||
# Ports 80/443 stay reachable so in-cluster calls through the ingress are not
|
||||
# broken; the ingress is the authentication boundary there.
|
||||
networking.firewall.extraInputRules = lib.mkAfter ''
|
||||
ip saddr 10.0.0.0/8 tcp dport { 22, 2049, 2283, 5432, 5900, 5901, 5902, 5903, 8000, 8081, 8082, 9090, 9093, 9100, 9633, 9835 } drop
|
||||
ip6 saddr fd00::/104 tcp dport { 22, 2049, 2283, 5432, 5900, 5901, 5902, 5903, 8000, 8081, 8082, 9090, 9093, 9100, 9633, 9835 } drop
|
||||
ip saddr 10.0.0.0/8 accept
|
||||
ip6 saddr fd00::/104 accept
|
||||
'';
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
lib,
|
||||
outputs,
|
||||
}:
|
||||
lib.genAttrs (builtins.attrNames outputs.nixosConfigurations) (_: true)
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
lib,
|
||||
outputs,
|
||||
}:
|
||||
let
|
||||
# Index of the first occurrence of `needle` in `haystack`, or null.
|
||||
firstIndex =
|
||||
needle: haystack:
|
||||
let
|
||||
parts = lib.splitString needle haystack;
|
||||
in
|
||||
if builtins.length parts < 2 then null else builtins.stringLength (builtins.head parts);
|
||||
in
|
||||
lib.genAttrs (builtins.attrNames outputs.nixosConfigurations) (
|
||||
name:
|
||||
let
|
||||
cfg = outputs.nixosConfigurations.${name}.config;
|
||||
rules = cfg.networking.firewall.extraInputRules;
|
||||
dropAt = firstIndex "saddr 10.0.0.0/8 tcp dport" rules;
|
||||
acceptAt = firstIndex "saddr 10.0.0.0/8 accept" rules;
|
||||
in
|
||||
if !(cfg.services.k3s.enable or false) then
|
||||
true
|
||||
else
|
||||
# The port deny must exist and must precede the broad pod accept, because
|
||||
# nftables takes the first matching rule.
|
||||
dropAt != null
|
||||
&& acceptAt != null
|
||||
&& dropAt < acceptAt
|
||||
&& lib.hasInfix "2049" rules
|
||||
&& lib.hasInfix "5432" rules
|
||||
&& lib.hasInfix "5902" rules
|
||||
)
|
||||
Reference in new issue
Block a user