OpenResty と Lua ライブラリを活用した WAF 構築手法

環境の準備とパッケージインストール

OpenResty ベースのセキュリティゲートウェイを構築する際、まずは基盤となるミドルウェアと依存ライブラリのセットアップが重要です。RHEL クラスターのディストリビューションにおいて、公式のリポジトリ登録から始めてパッケージ管理を行います。

# リポジトリ設定(RHEL 8 以降は dnf を使用)
wget https://openresty.org/package/rhel/openresty.repo
sudo mv openresty.repo /etc/yum.repos.d/
sudo dnf check-update

# メインパッケージの導入
sudo dnf install -y openresty

# コマンドラインツール群の追加
sudo dnf install -y openresty-resty openresty-opm openresty-doc

Lua-WAF モジュールの導入

OpenResty のパッケージマネージャである opm を利用して、WAF 処理を行う Lua ライブラリを取得します。

opm get codiy1992/lua-resty-waf

Nginx 設定の実装

nginx.conf 内で Lua 実行環境と共有メモリ領域を設定します。ここでは変数名やメモリサイズをより具体的に変更し、可読性を高めています。

http {
    # Lua 実行設定
    lua_code_cache on;
    lua_need_request_body on;

    # 共有メモリ領域の定義
    # WAF 設定情報保存
    lua_shared_dict cfg_store 32k;
    # ブラック/ホワイトリスト管理
    lua_shared_dict deny_list 10m;
    # レート制限データ
    lua_shared_dict rate_store 10m;
    # アクセス計測統計
    lua_shared_dict stats_store 10m;
    # サンプリングデータ
    lua_shared_dict sample_store 10m;

    # ワーカー初期化時の設定読み込み
    init_worker_by_lua_block {
        local worker_id = ngx.worker.id()
        if worker_id == 0 then
            ngx.timer.at(0, require("resty.waf").initialize)
        end
    }

    # アクセス制御ロジック
    access_by_lua_block {
        local waf_engine = require("resty.waf")
        -- 主要モジュールのロード順を指定
        waf_engine.run({
            "config_manager",
            "request_filter",
            "rate_control",
            "request_meter",
            "data_sampler"
        })
    }
}

共有メモリの役割分担

メモリ不足発生時には、LRU 方式で古いエントリが自動削除されます。

  • cfg_store: WAF の動的設定情報を保持
  • deny_list: IP、デバイス ID、ユーザー ID などのフィルタリング対象リスト
  • rate_store: トランザクション頻度制限の情報
  • stats_store: クエリ回数の集計データ
  • sample_store: サンプルデータの抽出記録

設定アーキテクチャの詳細

システムは主にマッチャー(識別規則)、レスポンス定義、機能モジュールの 3 つの層で構成されます。

1. Matcher(ルール定義)

HTTP リクエストの各種パラメータ(IP, URI, ヘッダー等)に基づき判定を行します。演算子には以下のような種類があります。

演算子 説明
*デフォルト真(全件マッチ)
=大文字小文字無視での完全一致
==大文字小文字区別での完全一致
部分一致または正規表現マッチ
#テーブル内の存在確認
Exist値の有無チェック

JSON 形式でのルール定義例:

{
    "sql_injection_check": {
        "Args": {
            "name": "search",
            "operator": "≈",
            "value": "(union|select|insert|drop)"
        }
    },
    "suspicious_user_agent": {
        "UserAgent": {
            "operator": "≈",
            "value": "(curl|wget|python-requests|scrapy)"
        }
    },
    "private_network_range": {
        "IP": {
            "operator": "!≈",
            "value": "^(192\\.168\\.|10\\.|172\\.(1[6-9]|2[0-9]|3[01])\\.).*"
        }
    }
}

2. Response(応答定義)

ブロック判定時にクライアントへ返却する内容です。状態コードとメッセージボディをカスタマイズ可能です。

{
    "blocked_403": {
        "status": 403,
        "body": "{\"error\": \"access_denied\", \"reason\": \"security_policy\"}",
        "mime_type": "application/json"
    }
}

3. モジュール動作一覧

Manager:管理インターフェース

Basic 認証を用いて WAF の状態や設定を確認・変更するためのエンドポイントを提供します。/waf パス配下に配置されます。

Endpoint Method 機能
/waf/statusGETシステム稼働状況の確認
/waf/configGET/POST一時設定の取得・更新
/waf/config/reloadPOST設定の再読み込み(一時設定クリア)
/waf/list/reloadPOST阻止リストの反映
/waf/module/limiterGETレート制限ステータスの照会

Filter:要求フィルタリング

Matcher に合致したトラフィックに対して許可(accept)または遮断(block)を行います。リスト内にある IP またはユーザー ID を基準とした処理も可能ですが、その場合は by パラメータに ip:in_list 等を指定します。

{
    "enable": true,
    "rules": [
        {
            "action": "block",
            "matcher": "sql_injection_check",
            "code": "blocked_403",
            "enable": true
        },
        {
            "action": "block",
            "matcher": "any",
            "by": "uid:in_list",
            "enable": true
        }
    ]
}

Limiter:レート制限

IP や URI 単位でのアクセス頻度を制御します。

{
    "enable": true,
    "rules": [
        {
            "time_window": 60,
            "max_count": 100,
            "scope": "ip",
            "matcher": "any"
        }
    ]
}

Redis による永続化設定

設定情報を再構築せずに継続的に適用するには、Redis データストアとの連携が有効です。.env ファイルまたは環境変数 REDIS_HOST を経由して接続先を指定します。

Redis 上でのキー構造は以下の通りになります:

  • waf:config:*: 設定情報の Hash
  • waf:list: 阻止リストの Sorted Set

例として、特定の IP アドレスを永続的な阻止リストに追加する場合:

# Redis CLI 例
ZADD waf:list 1699999999 "bad_ip_address"
# 設定反映のため HTTP 経由で reload を発火させる必要があります

カスタム開発における注意点

OpenResty 環境下での Lua スクリプト開発時には、変数のスコープ管理に注意が必要です。

  • Module Level Variables: ワーカーごとに独立しており、読み取り専用となります。プロセス起動時のみ初期化されます。
  • ngx.var.*: ディクショナリへのアクセスコストが高く、型も文字列に限られます。内部リダイレクト時は値が破棄される可能性があります。
  • ngx.shared.DICT: ワーカー間でのデータ共有に最適化された領域です。
  • resty.lrucache: 同じワーカー内でのキャッシュ用途に適していますが、他のワーカーからは見えません。

これらの特性を理解し、適切にメモリ設計を行うことが、高負荷環境下での安定稼働につながります。

タグ: openresty lua WAF nginx redis

6月25日 22:50 投稿