Skip to content

Repository files navigation

coraza-envoy-go-filter

  • Coraza Web Application Firewall implemented as Envoy Go Filter.

Getting started

Running the docker image

Pull and start the image:

docker pull ghcr.io/united-security-providers/envoy-coraza:v2.0.0
docker run -p 8080:10000 ghcr.io/united-security-providers/envoy-coraza:v2.0.0

First visit http://localhost:8080 and then http://localhost:8080/alert('xss').

Note

The second request should be blocked and you should see WAF rule triggered: Javascript method detected in the container logs.

Configuration

Option Type Required Default Description
directives YAML map Yes - Defines the WAF configurations available to the filter, e.g. one with CRS fully enforced and one with the engine off. It is a map where each key is a WAF name and each value is an object with a simple_directives list of SecLang directive strings.
default_directive string Yes - The fallback WAF to use when no host mapping matches. Must be a key defined in directives.
host_directive_map YAML map No {} Defines how requests are mapped to WAFs. See matching behaviour below.
log_format string No text Filter log format. Valid values: text, json.
use_re2 boolean No true Use the RE2 regex engine. Only has effect in the performance build.
use_libinjection boolean No true Use libinjection for SQL injection and XSS detection. Only has effect in the performance build.

Example:

plugin_config:
  "@type": type.googleapis.com/xds.type.v3.TypedStruct
  value:
    directives:
      waf1:
        simple_directives:
          - "Include @coraza-setup"
          - "Include @crs-setup"
          - "SecDefaultAction \"phase:3,log,auditlog,pass\""
          - "SecDefaultAction \"phase:4,log,auditlog,pass\""
          - "SecDefaultAction \"phase:5,log,auditlog,pass\""
          - "SecDebugLogLevel 3"
          - "Include @owasp_crs/*.conf"
      off:
        simple_directives:
          - "SecRuleEngine Off"
    default_directive: "waf1"
    host_directive_map:
      "foo.example.com": "waf1"
      "bar.example.com": "off"
    log_format: "text"

For a complete example Envoy configuration please refer to envoy.yaml.

host_directive_map lookup

For each request the filter resolves the directive set as follows:

  1. Exact match: the Host header as received (e.g. foo.example.com:8443) is looked up directly.
  2. Hostname-only match: if the Host header contains a port and no exact match was found, the port is stripped and the lookup is retried (e.g. foo.example.com).
  3. Default: if neither lookup matched, default_directive is used.

To match traffic arriving on a specific port only, include the port in the host map key (e.g. "foo.example.com:80": "waf1"). A key without a port (e.g. "foo.example.com") matches any port not covered by a more specific entry.

Using CRS

The Core Rule Set comes embedded in the extension.

Additionally to the rules, configuration files for setting up the rule engine and coraza are embedded as well. To include embedded rules and config files, the @ sign is used when referencing a path.

Example loading entire coreruleset:

plugin_config:
  "@type": type.googleapis.com/xds.type.v3.TypedStruct
  value:
    directives:
      waf1:
        simple_directives:
          - "Include @coraza-setup"
          - "SecDebugLogLevel 9"
          - "SecRuleEngine On"
          - "Include @crs-setup"
          - "Include @owasp_crs/*.conf"
    default_directive: "waf1"

Loading some pieces of the ruleset:

plugin_config:
  "@type": type.googleapis.com/xds.type.v3.TypedStruct
  value:
    directives:
      waf1:
        simple_directives:
          - "Include @coraza-setup"
          - "SecDebugLogLevel 9"
          - "SecRuleEngine On"
          - "Include @crs-setup"
          - "Include @owasp_crs/REQUEST-901-INITIALIZATION.conf"
    default_directive: "waf1"

Recommendations using CRS with Envoy Go

  • In order to mitigate as much as possible malicious requests (or connections open) sent upstream, it is recommended to keep the CRS Early Blocking feature enabled (SecAction 900120).

FTW configuration files

If you want to run the ftw test suite (for example in your ci environment), the configuration files are included in the shared object as well:

Using custom rules or load a different CRS version

Additionally to the compiled in CRS, filter supports loading rules from filesystem. This can be useful to load custom rules, blocklists or another CRS version.

