ModSecurity with ingress-nginx

Making use of ModSecurity WAF in the ingress-nginx

Tags: Kubernetes, Nginx, Security, WAF, posted 2021-07-30 by Thomas Kooi

This post is about enabling the ModSecurity feature for ingress-nginx in practice.

ModSecurity is an open source web application firewall (WAF). It can help you provide an additional layer of security in front of your application. I will leave the the what and how on usnig a WAF for others to talk about (there quite a few good blog posts available on the web on this topic), and this post will soley focus on enabling the functionality in ingress-nginx.

Configuring ingress-nginx

Ingress-nginx uses a ConfigMap for configuration. Depending on your install, you either must modify this configmap, or tweak it in your Helm values file (for example).

To enable it, you have to provide the following settings:

controller:
  config:
    # Enables ModSecurity functionality
    enable-modsecurity: 'true'
    # Enables loading the core rule set (optional, can be enabled on specific ingresses only instead)
    enable-owasp-modsecurity-crs: 'true'

After this, ModSecurity is enabled, but not yet functional for your ingress resources. For this, you must also apply an annotation on your ingress:

nginx.ingress.kubernetes.io/modsecurity-snippet: |
  SecRuleEngine On

Additionally, you can also simply enable ModSecurity for just your ingress by using the following annotations:

nginx.ingress.kubernetes.io/enable-modsecurity: "true"
nginx.ingress.kubernetes.io/enable-owasp-core-rules: "true"
nginx.ingress.kubernetes.io/modsecurity-transaction-id: "$request_id"
nginx.ingress.kubernetes.io/modsecurity-snippet: |
  SecRuleEngine On

A fully example would look like this:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    # New annotation
    cert-manager.io/cluster-issuer: 'letsencrypt-prod'
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    
    # Configure ModSecurity
    nginx.ingress.kubernetes.io/enable-modsecurity: "true"
    nginx.ingress.kubernetes.io/enable-owasp-core-rules: "true"
    nginx.ingress.kubernetes.io/modsecurity-transaction-id: "$request_id"
    nginx.ingress.kubernetes.io/modsecurity-snippet: |
      SecRuleEngine On

  name: example
  namespace: example-service
spec:
  rules:
  - host: example.containerinfra.com
    http:
      paths:
      - backend:
          serviceName: example
          servicePort: http
        path: /
  tls:
  - hosts:
    - example.containerinfra.com
    secretName: example-containerinfra-com-tls

I’d recommend first running ModSecurity on a staging or test environment, and running your test sets against this. You may need to disable some rules for your application to work properly with ModSecurity enabled.

When a request is blocked, you will see a log event detailing which rule was triggered:

[error] 574#574: *378 [client 0.0.0.0] ModSecurity: Access denied with code 403 (phase 2). Matched "Operator `Ge' with parameter `5' against variable `TX:ANOMALY_SCORE' (Value: `30' ) [file "/etc/nginx/owasp-modsecurity-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "80"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 30)"] [data ""] [severity "2"] [ver "OWASP_CRS/3.3.0"] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "10.233.96.247"] [uri "/"] [unique_id "162773426746.083102"] [ref ""], client: 0.0.0.0, server: example.containerinfra.com, request: "GET /?path=..%2F..%2Fdrop%20table;&orgId=3 HTTP/2.0", host: "example.containerinfra.com"

Note the ID 949110 in the log message. You can disable this rule by using the modsecurity-snippet annotation:

nginx.ingress.kubernetes.io/modsecurity-snippet: |
  SecRuleEngine On
  SecRuleRemoveById 949110

Running it in a real environment

Unless you have very good test coverage, changes are you have missed some rule that should have been disabled for some edge cases to work for your application.

In order to be safe, you can disable the enable-owasp-core-rules setting and run ModSecurity in detection mode for a while.

Also, by default ingress-nginx and ModSecurity log audit events in /var/log/audit. You will find each request that triggered a rule present there. This may end up eating disk space, and while useful for debugging purpose, you may wish to disable this.

You can configure the following setting in your configMap:

    modsecurity-snippet: |
      SecAuditEngine Off

Memory Usage

Since ModSecurity must evaluate all requests, ingress-nginx obviously needs more memory. Depending on your traffic, this may be significantly more. So before rolling this out, prepare to increase your memory limits.