How to block IPs in your Traefik Proxy Server

Published by Okezie on

Script kitties are relentless. Your servers are under constant attack and for me, I find it fun to watch the attack attempts in my logging server with a glass of Malbec at sunset. But really, these are super annoying and they need to be stopped.

Traefik offers a way to provide a list of IPs that are allowed to access an endpoint while blocking all other requests. However, they don’t have a way to block specific IPs from accessing a publicly accessible endpoint.

TL;DRSkip here to see the solution in docker compose

Git repo with related files: >> HERE

Traefik ForwardAuth Middleware

As a workaround for implementing an IP block list in Traefik, we will use the ForwardAuth middleware.

The ForwardAuth middleware delegate the authentication to an external service. If the service response code is 2XX, access is granted and the original request is performed. Otherwise, the response from the authentication server is returned.

Read more here: https://docs.traefik.io/middlewares/forwardauth/
AuthForward

Setup Nginx as our IP blocklist server

Nginx will be used here with an extremely simple configuration. In this case simplicity is key. Because this is a middleware service, the server MUST have the following features:

  • Requests return in 5ms or less (±2ms)
  • Low resource usage
  • Reliable

You can use any webserver you like, but nginx seemed like a solid tried and true choice for this job.

Our docker container will run vanilla nginx with the following two files mounted to the /etc/nginx/conf.d/ directory.

  • blocklist.conf – This file is where the blocked IPs will be entered
  • nginx_default.conf – this is where we’ll define the route that will produce the response we need.
    • Return 2xx “OK” if the IP is not in the list of blocked IPs
    • Return 403 if the IP is in the blocked IP list.

Nginx Config files

These files are mounted to the nginx docker container. Minimal setup.

# blocklist.conf

geo $blocked_ip {
    default        0;

    192.25.0.56    1;
    192.168.1.0/24 1;
    10.1.0.0/16    1;

    ::1            2;
    2001:0db8::/32 1;
}
# nginx_default.conf

server {
    listen 8080;

    location /traefik {
        add_header Content-Type "default_type text/plain";

        if ($blocked_ip) {
            return 403;
        }
        return 200 "OK";
    }
}

Docker Compose – See it in action

version: '3.1'
services:
  todo:
    image: prologic/todo
    restart: always
    labels:
      - "traefik.enable=true"
      - "traefik.http.middlewares.blocklist.forwardauth.address=http://ipfilter:8080/traefik"
      - "traefik.http.routers.todo_app.entrypoints=web"
      - "traefik.http.routers.todo_app.middlewares=blocklist"
      - "traefik.http.routers.todo_app.rule=Host(`localhost`)"

  traefik:
    image: "traefik:v2.2.1"
    container_name: "traefik"
    command:
      # - "--log.level=DEBUG"
      - "--accesslog=true"
      - "--api.insecure=true"
      - "--api.dashboard=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:8080"
      - "--entrypoints.traefik.address=:8888"
    labels:
      traefik.enable: true
      traefik.http.routers.traefik.service: api@internal
      traefik.http.routers.traefik.entrypoints: traefik
    ports:
      - "8080:8080"
      - "8888:8888"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
  
  ipfilter:
    image: nginx
    container_name: ipfilter
    volumes:
      - "./nginx/nginx_default.conf:/etc/nginx/conf.d/default.conf"
      - "./nginx/blocklist.conf:/etc/nginx/conf.d/ip_filter.conf"
    ports:
      - "9999:8080"

Test app here is a simple todo app. You can test out denying yourself access by adding 0.0.0.0.

See below for sample request logs for allowed and blocked requests

# Allowed request - IP 172.26.0.1 is not in the blocklist

ipfilter    | 172.26.0.1 - - [21/Jun/2020:07:42:10 +0000] "GET /traefik HTTP/1.1" 200 2 "http://localhost:8080/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36" "172.26.0.1"
todo_1      | [todo] 2020/06/21 07:42:10 (172.26.0.1) "GET /icons/favicon.ico HTTP/1.1" 200 2227 680.4µs
traefik     | 172.26.0.1 - - [21/Jun/2020:07:42:10 +0000] "GET /icons/favicon.ico HTTP/1.1" 200 2227 "-" "-" 14 "todo_app@docker" "http://172.26.0.2:8000" 2ms
# Blocked request - IP 172.26.0.1 matches an address in the blocked list

ipfilter    | 172.26.0.1 - - [21/Jun/2020:07:45:04 +0000] "GET /traefik HTTP/1.1" 403 556 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36" "172.26.0.1"
traefik     | 172.26.0.1 - - [21/Jun/2020:07:45:04 +0000] "GET / HTTP/1.1" 403 556 "-" "-" 3 "todo_app@docker" "-" 1ms

I hope the traefik devs end up adding this as a native feature, but until then, this would have to do.

Git repo with related files: >> HERE