CloudFront lambda@Edge で、Basic認証を設定する

S3+CloudFrontなど、サーバーレスでBasic認証をかける方法を何年も検討していたのですが、CloudFrontにLambdaを割り当てられるようになり、試したところ実装することがで来ました。

まだPreview版ですが、フォームから申請することで、数日で許可が得られます。

イベントの種類

まず仕組みとしては、以下4つのイベントに、機能が絞られたLambda@Edgeを割り当てることがで来ます。

Viewer リクエスト

リクエスト時に呼ばれるイベント

Viewer レスポンス

レスポンス時に呼ばれるイベント

Origin リクエスト

オリジナルデータにリクエストが到達した時に呼ばれるイベント

Origin レスポンス

オリジナルデータからのレスポンス時に呼ばれるイベント

リクエストとレスポンスの違い

レスポンスの場合
  • レスポンスヘッダーの参照
  • レスポンスヘッダーの書き換え
  • レスポンスを返す

こちらはあまり使うことを検討していないので、もしかしたら他の機能もあるかもしれません。

リクエストの場合
  • リクエストヘッダーの参照
  • Origin へのリクエストヘッダーの書き換え
  • Origin の切り替え
  • Origin にアクセスせず、その場でレスポンスを返す

status が callback に渡した連想配列に含まれているかどうかで、Originへアクセスするかどうかの挙動が変わるようです。



今回は Viewer リクエストを使います。
Basic 認証の仕様に合わせてヘッダーを返すことで、Basic認証を実装することがで来ました。

exports.handler = (event, context, callback) => {
    
    const Allows = {
        "users": "users",
    };
    
    const request = event.Records[0].cf.request;
    const headers = request.headers;
    const authorization = headers.authorization || headers.Authorization;

    if (authorization) {
        
        const enc = authorization[0].split(" ")[1];
        const userPassword = new Buffer(enc, 'base64').toString();
        
        for (var key in Allows) {
            var val = Allows[key];
            if (`${key}:${val}` === userPassword) {
                callback(null, request);
                return
            }
        }
    }

    const response = {
        status: '401',
        statusDescription: 'Authorization Required',
        httpVersion: request.httpVersion,
        headers: {
          "WWW-Authenticate": ['Basic realm="Enter username and password."'],
          "Content-Type": ["text/plain; charset=utf-8"],
        },
        body: "401 Authorization Required",
    };

    callback(null, response);
};

event のサンプル

※ event に入ってくるデータは、以下のように小文字でくることもあれば、大文字始まりで受け取る場合もあるので、どちらにも対応しなければならないようです。

{
  "host":["dummy.dayo"],
  "authorization":["Basic dXNlcnM6dXNlcnM="],
  "cache-control":["no-cache"],
  "user-agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"],
  "accept":["*/*"],
  "accept-encoding":["gzip, deflate, sdch, br"],
  "accept-language":["ja,en;q=0.8,en-US;q=0.6"]
}


ログがでないと言うことと、Lambda@Edgeを割り当てたときだけでなく、コードを書き換えたあとも、数分待ち時間があることで、確認が大変でした。

ElasticBeanstalk+CloudFrontのときにも、このBasic認証の設定が活躍するのでうれしいです。