Custom Rule example

For example to load a file myrule.conf, we can first mount it into the container

docker run -v ./envoy.yaml:/etc/envoy/envoy.yaml -v ./myrule.conf:/etc/envoy/myrule.conf ghcr.io/united-security-providers/envoy-coraza:v2.0.0

If you run envoy directly this is of course not needed, simply put it somewhere on the filesystem

And in the envoy config we can include the file:

[...]
plugin_config:
  "@type": type.googleapis.com/xds.type.v3.TypedStruct
  value:
    directives:
      waf1:
        simple_directives:
          - "Include @coraza-setup"
          - "SecDebugLogLevel 9"
          - "SecRuleEngine On"
          - "Include @crs-setup"
          - "Include @owasp_crs/*.conf"
          - "Include /etc/envoy/myrule.conf"
    default_directive: "waf1"
[...]

Note the missing @, it means "try to load from filesystem"

Blocklist example

The following example shows how to mount and use blocklist.txt:

docker run  -v ./envoy.yaml:/etc/envoy/envoy.yaml -v ./blocklist.txt:/etc/envoy/blocklist.txt ghcr.io/united-security-providers/envoy-coraza:v2.0.0

And in the envoy config add the rule:

[...]
plugin_config:
  "@type": type.googleapis.com/xds.type.v3.TypedStruct
  value:
    directives:
      waf1:
        simple_directives:
          - "Include @coraza-setup"
          - "SecDebugLogLevel 9"
          - "SecRuleEngine On"
          - "Include @crs-setup"
          - "Include @owasp_crs/*.conf"
          - "SecRule REMOTE_ADDR \"@ipMatchFromFile /etc/envoy/blocklist.txt\" \"id:200003,phase:1,deny,status:403,msg:'IP Blocked by Blocklist'\""
    default_directive: "waf1"
[...]

Loading another CRS version example

Example loading CRS 4.22 (assuming you have the rules locally):

docker run  -v ./envoy.yaml:/etc/envoy/envoy.yaml -v ./coreruleset-4.22:/etc/envoy/crs-4.22  ghcr.io/united-security-providers/envoy-coraza:v2.0.0

And in the envoy config load the ruleset:

[...]
plugin_config:
  "@type": type.googleapis.com/xds.type.v3.TypedStruct
  value:
    directives:
      waf1:
        simple_directives:
          - "Include @coraza-setup"
          - "SecDebugLogLevel 9"
          - "SecRuleEngine On"
          - "Include /etc/envoy/crs-4.22/crs-setup.conf.example"
          - "Include /etc/envoy/crs-4.22/*.conf"
    default_directive: "waf1"
[...]

Log format

By default the filter writes plain text logs.

The log format can be changed to json using the log_format configuration option:

plugin_config:
  "@type": type.googleapis.com/xds.type.v3.TypedStruct
  value:
    log_format: "json"
    directives:
      waf1:
        simple_directives:
          [ ... ....  ]
    default_directive: "waf1"

Note that this setting does not automatically set the AuditLog Engine to JSON

If an audit log in json is desired, it must be configured with SecLang. For example:

plugin_config:
  "@type": type.googleapis.com/xds.type.v3.TypedStruct
  value:
    log_format: "json"
    directives:
      waf1:
        simple_directives:
          # ... other directives ...
          - "SecAuditLog /etc/envoy/logs/audit.log"
          - "SecAuditLogParts ABCFHKZ"
          - "SecAuditEngine RelevantOnly"
          - "SecAuditLogRelevantStatus ^(?:5|4)"
          - "SecAuditLogFormat JSON"
    default_directive: "waf1"

Using with EnvoyGateway

  1. Enable EnvoyPatchPolicy
apiVersion: v1
kind: ConfigMap
metadata:
  name: envoy-gateway-config
  namespace: envoy-gateway-system
data:
  envoy-gateway.yaml: |
    apiVersion: gateway.envoyproxy.io/v1alpha1
    kind: EnvoyGateway
    provider:
      type: Kubernetes
    gateway:
      controllerName: gateway.envoyproxy.io/gatewayclass-controller
    extensionApis:
      enableEnvoyPatchPolicy: true
  1. Update the EnvoyProxy to use the united-security-providers/envoy-coraza image:
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyProxy
metadata:
  name: eg
  namespace: envoy-gateway-system
