--- # Default-deny baseline (R-NET-01): deny all ingress and egress, allow only DNS. A platform namespace, # so Kyverno does not generate this and Flux is its only writer (namespace.yaml). apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny namespace: vuln-scanner labels: homelab.keithjackson.dev/baseline: default-deny spec: podSelector: {} policyTypes: - Ingress - Egress ingress: [] egress: - to: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: kube-system ports: - protocol: UDP port: 53 - protocol: TCP port: 53 --- # INGRESS — exactly three paths (docs/17 "Security of the scanner itself"), one client each: # :4180 from the shared Gateway only (the oauth2-proxy sidecar). The page itself (:8080, loopback) # has no Service port and no rule. # :8081 from the CI runner only. Job containers run inside the DinD sidecar of the act-runner pod in # namespace gitea-actions, so what the network sees is THAT pod's identity (verified live # 2026-11-09: pods carry app.kubernetes.io/name=act-runner). No wider than the runner pods. # :9090 from Prometheus only (verified live: app.kubernetes.io/name=prometheus in monitoring). apiVersion: cilium.io/v2 kind: CiliumNetworkPolicy metadata: name: vuln-scanner-allow-ingress namespace: vuln-scanner labels: app.kubernetes.io/name: vuln-scanner spec: endpointSelector: matchLabels: app.kubernetes.io/name: vuln-scanner ingress: - fromEntities: - ingress toPorts: - ports: - { port: "4180", protocol: TCP } - fromEndpoints: - matchLabels: io.kubernetes.pod.namespace: gitea-actions app.kubernetes.io/name: act-runner toPorts: - ports: - { port: "8081", protocol: TCP } - fromEndpoints: - matchLabels: io.kubernetes.pod.namespace: monitoring app.kubernetes.io/name: prometheus toPorts: - ports: - { port: "9090", protocol: TCP } --- # EGRESS (DNS is in the baseline above). # * kube-apiserver: the inventory. # * tcp/443 to the public internet for the vulnerability database and registries, OUT THE DIRECT # PATH (egress-class=internet, deployment.yaml; never the VPN gateway). Written as a CIDR rule # rather than `toEntities: world` because the spec excludes cluster and LAN ranges and `world` # includes every LAN address: RFC1918 + CGNAT + loopback + link-local + multicast are subtracted. # Consequence, recorded rather than worked around: the Gitea registry resolves to the LAN VIP, so # images hosted there are unreachable from here and report `failed` with that reason, until a # registry credential and a narrower LAN rule are decided (docs/17, "Private registry coverage"). # * The two Keycloak rules below are the ONE addition beyond the spec's list, and they are required: # the oauth2-proxy sidecar shares this pod's network identity with the scanner, and it must # reach Keycloak for OIDC discovery and the token exchange. keycloak. resolves in-cluster # to the Gateway VIP (split-horizon CoreDNS), which Cilium hands to the Gateway's Envoy # (reserved:ingress, tcp/443), and Envoy then re-evaluates THIS pod's egress against Keycloak's # real backend identity (enforce_policy_on_l7lb), the same pair catalog/render.py gives every SSO # app (buildkit/networkpolicy.yaml has the measurement). Without them login 403s. # * NOT listed: anything else in-cluster, any node entity, the LAN, the VPN. apiVersion: cilium.io/v2 kind: CiliumNetworkPolicy metadata: name: vuln-scanner-allow-egress namespace: vuln-scanner labels: app.kubernetes.io/name: vuln-scanner spec: endpointSelector: matchLabels: app.kubernetes.io/name: vuln-scanner egress: - toEntities: - kube-apiserver - toCIDRSet: - cidr: 0.0.0.0/0 except: - 10.0.0.0/8 - 100.64.0.0/10 - 127.0.0.0/8 - 169.254.0.0/16 - 172.16.0.0/12 - 192.168.0.0/16 - 224.0.0.0/4 toPorts: - ports: - { port: "443", protocol: TCP } - toEntities: - ingress toPorts: - ports: - { port: "443", protocol: TCP } - toEndpoints: - matchLabels: io.kubernetes.pod.namespace: keycloak app: keycloak