Web Application Firewall

Met een Web Application Firewall (WAF) kun je een applicatie beschermen tegen bekende aanvallen op basis van een voorgedefinieerde set aan regels. In deze sectie laten we zien hoe je een WAF kan opzetten op basis van de owasp/modsecurity Docker image. Deze image maakt gebruik van NGINX en ModSecurity. We voegen de WAF toe als sidecar aan een bestaande deployment.

Configuratie

We starten met het aanmaken van een ConfigMap die zowel de configuratie van NGINX als de regels voor ModSecurity bevat.

waf-configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: waf
  namespace: default
data:
  nginx.conf: |
    load_module modules/ngx_http_modsecurity_module.so;

    worker_processes 1;
    pid /tmp/nginx.pid;
    daemon off;

    events {
      worker_connections 1024;
    }

    http {
      include /etc/nginx/mime.types;
      default_type application/octet-stream;
      keepalive_timeout 60s;
      sendfile on;

      client_body_temp_path "/tmp/client_body" 1 2;
      proxy_temp_path "/tmp/proxy" 1 2;
      fastcgi_temp_path "/tmp/fastcgi" 1 2;
      scgi_temp_path "/tmp/scgi" 1 2;
      uwsgi_temp_path "/tmp/uwsgi" 1 2;

      map $http_upgrade $connection_upgrade {
        default upgrade;
        '' close;
      }

      server {
        listen 8000;
        server_name _;
        set $upstream http://127.0.0.1:80;

        modsecurity on;
        modsecurity_rules_file /config/mod_security.conf;

        location / {
          # only show mod_security generated errors to stdout
          access_log  off;
          error_log on;

          client_max_body_size 0;

          proxy_set_header Host $host;
          proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
          proxy_set_header Proxy "";
          proxy_set_header Upgrade $connection_upgrade;
          proxy_set_header Connection $connection_upgrade;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header X-Forwarded-Port $server_port;
          proxy_set_header X-Forwarded-Proto $scheme;

          proxy_http_version 1.1;
          proxy_buffering off;
          proxy_connect_timeout 60s;
          proxy_read_timeout 360s;
          proxy_redirect off;

          proxy_pass_header Authorization;
          proxy_pass $upstream;

          index index.html index.htm;
          root /usr/share/nginx/html;
        }

        location /healthz {
          access_log off;
          add_header Content-Type text/plain;
          return 200 "OK";
        }

        error_page 500 502 503 504  /50x.html;
        location = /50x.html {
          root /usr/share/nginx/html;
        }
      }
    }
  mod_security.conf: |
    SecRuleEngine On
    SecStatusEngine On
    SecRequestBodyAccess On
    SecDefaultAction "phase:1,log,auditlog,deny,status:403"
    SecDefaultAction "phase:2,log,auditlog,deny,status:403"
    # Log the transactions that are marked by a rule, as well as those that
    # level response status codes).
    #
    #SecAuditEngine RelevantOnly
    #SecAuditLogRelevantStatus "^(?:5|4(?!04))"
    # Log everything we know about a transaction.
    #SecAuditLogParts ABIJDEFHZ
    # Use a single file for logging. This is much easier to look at, but
    # assumes that you will use the audit log only ocassionally.
    #
    #SecAuditLogType Serial
    #SecAuditLog /tmp/modsec_audit.log
    #SecDebugLog /tmp/secdebug
    #SecDebugLogLevel 1

    # Add custom rules below
    SecRule REQUEST_URI "@beginsWith /admin" "phase:2,t:lowercase,id:2222,deny,msg:'block admin'"
    SecRule ARGS:testparam "@contains test" "id:1234,deny,status:403"
    # end custom rules

Pas in de configuratie de $upstream aan en verwijs naar de poort waarop de te beschermen service draait, bijvoorbeeld poort 80.

Maak vervolgens de configuratie aan met:

$ kubectl apply -f waf-configmap.yaml

Sidecar toevoegen

Voeg vervolgens de sidecar toe aan een bestaande deployment:

$ kubectl edit deployment hallo-wereld

Voeg onder de sectie containers waf-proxy toe:

      ...
      containers:
        - name: hallo-wereld
          image: registry.gitlab.com/commonground/haven/hallo-wereld:1.0
          ...
        - name: waf-proxy
          image: owasp/modsecurity:3
          command:
            - nginx
          args:
            - -c
            - /config/nginx.conf
          ports:
            - containerPort: 8000
              protocol: TCP
          resources:
            limits:
              cpu: '1'
              memory: 256Mi
            requests:
              cpu: 50m
              memory: 128Mi
          volumeMounts:
            - mountPath: /config
              name: waf-config
...

Voeg onder de sectie volumes toe met de ConfigMap die we hierboven hebben aangemaakt:

...
    spec:
      containers:
        ...
      volumes:
        - configMap:
            defaultMode: 422
            name: waf
          name: waf-config
...

Verwijder als laatste de ports: sectie van de applicatie die je wil beschermen. Hierdoor is de applicatie niet meer direct te bereiken in het cluster, maar alleen nog maar via de WAF.

Sla de wijzigingen van de deployment op. De deployment wordt nu vanzelf opnieuw opgestart met de sidecar.

De service aanpassen

Pas vervolgens de service aan zodat targetPort verwijst naar poort 8000, de standaard poort van de sidecar:

$ kubectl edit service hallo-wereld
apiVersion: v1
kind: Service
metadata:
  name: hallo-wereld
  namespace: default
spec:
  selector:
    app: hallo-wereld
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8000

Sla de wijziging op.

De service bevragen

Nu kunnen we de service bevragen om te kijken of de WAF werkt. We zetten met kubectl een port forward op:

kubectl port-forward services/hallo-wereld 8080:80

Nu wordt poort 8080 op je lokale machine doorgestuurd naar poort 80 van de service in het cluster. Ga met de browser naar http://localhost:8080/ om de applicatie te bekijken. De applicatie is normaal beschikbaar, maar de paden http://localhost:8080/admin en http://localhost:8080?testparam=test worden geblokkeerd door de WAF.

Core Rule Set (CRS) configureren

We hebben hierboven gebruik gemaakt van twee testregels om te laten zien hoe het configureren van een WAF werkt. Voor een productie-systeem vormt de Core Rule Set van OWASP een mooie basis. Raadpleeg hiervoor ook het owasp/modsecurity-crs Docker image.