ども、シリーズ化しても一回か二回で終わってしまう cloudpack の かっぱ (@inokara) です。
はじめに
HAProxy は最強の L4 / L7 のオープンソースロードバランサだと思います。特に L7 ロードバランサに関しては個人的には出来ないことは無いのではと思っています。
ということで、今回は HTTP リクエストヘッダから接続元 IP をオリジナルの HTTP ヘッダに挿入してバックエンドのアプリケーションでも接続元 IP を利用した制御が出来るような環境を作ってみたいと思います。
memo
環境と要件
以下のような環境と要件で構築と動作確認を行います。今回は HAProxy に直接するパターンと ELB を経由させた多段 Proxy の環境で試してみたいと思います。
接続元の IP とオリジナルヘッダの名前は以下の通りです。
内容 | |
---|---|
接続元 | xxx.xxx.xxx.xxx |
オリジナルヘッダ | Original-test-header |
アプリケーションサーバーポート番号 | 8081 |
HAProxy の設定
今回は HAProxy は以下のように設定しました。
frontend balancer-test monitor-uri /alive mode http option forwardfor bind 0.0.0.0:81 default_backend test_backend backend test_backend http-request set-header Client-Source-IP %[req.hdr_ip(X-Forwarded-For,1)] mode http option forwardfor option httpchk GET /index.php server web01 127.0.0.1:8001 inter 3000 check server bak01 127.0.0.1:80 check
ポイントは…
http-request add-header
又はhttp-request set-header
を利用- 接続元の IP は
req.hdr_ip
オプションでパラメータとしてX-Forwarded-For
を設定 X-Forwarded-For
に含まれている IP アドレスから最初の IP を取得する為にサブパラメータとして1
を指定
req.hdr_ip について
ドキュメント には以下の通り書かれています。
This extracts the last occurrence of header in an HTTP request,
converts it to an IPv4 or IPv6 address and returns this address. When used
with ACLs, all occurrences are checked, and if is omitted, every value
of every header is checked. Optionally, a specific occurrence might be
specified as a position number. Positive values indicate a position from the
first occurrence, with 1 being the first one. Negative values indicate
positions relative to the last one, with -1 being the last one. A typical use
is with the X-Forwarded-For and X-Client-IP headers.
ざっくり意訳すると…
- HTTP リクエストヘッダから
X-Forwarded-For
又はX-Client-IP
をチェックして IPv4 又は IPv6 アドレスに変換して値を返す - ポジション番号を指定することで複数の値から任意の値を取得することが出来る
1
を指定すると最初の値を返す-1
を指定すると末尾から相対的な値を返す
とのことですので、ちょっとだけ試してみたいと思います。
アプリケーションから見て X-Forwarded-for
には以下のような値が入ってきてる場合を想定して確認。
X-Forwarded-For: xxx.xxx.xxx.xxx, zzz.zzz.zzz.zzz, 127.0.0.1
以下のように set-header
を設定。
backend test_bk http-request set-header Original-test-header %[req.hdr_ip(X-Forwarded-For,-1)]
Original-test-header
は以下のような結果となります。
Original-test-header: zzz.zzz.zzz.zzz
後ほど、もう少し詳しく試していきたいと思います。
バックエンドのアプリケーション
バックエンドのアプリケーションは以下のような簡単な PHP プログラムを用意。
$value) { echo "$name: $valuen"; }
アプリケーションと呼ぶのが憚れますが、一応、アプリケーション。リクエストヘッダを画面上、コンソール上に出力します。
パターン 1 HAProxy へ直接アクセス
上記の設定が行われた状態の HAProxy に直接にアクセスしてみると以下のようなレスポンスが得られます。
% http http://${HAProxy}:81/ HTTP/1.1 200 OK Content-Length: 192 Content-Type: text/html; charset=UTF-8 Date: Sun, 01 Feb 2015 13:26:36 GMT Server: Apache/2.x.xx X-Powered-By: PHP/5.x.xx Host: ${HAProxy}:81 Accept-Encoding: gzip, deflate Accept: */* User-Agent: HTTPie/0.8.0 X-Forwarded-For: xxx.xxx.xxx.xxx, 127.0.0.1 X-Client: 127.0.0.1 Original-test-header: xxx.xxx.xxx.xxx
Original-test-header
には X-Forwarded-For
に記録されている IP の中から xxx.xxx.xxx.xxx
がセットされています。
パターン 2 ELB を経由して HAProxy にアクセス
上記の設定が行われた状態の HAProxy に ELB を経由してアクセスすると以下のようなレスポンスが得られます。
% http http://xxxxxxxxxxxxxxxxx.ap-northeast-1.elb.amazonaws.com:8081/ HTTP/1.1 200 OK Connection: keep-alive Content-Length: 292 Content-Type: text/html; charset=UTF-8 Date: Sun, 01 Feb 2015 13:31:54 GMT Server: Apache/2.x.xx X-Powered-By: PHP/5.x.xx host: xxxxxxxxxxxxxxxxx.ap-northeast-1.elb.amazonaws.com:8081 Accept: */* Accept-Encoding: gzip, deflate User-Agent: HTTPie/0.8.0 X-Forwarded-For: xxx.xxx.xxx.xxx, zzz.zzz.zzz.zzz, 127.0.0.1 X-Forwarded-Port: 8081 X-Forwarded-Proto: http X-Client: 127.0.0.1 Original-test-header: xxx.xxx.xxx.xxx
Original-test-header
には X-Forwarded-For
に記録されている IP の中から xxx.xxx.xxx.xxx
がセットされています。さらに、既存のヘッダを上書き出来るか試してみたいので以下のように HAProxy の設定を行います。
frontend balancer-test monitor-uri /alive mode http option forwardfor bind 0.0.0.0:81 default_backend test_backend backend test_backend http-request set-header X-Client %[req.hdr_ip(X-Forwarded-For,1)] mode http option forwardfor option httpchk GET /index.php server web01 127.0.0.1:8001 inter 3000 check server bak01 127.0.0.1:80 check
X-Client
を接続元の IP を設定してみたいと思います。
% http http://xxxxxxxxxxxxxxxxx.ap-northeast-1.elb.amazonaws.com:8081/ HTTP/1.1 200 OK Connection: keep-alive Content-Length: 326 Content-Type: text/html; charset=UTF-8 Date: Sun, 01 Feb 2015 13:35:59 GMT Server: Apache/2.x.xx X-Powered-By: PHP/5.x.xx host: xxxxxxxxxxxxxxxxx.ap-northeast-1.elb.amazonaws.com:8081 Accept: */* Accept-Encoding: gzip, deflate User-Agent: HTTPie/0.8.0 X-Forwarded-For: xxx.xxx.xxx.xxx, zzz.zzz.zzz.zzz, 127.0.0.1 X-Forwarded-Port: 8081 X-Forwarded-Proto: http X-Client: xxx.xxx.xxx.xxx
上記のように X-Client
には接続元の IP が記録されていることが解ります。
さいごに
今回得た知見は以下の通りです。
- HTTP ヘッダの操作は
http-request add-header
又はhttp-request set-header
を利用する - HAProxy 内で変数っぽい設定は
%[req.hdr_ip(X-Forwarded-For,1)]
のように%[ ]
を利用する - 接続元の IP は
req.hdr_ip
オプションでパラメータとしてX-Forwarded-For
を設定することで取得出来る X-Forwarded-For
に含まれている IP アドレスから最初の IP を取得する為にサブパラメータとして数値を指定することで任意の IP アドレスを取得することが出来る
ということで…またまた HAProxy の奥深さを垣間見ることが出来ました。
元記事はこちらです。
「俺のメモ – HAProxy で HTTP ヘッダを操作するメモ(1)〜多段 Proxy 環境でも接続元 IP をオリジナル HTTP ヘッダに挿入する〜」