🔖

AWS CLI を使って Web ACL を作成し CloudFront にアタッチする

7 min read

はじめに

以前、AWS 上での S3 + CloudFront 構成のフロントエンドでメンテナンス状態にする方法を紹介しました。

https://zenn.dev/ysmtegsr/articles/bd9b5935f40d73f80d8a

この際に、メンテナンス中でも特定の IP からのアクセスは許可する方法として WAF を使って行いました。しかし上記の記事で紹介したフローを、メンテナンスの度にやっていては時間がかかりますし、手作業によるミスも発生します。
そこで AWS CLI を使ってスクリプトにしておこうと思いやってみました。

作ったもの

コードは下記に公開しています。全体を見たい方はご覧ください。

https://gist.github.com/ysmtegsr/fa2fead099e7ea376805315dcb2ae7a2

AWS CLI を使う準備

macOS 上で行っていきます。

$ sw_vers
ProductName:    macOS
ProductVersion: 11.0.1
BuildVersion:   20B50

使用するコマンドです。

# AWS CLI
$ aws --version
aws-cli/2.0.6 Python/3.7.4 Darwin/20.1.0 botocore/2.0.0dev10

# JSON を整形するのに jq を使います。
$ jq --version
jq-1.6

WAF を作成

IP set を作成

まずは、Web ACL に使う許可された IP set を作成します。リモートワークで作業している場合などを考えると、複数の IP アドレスを許可したい(個数は場合によって変動する)ということが想定されます。なので、テキストファイルを別で管理します。

XXX.XX.XXX.XX/32
YYY.YY.YYY.YYY/32

次に AWS CLI を使って IP set を作っていきます。

サブコマンドは create-ip-set です。

$ IP_LIST=$(paste -s -d " " ./ip.txt) # こういうふうにスペース区切りで連結される -> "XXX.XX.XXX.XX/32 YYY.YY.YYY.YYY/32"
$ IP_SET=$(aws wafv2 create-ip-set \
    --name "maintenance-developers" \
    --description "This is a test ip set" \
    --scope CLOUDFRONT \
    --region us-east-1 \
    --ip-address-version IPV4 \
    --addresses $IP_LIST)

ここでのポイントは、--scope , --region オプションです。CloudFront に対して Web ACL を適用する場合は、--scope CLOUDFRONT の他に --region us-east-1 を指定する必要があります。

また、--addresses オプションでは先程のテキストファイルを paste コマンドで連結して引数に与えています。

後で使用するため IP_SET という変数に入れています。

下記の Output という項目を見るとどんな値が受け取れるか事前に確認できます。
https://docs.aws.amazon.com/cli/latest/reference/wafv2/create-ip-set.html#output

Output は下記のように Summary オブジェクトが返却されます。
Output
{
    "Summary": {
        "Name": "maintenance-developers",
        "Id": "XXXXX-XXXXX-XXXXX",
        "Description": "This is a test ip set",
        "ARN": "XXXXX-XXXXX-XXXXX",
        "LockToken": "XXXXX-XXXXX-XXXXX"
    }
}

Web ACL を作成

続いて Web ACL を作成していきます。サブコマンドは create-web-acl です。

先んじてルールを定義したテンプレート用の JSON ファイルを用意しておきます。
waf-rule.json
[
  {
    "Name": "project-maintenance",
    "Priority": 0,
    "Statement": {
      "IPSetReferenceStatement": {
        "ARN": ""  // 後の jq の処理でここに先程作成した IP set の ARN が入ります
      }
    },
    "Action": {
      "Allow": {}
    },
    "VisibilityConfig": {
      "SampledRequestsEnabled": true,
      "CloudWatchMetricsEnabled": true,
      "MetricName": "project-maintenance"
    }
  }
]

上のテンプレート用の JSON ファイルを jq で整形し、IP set の Arn を代入して Web ACL 作成コマンドのオプションに指定します。

# jq で Summary.Arn を取り出す
$ IPSET_ARN=$(echo $IP_SET | jq .Summary.ARN)

# 取りだした Arn を付け加えた状態で JSON ファイルを作成する
$ cat ./waf-rule.json | jq '.[].Statement.IPSetReferenceStatement.ARN |= '"${IPSET_ARN}"'' > ./tmp-waf-rule.json

