Skip to content

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):

# Similar pattern — allow specific port (8200) to specific pod (vault)

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):

# Allow health check probes on standard port
ingress:
- ports:
  - protocol: TCP
    port: 8080

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:

TOOL: ufw
RUN: sudo ufw status numbered
RUN: sudo ufw show raw

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