Block incoming connections based on quantity of http errors
449
This is an simple nginx add-on that block connections based on http status. I know that exists a lot of tools like this but I made for my own to be lightier and also, to be as an "first shield" for applications running behind Nginx. The configuration should be simple and mainly you should be using Nginx on a docker container, but also works for a common Nginx instalation. Ps: I could do an Nginx with httpE2Ban running inside, but who is going to trust something like this? lol.
I've tested on these configurations.
| Docker version | Operational system | Test result |
|---|---|---|
| 27.1.2 | Fedora 39 | :white_check_mark: |
You have to set an configuration policy, that says how much http code an client can have over time. For example:
{
"404": {"limit": 10, "window": 60},
"403": {"limit": 5, "window": 60},
"401": {"limit": 5, "window": 60}
}
In this example, on first statement, a client can throw only ten 404 http errors over 60 seconds; on second, only five 403 http error over 60 seconds and so on. Simple, right?
On your nginx.conf you'll have to include the ban file and a block condition, for exampleS:
...
include /etc/nginx/conf.d/banned.conf;
server {
if ($blocked) {
return 444; # You can choose whatever http status you want
}
}
# you can also redirect for an custom error page.
...
Finally, the httpE2Ban need to access the Nginx access.log (assuming you're using nginx default log pattern). With this policy and ban file, the httpE2Ban is going to listen the access.log, populate the ban file and send an reload signal to Nginx. (Using docker, nginx access logs is being showed in httpE2Ban container log)
For setup you have to do three steps:
docker compose upBLOCK_TTL environment variable value.Here is an example on a more complex cenario.
name: nginx-httpE2Ban
services:
nginx:
container_name: nginx-container-name
volumes:
- ./site-example:/usr/share/nginx/html:ro
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./access.log:/var/log/nginx/access.log #¹
- ./banned.conf:/etc/nginx/conf.d/banned.conf:ro #²
image: nginx
ports:
- 8080:80
environment:
- NGINX_PORT=80
- TZ=America/Sao_Paulo #³
nginx_httpe2ban:
container_name: httpe2ban
volumes:
- ./access.log:/httpe2ban/access.log:ro #¹
- ./banned.conf:/httpe2ban/banned.conf:rw #²
- /var/run/docker.sock:/var/run/docker.sock #£
- /usr/bin/docker:/usr/bin/docker #£
image: aleixolucas/nginx-httpe2ban #£
environment:
TZ: "America/Sao_Paulo" #³
POLICY: >
{
"404": {"limit": 10, "window": 60},
"403": {"limit": 5, "window": 60},
"401": {"limit": 5, "window": 60}
}
LOG_LEVEL: "INFO"
BLOCK_TTL: 7200
depends_on:
- nginx
RELOAD_NGINX_CUSTOM_CMD so httpE2Ban can use this command to access the nginx and send nginx reload. Check if your docker socked and binary are in the same path as docker compose if you have any trouble.To run withou docker, you should have pip3 and python3.9 installed.
python3 -m venv .venv
source .venv/bin/activate # Linux
C:\ .venv\Scripts\activate.bat # Window
pip3 install -r requirements.txt
# Should be exacly like this when first create
cat <<EOF > $HOME/nginx-httpE2Ban/banned.conf
map \$remote_addr \$blocked {
default 0;
}
EOF
export RELOAD_NGINX_CUSTOM_CMD="ssh [email protected] 'nginx -s reload'"
export NGINX_LOG_PATH="/var/log/nginx/access.log"
export POLICY_FILE="$HOME/nginx-httpE2Ban/policy.json"
export BANNED_CONF_FILE="$HOME/nginx-httpE2Ban/banned.conf"
# read the documentation below if necessary.
python3 main.py
| Name | Example | Required | Description |
|---|---|---|---|
| TZ | America/Sao_Paulo | No | Your Timezone. If not set, is going to use America/Sao_Paulo. |
| NGINX_CONTAINER_NAME | nginx-prod | No | Nginx container name. If not set, httpE2Ban will restart the first container that is running Nginx image. |
| RELOAD_NGINX_CUSTOM_CMD | ssh [email protected] "nginx -s reload" | No | Custom command to restart Nginx. If set, won't restart Nginx using docker. |
| NGINX_LOG_PATH | ./access.log | Yes | Absolute path for file containing Nginx access.log. If not set is going to use acces.log as default. |
| BANNED_CONF_FILE | ./banned.conf | No | Absolute path for file containing blocked IPs. If not set, is going to use banned.conf as default. |
| POLICY | {"404": {"limit": 10, "window": 60,...}} | No | Policy variable, you can also use an file if get hard to mantain. |
| POLICY_FILE | ./policy.json | Yes | Absolute path for policy file. |
| STARTUP_DELAY | 10 | No | In seconds, how long to wait until start httpE2Ban. Default value is 5. |
| LOG_LEVEL | DEBUG | No | Possibilities: DEBUG, INFO, WARNING, ERROR, CRITICAL. Default is INFO. |
| NGINX_LOG_JSON_MAP | {"ip_address":"remote_addr"...} | No | If your log format is not in default and you are using json as log, this is an workaround to use httpE2Ban. |
| BLOCK_TTL_CHECK_DELAY | 60 | No | In seconds, how long to wait to unban an IP. Default is 60. |
| BLOCK_TTL | 7200 | No | The time the IP will be blocked. If not configured, the block is permanent. |
If your log format is not the default format that Nginx provide and you are using a json format, you can still use httpE2Ban, you just need to map some information that httpE2Ban needs. Your log should not contain nothing besides json and should start and finish with brackets. If you are not using one of these formats (default/json) the only way to make it work is change the source code and rebuild the httpE2Ban.
The map is done using NGINX_LOG_JSON_MAP environment variable, in a key value json format, you can find an example in a commented line on docker compose file. httpE2Ban needs these information:
| Name | Description |
|---|---|
| ip_address | The client remote address. (This is the ip that should be blocked) |
| datetime | Request time, generally on log |
| request | The request received |
| url | Url requested |
| http_version | Wich http version being used, like "HTTP/1.1" |
| status_code | The request status code present in log |
| user_agent | The user agend that client used to reach Nginx |
If the Nginx log line is like:
{"msec":"1724864433.591","connection":"1","connection_requests":"1","pid":"42","request_id":"bf76bf04c6a423ed20bd2cfb49c5913a","request_length":"837","remote_addr":"172.18.0.1","remote_user":"-","remote_port":"49094","time_local":"28/Aug/2024:14:00:33 -0300","time_iso8601":"2024-08-28T14:00:33-03:00","request":"GET / HTTP/1.1","request_uri":"/","args":"-","status":"304","body_bytes_sent":"0","bytes_sent":"179","http_referer":"-","http_user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:129.0) Gecko/20100101 Firefox/129.0","http_x_forwarded_for":"-","http_host":"localhost:8080","server_name":"site-example.com","request_time":"0.000","upstream":"-","upstream_connect_time":"-","upstream_header_time":"-","upstream_response_time":"-","upstream_response_length":"-","upstream_cache_status":"-","ssl_protocol":"-","ssl_cipher":"-","scheme":"http","request_method":"GET","server_protocol":"HTTP/1.1","pipe":".","gzip_ratio":"-","http_cf_ray":"-"}
So the NGINX_LOG_JSON_MAP variable should be:
NGINX_LOG_JSON_MAP={"ip_address":"remote_addr","datetime":"time_local","request":"request","url":"http_referer","http_version":"server_protocol","status_code":"status","user_agent":"http_user_agent"}
ERROR or CRITICAL.Content type
Image
Digest
sha256:1ea3d7ebf…
Size
233 MB
Last updated
about 2 years ago
docker pull aleixolucas/nginx-httpe2ban