はじめに
AWSでサーバレスなアーキテクチャを採用していて、 各エンドポイント(CloudFront や API Gateway)にIP制限をかけたい場合 AWS WAF を用いることが多いかと思います。
この記事では、CloudFormation から各リソースと WAF のリソースを生成する際にハマりポイントが多かったのでまとめてみました。
AWS::WAF と AWS::WAFRegional
まずハマりやすいのが、この2つのリソースタイプがあること。
設定できるリソースやパラメータにあまり違いはないのですが、使い分けとしては、リージョンを持つサービスに紐づけるか、リージョンを持たないサービス(CloudFront)に紐づけるか、でどちらを選択するか決まります。
- AWS::WAFRegional → API Gateway, ALB, etc...
- AWS::WAF → CloudFront
と考えておけばOKだと思います。(これを逆にすると権限のエラーで失敗します。)
AWS WAF と AWS WAF Classic
この2つの違いも初見の人は注意が必要です。
AWS WAF は2019年11月にアップデートされていて、コンソールからサービスを利用する場合、はじめに新バージョンのサービスが表示されます。
旧バージョンの AWS WAF を利用する場合、左部メニューにこっそりある Switch to AWS WAF Classic
をクリックすると切り替わります。
ここでハマりどころなのが、 CloudFormation のリソースタイプの AWS::WAF
と AWS::WAFRegional
は、どちらも旧バージョン(AWS WAF Classic)のリソースを作成するものであるということです。
AWSサポートに確認したわけではないので確かではありませんが、現時点(2019年12月時点)では、CloudFormation から新バージョンの AWS WAF のリソースを生成することはできなそうです。(今後対応されそう)
template ファイル
今回、実際に CloudFront と API Gateway それぞれに紐づいた AWS WAF (Classic) を CloudFormation から生成しました。
その際、template の記述に関しても注意すべき点があったので、 コメントを追記した template.yaml
を載せておきます。
共通
IP制限を行う際に、 WebACL
+ Rule
+ IPSet
のリソースをセットで使用するのは、 AWS::WAF
も AWS::WAFRegional
も同様。
AWS::CloudFront + AWS::WAF パターン
AWS::WAF
の場合は、 CloudFront のリソース側から紐付けを行います。
Parameters:
AppName:
Type: String
AllowedIP:
Type: String
Resources:
MyDistribution:
Type: AWS::CloudFront::Distribution
Properties:
# 〜その他のプロパティは省略〜
DistributionConfig:
WebACLId: !Ref MyWebACL #紐付けはここで行う
# AWS::WAF の場合、リソースの紐付けに WebACLAssociation は使用しない
MyWebACL:
Type: AWS::WAF::WebACL
Properties:
Name: !Sub ${AppName}WebACL #ハイフンやアンスコは使用不可
DefaultAction:
Type: BLOCK
MetricName: !Sub ${AppName}WebACL
Rules:
-
Action:
Type: ALLOW
Priority: 1
RuleId: !Ref MyRule
MyRule:
Type: AWS::WAF::Rule
Properties:
Name: !Sub ${AppName}AllowedRule #ハイフンやアンスコは使用不可
MetricName: !Sub ${AppName}AllowedRule
Predicates:
-
DataId: !Ref MyIPSet
Negated: false
Type: IPMatch
MyIPSet:
Type: AWS::WAF::IPSet
Properties:
Name: !Sub ${AppName}AllowedIPSet #ハイフンやアンスコは使用不可
IPSetDescriptors:
-
Type: IPV4
Value: !Sub ${AllowedIP}/32 #IPは末尾に「/32」を付与
AWS::Serverless::Api + AWS::WAFRegional パターン
AWS::WAFRegional
の場合、 WebACLAssociation
というリソースを利用して紐付けを行います。
公式ドキュメントでは、ロードバランサーとの紐付けの例しかありませんが、API Gateway と紐付けを行う場合は以下のように記述します。
Parameters:
AppName:
Type: String
Stage:
Type: String
AllowedIP:
Type: String
Resources:
MyApi:
Type: AWS::Serverless::Api
Properties:
# 〜その他のプロパティは省略〜
StageName: !Ref Stage
MyWebACLAssociation: #紐付けはこのリソースで行う
Type: "AWS::WAFRegional::WebACLAssociation"
Properties:
ResourceArn: !Sub arn:aws:apigateway:${AWS::Region}::/restapis/${MyApi}/stages/${Stage}
# ResourceArn は !Ref MyApi ではないので注意
WebACLId: !Ref MyWebACL
MyWebACL:
Type: AWS::WAFRegional::WebACL
Properties:
Name: !Sub ${AppName}APIWebACL #ハイフンやアンスコは使用不可
DefaultAction:
Type: BLOCK
MetricName: !Sub ${AppName}APIWebACL
Rules:
-
Action:
Type: ALLOW
Priority: 1
RuleId: !Ref MyRule
MyRule:
Type: AWS::WAFRegional::Rule
Properties:
Name: !Sub ${AppName}APIAllowedRule #ハイフンやアンスコは使用不可
MetricName: !Sub ${AppName}APIAllowedRule
Predicates:
-
DataId: !Ref MyIPSet
Negated: false
Type: IPMatch
MyIPSet:
Type: AWS::WAFRegional::IPSet
Properties:
Name: !Sub ${AppName}APIAllowedIPSet #ハイフンやアンスコは使用不可
IPSetDescriptors:
-
Type: IPV4
Value: !Sub ${AllowedIP}/32 #IPは末尾に「/32」を付与
最後に
今回は、サーバレスアーキテクチャでの CloudFormation を用いた WAF の設定のハマりポイントを解説しました。
AWSの公式ドキュメントは本当にわかりづらいので、同じようなことでハマっている人は参考にしていただければ幸いです。