Skip to content

convox-examples/coraza-waf

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Coraza WAF Example for Convox

A web application firewall (WAF) using Coraza and the OWASP Core Rule Set deployed as a reverse proxy service in front of your application on Convox.

This example demonstrates how to protect a backend service with an in-cluster WAF that blocks common web attacks — SQL injection, cross-site scripting (XSS), local file inclusion, and more — without any changes to your application code. The WAF runs as a separate Convox service using a pre-built Docker image maintained by the OWASP CRS project, so there's nothing to compile or patch.

Deploy to Convox Cloud for a fully-managed platform experience, or to your own Convox Rack for complete control over your infrastructure. Either way, you'll get automatic SSL, load balancing, and zero-downtime deployments out of the box.

How It Works

Internet → NLB → nginx ingress → waf service (Caddy + Coraza) → web service (internal)

The waf service is the public-facing entry point. It inspects every request through Coraza and the OWASP Core Rule Set, then forwards clean traffic to the web service. Setting internal: true on web ensures nothing bypasses the WAF.

Deploy to Convox Cloud

  1. Create a Cloud Machine at console.convox.com

  2. Create the app:

convox cloud apps create coraza-waf -i your-machine-name
  1. Deploy the app:
convox cloud deploy -a coraza-waf -i your-machine-name
  1. View your app:
convox cloud services -a coraza-waf -i your-machine-name

Deploy to Convox Rack

  1. Create the app:
convox apps create coraza-waf
  1. Deploy the app:
convox deploy -a coraza-waf
  1. View your app:
convox services -a coraza-waf

Test the WAF

Once deployed, grab the WAF service URL from convox services output.

WAF_URL="https://your-waf-url"

# Normal request — should return 200
curl -sk "$WAF_URL/"

# SQL injection — should return 403 when enforcement is on
curl -sk "$WAF_URL/?id=1%20OR%201=1--"

# XSS — should return 403 when enforcement is on
curl -sk "$WAF_URL/?q=<script>alert(1)</script>"

# .env probe — should return 403 when enforcement is on
curl -sk "$WAF_URL/.env"

This example starts in tuning mode — the rule engine is on but the anomaly score threshold is set high enough that nothing gets blocked. Malicious requests are logged so you can tune out false positives before enabling enforcement.

Configuration

Key Environment Variables

Variable Default Description
CORAZA_RULE_ENGINE On Must be On for paranoia level gating to work correctly. See note below.
ANOMALY_INBOUND 999999 Anomaly score threshold before a request is blocked. Set to 999999 for tuning (log only), 5 for enforcement.
BACKEND web:3000 Backend service address. Use the Convox service name and port.
PORT 8080 Port the WAF container listens on.

Why not DetectionOnly? The Coraza WAF engine has a bug where skipAfter flow-control actions — the mechanism CRS uses for paranoia level gating — do not execute in DetectionOnly mode. This causes all rules at all paranoia levels to fire regardless of your configured paranoia level, flooding logs with false positives. Using On mode with a high anomaly threshold gives the same log-only behavior but with correct paranoia gating.

Note on paranoia level: The PARANOIA and BLOCKING_PARANOIA environment variables are passed to the image's entrypoint script but have known issues with CRS v4 variable names. This example sets the paranoia level directly in waf/plugins/paranoia-before.conf as the authoritative control. To change the paranoia level, edit that file and update the setvar values.

Recommended Rollout

  1. Start in tuning mode (the default). The rule engine is On but the anomaly threshold is set to 999999 so nothing gets blocked:
    environment:
      - CORAZA_RULE_ENGINE=On
      - ANOMALY_INBOUND=999999
  1. Watch the logs for what Coraza flags:
convox logs -a coraza-waf | grep "paranoia-level"
  1. Tune out false positives by adding custom rule exclusions (see Custom Rules below).

  2. Enable enforcement by lowering the anomaly threshold to 5 (the CRS default):

convox env set ANOMALY_INBOUND=5 -a coraza-waf
  1. Raise paranoia level once stable — edit waf/plugins/paranoia-before.conf and change both values to 2, then redeploy:
SecAction "id:1000002,phase:1,nolog,pass,t:none,setvar:tx.blocking_paranoia_level=2,setvar:tx.detection_paranoia_level=2"

Paranoia Levels

Level Coverage False Positives Best For
1 Catches obvious attacks Very few Getting started
2 Broader coverage Some tuning needed Most production apps
3 Extensive rules Significant tuning High-security environments
4 Maximum coverage Expect heavy tuning Compliance-driven use cases

Adapting to Your Application

Replace the example backend with your own service. The only changes needed are in convox.yml:

services:
  waf:
    build: ./waf
    port: 8080
    health:
      path: /health
      interval: 10
      timeout: 9
    environment:
      - CORAZA_RULE_ENGINE=On
      - ANOMALY_INBOUND=999999
      - BACKEND=web:3000
      - PORT=8080
    scale:
      count: 2
      memory: 512
  web:
    build: .
    port: 3000
    internal: true

Point BACKEND to your service name and port. Convox configures DNS search domains on every pod, so the bare service name resolves automatically within the same app — no need for a fully-qualified domain name.

Health check tip: Point the health check at a lightweight backend endpoint (like /health) rather than / to avoid the health check going through full WAF inspection and hitting any backend rate limiting.

Custom Rules

The WAF image supports three directories for customization, loaded at different times:

