Dynamic IP Denylisting with NGINX Plus and fail2ban

NGINX | September 19, 2017

Photo: Arnold Reinhold – Own work, CC BY‑SA 3.0

You may not realize it, but your website is under constant threat. If it’s running WordPress, bots are trying to spam you. If it has a login page, there are brute‑force password attacks. You may also consider search engine spiders as unwanted visitors.

Defending your site from unwanted, suspicious, and malicious activity is no easy task. Web application firewalls, such as NGINX App Protect and the NGINX Plus Certified Modules available from our partners, are effective tools and must be considered as part of your security stack. For most environments, there’s no such thing as too much security, and a multi‑layered approach is invariably the most effective.

In this blog post, we’ll discuss the use of fail2ban as another layer of the web security stack. Fail2ban is an intrusion detection system (IDS) which continually monitors log files for suspicious activity, and then takes one or more preconfigured actions. Typically fail2ban monitors for failed login attempts and then blocks (bans) the offending IP address for a period of time. This is a simple yet effective defense against brute‑force password attacks.

In NGINX Plus R13 we introduced a native key‑value store and a new NGINX Plus API. This allows for a wealth of dynamic configuration solutions in which NGINX Plus configuration and behavior can be driven by an external system. In this blog post, we’ll also show how fail2ban can be used to automatically reconfigure NGINX Plus to ignore requests from IP addresses that have caused multiple failed authentication events.

Editor – In NGINX Plus R16 and later, key‑value stores can be synchronized across all NGINX Plus instances in a cluster. (State sharing in a cluster is available for other NGINX Plus features as well.) For details, see our blog and the NGINX Plus Admin Guide.

Using a Key‑Value Store for IP Denylisting

The NGINX Plus key‑value store is a native, in‑memory store with three primary characteristics:

  1. Key‑value pairs are represented as JSON objects
  2. Key‑value pairs are managed entirely through an API
  3. The values are available to NGINX Plus as regular configuration variables

The key‑value store is defined by creating a shared memory zone named by the keyval_zone directive. This key‑value store can then be populated with an initial set of values by using the HTTP POST method to submit a JSON object to the API. The keyval directive then defines which existing variable will be used as the lookup key ($remote_addr in the example), and the name of a new variable ($num_failures) that will be evaluated from that key’s corresponding value.

NGINX Plus key-value store configuration and management

The API is enabled by designating a location block as the NGINX Plus API endpoint.

