spike(apisix): opt-in Coraza WAF proxy-wasm image + Helm wiring - #5101
spike(apisix): opt-in Coraza WAF proxy-wasm image + Helm wiring#5101blarghmatey wants to merge 5 commits into
Conversation
Spike for mitigating internet bot/vulnerability-scanner noise at the APISIX gateway layer (see the QA rollout investigation for #5096/#5099). Evaluated chaitin-waf (already loaded in our plugin list) and other self-hosted WAFs first, but chaitin-waf requires a separate SafeLine detection-engine service to operate. Coraza runs in-process as a proxy-wasm filter instead -- no separate service, and the wasm runtime it needs is already compiled into every standard apisix-base build (confirmed against api7/apisix-build-tools' build-apisix-base.sh: wasm-nginx-module is an unconditional --add-module, not an opt-in variant), so the only gap is getting the compiled coraza-proxy-wasm binary into the image. dockerfiles/apisix-waf/Dockerfile: two-stage build, copies the pre-built coraza-proxy-wasm.wasm binary (OWASP Core Rule Set is embedded in it at compile time by the upstream project -- confirmed via their README, no separate ruleset files needed) from ghcr.io/corazawaf/coraza-proxy-wasm onto the stock apache/apisix image. Published as mitodl/apisix-waf (ECR only) via src/ol_concourse/pipelines/container_images/apisix_waf.py, following the ol_python_base.py/kubewatch build-and-push pattern. Deliberately not chained to a pulumi_jobs_chain -- image builds here never trigger a deploy on their own. apisix_official.py: new opt-in, per-cluster config (apisix_custom_image_repository/apisix_custom_image_tag/apisix_wasm_plugins) that only diverges from today's stock-image, wasm-disabled behavior when a stack explicitly sets it -- verified zero-diff on applications.QA with nothing set, and a clean isolated image/wasm diff when simulating the opt-in values via CLI config overrides (not persisted to any stack file). Next step to actually pilot this: set the three config keys on one QA cluster and attach the coraza-filter plugin to a low-traffic test route to validate it blocks known scanner patterns before considering it for any shared plugin config. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…lications clusters Registers apisix-waf-docker with the container-images-meta pipeline (container_images/meta.py's PIPELINE_CONFIGS) so it gets auto-created on merge, following every other entry's pattern. Opts applications.CI and applications.QA into the Coraza image + wasm plugin registration for a low-risk, long-horizon soak test: apisix_custom_image_repository/apisix_custom_image_tag point APISIX at mitodl/apisix-waf:latest, and apisix_wasm_plugins loads the coraza-filter plugin. wasm.enabled loads the plugin globally, but nothing routes through it yet -- no ApisixRoute or shared-plugin-config references coraza-filter by name -- so this validates the image/wasm-runtime path stays stable over time without touching live request handling. Deliberately scoped to pre-production only. applications.Production verified untouched by preview (its only pending diff is the unrelated, already-known Traefik NLB fix from #5099). apisix_official.py: pullPolicy switches to "Always" when a custom image is configured, since :latest needs re-checking on each pod restart -- the stock image path is unaffected (still IfNotPresent, already digest-cached via cached_image_uri). Verified via preview on all three stacks: - applications.QA / applications.CI: identical isolated diff (image repository/tag/pullPolicy + new wasm block), 1 resource updated each. - applications.Production: zero apisix-related diff. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds an opt-in path to run APISIX with the Coraza proxy-wasm WAF filter by (a) defining a custom image build pipeline and Dockerfile, and (b) wiring Helm values in the EKS APISIX deployment to optionally reference that image and register wasm plugins.
Changes:
- Introduces a new
apisix-wafDockerfile and a Concourse pipeline to build/publish the image to ECR. - Adds per-stack Pulumi config knobs to optionally override the APISIX image repo/tag and enable wasm plugins.
- Updates
applicationsCI/QA stack configs to set those new knobs (currently contradicting the PR’s “no cluster opted-in” description).
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/ol_infrastructure/infrastructure/aws/eks/Pulumi.applications.QA.yaml | Opts the QA stack into the custom APISIX WAF image + wasm plugin configuration. |
| src/ol_infrastructure/infrastructure/aws/eks/Pulumi.applications.CI.yaml | Opts the CI stack into the custom APISIX WAF image + wasm plugin configuration. |
| src/ol_infrastructure/infrastructure/aws/eks/apisix_official.py | Adds opt-in Helm wiring for custom image repo/tag and wasm plugins. |
| src/ol_concourse/pipelines/container_images/meta.py | Registers the new apisix-waf container-image pipeline in the container-images meta pipeline list. |
| src/ol_concourse/pipelines/container_images/apisix_waf.py | Defines the Concourse pipeline to build and push the apisix-waf image to ECR. |
| dockerfiles/README.md | Documents the new apisix-waf image and how it’s published. |
| dockerfiles/apisix-waf/Dockerfile | Adds the two-stage image that copies the Coraza .wasm artifact into an APISIX base image. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Addresses Copilot review feedback on #5101: repository set without a tag, or wasm_plugins set without a custom image, would previously fail at Helm-apply/pod-startup time (ImagePullBackOff pulling an untagged image that doesn't exist under our tag scheme, or APISIX failing to find a wasm plugin file the stock image never had) instead of at pulumi preview/up. Verified both invalid combinations raise and both valid combinations (unconfigured, and applications.QA/CI's actual config) pass -- re-previewed applications.QA, same clean diff as before this change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
src/ol_infrastructure/infrastructure/aws/eks/apisix_official.py:225
apisix_custom_image_repositoryandapisix_custom_image_tagare treated as independent optional config values, but an incomplete configuration (only one set) will produce invalid Helm values (e.g., custom repo with chart default tag that likely doesn’t exist, or overriding the stock image tag unintentionally). Consider enforcing that these two settings must be provided together so misconfiguration fails fast during preview/up.
apisix_custom_image_repository = eks_config.get("apisix_custom_image_repository")
apisix_custom_image_tag = eks_config.get("apisix_custom_image_tag")
# e.g. [{"name": "coraza-filter", "priority": 7999,
# "file": "/usr/local/apisix/coraza-filter.wasm"}]
apisix_wasm_plugins = eks_config.get_object("apisix_wasm_plugins") or []
Addresses Copilot review feedback on #5101: cached_image_uri() only rewrites the repository to our ECR pull-through cache -- it doesn't pin a digest. The stock image path was never digest-pinned; wording corrected to not imply otherwise. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Addresses Sentry AI review feedback on #5101 (bug prediction, low severity but real): the existing validation only checked repository-without-tag, missing the inverse -- apisix_custom_image_tag set without apisix_custom_image_repository silently applies the tag to the stock apache/apisix image instead of raising, since the two Helm values keys are built from independent conditionals. Verified all six config combinations (three invalid, three valid including applications.QA/CI's actual config) behave correctly, and re-previewed applications.QA -- diff unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Addresses Sentry AI review feedback on #5101 (bug prediction, low severity but real): the existing validation only checked repository-without-tag, missing the inverse -- apisix_custom_image_tag set without apisix_custom_image_repository silently applies the tag to the stock apache/apisix image instead of raising, since the two Helm values keys are built from independent conditionals. Verified all six config combinations (three invalid, three valid including applications.QA/CI's actual config) behave correctly, and re-previewed applications.QA -- diff unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
c8a45ec to
f80fae2
Compare
|
This should be turned into a project that includes some developers for a full blown assessment on how best to handle this. We already have existing tools/services in place that could be leveraged, however there's an additional cost associated with enabling those features. There are also some "free" tools such as the one proposed and others that could perform the job however it's added complexity and additional things to manage. |
What are the relevant tickets?
N/A. Follow-up spike from the investigation on #5096/#5099 (bot/scanner noise mitigation at the APISIX layer).
Description (What does it do?)
Spike, not a full rollout: builds a custom APISIX image bundling the Coraza WAF proxy-wasm filter, registers its build pipeline, and opts pre-production
applicationsclusters into a low-risk long-horizon soak test before considering it for production.Why Coraza over other options evaluated:
chaitin-wafis already in our loaded APISIX plugin list, but it isn't self-contained — it proxies every request to a separate self-hosted Chaitin SafeLine WAF detection-engine service (APISIX >=3.5.0, SafeLine >=5.6.0) that would need to be deployed and operated as new infrastructure.api7/apisix-build-tools'sbuild-apisix-base.sh) thatwasm-nginx-moduleis compiled into every standardapisix-basebuild unconditionally (--add-module=../wasm-nginx-module-..., not an opt-in variant) — so the wasm/proxy-wasm runtime our stockapache/apisiximage already ships is sufficient; no custom OpenResty compile needed.coraza-proxy-wasm.wasmbinary into the image. Confirmed via thecoraza-proxy-wasmproject's own README that OWASP Core Rule Set "comes embedded in the extension" at compile time by the upstream project — no separate ruleset files to deliver or mount.Changes:
dockerfiles/apisix-waf/Dockerfile: two-stage build —COPY --from=ghcr.io/corazawaf/coraza-proxy-wasm:0.6.0 /plugin.wasmontoapache/apisix:3.17.0-ubuntu(tracksAPISIX_CHART_VERSION's defaultappVersion).src/ol_concourse/pipelines/container_images/apisix_waf.py: build-and-push-to-ECR pipeline, following the existingol_python_base.py/kubewatchpattern (container_build_task+ensure_ecr_task+PutStep). Published asmitodl/apisix-waf:latest(+ git-short-ref secondary tag), ECR only (not Docker Hub — internal security tooling, not meant for public distribution). Deliberately not chained into apulumi_jobs_chain— image builds here never trigger a deploy on their own.src/ol_concourse/pipelines/container_images/meta.py: registersapisix-waf-dockerinPIPELINE_CONFIGSso the meta-pipeline auto-creates/updates it on merge, same as every other image-build pipeline.src/ol_infrastructure/infrastructure/aws/eks/apisix_official.py: new opt-in, per-cluster Pulumi config (apisix_custom_image_repository,apisix_custom_image_tag,apisix_wasm_plugins) that only diverges from today's behavior when a stack explicitly sets it, following the existingeks_config.get(...) or <default>pattern already used throughout this file for other per-cluster tunables (apisix_min_replicas,apisix_memory, etc.).pullPolicyswitches toAlwayswhen a custom image is configured (since:latestneeds re-checking on pod restart); the stock image path is unaffected.Pulumi.applications.CI.yaml/Pulumi.applications.QA.yaml: opt in with the three new config keys.applications.Productionis deliberately not touched.dockerfiles/README.md: documents the new image per the directory's existing convention.Why this is low-risk despite being "live" on two clusters:
wasm.enabled: trueloads thecoraza-filterplugin globally, but noApisixRouteor shared-plugin-config references it by name yet, so it's inert with respect to live traffic. What this soak-tests is narrower and specific: does APISIX start up and run stably over time on the custom image with the wasm runtime active. That's the operational confidence needed before attaching the plugin to any real route.Screenshots (if appropriate):
N/A - infrastructure/backend only.
How can this be tested?
pulumi previewagainstapplications.QAandapplications.CI, both configured: identical isolated diff on each (image.repository/image.tag/image.pullPolicy+ newwasmblock), 1 resource updated, 167 unchanged.pulumi previewagainstapplications.Production: zero apisix-related diff (only the unrelated, already-known pending Traefik NLB fix from fix(eks): preserve real client IPs on the Traefik gateway NLB #5099 shows up).Not done in this PR (deliberately, this is a spike, not a full rollout):
coraza-filterattached — it isn't wired into the sharedOLApisixSharedPluginsConfigany app depends on, and won't be until the soak test on CI/QA shows it's stable.Additional Context
Suggested next step after this merges and the soak test has run for a while with no stability issues: attach
coraza-filterto a single low-traffic test route on one of the opted-in clusters (directives:Include @demo-conf,Include @crs-setup-conf,SecRuleEngine On,Include @owasp_crs/*.conf) to confirm it actually blocks known scanner traffic patterns (/wls-wsat/,/seeyon/htmlofficeservlet, etc.) before considering it for the shared plugin config any real app traffic depends on.