Blog

BASIC認証とリダイレクトをTerraformで管理するCloudFront FunctionsでのBASIC認証およびリダイレクト

岡本 渉 技術ブログ

ちょっと前まで大雪で寒かったのに、もうだいぶ暖かくなってきましたね。花粉が飛散しだすのでは無いかと戦々恐々としています。

さて、サイト移行のときって、だいたいこの2つが同時に来ませんか。

  • ステージングだけBasic認証をかけたい
  • 旧URLや旧ドメインからのリダイレクトを大量にさばきたい
今回はこの2つを CloudFront Functions の viewer-request でまとめて処理し、Terraformで管理した構成を紹介します。

やりたいこと

やりたいことはシンプルです。

  1. Basic認証の判定
  2. 旧ドメインから正式ドメインへの301
  3. HTTPからHTTPSへの301
  4. apexからcanonical hostへの301(`/.well-known/*` は除外)
  5. KeyValueStoreにあるパスリダイレクトの適用
  6. .../ を .../index.html に書き換え

この順序にしておくと、無駄なオリジン到達を減らせます。ループの温床も作りにくいです。

構成

  • CloudFront Distribution
  • CloudFront Function(`viewer-request`)
  • CloudFront KeyValueStore(リダイレクト辞書)
  • S3
  • Terraform module

Terraform側の定義

Function本体はテンプレートに分離し、Terraformから値を注入します。
resource "aws_cloudfront_function" "rewrite_index" {
  name                         = local.rewrite_function_name
  runtime                      = "cloudfront-js-2.0"
  comment                      = "Apply redirects and append index.html"
  publish                      = true
  key_value_store_associations = [aws_cloudfront_key_value_store.redirects.arn]

  code = templatefile("${path.module}/function.js.tftpl", {
    legacy_host_redirect_from = var.legacy_host_redirect_from
    legacy_host_redirect_to   = var.legacy_host_redirect_to
    apex_host                 = var.apex_host
    canonical_host            = var.canonical_host
    basic_auth_enabled        = var.basic_auth_enabled
    basic_auth_header         = local.basic_auth_authorization_header
  })
}

viewer-request に関連付けるのがポイントです。オリジンへ到達する前の段階で判定できます。

default_cache_behavior {
  # ...
  function_association {
    event_type   = "viewer-request"
    function_arn = aws_cloudfront_function.rewrite_index.arn
  }
}

Function実装のイメージ

実装イメージはこんな感じです。
import cf from "cloudfront";

const kvs = cf.kvs();
const BASIC_AUTH_ENABLED = true; // Terraformから注入
const BASIC_AUTH_HEADER = "Basic <base64(username:password)>";

function createRedirectResponse(location) {
  return {
    statusCode: 301,
    statusDescription: "Moved Permanently",
    headers: {
      location: { value: location }
    }
  };
}

function createUnauthorizedResponse() {
  return {
    statusCode: 401,
    statusDescription: "Unauthorized",
    headers: {
      "www-authenticate": { value: 'Basic realm="Restricted"' }
    }
  };
}

async function handler(event) {
  const request = event.request;
  const uri = request.uri || "/";

  // 1) Basic auth
  if (BASIC_AUTH_ENABLED) {
    const auth = request.headers.authorization?.value || "";
    if (auth !== BASIC_AUTH_HEADER) {
      return createUnauthorizedResponse();
    }
  }

  // 2-4) host/protocol redirect(省略)

  // 5) path redirect by KVS
  try {
    const target = await kvs.get(uri);
    if (target) {
      return createRedirectResponse(target);
    }
  } catch (_e) {
    // KVS miss時は通常フローを継続
  }

  // 6) trailing slash rewrite
  if (request.uri.endsWith("/")) {
    request.uri += "index.html";
  }

  return request;
}

リダイレクトは .htaccess を正本にする

既存運用で .htaccess があるなら Redirect permanent を抽出して KVS に流し込む運用が楽です。
locals {
  htaccess_content        = file(var.htaccess_file_path)
  htaccess_redirect_lines = regexall(
    "(?m)^Redirect\\s+permanent\\s+(\\S+)\\s+(\\S+)\\s*$",
    local.htaccess_content
  )
  htaccess_redirect_pairs = [
    for m in local.htaccess_redirect_lines : {
      source = m[0]
      target = m[1]
    }
  ]
}

環境差分

同じmoduleを使い、本番環境・ステージング環境の差分は tfvars に寄せます。
# staging
basic_auth_enabled  = true
basic_auth_username = ""
basic_auth_password = ""
# production
basic_auth_enabled = false

動作確認

最低限、ここは確認しておくと安心です。
# Basic auth未指定のため401
curl -I https://stg.example.com/

# Basic auth指定あり
curl -I -u ':' https://stg.example.com/

# 旧URLから301
curl -I https://www.legacy.example.com/old-path/

# HTTPからHTTPSへ301
curl -I http://www.example.com/some-path/

注意点

  • Basic認証の資格情報は `tfvars` に平文で置かない
  • CI/CDのSecret、SSM Parameter Store、Secrets Managerなどから注入する
  • 301ルールに循環参照がないか事前チェックする
  • Query stringを保持して転送し、トラッキング欠落を防ぐ

まとめ

CloudFront Functionsを viewer-request で使うと、Basic認証、リダイレクト、URL正規化をエッジに寄せて運用できます。

移行フェーズでリダイレクトが多い案件だと、.htaccess を正本にして KVS に反映する構成はかなり扱いやすいです。
Terraformでmodule化しておけば、環境差分の管理も素直にできます。

現場からは以上です。
一覧にもどる