NGINX Plusとfail2banによる動的IP拒否リスト

NGINX | 2017年9月19日

写真:Arnold Reinhold – 自作、CC BY‑SA 3.0

気づいていないかもしれませんが、Webサイトは常に脅威にさらされています。WordPressを使用している場合は、ボットがスパムを送り込もうとします。ログインページがある場合は、パスワードに対するブルートフォース攻撃を受けます。また、検索エンジンのクローラーも望ましくない訪問者と見なすことができます。

望ましくないアクティビティ、不審なアクティビティ、悪意のあるアクティビティからサイトを守るのは容易ではありません。Webアプリケーションファイアウォール(WAF)であるNGINX App Protectや、F5のパートナーが提供するNGINX Plus Certified Modulesは効果的なツールであり、セキュリティスタックの一部として検討する必要があります。ほとんどの環境では、セキュリティ対策を強化しすぎるということはなく、多層的なアプローチが常に最も効果的です。

このブログ記事では、Webセキュリティスタックのもう1つのレイヤーとして、fail2banを使用する方法について説明します。Fail2banは、ログファイルを継続的に監視して不審なアクティビティを検出し、事前設定された1つ以上のアクションを実行する侵入検知システム(IDS)です。通常、fail2banはログイン試行の失敗を監視し、原因となったIPアドレスを一定期間ブロック(禁止)します。これは、ブルートフォースによるパスワード攻撃に対するシンプルかつ効果的な防御策です。

In NGINX Plus R13では、ネイティブのキーバリューストアと新しいNGINX Plus APIを導入しました。これにより、外部システムからNGINX Plusの設定や動作を制御する、多様な動的設定ソリューションが可能になります。このブログ記事では、認証に複数回失敗したIPアドレスからのリクエストを無視するよう、fail2banを使用してNGINX Plusを自動的に再設定する方法も紹介します。

編集者注 – NGINX Plus R16以降では、クラスター内のすべてのNGINX Plusインスタンス間でキーバリューストアを同期できます。(クラスター内での状態共有は、NGINX Plusの他の機能でも利用できます。)詳しくは、こちらの「ブログ」および「NGINX Plus管理ガイド」をご覧ください。

IP拒否リストへのキーバリューストアの使用

NGINX Plusのキー・バリューストアは、次の3つの主な特性を備えたネイティブのインメモリストアです。

  1. キーと値のペアはJSONオブジェクトとして表現されます
  2. キーと値のペアは、すべてAPIを通じて管理されます
  3. 値は、通常の設定変数としてNGINX Plusで使用できます

キーと値のストアは、keyval_zoneディレクティブで名前を指定した共有メモリゾーンを作成することで定義します。その後、HTTP POSTメソッドを使用してJSONオブジェクトをAPIに送信し、このキーと値のストアに初期値のセットを格納できます。続いて、keyvalディレクティブで、検索キーとして使用する既存の変数(この例では$remote_addr)と、そのキーに対応する値から評価される新しい変数($num_failures)の名前を定義します。

NGINX Plusキーバリューストアの設定と管理

APIを有効にするには、locationブロックをNGINX Plus APIエンドポイントとして指定します。


server {
    listen 1111;
    allow  127.0.0.1; # Only allow access from localhost,
    deny   all;       # and prevent remote access.

    location /api {
        api write=on; # The NGINX Plus API endpoint in read/write mode
    }
}
keyval_zone zone=denylist:1M;
keyval $remote_addr $num_failures zone=denylist;

server {
    listen 80;

    location / {
        root /usr/share/nginx/html;
        if ($num_failures) {
            return 403;
        }
    }
}

vim: syntax=nginx

キーと値のペアを追加する前に、denylistストアの内容をリクエストすると、空のJSONオブジェクトが返されます。

$ curl http://localhost:1111/api/6/http/keyvals/denylist{}

HTTP POSTメソッド(curlコマンドの-d引数として指定)を使用して新しいJSONオブジェクトを送信することで、キー・バリューストアに最初のキー・バリューペアを格納できるようになりました。空のキー・バリューストアには複数のキー・バリューペアをPOSTでき、それ以降は1つずつ追加できます。

