--- # Default-deny for the namespace, then two narrow allows. # # WHO MAY CONNECT — AND WHY THE ACT-RUNNER POD IS THE RIGHT SOURCE (measured 2026-10-29). # CI jobs do not run as pods. act_runner starts each job as a container on its DinD sidecar, on a # docker bridge network INSIDE the act-runner pod's network namespace, and dockerd masquerades it: # kubectl -n gitea-actions exec deploy/act-runner -c dind -- iptables -t nat -S POSTROUTING # -A POSTROUTING -s 172.18.0.0/16 ! -o br-c0f13d041ad6 -j MASQUERADE # -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE # So a job's connection leaves with the act-runner POD's IP and therefore Cilium's identity for # that pod (namespace gitea-actions, app.kubernetes.io/name=act-runner). That is exactly what the # ingress rule names. It cannot distinguish one job from another, or a job from act_runner # itself — per-job authentication is the mTLS client certificate, not this policy. What the # policy adds is that NOTHING ELSE in the cluster can even open the port. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny namespace: buildkit 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 --- apiVersion: cilium.io/v2 kind: CiliumNetworkPolicy metadata: name: buildkitd-allow-ci-ingress namespace: buildkit labels: app.kubernetes.io/name: buildkitd spec: endpointSelector: matchLabels: app.kubernetes.io/name: buildkitd ingress: - fromEndpoints: - matchLabels: io.kubernetes.pod.namespace: gitea-actions app.kubernetes.io/name: act-runner toPorts: - ports: - { port: "1234", protocol: TCP } --- # WHAT A BUILD NEEDS TO REACH. Build steps download (dnf mirrors, GitHub releases, dl.k8s.io, # npm, PyPI, Fedora/Docker registries) and the result is pushed to the Gitea registry. # * world 443/80: the downloads. 80 because Fedora's mirror list includes plain-http mirrors # (dnf verifies package GPG signatures, not transport). # * ingress (the Gateway's Envoy, reserved:ingress): the push. gitea.home.keithjackson.dev # resolves in-cluster to the Gateway VIP (split-horizon CoreDNS), and Cilium hands that # traffic to the Gateway, whose identity is `ingress` — the same identity gitea's own # ingress allow admits. # * gitea's BACKEND identity, :3000 only: ALSO the push, and the half that `ingress` alone does # not cover. Cilium >= 1.16.8/1.17.2 (CVE-2025-30162; this cluster runs 1.20.1) makes the # Gateway's Envoy re-evaluate THIS pod's egress policy against the identity of the backend it # resolves the request to (enforce_policy_on_l7lb). `toEntities: ingress` gets the TCP # connection to Envoy; without this rule Envoy then refuses the HTTP request itself with # `403 Access denied`, and Gitea never sees it. Measured 2026-10-30 (images run 337 failed # this way): Hubble on buildkitd's node logged `buildkit/buildkitd (ingress) -> # gateway/cilium-gateway-shared:443 http-request DROPPED`, traffic_direction EGRESS, and # Gitea's router log had no entry for it — while the same unauthenticated GET /v2/ from the # act-runner pod (egress unrestricted) reached Gitea and got its normal 401. It is the same # fix catalog/render.py applies for every SSO app's hop to Keycloak (commit 68690e5). # Gitea's INGRESS needs nothing: the request arrives there as `reserved:ingress`, already # admitted. Do not add buildkit to catalog/gitea.yaml's inClusterClients — that would open # the ClusterIP path from build code to Gitea, which nothing here uses. # Nothing else in the cluster: not the API server, not other pods. A build step is code from a # pull request. apiVersion: cilium.io/v2 kind: CiliumNetworkPolicy metadata: name: buildkitd-allow-egress namespace: buildkit labels: app.kubernetes.io/name: buildkitd spec: endpointSelector: matchLabels: app.kubernetes.io/name: buildkitd egress: - toEntities: - world toPorts: - ports: - { port: "443", protocol: TCP } - { port: "80", protocol: TCP } - toEntities: - ingress - toEndpoints: - matchLabels: io.kubernetes.pod.namespace: gitea app.kubernetes.io/name: gitea toPorts: - ports: - { port: "3000", protocol: TCP }