$ WEB_ACL=$(aws wafv2 create-web-acl \
    --name "maintenance" \
    --scope CLOUDFRONT \
    --region us-east-1 \
    --default-action Block={} \
    --visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=MaintenanceWebAclMetrics \
    --rules file://tmp-waf-rule.json)

IP set と同様に --scope CLOUDFRONT , --region us-east-1 をオプションで付与する必要があります。またもや CloudFront へのアタッチ時に使用するため変数に格納しています。

CloudFront へアタッチ

いよいよ作成した Web ACL を該当の CloudFront ディストリビューションにアタッチしていきます。WAF API を見てみると、associate-web-acl というサブコマンドが用意されています。

しかし、これは CloudFront に対しては使用できないようです。

For Amazon CloudFront, don't use this call. Instead, use your CloudFront distribution configuration. To associate a web ACL, in the CloudFront call UpdateDistribution , set the web ACL ID to the Amazon Resource Name (ARN) of the web ACL. For information, see UpdateDistribution.

仕方ないので、CloudFront の update-distribution コマンドを使ってやっていきます。

ただ、コマンドのオプションを見てみるとめちゃくちゃ項目が多くネストしているので、--cli-input-json を使った方が楽そうです。以下の手順でやっていきます。

  1. CloudFront ディストリビューションの構成情報を JSON で取得
  2. 取得した JSON を整形
  3. CloudFront ディストリビューションを変更

では、順番にやっていきます。

CloudFront ディストリビューションの構成情報を JSON で取得

AWS CLI の CloudFront のサブコマンド get-distribution-config を使用します。

$ DISTRIBUTION_ID=XXXXXXXX
$ aws cloudfront get-distribution-config --id $DISTRIBUTION_ID | jq . > ./dist.json

取得した JSON を整形

# 先程作成した Web ACL から ARN を抜き出す
$ WEB_ACL_ARN=$(echo $WEB_ACL | jq .Summary.ARN)

$ ERROR_RESPONSE=$(cat << EOS
{
  "ErrorCode" : 403,
  "ResponsePagePath": "/maintenance.html",
  "ResponseCode": "503",
  "ErrorCachingMinTTL": 0
}
EOS
)

$ cat ./dist.json | jq '. |= .+ {"IfMatch": .ETag} | del(.ETag)
    | .DistributionConfig.CustomErrorResponses.Items |= map((select(.ErrorCode == 403) |= '"$ERROR_RESPONSE"') // .)
    | (.DistributionConfig.WebACLId |= '"${WEB_ACL_ARN}"')' \
    > ./new-dist.json

非常に可読性の低いコードになってしまいました…。ぱっと見何を行っているかわかりにくいので簡単に解説します。

  • ETag
    • get-distribution-config で取得した JSON には ETag プロパティが存在するので jq の del 関数で削除します。
  • CustomErrorResponses
    • メンテナンス状態の場合 503 エラーを返したいため 403 エラー(= IP set に定義していないアクセス)の場合は、503 エラーを返すようにします。
  • WebACLId
    • 作成した Web ACL の ARN を設定します。

そしてその整形した JSON を別ファイルに書き出しています。

CloudFront ディストリビューションを変更

最後に CloudFront の設定を変更します。

$ aws cloudfront update-distribution \
    --id $DISTRIBUTION_ID \
    --cli-input-json file://new-dist.json > ./result.json

以上で完了です。ここまできて S3 + CloudFront で配信しているドメインにアクセスしてみて許可 IP からは通常にアクセスできて、それ以外からはメンテナンス画面が表示されていたら無事に達成です。

まとめ

今回は、S3 + CloudFront 環境下におけるメンテナンス状態を AWS CLI を使って行いました。以下にポイントをまとめます。

  • 簡単にできるだろうと思ったら AWS CLI では必須オプションが多かったり、ユースケースによってはコマンドが使えなかったりして大変だった。
  • ただ、一度書いてしまえば 2 回目からはスクリプトを実行するだけなので、手作業よりは遥かに効率的できた。
  • JSON の整形に jq が非常に便利だが、可読性も考慮したい。

ここまで読んでくださりありがとうございます。
他にも「こういった方法があるよ」「こっちが楽にできるよ」などありましたらコメントいただけると幸いです。

参考にさせていただいたサイト

https://docs.aws.amazon.com/cli/latest/reference/wafv2/index.html
https://docs.aws.amazon.com/cli/latest/reference/cloudfront/index.html#cli-aws-cloudfront
https://qiita.com/takeshinoda@github/items/2dec7a72930ec1f658af
https://www.setouchino.cloud/blogs/119
https://qiita.com/pickles/items/aa5b6d5453a84511db21
GitHubで編集を提案

Discussion

ログインするとコメントできます