mirror of
https://github.com/ryan4yin/nix-config.git
synced 2026-11-13 12:13:36 +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;
|
`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
|
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.
|
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
|
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,
|
queried. Secret contents and raw kubeconfigs were not inspected. No live access policy, workload,
|
||||||
|
|||||||
@@ -25,10 +25,16 @@ in
|
|||||||
enableIPv6 = true;
|
enableIPv6 = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
# Cilium pods reach host services (e.g. the API server) from the pod CIDR.
|
# Pods may reach the host services they need, but they are not part of the
|
||||||
# `mkAfter` keeps this after the shared base rules (incl. the node_exporter
|
# host's trusted LAN. Deny the credential-bearing host ports before the broad
|
||||||
# drop), so pods cannot scrape node_exporter either.
|
# 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 ''
|
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
|
ip saddr 10.0.0.0/8 accept
|
||||||
ip6 saddr fd00::/104 accept
|
ip6 saddr fd00::/104 accept
|
||||||
'';
|
'';
|
||||||
|
|||||||
@@ -52,10 +52,16 @@ in
|
|||||||
enableIPv6 = true;
|
enableIPv6 = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
# Cilium pods reach host services (e.g. the API server) from the pod CIDR.
|
# Pods may reach the host services they need, but they are not part of the
|
||||||
# `mkAfter` keeps this after the shared base rules (incl. the node_exporter
|
# host's trusted LAN. Deny the credential-bearing host ports before the broad
|
||||||
# drop), so pods cannot scrape node_exporter either.
|
# 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 ''
|
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
|
ip saddr 10.0.0.0/8 accept
|
||||||
ip6 saddr fd00::/104 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