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 が見つからない、、。