nginx-ingress
Dieser Block installiert den ingress-nginx Controller per Helm Chart in Kubernetes.
Actions
install
Installiert nginx-ingress im Cluster.
polycrate run ingress install
uninstall
Deinstalliert nginx-ingress aus dem Cluster.
polycrate run ingress uninstall
status
Zeigt den aktuellen Status der nginx-Pods und des LoadBalancer-Services an.
polycrate run ingress status
Konfiguration
Standardinstallation mit Metriken und Dashboards
blocks:
- name: ingress
from: cargo.ayedo.cloud/ayedo/k8s/nginx
kubeconfig:
from: k8s
config:
metrics:
enabled: true
dashboards:
enabled: true
vmpodscrape:
enabled: true
Fixe LoadBalancer-IP (Cilium / MetalLB)
Mit controller.service.loadbalancer_ip wird dem Ingress Controller eine bestimmte IP zugewiesen.
Der Block setzt automatisch die passenden Annotations für beide gängigen LB-Implementierungen:
- Cilium IPAM:
lbipam.cilium.io/ips - MetalLB:
metallb.universe.tf/loadBalancerIPs
config:
controller:
service:
loadbalancer_ip: "10.0.0.10"
Die IP muss im konfigurierten LB-Pool (CiliumLoadBalancerIPPool oder MetalLB IPAddressPool) vorhanden sein. Leer lassen, um eine IP automatisch zuweisen zu lassen.
Fixe LoadBalancer-IP (OpenStack Octavia / CCM)
Interner LB im K8s-Netz (keine Floating IP). Neutron-Port wird vom openstack-Block angelegt (ensure-lb-ports / install).
polycrate run openstack ensure-lb-ports -w . # oder openstack install
polycrate run ingress install -w .
# openstack-Block (workspace.poly)
load_balancer_ports:
- name: vivavis-poc-ingress-lb
subnet_id: "d6b0123b-a47a-4bfe-afe7-1df3f5009f2a"
address: "172.16.2.200"
# ingress-Block
config:
controller:
service:
loadbalancer_ip: "172.16.2.200"
openstack:
internal: true
subnet_id: "d6b0123b-a47a-4bfe-afe7-1df3f5009f2a"
lb_port_name: vivavis-poc-ingress-lb
Schreibt artifacts/openstack-lb-ports.yml — nginx liest port_id daraus via lb_port_name.
Voraussetzung: openstack install (CCM) und service_lb: false im k8s-Block.
externalTrafficPolicy
Steuert, wie der LoadBalancer-Service Traffic an nginx-Pods weiterleitet:
| Wert | Verhalten | Source-IP |
|---|---|---|
Local |
Nur Nodes mit nginx-Pod nehmen Traffic an | Erhalten |
Cluster |
Alle Nodes nehmen Traffic an, SNAT auf Pod-Node | Verloren |
Empfehlung je Setup:
| Setup | Empfehlung | Begründung |
|---|---|---|
| BGP-Anycast + Cilium DSR | Local |
Cilium kündigt LB-IP nur von Nodes mit aktivem Pod an → Traffic landet immer am richtigen Node. DSR-Response-Path bleibt symmetrisch. |
| Hetzner / Cloud LB | Cluster |
Cloud-LB verteilt auf alle Nodes unabhängig von Pod-Platzierung. Mit Local würden Nodes ohne nginx-Pod Traffic droppen. |
config:
controller:
service:
external_traffic_policy: Local # Default: Cluster
Wichtig bei
Local: Jeder Node, der Traffic empfangen kann, muss einen nginx-Pod haben. Beireplicas: 1läuft der Pod nur auf einem Node — Cilium sorgt via BGP dafür, dass nur dieser Node die IP ankündigt. Für HAreplicas: 3mittopologySpreadConstraintsverwenden.
Hetzner: Proxy Protocol aktivieren
Wird benötigt, wenn der Hetzner Load Balancer Proxy Protocol an nginx weitergibt.
config:
controller:
config:
proxy_protocol: true
use_forwarded_headers: true
Extra Ports (TCP/UDP)
config:
extra_ports:
tcp:
8080: "default/example-tcp-svc:9000"
udp:
53: "kube-system/kube-dns:53"
ModSecurity WAF
Der Block unterstützt ModSecurity als Web Application Firewall, wahlweise global für alle Ingresses oder individuell pro Ingress.
Konzept
| Ebene | Steuerung | Beschreibung |
|---|---|---|
| Global | block.config.modsecurity.* |
Aktiviert ModSecurity für alle Ingresses über den nginx ConfigMap |
| Pro Ingress | Kubernetes Annotations | Überschreibt die globale Einstellung für einzelne Ingress-Objekte |
ModSecurity läuft standardmäßig im Detection-Only-Modus – es werden nur Logs geschrieben, keine Requests geblockt. Blocking wird erst durch mode: On aktiviert.
Globale Aktivierung
blocks:
- name: ingress
from: cargo.ayedo.cloud/ayedo/k8s/nginx
config:
controller:
modsecurity:
enabled: true # ModSecurity global aktivieren
owasp_crs: true # OWASP Core Rule Set aktivieren
mode: DetectionOnly # DetectionOnly (nur Logs) oder On (blockieren)
snippet: "" # Optionale zusätzliche SecRule-Direktiven
Mit dieser Konfiguration wird im nginx ConfigMap gesetzt:
enable-modsecurity: "true"
enable-owasp-modsecurity-crs: "true"
modsecurity-snippet: |
SecRuleEngine DetectionOnly
Blocking-Modus aktivieren
config:
controller:
modsecurity:
enabled: true
owasp_crs: true
mode: On
Eigene Regeln hinzufügen
Das snippet-Feld wird direkt an den globalen modsecurity-snippet angehängt:
config:
controller:
modsecurity:
enabled: true
owasp_crs: true
mode: On
snippet: |
SecRule REQUEST_HEADERS:User-Agent "nikto" \
"id:1001,phase:1,deny,status:403,msg:'Nikto scanner blocked'"
Pro-Ingress-Konfiguration
Unabhängig von der globalen Einstellung kann ModSecurity für jeden Ingress individuell gesteuert werden. Da annotations_risk_level: Critical gesetzt ist, sind alle Annotations erlaubt.
ModSecurity für einen einzelnen Ingress aktivieren
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/enable-modsecurity: "true"
nginx.ingress.kubernetes.io/enable-owasp-core-rules: "true"
Blocking-Modus per Ingress aktivieren
metadata:
annotations:
nginx.ingress.kubernetes.io/enable-modsecurity: "true"
nginx.ingress.kubernetes.io/modsecurity-snippet: |
SecRuleEngine On
OWASP CRS + Blocking + eigene Regeln kombinieren
metadata:
annotations:
nginx.ingress.kubernetes.io/enable-modsecurity: "true"
nginx.ingress.kubernetes.io/modsecurity-snippet: |
Include /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf
SecRuleEngine On
SecRule REQUEST_HEADERS:User-Agent "nikto" \
"id:1001,phase:1,deny,status:403,msg:'Nikto scanner blocked'"
Hinweis: Wenn
modsecurity-snippetgesetzt ist, überschreibt esenable-owasp-core-rules. Um den OWASP CRS trotzdem zu nutzen, muss dasIncludeexplizit in den Snippet aufgenommen werden.
ModSecurity für einen Ingress deaktivieren (bei globaler Aktivierung)
metadata:
annotations:
nginx.ingress.kubernetes.io/enable-modsecurity: "false"
Referenz: Alle block.config.modsecurity-Felder
| Feld | Typ | Default | Beschreibung |
|---|---|---|---|
enabled |
bool | false |
ModSecurity global für alle Ingresses aktivieren |
owasp_crs |
bool | false |
OWASP Core Rule Set global aktivieren |
mode |
string | DetectionOnly |
DetectionOnly (nur Logs) oder On (blockieren) |
snippet |
string | "" |
Zusätzliche SecRule-Direktiven, die global angehängt werden |
Referenz: Per-Ingress Annotations
| Annotation | Wert | Beschreibung |
|---|---|---|
nginx.ingress.kubernetes.io/enable-modsecurity |
"true" / "false" |
ModSecurity für diesen Ingress aktivieren oder deaktivieren |
nginx.ingress.kubernetes.io/enable-owasp-core-rules |
"true" |
OWASP CRS für diesen Ingress aktivieren |
nginx.ingress.kubernetes.io/modsecurity-snippet |
Multi-line string | Custom SecRules; überschreibt enable-owasp-core-rules |
Vollständige Dokumentation: ingress-nginx ModSecurity · ingress-nginx Annotations
ModSecurity Logs einsehen
ModSecurity schreibt Audit-Logs per Default als JSON nach stdout (SecAuditLog /dev/stdout, SecAuditLogFormat JSON). Damit landen alle WAF-Events automatisch in VictoriaLogs – kein separater Audit-Log-Pfad, kein ephemerer Container-Storage.
Gesteuert über block.config.modsecurity.audit_log:
modsecurity:
enabled: true
audit_log:
enabled: true # stdout-Weiterleitung an/aus
engine: RelevantOnly # On (alle Requests), RelevantOnly (nur Treffer), Off
format: JSON # JSON (strukturiert) oder Native
Live-Logs verfolgen
# Alle ModSecurity-Events (JSON-Zeilen)
kubectl logs -n ingress -l app.kubernetes.io/name=ingress-nginx -f | grep '"transaction"'
# Blocking-Events (mode: On)
kubectl logs -n ingress -l app.kubernetes.io/name=ingress-nginx | grep '"Access denied"'
Nginx-Blocking-Events erscheinen zusätzlich im nginx error-log:
[error] ModSecurity: Access denied with code 403 (phase 2). Inbound Anomaly Score Exceeded (Total Score: 20) ...
VictoriaLogs – Audit-Log abfragen
Alle ModSecurity WAF-Events (JSON Audit-Log):
{namespace="ingress"} | json | transaction_request_uri != ""
Nur Blocking-Events (mode: On, Score überschritten):
{namespace="ingress"} |= "Access denied"
Treffer nach Regel-ID filtern (z.B. XSS Rule 941100):
{namespace="ingress"} | json | _msg |= "941100"
Alle Treffer der letzten Stunde zählen:
count_over_time({namespace="ingress"} | json | transaction_request_uri != "" [1h])
Top angegriffene URIs:
{namespace="ingress"} | json | transaction_request_uri != ""
| stats by (transaction_request_uri) count() as hits
| sort by (hits desc)
| limit 10