Directory Loaded Use For
/opt/coraza/config.d/ Before crs-setup.conf Rule exclusions (ctl:ruleRemoveById)
/opt/coraza/plugins/ After crs-setup.conf, before CRS rules Paranoia level overrides
/opt/coraza/rules.d/ After CRS rules Additional rules, blanket removals

This distinction matters: conditional exclusions using ctl:ruleRemoveById must go in config.d/ so they load before the rules they're suppressing. Paranoia level settings must go in plugins/ so they load after crs-setup.conf (which would otherwise overwrite them) but before the CRS rules evaluate them.

Included Exclusions

This example ships with a config.d/convox-exclusions.conf that suppresses false positives from normal Convox platform behavior:

  • Rule 920350 (Host header is numeric IP) — Kubernetes health checks hit the pod IP directly, so the Host header is always a numeric IP. This is expected behavior, not an attack. The exclusion only applies to requests with a kube-probe user-agent so normal traffic is unaffected.

Adding Your Own Exclusions

Edit waf/custom-rules/application-exclusions.conf to add exclusions specific to your application. The file includes commented-out examples for common patterns.

Important: Use single-line format for SecRule directives. Backslash line continuations can fail silently in Coraza depending on file encoding and trailing whitespace.

Exclude third-party cookies from inspection — CloudFront signed cookies, Google Analytics (_ga), and Cloudflare (_cf*) cookies contain base64, timestamps, and special characters that trigger SQL injection and RCE rules as false positives:

SecRule REQUEST_URI "@rx .*" "id:1000100,phase:1,pass,nolog,ctl:ruleRemoveTargetById=942420;REQUEST_COOKIES,ctl:ruleRemoveTargetById=942421;REQUEST_COOKIES,ctl:ruleRemoveTargetById=932240;REQUEST_COOKIES"

Exclude a rule for a specific URL path — when an endpoint legitimately triggers a rule (e.g., a search endpoint or a socket.io endpoint):

SecRule REQUEST_URI "@beginsWith /api/search" "id:1000101,phase:1,pass,nolog,ctl:ruleRemoveById=942100"
SecRule REQUEST_URI "@beginsWith /socket.io/" "id:1000102,phase:1,pass,nolog,ctl:ruleRemoveById=921180"

Exclude a rule for a specific parameter — when a form field contains content that looks like an attack (e.g., a rich text editor that sends HTML):

SecRule REQUEST_URI "@beginsWith /api/posts" "id:1000103,phase:1,pass,nolog,ctl:ruleRemoveTargetById=941100;ARGS:body,ctl:ruleRemoveTargetById=941160;ARGS:body"

Blanket-remove a rule entirely — as a last resort when a rule causes widespread false positives:

SecRuleRemoveById 920300

Note: if your exclusion uses ctl:ruleRemoveById or ctl:ruleRemoveTargetById, move it to waf/config.d/ instead. Blanket removals with SecRuleRemoveById work from either directory. Always use single-line format — backslash line continuations can fail silently.

WAF Directory Structure

waf/
├── Dockerfile                              # Extends the base Coraza CRS image
├── config.d/
│   └── convox-exclusions.conf              # Platform exclusions (loaded before crs-setup.conf)
├── plugins/
│   └── paranoia-before.conf                # Paranoia level override (loaded after crs-setup.conf)
└── custom-rules/
    └── application-exclusions.conf         # Your app-specific exclusions (loaded after CRS rules)

On startup you'll see confirmation that both directories were loaded:

- User configuration files loaded from /opt/coraza/config.d
- User defined rule sets loaded from /opt/coraza/rules.d

Project Structure

.
├── convox.yml              # Convox deployment configuration
├── waf/
│   ├── Dockerfile          # Extends Coraza CRS with custom rules
│   ├── config.d/           # Rule exclusions (loaded before crs-setup.conf)
│   │   └── convox-exclusions.conf
│   ├── plugins/            # Paranoia level override (loaded after crs-setup.conf)
│   │   └── paranoia-before.conf
│   └── custom-rules/       # Additional rules (loaded after CRS rules)
│       └── application-exclusions.conf
└── backend/
    ├── Dockerfile          # Simple Node.js backend
    └── server.js           # Example application server

Why Caddy Over Nginx

The Coraza CRS Docker project offers both nginx and caddy variants. This example uses caddy-alpine because:

  • Dynamic DNS resolution — Caddy resolves backend addresses on each request. Nginx resolves at startup and crashes if the backend isn't ready yet, which is a common race condition in Kubernetes.
  • Smaller image — Alpine-based, lighter footprint.
  • Same WAF behavior — Both variants use identical Coraza and CRS rule sets.

Scaling

Convox Cloud

convox cloud scale waf --count 3 --cpu 512 --memory 1024 -a coraza-waf -i your-machine-name

Convox Rack

convox scale waf --count 3 --cpu 512 --memory 1024 -a coraza-waf

Scale the WAF and backend services independently based on traffic patterns.

Common Commands

View WAF audit logs

Cloud:

convox cloud logs -a coraza-waf -i your-machine-name | grep "transaction"

Rack:

convox logs -a coraza-waf | grep "transaction"

Access container shell

Cloud:

convox cloud exec waf sh -a coraza-waf -i your-machine-name

Rack:

convox run waf sh -a coraza-waf

Further Reading

About

Coraza WAF — In-cluster web application firewall using OWASP Core Rule Set on Convox

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors