chaitin-waf
The chaitin-waf plugin sends request information to the SafeLine detection service before proxying traffic upstream. SafeLine identifies web threats such as SQL injection, cross-site scripting, command injection, and path traversal.
The plugin can block detected threats or monitor them without interrupting traffic. A route-level mode overrides the mode configured in plugin metadata.
Response Headers
When response headers are enabled, the plugin can add the following fields:
| Header | Description |
|---|---|
X-APISIX-CHAITIN-WAF | Processing result.yes: SafeLine checked the request.no: matching rules skipped the check.off: the plugin mode disabled checks.unhealthy: no detector was available.err: plugin processing failed.waf-err: detector communication failed.timeout: the detector request timed out. |
X-APISIX-CHAITIN-WAF-TIME | SafeLine request round-trip time, in milliseconds. |
X-APISIX-CHAITIN-WAF-STATUS | Status reported by the SafeLine detector. |
X-APISIX-CHAITIN-WAF-ACTION | SafeLine decision: pass or reject. |
X-APISIX-CHAITIN-WAF-ERROR | Debug header containing an error message. |
X-APISIX-CHAITIN-WAF-SERVER | Debug header identifying the selected detector. |
append_waf_resp_header controls the standard headers. append_waf_debug_header additionally exposes the error and server headers and should normally be limited to troubleshooting.
Report Responses to WAF
Response reporting was introduced in API7 Enterprise 3.9.20 and 3.10.7, and in APISIX 3.19.0. It lets the WAF service observe the response status, headers, and a bounded body sample after the gateway serves the response. It does not block or modify that response.
Enable reporting in the plugin's config object on a route, or in the metadata config object to set the default for all routes:
{
"log_resp": true,
"resp_body_size": 4
}
The example keeps the default body limit of 4 KiB; reporting is disabled by default. Set the body limit to 0 to report only status and headers. Response content types in the built-in ignored list or extra_ignored_content_types are not reported at all.
The report is sent asynchronously, but collecting the sample still uses gateway memory during the response. Account for concurrent responses when increasing the limit, avoid sending sensitive response data unnecessarily, and monitor response-reporting errors in the gateway log.
Examples
The following examples configure SafeLine and use the plugin to block or monitor malicious requests.
Set Up SafeLine
The examples use a SafeLine detector that the gateway reaches through the T1K protocol on TCP port 8000. Install SafeLine by following the official deployment guide.
In the SafeLine installation directory, update the existing detector configuration with these settings:
bind_addr: 0.0.0.0
listen_port: 8000
Restart the detector after saving the configuration:
docker restart safeline-detector
For a gateway running in Docker on the same host, connect it to SafeLine's installation network and use the detector container name:
export GATEWAY_CONTAINER=replace-with-gateway-container-name
docker network connect safeline-ce "$GATEWAY_CONTAINER"
export WAF_HOST=safeline-detector
For other deployments, expose TCP port 8000 only to the gateway network and set WAF_HOST to a reachable DNS name or IP address:
export WAF_HOST=replace-with-safeline-detector-host
Configure the Detector Address
The detector node list is plugin metadata shared by all chaitin-waf instances.
- Admin API
- ADC
- Ingress Controller
curl "http://127.0.0.1:9180/apisix/admin/plugin_metadata/chaitin-waf" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d @- <<EOF
{
"nodes": [
{
"host": "$WAF_HOST",
"port": 8000
}
]
}
EOF
Plugin metadata is a global collection and cannot be isolated with a label selector. Export the complete collection before changing this entry:
adc dump -o adc-metadata.yaml --with-id \
--include-resource-type plugin_metadata
Add or update the chaitin-waf entry while preserving every other entry in adc-metadata.yaml:
plugin_metadata:
# Keep all other plugin metadata entries from the exported file.
chaitin-waf:
nodes:
- host: "${WAF_HOST}"
port: 8000
Preview the complete metadata change and confirm that it contains no unintended updates or deletions:
adc diff -f adc-metadata.yaml \
--include-resource-type plugin_metadata
Synchronize the reviewed plugin metadata collection:
adc sync -f adc-metadata.yaml \
--include-resource-type plugin_metadata
Add the following entry under spec.pluginMetadata in the complete GatewayProxy manifest used by the deployment:
chaitin-waf:
nodes:
- host: replace-with-safeline-detector-host
port: 8000
Apply the updated complete manifest through the deployment's normal Kubernetes or GitOps workflow.
When neither plugin metadata nor a route sets mode, current gateway releases use monitor.
With the detector address configured, the following examples block threats on one route, monitor threats without blocking, and override a metadata-level mode on an individual route.
Block Malicious Requests
The following example demonstrates how to configure a route that blocks threats detected by SafeLine.
Configure the route:
- Admin API
- ADC
- Ingress Controller
curl "http://127.0.0.1:9180/apisix/admin/routes/chaitin-waf-block" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"uri": "/anything/*",
"plugins": {
"chaitin-waf": {
"mode": "block",
"append_waf_resp_header": true,
"append_waf_debug_header": true
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
services:
- name: chaitin-waf-block
labels:
docs-example: chaitin-waf-block
routes:
- name: chaitin-waf-block
uris:
- /anything/*
plugins:
chaitin-waf:
mode: block
append_waf_resp_header: true
append_waf_debug_header: true
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
Preview changes owned by this example and confirm that the diff contains no unintended updates or deletions:
adc diff -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=chaitin-waf-block
Synchronize the reviewed service configuration:
adc sync -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=chaitin-waf-block
- Gateway API
- APISIX CRD
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
ports:
- name: http
port: 80
targetPort: 80
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: chaitin-waf-block
spec:
plugins:
- name: chaitin-waf
config:
mode: block
append_waf_resp_header: true
append_waf_debug_header: true
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: chaitin-waf-block
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: PathPrefix
value: /anything
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: chaitin-waf-block
backendRefs:
- name: httpbin-external-domain
port: 80
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: chaitin-waf-block
spec:
ingressClassName: apisix
http:
- name: chaitin-waf-block
match:
paths:
- /anything/*
upstreams:
- name: httpbin-external-domain
plugins:
- name: chaitin-waf
enable: true
config:
mode: block
append_waf_resp_header: true
append_waf_debug_header: true
Apply the configuration:
kubectl apply -f chaitin-waf-ic.yaml
mode: block rejects detected threats. The response headers expose the SafeLine decision and, for this diagnostic example, the selected detector.
Send a normal request:
curl -i "http://127.0.0.1:9080/anything/get"
The response should be 200 OK and include X-APISIX-CHAITIN-WAF-ACTION: pass.
Send a request containing SQL injection:
curl -i "http://127.0.0.1:9080/anything/get?id=1%20union%20select%201,2,3"
The response should contain fields similar to the following:
HTTP/1.1 403 Forbidden
X-APISIX-CHAITIN-WAF-ACTION: reject
X-APISIX-CHAITIN-WAF-STATUS: 403
X-APISIX-CHAITIN-WAF: yes
{"code": 403, "success":false, "message": "blocked by Chaitin SafeLine Web Application Firewall", "event_id": "..."}
Monitor Threats without Blocking
The following example demonstrates how to monitor detected threats without interrupting traffic.
Set the shared metadata mode to monitor while retaining the detector nodes configured earlier:
- Admin API
- ADC
- Ingress Controller
curl "http://127.0.0.1:9180/apisix/admin/plugin_metadata/chaitin-waf" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d @- <<EOF
{
"nodes": [
{
"host": "$WAF_HOST",
"port": 8000
}
],
"mode": "monitor"
}
EOF
In the complete adc-metadata.yaml exported earlier, add mode: monitor to the existing entry:
plugin_metadata:
# Keep all other plugin metadata entries from the exported file.
chaitin-waf:
nodes:
- host: "${WAF_HOST}"
port: 8000
mode: monitor
Preview the complete metadata change and confirm that it contains no unintended updates or deletions:
adc diff -f adc-metadata.yaml \
--include-resource-type plugin_metadata
Synchronize the reviewed plugin metadata collection:
adc sync -f adc-metadata.yaml \
--include-resource-type plugin_metadata
Add mode: monitor to the chaitin-waf entry under spec.pluginMetadata in the complete GatewayProxy manifest:
chaitin-waf:
nodes:
- host: replace-with-safeline-detector-host
port: 8000
mode: monitor
Apply the updated complete manifest through the deployment's normal Kubernetes or GitOps workflow.
Create a route that inherits the metadata mode:
- Admin API
- ADC
- Ingress Controller
curl "http://127.0.0.1:9180/apisix/admin/routes/chaitin-waf-monitor" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"uri": "/monitor/*",
"plugins": {
"chaitin-waf": {
"append_waf_resp_header": true,
"append_waf_debug_header": true
},
"proxy-rewrite": {
"regex_uri": ["^/monitor/(.*)", "/anything/$1"]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
services:
- name: chaitin-waf-monitor
labels:
docs-example: chaitin-waf-monitor
routes:
- name: chaitin-waf-monitor
uris:
- /monitor/*
plugins:
chaitin-waf:
append_waf_resp_header: true
append_waf_debug_header: true
proxy-rewrite:
regex_uri:
- ^/monitor/(.*)
- /anything/$1
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
Preview changes owned by this example and confirm that the diff contains no unintended updates or deletions:
adc diff -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=chaitin-waf-monitor
Synchronize the reviewed service configuration:
adc sync -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=chaitin-waf-monitor
Reuse the httpbin-external-domain upstream from the previous example and create a route whose chaitin-waf configuration omits mode:
- Gateway API
- APISIX CRD
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: chaitin-waf-monitor
spec:
plugins:
- name: chaitin-waf
config:
append_waf_resp_header: true
append_waf_debug_header: true
- name: proxy-rewrite
config:
regex_uri:
- ^/monitor/(.*)
- /anything/$1
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: chaitin-waf-monitor
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: PathPrefix
value: /monitor
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: chaitin-waf-monitor
backendRefs:
- name: httpbin-external-domain
port: 80
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: chaitin-waf-monitor
spec:
ingressClassName: apisix
http:
- name: chaitin-waf-monitor
match:
paths:
- /monitor/*
upstreams:
- name: httpbin-external-domain
plugins:
- name: chaitin-waf
enable: true
config:
append_waf_resp_header: true
append_waf_debug_header: true
- name: proxy-rewrite
enable: true
config:
regex_uri:
- ^/monitor/(.*)
- /anything/$1
Apply the route configuration:
kubectl apply -f chaitin-waf-monitor-ic.yaml
Send the SQL injection request through the monitoring route:
curl -i "http://127.0.0.1:9080/monitor/get?id=1%20union%20select%201,2,3"
The request should reach the upstream and return 200 OK. The response headers should still include X-APISIX-CHAITIN-WAF-ACTION: reject and X-APISIX-CHAITIN-WAF-STATUS: 403, while the gateway error log records chaitin-waf monitor mode: request would have been rejected.
Override Monitor Mode on a Route
The following example demonstrates how route-level configuration overrides the shared monitoring mode for one route.
Set mode: block on the monitoring route to override the metadata-level mode:
- Admin API
- ADC
- Ingress Controller
curl "http://127.0.0.1:9180/apisix/admin/routes/chaitin-waf-monitor" -X PATCH \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"plugins": {
"chaitin-waf": {
"mode": "block",
"append_waf_resp_header": true,
"append_waf_debug_header": true
}
}
}'
Add mode: block to the route's plugin configuration:
services:
- name: chaitin-waf-monitor
labels:
docs-example: chaitin-waf-monitor
routes:
- name: chaitin-waf-monitor
uris:
- /monitor/*
plugins:
chaitin-waf:
mode: block
append_waf_resp_header: true
append_waf_debug_header: true
proxy-rewrite:
regex_uri:
- ^/monitor/(.*)
- /anything/$1
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
Preview the update:
adc diff -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=chaitin-waf-monitor
Synchronize the reviewed service configuration:
adc sync -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=chaitin-waf-monitor
Update the route resource for the API in use:
- Gateway API
- APISIX CRD
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: chaitin-waf-monitor
spec:
plugins:
- name: chaitin-waf
config:
mode: block
append_waf_resp_header: true
append_waf_debug_header: true
- name: proxy-rewrite
config:
regex_uri:
- ^/monitor/(.*)
- /anything/$1
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: chaitin-waf-monitor
spec:
ingressClassName: apisix
http:
- name: chaitin-waf-monitor
match:
paths:
- /monitor/*
upstreams:
- name: httpbin-external-domain
plugins:
- name: chaitin-waf
enable: true
config:
mode: block
append_waf_resp_header: true
append_waf_debug_header: true
- name: proxy-rewrite
enable: true
config:
regex_uri:
- ^/monitor/(.*)
- /anything/$1
Apply the complete updated resource:
kubectl apply -f chaitin-waf-monitor-ic.yaml
Repeat the SQL injection request. The route should now return 403 Forbidden, confirming that route configuration takes precedence over plugin metadata.