gateway-routing

📁 ionfury/homelab 📅 3 days ago
9
总安装量
7
周安装量
#31976
全站排名
安装命令
npx skills add https://github.com/ionfury/homelab --skill gateway-routing

Agent 安装分布

opencode 7
gemini-cli 7
amp 7
cline 7
github-copilot 7
codex 7

Skill 文档

Gateway Routing

The homelab uses Kubernetes Gateway API with Istio as the gateway controller. Two gateways handle traffic:

  • internal — accessible only within the home network
  • external — accessible from the internet, protected by Coraza WAF

All gateway resources live in the istio-gateway namespace. HTTPRoutes in any namespace reference these gateways via parentRefs.

Gateway Selection Decision Tree

Does this service need internet access?
|
+-- YES --> external gateway
|           - Domain: *.${external_domain}
|           - TLS: letsencrypt-production (Cloudflare DNS-01)
|           - WAF: Coraza OWASP CRS active
|           - IP: ${external_ingress_ip} (Cilium LB)
|
+-- NO  --> internal gateway
|           - Domain: *.${internal_domain}
|           - TLS: homelab-ca (self-signed CA)
|           - WAF: None
|           - IP: ${internal_ingress_ip} (Cilium LB)
|
+-- BOTH -> Create two HTTPRoutes (one per gateway)
            Examples: Authelia, Immich, Kromgo

Rule of thumb: Most platform dashboards (Grafana, Prometheus, Alertmanager, Longhorn, Hubble, Garage) are internal-only. User-facing apps (Authelia, Immich, Zipline) need external access and often also an internal route for LAN users.

Creating an HTTPRoute

Step 1: Choose Gateway and Hostname

Determine which gateway (or both) your service needs and the subdomain.

Step 2: Create the HTTPRoute YAML

Internal-only route (most common for platform services):

---
# yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/gateway.networking.k8s.io/httproute_v1.json
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: my-app
spec:
  parentRefs:
    - name: internal
      namespace: istio-gateway
  hostnames:
    - "my-app.${internal_domain}"
  rules:
    - backendRefs:
        - name: my-app-service
          port: 8080

External route (internet-facing, WAF-protected):

---
# yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/gateway.networking.k8s.io/httproute_v1.json
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: my-app-external
  namespace: my-app
spec:
  parentRefs:
    - name: external
      namespace: istio-gateway
  hostnames:
    - "my-app.${external_domain}"
  rules:
    - backendRefs:
        - name: my-app-service
          port: 8080

Step 3: Place the Route File

Service Type Location Example
Platform service kubernetes/platform/config/<subsystem>/ config/monitoring/grafana-route.yaml
Cluster-specific app kubernetes/clusters/<cluster>/config/<app>/ clusters/live/config/authelia/external-route.yaml

Add the route file to the subsystem’s kustomization.yaml.

Step 4: Network Policy

Ensure the app namespace has the correct network policy profile label:

Gateway Used Required Profile
Internal only internal or internal-egress
External only standard
Both standard

Set in kubernetes/platform/namespaces.yaml:

- name: my-app
  labels:
    network-policy.homelab/profile: standard

parentRefs Structure

The parentRefs field links an HTTPRoute to a Gateway listener. Key details:

parentRefs:
  - name: internal          # Gateway name: "internal" or "external"
    namespace: istio-gateway # Gateways live in istio-gateway namespace
    sectionName: https       # Optional: target specific listener (https or http)
  • namespace is required when the HTTPRoute is in a different namespace than the Gateway (which is always the case — gateways are in istio-gateway, routes are in app namespaces or the gateway namespace for platform routes)
  • sectionName is optional. Omit it to match any listener. Use http only for redirect routes.
  • Both gateways use allowedRoutes.namespaces.from: All on the HTTPS listener, so any namespace can attach routes.

Route Patterns from the Codebase

Simple Backend (most common)

rules:
  - backendRefs:
      - name: service-name
        port: 80

Used by: Grafana, Longhorn, Hubble, Alertmanager, Prometheus, Garage, Kromgo.

Dual Gateway Exposure (external + internal)

Create two separate HTTPRoute resources, one per gateway. Examples:

  • authelia-external + authelia-internal (same backend, different gateways/domains)
  • immich-external + immich-internal
  • kromgo-external + kromgo-internal

The routes are identical except for parentRefs.name and hostnames domain.

HTTP-to-HTTPS Redirect (platform-managed)

Both gateways have automatic HTTP-to-HTTPS redirects configured in config/gateway/http-to-https-redirect.yaml. You do not need to create redirect routes for new services.

TLS Certificate Setup

Architecture

Certificates are provisioned at the gateway level, not per-route. Each gateway has a wildcard certificate:

Gateway Certificate Secret Issuer Domain
external external external-tls ${tls_issuer:-cloudflare} *.${external_domain}
internal internal internal-tls ${tls_issuer:-cloudflare} *.${internal_domain}

The tls_issuer variable defaults to cloudflare (Let’s Encrypt DNS-01) but can be overridden to homelab-ca per cluster via .cluster-vars.env.

ClusterIssuers

Issuer Name Type Use Case Secret Source
cloudflare ACME (DNS-01) Public certs via Let’s Encrypt ExternalSecret from SSM (cloudflare-api-token)
homelab-ca CA Internal services, dev/integration clusters ExternalSecret from SSM (homelab-ingress-root-ca)
istio-mesh-ca CA Istio mesh mTLS (workload identity) ExternalSecret from SSM (shared across clusters)

Adding a New Subdomain

No certificate changes needed — the wildcard *.${external_domain} and *.${internal_domain} cover all subdomains. Just create the HTTPRoute.

Debugging TLS Issues

# Check certificate status
KUBECONFIG=~/.kube/<cluster>.yaml kubectl get certificates -n istio-gateway

# Check certificate details (Ready condition)
KUBECONFIG=~/.kube/<cluster>.yaml kubectl describe certificate external -n istio-gateway

# Check issuer health
KUBECONFIG=~/.kube/<cluster>.yaml kubectl get clusterissuers

# Check CertificateRequests (shows issuance attempts)
KUBECONFIG=~/.kube/<cluster>.yaml kubectl get certificaterequests -n istio-gateway

# Check the actual TLS secret
KUBECONFIG=~/.kube/<cluster>.yaml kubectl get secret external-tls -n istio-gateway -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -text

# If cert is stuck, check cert-manager logs
KUBECONFIG=~/.kube/<cluster>.yaml kubectl logs -n cert-manager deploy/cert-manager -f
Symptom Likely Cause Fix
Certificate not Ready Issuer secret missing Check ExternalSecret sync for cloudflare-api-token
ACME challenge failing DNS propagation / API token issue Verify Cloudflare token has Zone:DNS:Edit permission
homelab-ca not Ready Root CA secret missing Check ExternalSecret for homelab-ingress-root-ca
Browser TLS warning (internal) Self-signed CA not trusted Expected for homelab-ca; add CA to trusted store or use -k flag

Coraza WAF (External Gateway Only)

How It Works

The Coraza Web Application Firewall runs as an Istio WasmPlugin attached only to the external gateway:

# kubernetes/platform/config/gateway/coraza-wasm-plugin.yaml
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
  name: coraza-waf
spec:
  selector:
    matchLabels:
      gateway.networking.k8s.io/gateway-name: external  # External only
  url: oci://ghcr.io/corazawaf/coraza-proxy-wasm:0.6.0@sha256:...
  phase: AUTHN             # Runs before authentication
  failStrategy: FAIL_OPEN  # Traffic flows if WAF errors
  pluginConfig:
    directives_map:
      default:
        - Include @recommended-conf
        - Include @crs-setup-conf
        - Include @owasp_crs/*.conf
        - SecRuleEngine On
        - SecAction "id:900000,phase:1,pass,t:none,nolog,setvar:tx.blocking_paranoia_level=1"

Key settings:

  • Paranoia Level 1: Lowest false positive rate, catches common attacks
  • FAIL_OPEN: Prioritizes availability over security — if WASM fails to load, traffic passes unfiltered
  • AUTHN phase: WAF runs early in the filter chain, before any authentication checks
  • External only: Internal gateway traffic is not filtered by WAF

FAIL_OPEN Implications

If the WASM binary fails to load (wrong digest, image unavailable, OOM), traffic flows unfiltered. Check gateway pod logs for:

error in converting the wasm config to local: cannot fetch Wasm module...
applying allow RBAC filter

WAF Rule Customization

Rules are inlined in the WasmPlugin spec (Istio WasmPlugin does not support volume mounts). The coraza-config.yaml ConfigMap serves as documentation only.

To disable a rule causing false positives:

# Add to the directives_map.default array in coraza-wasm-plugin.yaml
- SecRuleRemoveById 920350  # Example: Host header validation

Testing WAF-Protected Endpoints

Istio gateway listeners match on SNI (Server Name Indication). Raw IP requests are rejected:

# WRONG -- no SNI, connection reset
curl -kI "https://192.168.10.53/"

# CORRECT -- send proper SNI with --resolve
GATEWAY_IP=$(KUBECONFIG=~/.kube/<cluster>.yaml kubectl get gateway external -n istio-gateway -o jsonpath='{.metadata.annotations.lbipam\.cilium\.io/ips}')
curl -kI --resolve "app.${external_domain}:443:${GATEWAY_IP}" \
  "https://app.${external_domain}/"

Attack Pattern Verification (expect 403)

# SQL Injection
curl -k --resolve "app.${external_domain}:443:${GATEWAY_IP}" \
  "https://app.${external_domain}/?id=1'%20OR%20'1'='1"

# XSS
curl -k --resolve "app.${external_domain}:443:${GATEWAY_IP}" \
  "https://app.${external_domain}/?q=<script>alert(1)</script>"

# Command Injection
curl -k --resolve "app.${external_domain}:443:${GATEWAY_IP}" \
  "https://app.${external_domain}/?cmd=;cat%20/etc/passwd"

WAF Monitoring

Metric What It Shows
istio_requests_total{source_workload=~"external-istio", response_code="403"} WAF-blocked requests
istio_requests_total{source_workload=~"external-istio"} Total external gateway traffic

Alerts configured in config/gateway/coraza-waf-rules.yaml:

Alert Condition Meaning
CorazaWAFDegraded No Istio metrics from external gateway for 5m Gateway may not be processing traffic
CorazaWAFHighBlockRate >10% of requests returning 403 for 10m Possible attack or WAF false positives
CorazaWAFHighLatency p99 gateway latency >50ms for 5m WAF overhead too high, tune rule exclusions

Common Issues

Issue Cause Resolution
Route not working Missing namespace: istio-gateway in parentRefs Add namespace to parentRefs
404 on valid hostname HTTPRoute not attached to gateway Check parentRefs gateway name matches exactly
Connection reset on external SNI mismatch (testing with IP) Use --resolve flag with proper hostname
Pods unreachable from gateway Missing network policy profile Add network-policy.homelab/profile label to namespace
503 Service Unavailable Backend service not found or port wrong Verify service name and port in backendRefs
Both internal and external needed Only one route created Create two separate HTTPRoute resources
WAF blocking legitimate traffic False positive on CRS rule Add SecRuleRemoveById <ID> to WasmPlugin directives

Cross-References

Document Focus
kubernetes/platform/config/gateway/ Gateway definitions, WAF config
kubernetes/platform/config/issuers/ ClusterIssuer definitions
kubernetes/platform/config/certs/ Certificate resources
kubernetes/platform/config/network-policy/CLAUDE.md Network policy profiles
kubernetes/platform/CLAUDE.md Variable substitution, platform structure
deploy-app skill Full app deployment workflow including routing