DOMAIN:NETWORKING:NETWORK_SECURITY¶
OWNER: stef (implementation), victoria (policy) UPDATED: 2026-03-24 SCOPE: all network security across GE's three-zone architecture AGENTS: stef (firewall, NetworkPolicies, VPN), victoria (security policy), gerco (Zone 1), thijmen (Zone 2), rutger (Zone 3), karel (edge WAF)
NETWORK:OVERVIEW¶
PURPOSE: defense-in-depth network security across all GE zones PRINCIPLE: default deny + explicit allow — no implicit trust between any services LAYERS: 1. Edge (BunnyCDN WAF, DDoS) — Karel 2. Ingress (Traefik, TLS termination) — Stef + Gerco 3. Cluster (k8s NetworkPolicies) — Stef + Gerco/Thijmen/Rutger 4. Host (ufw firewall) — Gerco (Zone 1) 5. Cloud (UpCloud security groups) — Arjan + Stef (Zone 2/3) 6. Service (mTLS, application-level auth) — developers
NETWORK:K8S_NETWORKPOLICIES¶
DEFAULT_DENY¶
EVERY namespace gets a default deny policy. No exceptions.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: {namespace}
spec:
podSelector: {} # all pods
policyTypes:
- Ingress
- Egress
EFFECT: no pod can send or receive traffic unless explicitly allowed NAMESPACES_WITH_DEFAULT_DENY: ge-agents, ge-system, ge-hosting, ge-ingress
ALLOW_PATTERNS¶
AGENTS_TO_REDIS (ge-agents -> ge-system):
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: agents-to-redis
namespace: ge-agents
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
app.kubernetes.io/name: growing-europe
podSelector:
matchLabels:
app: redis
ports:
- protocol: TCP
port: 6381
AGENTS_TO_VAULT (ge-agents -> ge-system):
EXTERNAL_HTTPS (ge-agents -> internet):
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: executors-external-https
namespace: ge-agents
spec:
podSelector:
matchExpressions:
- key: component
operator: In
values:
- shared-executor
- dedicated-agent
- watcher
- guardian
- orchestrator
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8 # block private ranges
- 172.16.0.0/12 # only external HTTPS
- 192.168.0.0/16
ports:
- protocol: TCP
port: 443
DNS_EGRESS (all namespaces -> kube-system):
# Every namespace needs DNS egress to kube-system (CoreDNS)
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
HEALTH_PROBES (kubelet -> pods):
NETWORKPOLICY_DECISION_TREE¶
FOR_EVERY_NEW_SERVICE:
1. What does it need to RECEIVE traffic from? (Ingress rules)
2. What does it need to SEND traffic to? (Egress rules)
3. Create NetworkPolicy with ONLY those flows
4. Test connectivity
5. Document in service manifest comments
IF: service needs external HTTPS → use ipBlock excluding private ranges
IF: service needs internal service → use namespaceSelector + podSelector
IF: service needs DNS → add DNS egress (almost always needed)
IF: service has health probes → add health port ingress
MONITORING_SCRAPE_POLICY¶
# Allow Prometheus to scrape all namespaces
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: monitoring-scrape
namespace: ge-monitoring
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector: {} # all namespaces
ports:
- protocol: TCP
port: 8080 # standard health/metrics port
- protocol: TCP
port: 3100 # Loki
- protocol: TCP
port: 9090 # Prometheus
NETWORK:TRAEFIK_INGRESS¶
CONFIGURATION¶
Traefik is GE's IngressController, running in ge-ingress namespace.
SECURITY_DECISIONS (from VULN-2026-001 audit): 1. Dashboard DISABLED — exposed routing rules, backends, TLS certs without auth 2. Namespace scope restricted — only ge-ingress, ge-hosting, ge-system, ge-wiki 3. Egress restricted — only to specific hosting/system namespaces 4. ACME egress allowed — port 443 to internet for Let's Encrypt challenges
# Traefik EntryPoints
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https # ALL HTTP → HTTPS redirect
websecure:
address: ":443"
http:
tls:
options: default # TLS 1.2+, secure ciphers
TRAEFIK_NETWORK_POLICY¶
# Traefik can receive from internet, can only reach specific namespaces
ingress:
- ports:
- protocol: TCP
port: 80
- protocol: TCP
port: 443
egress:
# Only to hosting namespaces
- to:
- namespaceSelector:
matchLabels:
app.kubernetes.io/part-of: ge-hosting-platform
ports:
- protocol: TCP
port: 8080
- protocol: TCP
port: 80
# Only to ge-system (admin-ui)
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ge-system
ports:
- protocol: TCP
port: 80
port: 443
# DNS
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
# ACME (Let's Encrypt challenges)
- to:
- ipBlock:
cidr: 0.0.0.0/0
ports:
- protocol: TCP
port: 443
INGRESSROUTE (Traefik CRD)¶
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: {app}
namespace: {namespace}
spec:
entryPoints:
- websecure
routes:
- match: Host(`{domain}`)
kind: Rule
services:
- name: {app-service}
port: 80
middlewares:
- name: security-headers
tls:
secretName: {domain}-tls-secret
SECURITY_HEADERS_MIDDLEWARE¶
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: security-headers
spec:
headers:
customResponseHeaders:
X-Content-Type-Options: "nosniff"
X-Frame-Options: "DENY"
Referrer-Policy: "strict-origin-when-cross-origin"
Permissions-Policy: "camera=(), microphone=(), geolocation=()"
stsSeconds: 31536000 # HSTS 1 year
stsIncludeSubdomains: true
stsPreload: true
contentTypeNosniff: true
browserXssFilter: true
frameDeny: true
NETWORK:HOST_FIREWALL (Zone 1)¶
UFW (Uncomplicated Firewall)¶
LOCATION: fort-knox-dev (Minisforum 790 Pro) OWNER: gerco
TOOL: ufw
RUN: sudo ufw status verbose
DEFAULT_RULES:
→ incoming: deny (default deny inbound)
→ outgoing: allow (allow outbound — k8s needs external access)
→ routed: deny
ALLOWED_INBOUND:
→ 22/tcp (SSH) from 192.168.1.0/24 only (LAN)
→ 80/tcp (HTTP) from anywhere (Traefik, redirects to HTTPS)
→ 443/tcp (HTTPS) from anywhere (Traefik, TLS termination)
→ 6443/tcp (k8s API) from 192.168.1.0/24 only (LAN admin)
→ 30000-32767/tcp (NodePort range) from anywhere (k8s services)
VERIFY:
RULE: gerco manages ufw rules, stef reviews for network security RULE: never open Redis (6381) or Vault (8200) to external network
NETWORK:CLOUD_FIREWALL (Zone 2/3)¶
UPCLOUD_SECURITY_GROUPS¶
MANAGED_BY: arjan (Terraform), reviewed by stef + victoria
resource "upcloud_firewall_rules" "production" {
server_id = upcloud_server.production.id
# Allow HTTPS from anywhere
firewall_rule {
action = "accept"
direction = "in"
protocol = "tcp"
destination_port_start = "443"
destination_port_end = "443"
family = "IPv4"
comment = "HTTPS from internet"
}
# Allow SSH from VPN only
firewall_rule {
action = "accept"
direction = "in"
protocol = "tcp"
destination_port_start = "22"
destination_port_end = "22"
source_address_start = "10.0.0.1"
source_address_end = "10.0.0.255"
family = "IPv4"
comment = "SSH from WireGuard VPN only"
}
# Default deny all other inbound
firewall_rule {
action = "drop"
direction = "in"
family = "IPv4"
comment = "Default deny inbound"
}
}
RULE: ALL Zone 3 firewall changes require Victoria review before applying RULE: all firewall rules managed via Terraform (auditable, version-controlled)
NETWORK:MTLS (Mutual TLS)¶
BETWEEN_SERVICES¶
PURPOSE: service-to-service authentication — both client and server present certificates USE_WHEN: high-security internal communication (Vault, inter-zone traffic)
CURRENT_GE_STATUS: - Vault: uses its own TLS (self-managed certificates) - Internal services (Grafana, Loki, Wiki): internal CA TLS (server-only, not mutual) - Redis: no TLS (protected by NetworkPolicy, internal-only) - Agent-to-API: bearer token auth over HTTPS (not mTLS)
FUTURE: consider service mesh (Linkerd/Istio) for automatic mTLS between all services DEFERRED: complexity vs benefit analysis — NetworkPolicies sufficient for current scale
NETWORK:VPN (Zone-to-Zone Communication)¶
WIREGUARD¶
PURPOSE: secure tunnel between Zone 1 (fort-knox-dev) and Zone 2/3 (UpCloud) TOOL: WireGuard (kernel-level, fast, simple, free) OWNER: stef (configuration), gerco (Zone 1 endpoint)
TOPOLOGY:
Zone 1 (fort-knox-dev) ←→ Zone 2 (UpCloud staging)
Zone 1 (fort-knox-dev) ←→ Zone 3 (UpCloud production)
Zone 2 ←→ Zone 3 (optional, for cross-cluster communication)
NETWORK:
WireGuard subnet: 10.0.0.0/24
Zone 1 peer: 10.0.0.1
Zone 2 peer: 10.0.0.2
Zone 3 peer: 10.0.0.3
Port: 51820/UDP
WIREGUARD_CONFIG (Zone 1):
[Interface]
PrivateKey = {from Vault}
Address = 10.0.0.1/24
ListenPort = 51820
[Peer]
# Zone 2 (UpCloud staging)
PublicKey = {zone2_public_key}
AllowedIPs = 10.0.0.2/32, 172.16.0.0/16
Endpoint = {zone2_public_ip}:51820
PersistentKeepalive = 25
[Peer]
# Zone 3 (UpCloud production)
PublicKey = {zone3_public_key}
AllowedIPs = 10.0.0.3/32, 172.17.0.0/16
Endpoint = {zone3_public_ip}:51820
PersistentKeepalive = 25
VERIFY:
TOOL: wg
RUN: sudo wg show
CHECK: handshake recent (< 2 minutes), transfer bytes increasing
RUN: ping 10.0.0.2 # ping Zone 2 through VPN
RUN: ping 10.0.0.3 # ping Zone 3 through VPN
VPN_FOR_ADMIN_ACCESS¶
PURPOSE: remote administration of servers (SSH over VPN only) RULE: SSH never exposed to internet — VPN-only access RULE: all admin access to Zone 2/3 goes through WireGuard VPN
NETWORK:RATE_LIMITING¶
EDGE_LEVEL (BunnyCDN — Karel)¶
LAYER: CDN edge (before request reaches origin)
METHOD: Bunny Shield rate limiting (per IP)
LIMITS: per Victoria's policy (typically 200 req/sec/IP)
BLOCKED: 429 Too Many Requests from edge
BENEFIT: origin server never sees rate-limited traffic
APPLICATION_LEVEL (backend developers)¶
LAYER: application code (Hono middleware)
METHOD: token bucket or sliding window (per user/IP)
LIMITS: API-specific (e.g., 100 req/min for auth endpoints)
BENEFIT: fine-grained control per endpoint, user-aware
COMPARISON¶
| Aspect | Edge Rate Limiting | Application Rate Limiting |
|---|---|---|
| Speed | Fastest (no origin hit) | Slower (request reaches server) |
| Granularity | Per IP only | Per user, API key, endpoint |
| Context | No application context | Full application context |
| Cost | Free (blocked at edge) | Uses server resources |
| Use case | DDoS, brute force | Business logic limits (API quotas) |
RECOMMENDATION: both layers — edge for volumetric protection, application for business logic
NETWORK:CROSS_ZONE_ARCHITECTURE¶
TRAFFIC_FLOWS¶
EXTERNAL (internet):
→ BunnyCDN edge (Karel) — WAF, DDoS, caching
→ Traefik ingress (Stef/Gerco) — TLS termination, routing
→ Application pods (Rutger) — business logic
→ PostgreSQL (Boris) — data
→ Redis (Gerco) — streams, cache
INTERNAL (agent execution):
Orchestrator → Redis Streams → Executor pods
Executor pods → Vault (secrets) → LLM APIs (Anthropic/OpenAI)
Executor pods → Admin-UI API (completion reports)
ADMIN:
SSH over WireGuard VPN → host machine
kubectl over WireGuard VPN → k8s API
Grafana/Loki dashboards (internal CA TLS)
NAMESPACE_ISOLATION¶
ge-ingress: Traefik only, restricted egress to hosting/system
ge-hosting: client workloads, default deny, ingress from Traefik only
ge-system: Redis, Vault, admin-ui — ingress from agents and Traefik
ge-agents: executors, orchestrator — egress to Redis, Vault, external HTTPS
ge-monitoring: Prometheus, Grafana, Loki — scrape access to all namespaces
ge-wiki: MkDocs — internal access only
NETWORK:AGENT_WORKFLOW¶
FOR_STEF¶
ON_FIREWALL_TASK: 1. READ this page for network security standards 2. DETERMINE what traffic flows are needed 3. WRITE NetworkPolicy / firewall rule (default deny + explicit allow) 4. IF Zone 3: send to Victoria for review BEFORE applying 5. APPLY rule 6. VERIFY connectivity: test from source to destination 7. DOCUMENT rule in Terraform (auditable)
ON_DEPLOY_CHAIN: 1. VERIFY DNS resolving correctly 2. VERIFY TLS certificate valid and terminating 3. VERIFY firewall rules allow required traffic 4. CONFIRM to Leon: network ready
FOR_VICTORIA (policy)¶
ON_SECURITY_REVIEW: 1. REVIEW proposed firewall rules 2. CHECK: default deny preserved? Only necessary traffic allowed? 3. CHECK: no overly broad rules (0.0.0.0/0 ingress)? 4. CHECK: no sensitive ports exposed (Redis, Vault, SSH)? 5. APPROVE or REQUEST changes 6. LOG review decision for compliance evidence
NETWORK:ANTI_PATTERNS¶
BEFORE_EVERY_NETWORK_ACTION: 1. Am I creating an allow-all NetworkPolicy? (NEVER — default deny only) 2. Am I exposing Redis (6381) to the internet? (NEVER) 3. Am I exposing Vault (8200) to the internet? (NEVER) 4. Am I allowing SSH from the internet? (NEVER — VPN only) 5. Am I using hostNetwork: true in k8s? (NEVER — causes port conflicts on rolling updates) 6. Am I skipping Victoria's review for Zone 3 changes? (NEVER) 7. Am I making manual firewall changes? (NEVER — Terraform/kubectl only) 8. Am I trusting network as the only security layer? (NEVER — defense in depth) 9. Am I assuming k3s ClusterIP works from pods? (BROKEN — use service DNS names)
NETWORK:CROSS_REFERENCES¶
DNS_MANAGEMENT: domains/networking/dns-management.md — DNS configuration TLS_CERTIFICATES: domains/networking/tls-certificates.md — TLS termination, cert management CDN_EDGE: domains/networking/cdn-edge.md — WAF, DDoS protection at edge KUBERNETES_OPERATIONS: domains/infrastructure/kubernetes-operations.md — k8s networking SECURITY: domains/security/index.md — OWASP, application security INCIDENT_RESPONSE: domains/incident-response/index.md — security incident procedures COMPLIANCE: domains/compliance-frameworks/index.md — ISO 27001 network controls