1# ============================================================================= 2# NetworkPolicy (TASK-0135, phase-15) — per-app ingress+egress allow-lists, 3# ns `factory`. 4# 5# DESIGN CHOICE (recorded in the TASK-0135 report, AGENTS rule 29): 6# - ONE policy per app pod (api, web, worker, realtime) with BOTH 7# policyTypes [Ingress, Egress] → a default-deny EFFECT for those four 8# pods only, with the explicit edges below. 9# - NO namespace-wide default-deny: the namespace also hosts the 10# coordinator-owned postgres/redis/pgbouncer/replica workloads 11# (infrastructure/kubernetes/postgres/**, redis.yaml — NOT touched by 12# this task), whose edges (postgres→S3 for wal-g WAL ship + base 13# backups, replica→primary WAL streaming, pgbouncer→postgres) are owned 14# by that workstream. A catch-all `podSelector: {}` default-deny would 15# silently break those pods until their own policies exist. If the 16# postgres workstream later adds its own policies, a namespace default- 17# deny can be added on top. 18# 19# VERIFICATION NOTE (staging-only): a wrong policy breaks the app. The 20# release pipeline's smoke suite (login + page create + search) exercises 21# every edge below; a curl matrix is the deny-test. Not runnable in this 22# environment (AGENTS rule 29 — recorded in the TASK-0135 report). 23# 24# EDGE MAP (verified against the code this task): 25# ingress-nginx → web:3000 (/), api:4000 (/api), realtime:4001 (/ws) 26# web → api:4000 (server-side: the /health/ready route's 27# liveness fetch; client traffic is browser→nginx) 28# realtime → api:4000 (internal endpoints, http-store.ts), 29# redis:6379 (presence + update log), 30# postgres 6432/5432 (prisma-store.ts) 31# api → postgres/pgbouncer 6432+5432, redis:6379, opensearch:9200, 32# OTLP collector:4318, https/mail/webhooks 443+587+80 33# worker → postgres/pgbouncer 6432+5432, redis:6379, opensearch:9200, 34# OTLP collector:4318, https/mail/webhooks 443+587+80 35# monitoring → app metrics ports (ingress side of each policy below) 36# Broad-egress note: webhook deliveries are to arbitrary user-configured 37# URLs → 443/80/587 to any namespace is the one recorded wide edge 38# (per-host policies are not possible for user-supplied targets in v1). 39# ============================================================================= 40# --- api -------------------------------------------------------------------- 41apiVersion: networking.k8s.io/v1 42kind: NetworkPolicy 43metadata: 44 name: factory-api 45 namespace: factory 46 labels: 47 app.kubernetes.io/part-of: ai-software-factory 48spec: 49 podSelector: 50 matchLabels: 51 app.kubernetes.io/name: api 52 policyTypes: ["Ingress", "Egress"] 53 ingress: 54 - from: 55 - namespaceSelector: 56 matchLabels: 57 kubernetes.io/metadata.name: ingress-nginx 58 ports: 59 - { port: 4000, protocol: TCP } 60 # web's server-side readiness fetch (apps/web/src/app/health/ready) 61 - from: 62 - podSelector: 63 matchLabels: 64 app.kubernetes.io/name: web 65 ports: 66 - { port: 4000, protocol: TCP } 67 # Prometheus scrape (job factory-api, /metrics on 4000) 68 - from: 69 - namespaceSelector: 70 matchLabels: 71 kubernetes.io/metadata.name: monitoring 72 ports: 73 - { port: 4000, protocol: TCP } 74 egress: 75 # DNS 76 - to: 77 - namespaceSelector: {} 78 ports: 79 - { port: 53, protocol: UDP } 80 - { port: 53, protocol: TCP } 81 # postgres (pgbouncer + primary/replica) + redis 82 - to: 83 - podSelector: 84 matchLabels: 85 app.kubernetes.io/name: pgbouncer 86 - podSelector: 87 matchLabels: 88 app.kubernetes.io/name: postgres 89 - podSelector: 90 matchLabels: 91 app.kubernetes.io/name: redis 92 ports: 93 - { port: 6432, protocol: TCP } 94 - { port: 5432, protocol: TCP } 95 - { port: 6379, protocol: TCP } 96 # opensearch (configmap SEARCH_URL placeholder `opensearch:9200`) 97 - to: 98 - podSelector: {} 99 ports: 100 - { port: 9200, protocol: TCP } 101 # OTLP push (factory-config: otel-collector.monitoring.svc:4318) 102 - to: 103 - namespaceSelector: 104 matchLabels: 105 kubernetes.io/metadata.name: monitoring 106 podSelector: 107 matchLabels: 108 app.kubernetes.io/name: otel-collector 109 ports: 110 - { port: 4318, protocol: TCP } 111 # S3/MinIO + AI provider + mail relay + webhook deliveries (broad, see header) 112 - to: 113 - namespaceSelector: {} 114 ports: 115 - { port: 443, protocol: TCP } 116 - { port: 587, protocol: TCP } 117 - { port: 80, protocol: TCP } 118--- 119# --- web ---------------------------------------------------------------------- 120apiVersion: networking.k8s.io/v1 121kind: NetworkPolicy 122metadata: 123 name: factory-web 124 namespace: factory 125 labels: 126 app.kubernetes.io/part-of: ai-software-factory 127spec: 128 podSelector: 129 matchLabels: 130 app.kubernetes.io/name: web 131 policyTypes: ["Ingress", "Egress"] 132 ingress: 133 - from: 134 - namespaceSelector: 135 matchLabels: 136 kubernetes.io/metadata.name: ingress-nginx 137 ports: 138 - { port: 3000, protocol: TCP } 139 - from: 140 - namespaceSelector: 141 matchLabels: 142 kubernetes.io/metadata.name: monitoring 143 ports: 144 - { port: 3000, protocol: TCP } # scrape /api/metrics (path not matchable) 145 egress: 146 - to: 147 - namespaceSelector: {} 148 ports: 149 - { port: 53, protocol: UDP } 150 - { port: 53, protocol: TCP } 151 # api (readiness fetch) + OTLP push 152 - to: 153 - podSelector: 154 matchLabels: 155 app.kubernetes.io/name: api 156 ports: 157 - { port: 4000, protocol: TCP } 158 - to: 159 - namespaceSelector: 160 matchLabels: 161 kubernetes.io/metadata.name: monitoring 162 podSelector: 163 matchLabels: 164 app.kubernetes.io/name: otel-collector 165 ports: 166 - { port: 4318, protocol: TCP } 167--- 168# --- worker ------------------------------------------------------------------- 169# The worker has NO Service (its health/metrics server binds the pod IP:4003) 170# → only Prometheus (pod-SD job factory-worker) ever reaches it. 171apiVersion: networking.k8s.io/v1 172kind: NetworkPolicy 173metadata: 174 name: factory-worker 175 namespace: factory 176 labels: 177 app.kubernetes.io/part-of: ai-software-factory 178spec: 179 podSelector: 180 matchLabels: 181 app.kubernetes.io/name: worker 182 policyTypes: ["Ingress", "Egress"] 183 ingress: 184 - from: 185 - namespaceSelector: 186 matchLabels: 187 kubernetes.io/metadata.name: monitoring 188 ports: 189 - { port: 4003, protocol: TCP } 190 egress: 191 - to: 192 - namespaceSelector: {} 193 ports: 194 - { port: 53, protocol: UDP } 195 - { port: 53, protocol: TCP } 196 - to: 197 - podSelector: 198 matchLabels: 199 app.kubernetes.io/name: pgbouncer 200 - podSelector: 201 matchLabels: 202 app.kubernetes.io/name: postgres 203 - podSelector: 204 matchLabels: 205 app.kubernetes.io/name: redis 206 ports: 207 - { port: 6432, protocol: TCP } 208 - { port: 5432, protocol: TCP } 209 - { port: 6379, protocol: TCP } 210 - to: 211 - podSelector: {} 212 ports: 213 - { port: 9200, protocol: TCP } 214 - to: 215 - namespaceSelector: 216 matchLabels: 217 kubernetes.io/metadata.name: monitoring 218 podSelector: 219 matchLabels: 220 app.kubernetes.io/name: otel-collector 221 ports: 222 - { port: 4318, protocol: TCP } 223 - to: 224 - namespaceSelector: {} 225 ports: 226 - { port: 443, protocol: TCP } 227 - { port: 587, protocol: TCP } 228 - { port: 80, protocol: TCP } 229--- 230# --- realtime ----------------------------------------------------------------- 231apiVersion: networking.k8s.io/v1 232kind: NetworkPolicy 233metadata: 234 name: factory-realtime 235 namespace: factory 236 labels: 237 app.kubernetes.io/part-of: ai-software-factory 238spec: 239 podSelector: 240 matchLabels: 241 app.kubernetes.io/name: realtime 242 policyTypes: ["Ingress", "Egress"] 243 ingress: 244 - from: 245 - namespaceSelector: 246 matchLabels: 247 kubernetes.io/metadata.name: ingress-nginx 248 ports: 249 - { port: 4001, protocol: TCP } 250 - from: 251 - namespaceSelector: 252 matchLabels: 253 kubernetes.io/metadata.name: monitoring 254 ports: 255 - { port: 4001, protocol: TCP } 256 egress: 257 - to: 258 - namespaceSelector: {} 259 ports: 260 - { port: 53, protocol: UDP } 261 - { port: 53, protocol: TCP } 262 - to: 263 - podSelector: 264 matchLabels: 265 app.kubernetes.io/name: api 266 - podSelector: 267 matchLabels: 268 app.kubernetes.io/name: redis 269 - podSelector: 270 matchLabels: 271 app.kubernetes.io/name: pgbouncer 272 - podSelector: 273 matchLabels: 274 app.kubernetes.io/name: postgres 275 ports: 276 - { port: 4000, protocol: TCP } 277 - { port: 6379, protocol: TCP } 278 - { port: 6432, protocol: TCP } 279 - { port: 5432, protocol: TCP } 280 - to: 281 - namespaceSelector: 282 matchLabels: 283 kubernetes.io/metadata.name: monitoring 284 podSelector: 285 matchLabels: 286 app.kubernetes.io/name: otel-collector 287 ports: 288 - { port: 4318, protocol: TCP } 289