$ curl -iX POST -d '{"10.0.0.1":"1"}' http://localhost:1111/api/6/http/keyvals/denylistHTTP/1.1 201 Created
...

キーと値のペアを削除するには、キーの値をPATCHでnullに設定します。

$ curl -iX PATCH -d '{"10.0.0.1":null}' http://localhost:1111/api/6/http/keyvals/denylistHTTP/1.1 204 No Content
...

All key‑value pairs can be removed from the key‑value store by sending the DELETEメソッドを送信すると、キー・バリューストアからすべてのキー・バリューペアを削除できます。

$ curl -iX DELETE http://localhost:1111/api/6/http/keyvals/denylistHTTP/1.1 204 No Content
...

IP拒否リストのシンプルな実装を設定できるようになりました。


server {
    listen 1111;
    allow  127.0.0.1; # Only allow access from localhost,
    deny   all;       # and prevent remote access.

    location /api {
        api write=on; # The NGINX Plus API endpoint in read/write mode
    }
}
keyval_zone zone=denylist:1M;
keyval $remote_addr $num_failures zone=denylist;

server {
    listen 80;

    location / {
        root /usr/share/nginx/html;
        if ($num_failures) {
            return 403;
        }
    }
}

vim: syntax=nginx

この構成スニペットでは、ポート80をWebサーバーとして構成しています。18行目では、$num_failures変数を、denylist共有メモリゾーン内にあるキーと値のペアのうち、$remote_addr変数(クライアントIPアドレス)に対応するキーと一致するペアの値として評価します。$num_failuresを評価するこの処理は、順を追うとより明確に表現できます。

  1. Pass the value of $remote_addrの値をdenylistキーバリューストアに渡す
  2. If $remote_addrがキーと完全に一致する場合、そのキーと値のペアの値を取得する
  3. 値を$num_failuresとして返す

したがって、クライアントIPアドレスがKey-ValueストアにPOSTされている場合、すべてのリクエストでHTTP 403 Forbiddenエラーが発生します。同じ目的でmapブロックを使用する方法と異なり、このアプローチには、外部システムからIP拒否リストを制御できるという利点があります。

fail2banを使用したIP拒否リストの動的管理

前述のとおり、fail2banは一般に、ログファイル内の不審なアクティビティや悪意のあるアクティビティを検出し、システムを保護するための措置を講じる目的で使用されます。デフォルトでは、iptablesを設定し、ログに記録されたIPアドレスから送信されるすべてのパケットを破棄します。この方法は悪意のある攻撃者をブロックするうえで効果的ですが、正規ユーザー、特にパスワードを忘れたユーザーの利便性を大きく損ないます。このようなユーザーには、Webサイトが単に利用できなくなったように見えます。

以下の設定では、問題のあるIPアドレスをNGINX Plusのアクションとして拒否リストのキーバリューストアに登録するfail2banの設定について説明します。NGINX Plusは、より有用なエラーページを表示すると同時に、そのIPアドレスからのリクエストにレート制限を適用します。これにより、一連の動作が攻撃の一部である場合、その攻撃を効果的に無力化します。

fail2banがNGINX Plusと同じホストにデフォルト設定でインストールされ、すべての設定が/etc/fail2banディレクトリに格納されていることを前提とします。

次のfail2banアクションでは、上記の簡単な例と同様に、NGINX Plus APIを使用して、denylistキーバリューストア内の「禁止対象」IPアドレスを追加および削除します。nginx-plus-denylist.confファイルは、/etc/fail2ban/action.dディレクトリに配置します。


[Definition]
actionban = curl -s -o /dev/null -d '{"":""}' http://localhost:1111/api/6/http/keyvals/denylist
actionunban = curl -s -o /dev/null -X PATCH -d '{"":null}' http://localhost:1111/api/6/http/keyvals/denylist

次のfail2ban jailは、NGINXのHTTP Basic Authenticationモジュールを使用したログイン試行の失敗を検出する組み込みフィルターを有効にし、nginx-plus-denylist.confで定義されたアクションを適用します。このほかにも多くの組み込みフィルターを利用でき、NGINXアクセスログ内の望ましくないアクティビティを検出するフィルターも簡単に作成できます。


