2023.01.07  

【Terraform】CloudFrontを経由してALBに接続する(WAFv2)

AWS,  Terraform    

CloudFrontを経由してパブリックALBに接続する際、ALBは基本的にCloudFrontからの通信のみ受け付ける設定にしたい場合の実装方法についてメモ書きします。

実装概要

今回Terraformで実装するのは下記の図で言うと、CloudFront --> ALB(※) --> WAF間の設定です。

※ メインの設定はWAFCloudFrontに記載するので、ALBについてはTerraformの記載を省略しています

WAFは特定のIPアドレスからのみ通信を受け付けるという設定が行えます。

しかし、今回はHTTPのヘッダーに特定のカスタムヘッダーが含まれる場合のみ通信を許可するように設定します。

具体的には、CloudFrontから送信されたデータにカスタムヘッダー「access-token」を含むように設定し、その値が特定の文字列(今回は55zABc1B123456C)である場合にのみALBへの通信を許可するように設定します。

特定の文字列については、terraformのresource「random」を使用して生成します。

生成した値はSystems Managerのパラメータストアに保存する運用とします。

注意書き

若干趣旨から逸れるので、ALBとRoute53回りのTerraform実装は本記事では省略します。

なので前提としては、ALBは事前に作成しておき、ALBに接続するためのドメイン(alb-xxx123.com)もRoute53等で設定、紐付けしておきます。

また、TerraformでCloudFrontの作成が終わったら、Route53等で作成しておいたドメイン(xxx123.com)のAレコードを任意の方法でCloudFrontに紐付けるものとします。

※ ドメインの証明書周りの設定も実施済みである前提

実装

この章ではterraformの実装を記載します。
コードが長いので分割して記載しますが、いずれもひとつのファイルにコードを書いている前提です。

最初にカスタムヘッダー「access-token」(次のコードで記載)の値に設定するランダムな文字列を生成するコードを記載します。

ランダム文字列の管理にはSystems Managerのパラメータストアを利用しています。

// ランダムな文字列を出力
resource "random" "this" {
  length      = 15 // 文字列の長さを設定
  min_lower   = 1  // 結果に含まれる小文字のアルファベットの最小数。デフォルト値は0
  min_upper   = 1  // 結果に含まれる大文字のアルファベット文字の最小数。デフォルト値は0 
  min_numeric = 1  // 結果の数字の最小数。デフォルト値は0 
  special     = false // 結果に特殊文字を含めます。これらは !@#$%&*()-_=+[]{}<>:? です。デフォルト値は true
}

//AWS Systems Managerのパラメータストアにランダムな文字列を保存
resource "aws_ssm_parameter" "this" {
  // パラメータストアの名前
  name        = "test-alb-web-acl-access-token"
  // 任意:説明文
  description = "test"
  // 文字列をシークレットな値として保護
  type        = "SecureString"
  // 上記処理で作成したランダムな文字列を設定
  value       = random.this.result

  lifecycle {
    // 2回目以降のterraform apply時にランダムな文字列が上書きされないようにする
    ignore_changes = [value]
  }
}

次にWAFv2の作成を行います。
ALBは作成済みである前提とし、arnはvar.target_resource_arnから取得する想定としています。

下記のsingle_headerでカスタムヘッダーの名前を指定しており「access-token」としています。
search_stringには上記で作成したランダムな文字列を指定しています。

resource "aws_wafv2_elb_acl" "this" {
  // WAFv2の「Web ACLs」に表示される名前
  name        = "test-alb-web-acl"
  // ALBが対象の場合はREGIONAL
  scope       = "REGIONAL"
  // 任意: Web ACLsの説明文
  description = "WAF ACL for ALB"

  default_action {
    block {}
  }

  rule {
    // WAFv2の「Web ACLs」に設定できる「Rule」の名前
    name     = "matched-allow-header-rule"
    // 設定の優先度
    priority = 1

    action {
      allow {}
    }

    statement {
      byte_match_statement {
        field_to_match {
          single_header {
            // カスタムヘッダーの名前を設定
            name = "access-token"
          }
        }
        // 「Rule」の設定値Positional constraintの設定。下記は「Exactly matches string」(文字列の完全一致)を設定
        positional_constraint = "EXACTLY"
        // single_headerで設定したカスタムヘッダーにランダムな文字列を設定
        search_string         = aws_ssm_parameter.this.ssm_param_value //上記で作成したリソース

        // search_stringを検査する前に文字列変換をするか設定。*OS間のパス区切り(/ or ¥)の違いなどを吸収するために使用
        text_transformation {
          priority = 1
          type     = "NONE"
        }
      }
    }
    // ブロック毎に設定が必要
    visibility_config {
      // 必須:関連付けられたリソースがメトリクスを CloudWatch に送信するかどうか
      cloudwatch_metrics_enabled = false
      // 必須:CloudWatch メトリクスのわかりやすい名前
      metric_name                = "matched-allow-header-rule"
      // 必須:AWS WAF がルールに一致するウェブリクエストのサンプリングを保存するかどうか。サンプリングされたリクエストは、AWS WAF コンソールから表示できます
      sampled_requests_enabled   = false
    }
  }
  // ブロック毎に設定が必要
  visibility_config {
    cloudwatch_metrics_enabled = false
    metric_name                = "test-alb-web-acl"
    sampled_requests_enabled   = false
  }
}

// wafv2とALBを紐付けする
resource "aws_wafv2_elb_acl_association" "this" {
  resource_arn = var.target_resource_arn // ALBのarnを設定する
  web_acl_arn  = aws_wafv2_elb_acl.this.arn //上記で作成したリソース
}

最後にcloudfrontの作成を行います。

cloudfrontからALBに接続するためのドメインは事前に作成しており、alb-xxx123.comとしていることが前提となります。

下記のcustom_headerの設定で指定したヘッダー名と値が、CloudFrontからALBに通信を行う度にCloudFront側で付与されるような仕組みとなっています。

※ CloudFrontの設定はかなり省略して記載しているため、apply時にエラーとなる可能性があります

resource "aws_cloudfront_distribution" "this" {
  // ディストリビューションがコンテンツに対するエンド ユーザーの要求を受け入れることができるかどうか。
  enabled             = true
  // ディストリビューションで IPv6 が有効になっているかどうか。
  is_ipv6_enabled     = false
  // 追加の CNAME (代替ドメイン名)を設定
  aliases             = ["xxx123.com"]

  origin {
    // albに設定したドメイン名を設定
    origin_id   = "alb-xxx123.com"
    domain_name = "alb-xxx123.com"

    // オリジンに送信されるヘッダーデータを指定できる
    custom_header {
      name  = "access-token"
      value = aws_ssm_parameter.this.ssm_param_value // randamリソースで自動生成した文字列
    }
  }
}

これらのコードをterraform applyしたら、最後にRoute53等で作成しておいたドメイン(xxx123.com)のAレコードをCloudFrontに紐付けます。

紐付けが終わり、https://xxx123.comにアクセスするとCloudFrontから通信を受け付ける設定となります。

普通にalb-xxx123.comにアクセスしてもALBには繋がりません。

ちなみに、直接ALBにアクセスしたい時は、次のようにヘッダーにアクセストークンを設定してalb-xxx123.comに通信をなげるとレスポンスが返ってきます。


※ 画像のツールはPostman

コメント
現在コメントはありません。
コメントする
コメント入力

名前 (※ 必須)

メールアドレス (※ 必須 画面には表示されません)

送信