[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop

Before we add key‑value pairs to it, a request for the contents of the denylist store returns an empty JSON object.

[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop

The key‑value store can now be populated with an initial key‑value pair using the HTTP POST method (in the form of the -d argument to the curl command) to submit a new JSON object. Multiple key‑value pairs can be POST‑ed to an empty key‑value store and singularly thereafter.

[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop

A key‑value pair is removed by PATCH‑ing the key with a value of null.

[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop

All key‑value pairs can be removed from the key‑value store by sending the DELETE method.

[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop

A simple implementation of IP denylistlisting can now be configured.

[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop

This configuration snippet configures port 80 as a web server. Line 18 evaluates the $num_failures variable to the value part of the key‑value pair in the denylist shared memory zone that matches the key that corresponds to the $remote_addr variable (client IP address). This process of evaluating $num_failures is more clearly expressed sequentially:

  1. Pass the value of $remote_addr to the denylist key‑value store
  2. If $remote_addr is an exact match for a key then obtain the value of that key‑value pair
  3. Return the value as $num_failures

And so, if the client IP address has been POST‑ed to the key‑value store then all requests result in an HTTP 403 Forbidden error. The advantage of this approach, as opposed to using a map block for the same purpose, is that the IP denylist can be controlled by an external system.

Using fail2ban to Dynamically Manage the IP Denylist

As mentioned above, fail2ban is commonly used to detect suspicious and/or malicious activity in log files, and then take action to protect the system. The default action is to configure iptables to drop all packets originating from the IP address that was logged. This approach, although effective in blocking a malicious actor, provides a very poor user experience for genuine users, especially if they’ve forgotten their password. To these users, it appears as if the website is simply unavailable.

The following configuration describes a fail2ban action that submits the offending IP address to the NGINX Plus denylist key‑value store. NGINX Plus then displays a more helpful error page and simultaneously applies a request rate limit to that IP address so that if the actions are part of an attack, the attack is effectively neutralized.

We assume a default installation of fail2ban on the same host as NGINX Plus, with all configuration under the /etc/fail2ban directory.

The following fail2ban action uses the NGINX Plus API to add and remove “banned” IP addresses within the denylist key‑value store in the same way as our simple example above. We place the nginx-plus-denylist.conf file in the /etc/fail2ban/action.d directory.

[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop

The following fail2ban jail enables the built‑in filter that detects failed login attempts using NGINX’s HTTP Basic Authentication module and applies the action defined in nginx-plus-denylist.conf. Many other built‑in filters are available, and can be easily created to detect unwanted activity in NGINX access logs.

[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop

The following NGINX Plus configuration snippet applies HTTP Basic authentication to the default “Welcome to NGINX” page. We place the password_site.conf file in the /etc/nginx/conf.d directory.

[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop

Line 11 defines the keyval_zone as in denylist_keyval.conf above, but with the addition of the state parameter, which specifies a file where the state of the key‑value store is stored and so persists when NGINX Plus is stopped and restarted. The key‑value store persists across regular configuration reloads without specifying a state file. (Lines 1–10 are not shown, but are the same as in denylist_keyval.conf.)

Line 14 creates a shared memory zone called 20permin and specifies a maximum request rate of 20 requests per minute for each client IP address. This rate limit is then applied when an IP address is added to the denylist by fail2ban (line 30).

The server block defines a web server listening on the default port (80), where all requests are matched by the location / block (line 20). The root directive (line 18) specifies where our web content resides. Lines 21–22 specify that HTTP Basic authentication is required for this site and that the database of passwords for authorized users is in the file users.htpasswd. The documentation for the auth_basic_user_file directive describes the format of that file. Note that this configuration is unencrypted for testing purposes and any production web site using HTTP Basic authentication should use SSL/TLS for transport security.

When the client IP address has been denylisted (lines 24–26), instead of returning the HTTP 403 error code, we rewrite the request to be /banned.html which is then processed in the location block that exactly matches that URI (line 29). This returns a static web page that explains that the user’s IP address has been blocked due to excessive login failures. It also applies a restrictive rate limit (line 30) that prevents a malicious client from unnecessarily consuming system resources.

Page that users see when their IP address is denylisted

(The HTML for this sample web page is available as banned.html in the GitHub repository for this post.)

We can simulate successive failed login attempts and the corresponding fail2ban activity as follows.

[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop

This dynamic IP denylisting solution can now run without any further configuration changes. Fail2ban watches the NGINX log files and adds banned IP addresses to the NGINX Plus key‑value store using the API. After 120 seconds (the bantime configured in jail.local), the offending IP address is removed from the denylist, again using the NGINX Plus API, and login attempts are once more accepted from that address.

The NGINX Plus API and key‑value store make this type of integration possible. Advanced configuration solutions can now be achieved without requiring the construction of NGINX Plus configuration files and performing reloads. We’d love to hear about how you’re using these features to create your own dynamic configuration solutions. Please add a comment below.

To try NGINX Plus, start your free 30-day trial today or contact us to discuss your use cases.


Share

About the Author

Liam Crilly
Liam CrillySr Director, Product Management

More blogs by Liam Crilly

Related Blog Posts

Automating Certificate Management in a Kubernetes Environment
NGINX | 10/05/2022

Automating Certificate Management in a Kubernetes Environment

Simplify cert management by providing unique, automatically renewed and updated certificates to your endpoints.

Secure Your API Gateway with NGINX App Protect WAF
NGINX | 05/26/2022

Secure Your API Gateway with NGINX App Protect WAF

As monoliths move to microservices, applications are developed faster than ever. Speed is necessary to stay competitive and APIs sit at the front of these rapid modernization efforts. But the popularity of APIs for application modernization has significant implications for app security.

How Do I Choose? API Gateway vs. Ingress Controller vs. Service Mesh
NGINX | 12/09/2021

How Do I Choose? API Gateway vs. Ingress Controller vs. Service Mesh

When you need an API gateway in Kubernetes, how do you choose among API gateway vs. Ingress controller vs. service mesh? We guide you through the decision, with sample scenarios for north-south and east-west API traffic, plus use cases where an API gateway is the right tool.

Deploying NGINX as an API Gateway, Part 2: Protecting Backend Services
NGINX | 01/20/2021

Deploying NGINX as an API Gateway, Part 2: Protecting Backend Services

In the second post in our API gateway series, Liam shows you how to batten down the hatches on your API services. You can use rate limiting, access restrictions, request size limits, and request body validation to frustrate illegitimate or overly burdensome requests.

New Joomla Exploit CVE-2015-8562
NGINX | 12/15/2015

New Joomla Exploit CVE-2015-8562

Read about the new zero day exploit in Joomla and see the NGINX configuration for how to apply a fix in NGINX or NGINX Plus.

Why Do I See “Welcome to nginx!” on My Favorite Website?
NGINX | 01/01/2014

Why Do I See “Welcome to nginx!” on My Favorite Website?

The ‘Welcome to NGINX!’ page is presented when NGINX web server software is installed on a computer but has not finished configuring

Deliver and Secure Every App
F5 application delivery and security solutions are built to ensure that every app and API deployed anywhere is fast, available, and secure. Learn how we can partner to deliver exceptional experiences every time.
Connect With Us