[DEFAULT]
bantime   = 120
banaction = nginx-plus-denylist
 
[nginx-http-auth]
enabled = true

次のNGINX Plus構成スニペットでは、デフォルトの「Welcome to NGINX」ページにHTTP Basic認証を適用します。password_site.confファイルは、/etc/nginx/conf.dディレクトリに配置します。


server {
    listen 1111;
    allow  127.0.0.1; # Only allow access from localhost,
    deny   all;       # and prevent remote access.

    location /api {
        api write=on; # The NGINX Plus API endpoint in read/write mode
    }
}

keyval_zone zone=denylist:1M state=denylist.json;
keyval $remote_addr $num_failures zone=denylist;

limit_req_zone $binary_remote_addr zone=20permin:10M rate=20r/m;

server {
    listen 80;
    root /usr/share/nginx/html;

    location / {
        auth_basic "closed site";
        auth_basic_user_file users.htpasswd;

        if ($num_failures) {
            rewrite ^.* /banned.html;
        }
    }

    location = /banned.html {
        limit_req zone=20permin burst=100;
    }
}

vim: syntax=nginx

11行目では、上記のkeyval_zoneをdenylist_keyval.confと同様に定義していますが、stateパラメーターが追加されています。このパラメーターは、キーと値のストアの状態を保存するファイルを指定するため、NGINX Plusを停止して再起動しても状態が保持されます。状態ファイルを指定しなくても、通常の設定リロードではキーと値のストアが保持されます。(1~10行目は省略されていますが、denylist_keyval.confと同じです。)

14行目では、20perminという共有メモリゾーンを作成し、クライアントIPアドレスごとの最大リクエストレートを1分あたり20リクエストに指定します。このレート制限は、fail2banによってIPアドレスが拒否リストに追加されたときに適用されます(30行目)。

The serverブロックは、デフォルトのポート(80)でリッスンするWebサーバーを定義します。すべてのリクエストは、location /ブロック(20行目)にマッチします。rootディレクティブ(18行目)は、Webコンテンツの配置場所を指定します。21~22行目では、このサイトにHTTP Basic認証が必要であること、および認証済みユーザーのパスワードデータベースがusers.htpasswdファイルに格納されていることを指定します。auth_basic_user_fileディレクティブのドキュメントには、このファイルの形式が記載されています。この構成はテスト用であり、暗号化されていない点に注意してください。HTTP Basic認証を使用する本番環境のWebサイトでは、転送時のセキュリティを確保するためにSSL/TLSを使用する必要があります。

クライアントのIPアドレスが拒否リストに登録されている場合(24~26行目)、HTTP 403エラーコードを返す代わりに、リクエストを/banned.htmlに書き換えます。その後、このリクエストは、そのURIに完全一致するlocationブロック(29行目)で処理されます。これにより、ログインの失敗回数が多すぎたためユーザーのIPアドレスがブロックされたことを説明する静的Webページが返されます。また、厳しいレート制限(30行目)を適用し、悪意のあるクライアントによる不要なシステムリソースの消費を防ぎます。

ユーザーのIPアドレスが拒否リストに登録された場合に表示されるページ

(このサンプルWebページのHTMLは、この投稿のGitHubリポジトリで「banned.html」として公開されています。)

連続するログイン失敗と、それに対応するfail2banの動作は、次のようにシミュレートできます。

$ curl -i http://admin:password@www.example.com/HTTP/1.1 401 Unauthorized ...
$ curl -i http://admin:admin@www.example.com/
HTTP/1.1 401 Unauthorized ...
$ curl -i http://admin:pass1234@www.example.com/
HTTP/1.1 401 Unauthorized ...
$ curl -i http://admin:letmein@www.example.com/
HTTP/1.1 401 Unauthorized ...
$ curl -i http://admin:fred@www.example.com/
HTTP/1.1 401 Unauthorized ...
$ curl http://admin:P@ssw0rd@www.example.com/
<!DOCTYPE html>
<html>
<head>
<title>Banned</title>
...
$ tail -f /var/log/fail2ban.log
2017-09-15 13:55:18,903 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:55:28,836 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:57:49,228 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:57:50,286 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:57:52,015 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:57:52,088 fail2ban.actions  [28498]: NOTICE  [nginx-http-auth] Ban 172.16.52.1
2017-09-15 13:59:52,379 fail2ban.actions  [28498]: NOTICE  [nginx-http-auth] Unban 172.16.52.1

この動的IP拒否リストソリューションは、追加の設定変更なしで実行できるようになりました。Fail2banはNGINXのログファイルを監視し、APIを使用して、ブロック対象のIPアドレスをNGINX Plusのキーバリューストアに追加します。120秒後(bantimeとしてjail.localで設定)、違反元のIPアドレスはNGINX Plus APIを使用して拒否リストから削除され、そのアドレスからのログイン試行が再び許可されます。

NGINX Plus APIとキーバリューストアにより、このような統合が可能になります。NGINX Plusの設定ファイルを作成したり、リロードしたりすることなく、高度な設定ソリューションを実現できるようになりました。これらの機能を活用して独自の動的設定ソリューションを構築した事例を、ぜひお聞かせください。以下にコメントをお寄せください。

NGINX Plusをお試しいただくには、30日間の無料トライアルを今すぐ開始するか、ユースケースについてご相談ください。


共有

著者について

Liam Crilly
Liam Crillyプロダクトマネジメント担当シニアディレクター

Liam Crillyのその他のブログ記事

関連するブログ記事

チュートリアル:NGINXを使用してアプリケーション向けにOpenTelemetryを構成する
NGINX | 2024/01/18

チュートリアル:NGINXを使用してアプリケーション向けにOpenTelemetryを構成する

OpenTelemetryを使用してNGINXサーバーの状態と健全性をより明確に把握することで、問題のトラブルシューティングを行い、Webアプリケーションのパフォーマンスを向上できます。

AkitaのNGINX Plus認定モジュールでAPIエンドポイントの問題を特定・修正
NGINX | 2023/05/31

AkitaのNGINX Plus認定モジュールでAPIエンドポイントの問題を特定・修正

Akita SoftwareのAPIオブザーバビリティ向け新しいNGINX Plus Certified Moduleを活用して、APIエンドポイントの問題をより迅速に特定し、修正できます。

JavaプロジェクトをスキャフォールディングしてKubernetesネイティブアプリを構築する方法
NGINX | 2023/02/09

JavaプロジェクトをスキャフォールディングしてKubernetesネイティブアプリを構築する方法

Red HatのシニアプリンシパルデベロッパーアドボケイトであるDaniel Oh氏が、Javaプロジェクトをゼロから構築するためのひな形を作成し、Kubernetesネイティブアプリケーションとしてビルドする方法を解説します。

F5 NGINXaaS for Azureのご紹介:Microsoft Azureでネイティブに利用できるSaaS型ロードバランシング
NGINX | 2022/05/24

F5 NGINXaaS for Azureのご紹介:Microsoft Azureでネイティブに利用できるSaaS型ロードバランシング

NGINXaaS for Azureは、NGINX PlusをAzureネイティブのSaaSとして利用できるサービスです。NGINXの設定を容易にリフト&シフトできるため、クラウド導入とモダンアプリの展開を加速できます。

SELinuxでNGINXおよびNGINX Plusを使用する
NGINX | 2018/08/17

SELinuxでNGINXおよびNGINX Plusを使用する

Red Hat Enterprise Linux(RHEL)および関連ディストリビューションでSecurity-Enhanced Linux(SELinux)を有効にすると、デフォルト設定によりNGINXとNGINX Plusの一部の操作が制限されます。この記事では、すべての機能を利用できるようにSELinuxの設定を変更する方法について説明します。

NGINX PlusによるAWS Auto Scalingグループの負荷分散
NGINX | 2017/03/06

NGINX PlusによるAWS Auto Scalingグループの負荷分散

新しいnginx-asg-sync統合ソフトウェアにより、NGINX PlusはAWS Auto Scalingグループの負荷を直接分散できるため、クラウドでの負荷分散がさらに柔軟になります