spec:
  provider:
    type: Kubernetes
    kubernetes:
      envoyDeployment:
        container:
          image: ghcr.io/united-security-providers/envoy-coraza:v2.0.0
  1. Enable the plugin with an EnvoyPatchPolicy:

Note: The correct path in the JSONPatch depends on the listener protocol. Envoy Gateway uses default_filter_chain for HTTP listeners and filter_chains/0 for HTTPS listeners (which use SNI-based filter chain matching). Using the wrong path will cause the patch to fail to apply.

For an HTTPS listener:

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyPatchPolicy
metadata:
  name: coraza-patch-policy
  namespace: envoy-gateway-system
spec:
  targetRef:
    group: gateway.networking.k8s.io
    kind: Gateway
    name: eg-internal
  type: JSONPatch
  jsonPatches:
  - type: "type.googleapis.com/envoy.config.listener.v3.Listener"
    ## The name is in the format <namespace>/<gateway>/<listener> as per the XDS Name Scheme V2 - https://gateway.envoyproxy.io/docs/tasks/extensibility/envoy-patch-policy/#xds-name-scheme-v2
    name: envoy-gateway-system/eg/https
    operation:
      op: add
      ## Needs to be added as the first item in the 'http_filters' array
      ## HTTPS listeners use filter_chains/0; HTTP listeners use default_filter_chain
      path: "/filter_chains/0/filters/0/typed_config/http_filters/0"
      value:
        name: envoy.filters.http.golang
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config
          library_id: coraza-waf
          library_path: /etc/envoy/coraza-waf.so
          plugin_name: coraza-waf
          plugin_config:
            "@type": type.googleapis.com/xds.type.v3.TypedStruct
            value:
              ## setting the logs to json format, so they are the same as the default EnvoyGateway Access Logs
              log_format: "json"
              ## configure coraza/CRS
              directives:
                default:
                  simple_directives:
                    - "Include @coraza-setup"
                    - "SecDebugLogLevel 9"
                    - "SecRuleEngine On"
                    - "Include @crs-setup"
                    - "Include @owasp_crs/*.conf"
                off:
                  simple_directives:
                    - "SecRuleEngine Off"
              default_directive: "default"
              host_directive_map:
                "foo.example.com": "off"
                "bar.example.com": "default"

For an HTTP listener, replace the name and path fields accordingly:

    name: envoy-gateway-system/eg/http
    operation:
      op: add
      path: "/default_filter_chain/filters/0/typed_config/http_filters/0"

Compilation

See Makefile for all targets.

Building the filter

make build
# or
make performanceBuild

You will find the go waf plugin under ./build/coraza-waf.so.

Performance

There is a known performance issue with larger request bodies in Coraza. To help mitigate this, a new build target named performanceBuild has been introduced. This target compiles the filter with support for both re2 and libinjection to improve throughput. The only downside is that this build introduces runtime dependencies on re2 and libinjection.

You can enable this behavior through the configuration. For example:

  ...

  filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          stat_prefix: ingress_http
          http_filters:
            - name: envoy.filters.http.golang
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config
                library_id: coraza-waf
                library_path: /etc/envoy/coraza-waf.so
                plugin_name: coraza-waf
                plugin_config:
                  "@type": type.googleapis.com/xds.type.v3.TypedStruct
                  value:
                    use_re2: true
                    use_libinjection: true

Note

Setting these configuration options in the normal build will have no effect on coraza.

Testing

Running go-ftw (CRS Regression tests)

The following command runs the go-ftw test suite against the filter with the CRS fully loaded.

make ftw

Take a look at the config files ftw.yml and overrides.yml for details about tests currently excluded and overridden.

One can also run a single test by executing:

FTW_INCLUDE=920410 make ftw

Run the tests and abort on the first test that fails:

FTW_FAILFAST=1 make ftw

Running e2e tests

The following command runs a small set of end to end tests against the filter with the CRS fully loaded.

make e2e

About

Coraza WAF implementation as golang filter for Envoy proxy

Resources

Contributing

Security policy

Stars

37 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages