This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
A Kubernetes operator that manages Web Application Firewall (WAF) deployments using Coraza, integrated with Istio via WASM plugins. CRDs include RuleSource (stores SecLang rule text), RuleData (stores data files for @pmFromFile), RuleSet (ordered spec.sources listing RuleSource names + optional spec.data listing RuleData names), and Engine (attaches a RuleSet to a Gateway via Istio WasmPlugin).
Data flow: RuleSources + RuleData + RuleSet → RuleSetReconciler → RuleSetCache (HTTP server) → WASM plugin in Envoy → traffic filtering
make build # Build manager + kubectl-coraza (runs manifests, generate, fmt, vet, lint)
make test # Unit tests (sets ISTIO_VERSION automatically)
make lint # golangci-lint with -build-tags integration
make lint.fix # Auto-fix lint issues
make manifests generate helm.sync # Regenerate CRDs, RBAC, and sync to Helm chartRun a single unit test:
ISTIO_VERSION=1.28.2 go test -v -run TestMyFunction ./internal/controller/...Verify test files compile (go build silently ignores _test.go):
ISTIO_VERSION=1.28.2 go test -run ^$ ./...make test.integration # -tags=integration, needs KIND cluster
make test.e2e # -tags=e2e, needs KIND cluster
make test.conformance # -tags=conformance, runs CoreRuleSet FTW testsWithout the build tag, go test silently finds zero tests.
make cluster.kind # Create KIND cluster with Istio + MetalLB + operator
make clean.cluster.kind # Destroy it- RuleSetReconciler — watches RuleSet + referenced RuleSources + referenced RuleData (
get/list/watchonrulesourcesandruledatain its namespace only). Aggregates rules from RuleSources and data files from RuleData, validates via Coraza, checks for WASM-unsupported rules, stores in RuleSetCache. - EngineReconciler — watches Engine + referenced RuleSet + Gateways + Pods. When RuleSet is ready, applies a WasmPlugin resource (server-side apply) and discovers matched Gateway pods.
Both are initialized in internal/controller/manager.go with a shared RuleSetCache.
In-memory versioned cache (internal/rulesets/cache/) with an HTTP server (port 18080). WASM plugins poll /rules/{instance}/latest and /rules/{instance}/data. Garbage-collected by TTL and size limits.
When --operator-name is set, the manager creates a ServiceEntry + DestinationRule at startup (engine_controller_istio_prerequisites.go) so the cache server is mesh-reachable.
api/v1alpha1/— CRD types (Engine, RuleSet, RuleSource, RuleData, DriverConfig)internal/controller/— reconcilers and watch setupinternal/rulesets/— cache, memfs (virtual FS for rule validation), unsupported rule detectioncmd/manager/— operator entry pointcmd/kubectl-coraza/— CLI plugin for rule generationtest/framework/— integration test helpers (Scenario pattern, port-forwarding, assertions)
| Change | Edit (source of truth) | Then run |
|---|---|---|
| CRD fields/validation/status | api/**/*_types.go |
make manifests helm.sync |
| RBAC permissions | +kubebuilder:rbac markers in controllers |
make manifests helm.sync |
| Helm templates | charts/.../templates/*.yaml |
nothing |
| Helm values | charts/.../values.yaml |
nothing |
Never edit directly: config/crd/bases/*.yaml, config/rbac/role.yaml, charts/.../crds/*.yaml — these are all regenerated.
Unit tests download Istio CRDs at startup. Without ISTIO_VERSION, tests fail immediately. Always read the default from Makefile — never hardcode it:
grep '^ISTIO_VERSION' MakefileThe Engine controller uses predicate.GenerationChangedPredicate{} on its primary watch. Metadata-only changes (labels, annotations, finalizers) do NOT bump .metadata.generation, so the update event is filtered out.
If you introduce a finalizer to a controller that uses GenerationChangedPredicate, the finalizer-add write won't trigger an update event. You must use RequeueAfter (never Requeue, which is deprecated) to re-enter reconciliation:
return ctrl.Result{RequeueAfter: 100 * time.Millisecond}, nilThis also means unit tests will need two Reconcile() calls — the first to add the finalizer, the second to do the actual work. The current EngineReconciler does not use a finalizer, so a single Reconcile() call is sufficient in its tests.
The NetworkPolicy logic uses operatorNamespace to determine the target namespace. Missing it silently creates resources in the wrong namespace.
Kubernetes naming limits depend on the resource type. Many object names use the DNS subdomain constraint and may be up to 253 characters, while some fields and name segments are limited to 63. When constructing names from user input (for example, namespace + name), validate against the specific target resource's constraint and, where needed, truncate and append a stable hash suffix.
When watching resources the controller creates via server-side apply, filter out update events to prevent reconcile loops:
predicate.And(
predicate.NewPredicateFuncs(labelFilter),
predicate.Funcs{
CreateFunc: func(event.CreateEvent) bool { return true },
DeleteFunc: func(event.DeleteEvent) bool { return true },
UpdateFunc: func(event.UpdateEvent) bool { return false },
GenericFunc: func(event.GenericEvent) bool { return false },
},
)The operator disables HTTP/2 via NextProtos: []string{"http/1.1"} to mitigate CVE-2023-44487 (HTTP/2 Rapid Reset). Preserve this when modifying TLS config in cmd/manager/main.go.
When adding or modifying +kubebuilder:rbac markers, you must regenerate and sync:
make manifests generate helm.syncFor cluster tests, also redeploy: make deploy.
All integration tests must use the framework Scenario pattern — never reimplement port-forwarding or cleanup logic directly:
s := fw.NewScenario(t)
ns := s.GenerateNamespace("my-test")
s.Step("apply manifests")
s.ApplyManifest(ns, "path/to/manifest.yaml")
s.Step("verify behavior")s.OnCleanup(fn)— LIFO cleanup, automatic viat.Cleanups.ProxyToGateway(ns, name)— HTTP testing against a gateways.ProxyToPod(ns, selector, port)— port-forward to arbitrary podsframework.DefaultTimeout/framework.DefaultInterval— never hardcode durations
- RuleSources:
coraza.io/validation: "false"— skips per-source Coraza rule validation - RuleSets:
waf.k8s.coraza.io/skip-unsupported-rules-check: "true"— prevents degrading on unsupported rules