Varnish の設定ファイルを使って Basic 認証を実装する。
Varnish 本体に Basic 認証の機能はない。VCL を使って、Basic 認証を実装することならできる。
原始的な実装。
Authorizationヘッダが一致しなかったら401エラーを出す。
backend apache { .host = "127.0.0.1"; .port = "80"; } # vcl_recv は、クライアントからリクエストを受け取った時に実行される。 sub vcl_recv { set req.backend = apache; # Authorization ヘッダが一致しなかったら401エラー。 if (req.http.Authorization != "Basic dXNlcjpwYXNz" && req.http.Authorization != "Basic dXNlcjI6cGFzczI=" ) { error 401; } return (lookup); } # vcl_error はバックエンドもしくは VCL 内部でエラーが発生したら実行される。 sub vcl_error { if (obj.status == 401) { # WWW-Authenticate ヘッダで Basic 認証を要求する。 set obj.http.WWW-Authenticate = {"Basic realm="Authorization Required""}; synthetic {"Error 401 Unauthorized"}; return(deliver); } }
これで user/pass もしくは user2/pass2 でログインできる。
設定ファイルで認証機能を実装できてしまう。Varnish すごい。
Authorizationヘッダは次のようにして生成する。
$ echo -n "user:pass" | base64 dXNlcjpwYXNz $ echo -n "user2:pass2" | base64 dXNlcjI6cGFzczI=
Mac なら gbase64 が入ってるかもしれない。
Varnishの認証とバックエンドの認証を分ける。
vcl_error は VCL のエラーと、バックエンドのエラーを区別しない。
バックエンドの 401 エラーをそのまま返したい場合は、内部用に適当なHTTPステータスを設定すると良い。
例えば上の error 401; を error 701; に変えて、
sub vcl_error { if (obj.status == 701) { set obj.status = 401; set obj.response = "Unauthorized"; set obj.http.WWW-Authenticate = {"Basic realm="Authorization Required""}; synthetic {"Error 401 Unauthorized"}; return(deliver); } }
とする。
内部用とはいえ、勝手にステータスコードを増やすのはためらわれるが、、。wiki にも例が載っている。
平文のパスワードをハッシュにするため VMOD をインストールする。
上の設定ファイルには Base64 エンコードされただけの生のパスワードが書かれている。これではセキュリティ上よろしくない。せめてハッシュにしておきたい。
Varnish 3.0 から VMOD というプラグイン機能が使えるようになった。
Authentication Module というのが載っていて、おっと思ったが、リンクが切れている。
Digest module を使って、認証ヘッダをハッシュにすることができた。
コンパイルにはmhashが必要。手元のMacだと、
port installed | grep mhash
で見つかるのだが、dylibをリンクする方法がわからず、、 /usr/local にソースから入れた。
Digest module のインストールは、
./configure VARNISHSRC=/usr/local/src/varnish-3.0.2 VMODDIR=/usr/local/varnish-3.0.2/lib/varnish/vmods make sudo make install
とした。VMODDIR は varnishadm を使い、param.show コマンドで確認できる。
/usr/local/varnish/bin/varnishadm -S ./secret -T :6082 param.show
ドキュメントの生成にrst2manが必要と言われるが、無視した。
Digest モジュールを使って、 Authorization ヘッダをハッシュにする。
# モジュールを読み込む。 import digest; backend apache { .host = "127.0.0.1"; .port = "80"; } sub vcl_recv { set req.backend = apache; # アカウント毎にハッシュを生成するのは無駄なので、ヘッダを一時変数代わりに使う。 set req.http.X-Varnish-Basic-Auth = digest.hash_sha1("SALT " + req.http.Authorization); if (req.http.X-Varnish-Basic-Auth != "07af5b04d92d17b077d86a847cd683ea123503df" && req.http.X-Varnish-Basic-Auth != "264fdc6e41d3526dd282671acb4cc1cd8d670a73") { unset req.http.X-Varnish-Basic-Auth; error 401; } unset req.http.X-Varnish-Basic-Auth; return (lookup); } sub vcl_error { if (obj.status == 401) { set obj.http.WWW-Authenticate = {"Basic realm="Authorization Required""}; synthetic {"Error 401 Unauthorized"}; return(deliver); } }
こんな感じ。
Varnishにはローカル変数がないので、ハッシュを一旦ヘッダに設定し、使い終わったら消している。本当に効率がいいかはわからない。数回ならハッシュを計算したほうが速いかもしれない。
ハッシュの生成は、
#! /bin/bash SALT="SALT" PASS=`echo -n "$1" | base64` echo "Basic $PASS" echo -n "$SALT Basic $PASS" | sha1sum
で行った。申し訳程度にSALTを設定してみた。
手元だと、hash_md5 はうまく動かなかった。hash_sha1 はうまく動いたが、これで運用してみたわけではないのでご注意を。
その他の方法
認証機能だけを一旦バックエンドに送り、認証に成功したら restart を使ってコンテンツを取得し直す、という例をどこかで見た。けど URL が見つからない、、。