drmaxnix/banjo

By drmaxnix

Updated 15 days ago

Docker Web Application Firewall (WAF) to protect against Brute Force Attacks

Image
Security

25

Source Code: git.tjdev.de/DrMaxNix/banjo

🪕 Banjo

Docker Web Application Firewall (WAF) to protect against Brute Force Attacks

How It Works

  1. Your server stack consists of these three components:
    • the reverse proxy, which will terminate SSL connections and forwards traffic to the Banjo container
    • the Banjo container, which will either forward traffic to the application container, or deny access
    • the application container, which will detect failed login attempts and log them
  2. To allow Banjo to know when a failed login attempt has occurred, the application container's log must be proxied into the Banjo container.
  3. If Banjo detects a certain pattern in the application log which looks like a failed login attempt, it will increment a counter for this client's IP address
  4. If the counter hits a certain threshold, Banjo will show a 429 Too Many Requests page to this client instead of forwarding calls to the application container
  5. After a certain time, the ban will be lifted and the client's counter reset; calls will now be forwarded to the application again
Note
If your first thought after reading this is, that adding another proxy to the stack will increase latency, you are totally correct! I have measured the RTT of a stack with and without Banjo: Banjo adds about 0.2ms (that's 200µs) of latency. For most use cases, this should be completely fine an not really noticeable.
Caution
As the Banjo container and your application don't know the client's IP address (they only see the address of the container before them), the first link in the chain (eg. your reverse proxy) must forward the client's IP address to following containers using the `X-Forwarded-For` HTTP header. Banjo expects this header to be set; a missing or wrong `X-Forwarded-For` header value will lead to unexpected behavior!

Setup Instructions

1. Set up Logproxy in Application Container

As described above, the application container's log must be proxied into the Banjo container. For this purpose, there is a banjo_logproxy script, which will replace the application container's entrypoint ("init script"). The banjo_logproxy script will then call the real init script (the original entrypoint) and forward all of its output not only to the docker log, but also into a named fifo pipe, which will then be mounted into the Banjo container.

Determine the original entrypoint of your application container (replace exampleorg/exampleimage with your application's image name):

$ docker inspect -f '{{ .Config.Entrypoint }}' exampleorg/exampleimage

You will see something like this, where /usr/bin/entrypoint is the original entrypoint:

[/usr/bin/entrypoint]

Now mount the banjo_logproxy script into your application's container and set it as the new entry point. Make sure to replace /usr/bin/entrypoint with the output from above!

services:
  # ...
  
  app:
    # ...
    volumes:
      # ...
      - ./banjo_logproxy:/banjo_logproxy:ro
      - banjo_logproxy_data:/banjo_logproxy_data
    entrypoint: /banjo_logproxy /usr/bin/entrypoint

volumes:
  banjo_logproxy_data:
Tip
This assumes the `banjo_logproxy` file is placed in the same directory as the `docker-compose.yml` file. Make sure to change this accordingly. The `banjo_logproxy` script can be found in the root of this repo.

2. Set up Banjo

Add the Banjo service to your docker-compose.yml, configure the regex pattern and point it to your application container:

services:
  banjo:
    image: drmaxnix/banjo
    restart: unless-stopped
    environment:
      LOGMATCH_REGEX: '/Login failed: (?<ip>[0-9a-f\.\:]+)/'
      APPLICATION_HOST: "app"
    volumes:
      - ./data/banjo:/opt/banjo-data
      - banjo_logproxy_data:/banjo_logproxy_data
    ports:
      - 127.0.0.1:80:80
  
  # ...

volumes:
  banjo_logproxy_data:
Tip
Above example will store _iplist_ files in the `./data/banjo` directory. These files help Banjo restore its ban-list after a container restart. Feel free to change this to your needs.

3. Configure your reverse proxy

Now point your reverse proxy at port 80 of the Banjo container.

Important
Make sure, you don't point your reverse proxy directly at the application container and check that your application container has no publicly accessible http/https ports (eg. remove `ports` entries from `docker-compose.yml`)

4. Final docker-compose.yml Example

services:
  banjo:
    image: drmaxnix/banjo
    restart: unless-stopped
    environment:
      LOGMATCH_REGEX: '/Login failed: (?<ip>[0-9a-f\.\:]+)/'
      APPLICATION_HOST: "app"
    volumes:
      - ./data/banjo:/opt/banjo-data
      - banjo_logproxy_data:/banjo_logproxy_data
    ports:
      - 127.0.0.1:80:80
  
  app:
    # ...
    volumes:
      # ...
      - ./banjo_logproxy:/banjo_logproxy:ro
      - banjo_logproxy_data:/banjo_logproxy_data
    entrypoint: /banjo_logproxy /usr/bin/entrypoint

volumes:
  banjo_logproxy_data:

Configuration Options

Following configuration options are available by setting environment variables:

OptionTypeDefaultDescription
DEBUGboolfalseWhether debug logging should be enabled (not recommended for production usage)
LOGMATCH_ENABLEDbooltrueWhether the logmatch module should be enabled
LOGMATCH_FILEstring/banjo_logproxy_data/logPath to named pipe where log can be streamed from
LOGMATCH_REGEX🔸regexRegex pattern to use for finding failed login attempts in the log; the IP address to be banned must be matched either as the first capturing group, or a group named ip
LOGMATCH_REGEX_*regexemptyAdditional patterns (eg. LOGMATCH_REGEX_FOO)
LOGMATCH_FINDTIMEduration12hInterval hits are counted within
LOGMATCH_THRESHOLDint10Hit count threshold after which address is being banned
LOGMATCH_BANTIMEduration6hDuration of a ban
APPLICATION_HOST🔸host/ipHostname or ip address of application to forward requests to
APPLICATION_PORTint80Port number of application to forward requests to

Options marked with 🔸 are required

Explanation of types:

TypeDescriptionExample(s)
boola boolean value, either true or falsetrue
stringone or more character(s)TestTest 123 %$&/(&%$)
regexstring of the form /pattern/, where pattern is a valid regex pattern/[a-z0-9]+/
durationeither a number of seconds, or a number followed by a unit, which can be one of [smhd] for seconds, minutes, hours and days respectively15m = 15 minutes = 900 seconds
inta number contained in ℤ1337
host/ipa hostname, domain name, or IPv4/IPv6 addressapp
app.example.com
198.51.100.49
2001:db8::a17d:661e

Additional value restrictions may be applied on a semantic level (eg. port number must be in range 1-65535)

Docker Pull Command

docker pull drmaxnix/banjo