infra(public-tls): add gated Let's Encrypt issuers + tenant NetworkPolicy substrate
Cl-infra-2 (deep-regroup 2026-06-13). LE staging+prod ClusterIssuers (HTTP-01 via Traefik, DNS-01 stub) + a per-tenant default-deny NetworkPolicy template, under gated/public-tls/ OUTSIDE apps/ so the ApplicationSet does NOT auto-apply them (an applied ACME ClusterIssuer registers an account immediately). Internal *.iamworkin.lan TLS stays on step-ca. Inert until the operator opens the web-hosting public-exposure gate (R-1; 14/14 blockers red). Pairs with Codex Wh-C1 (hybrid public TLS) + Wh-C2 (isolation). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
59
gated/public-tls/tenant-networkpolicy-template.yaml
Normal file
59
gated/public-tls/tenant-networkpolicy-template.yaml
Normal file
@@ -0,0 +1,59 @@
|
||||
# ============================================================================
|
||||
# Per-tenant NetworkPolicy TEMPLATE — web-hosting isolation (Cl-infra-2 / Wh-C2)
|
||||
# ============================================================================
|
||||
# GATED substrate (outside apps/, not auto-applied). Modeled on the canonical
|
||||
# default-deny + allowlist shape in apps/fc-devicemgmt/network-policy.yaml.
|
||||
#
|
||||
# Purpose: when a public multi-tenant site is provisioned, each tenant's pods
|
||||
# get a NetworkPolicy that (a) default-denies all ingress/egress, then allows
|
||||
# only Traefik ingress + CoreDNS + that tenant's own DB. This enforces the
|
||||
# cross-tenant isolation Wh-C2 verifies with negative suites.
|
||||
#
|
||||
# Replace the {{TENANT}} placeholders and apply alongside the tenant's workload
|
||||
# (the MySQL/PHP managers should emit this when they create a tenant, or a
|
||||
# templating step in apps/ should render it). Kept here as the reference shape.
|
||||
# ----------------------------------------------------------------------------
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: tenant-{{TENANT}}-isolation
|
||||
namespace: fc-tenant-{{TENANT}}
|
||||
labels:
|
||||
app.kubernetes.io/part-of: flowercore
|
||||
flowercore.io/tenant-id: "{{TENANT}}"
|
||||
flowercore.io/created-by: bluejay-infra
|
||||
flowercore.io/gate: public-tls
|
||||
spec:
|
||||
podSelector: {} # all pods in the tenant namespace
|
||||
policyTypes: [Ingress, Egress]
|
||||
ingress:
|
||||
# Only Traefik may reach tenant pods (public traffic terminates at Traefik).
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: traefik-system
|
||||
ports:
|
||||
- { protocol: TCP, port: 80 }
|
||||
- { protocol: TCP, port: 443 }
|
||||
- { protocol: TCP, port: 8080 }
|
||||
egress:
|
||||
# CoreDNS resolution.
|
||||
- to:
|
||||
- namespaceSelector: {}
|
||||
podSelector:
|
||||
matchLabels:
|
||||
k8s-app: kube-dns
|
||||
ports:
|
||||
- { protocol: UDP, port: 53 }
|
||||
- { protocol: TCP, port: 53 }
|
||||
# This tenant's OWN MySQL only (NOT other tenants' DBs — that's the isolation).
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
flowercore.io/tenant-id: "{{TENANT}}"
|
||||
app.kubernetes.io/name: mysql
|
||||
ports:
|
||||
- { protocol: TCP, port: 3306 }
|
||||
# NOTE: deliberately NO blanket egress. Add per-tenant allowances explicitly
|
||||
# (object storage, mail relay, etc.) so a compromised tenant pod cannot reach
|
||||
# the rest of the fleet or other tenants.
|
||||
Reference in New Issue
Block a user