はじめに
概要
この記事では、AWSのCloudFormation(以下、Cfn)とLambdaを使用し、 WAF WebACL(以下、WebACL)に複数のルールグループを動的に関連付ける方法を紹介します。
こういう方におすすめ
- 開発環境ごとに異なるルールを設けるが、WebACLは同じものを共用で使いたい
- 開発環境(案件メンバーなど)の数が多く、手動で関連付けるのは煩わしい
各サービスの説明
CloudFormation
Lambda
WAF WebACL
WAF RuleGroup
手順
CfnテンプレートでWebACLを作成
AWSTemplateFormatVersion: '2010-09-09'
Description: AWS WAF WebACL with CloudFormation
Resources:
# WAF - WebACL
WebACL:
Type: "AWS::WAFv2::WebACL"
DeletionPolicy: Retain # またはDelete
Properties:
DefaultAction:
Allow: {}
Scope: "REGIONAL" # または'CLOUDFRONT'
VisibilityConfig:
CloudWatchMetricsEnabled: true
MetricName: "WebACL"
SampledRequestsEnabled: true
Rules:
- Name: "IPBlockRule"
Priority: 1
Action:
Block: {}
Statement:
IPSetReferenceStatement:
Arn: !GetAtt IPSet.Arn
VisibilityConfig:
CloudWatchMetricsEnabled: true
MetricName: "IPBlockRule"
SampledRequestsEnabled: true
# WAF - IPSet
IPSet:
Type: "AWS::WAFv2::IPSet"
DeletionPolicy: Retain # またはDelete
Properties:
Name: "BlockedIPs"
Scope: "REGIONAL" # または'CLOUDFRONT'
IPAddressVersion: "IPV4"
Addresses:
- "198.144.172.14/32"
「テンプレートファイルのアップロード」から上記のコードを添付
別のCfnテンプレートでRuleGroupを作成
- RuleGroupのARNをParameterStoreに保管
AWSTemplateFormatVersion: '2010-09-09'
Description: AWS WAF RuleGroup with CloudFormation
Resources:
# WAF - RuleGroup
MyRuleGroup:
Type: 'AWS::WAFv2::RuleGroup'
Properties:
Capacity: (実際の仕様に合わせて設定)
Scope: 'REGIONAL' # または'CLOUDFRONT'
VisibilityConfig:
CloudWatchMetricsEnabled: true
MetricName: !Sub 'MetricName${AWS::StackName}'
SampledRequestsEnabled: true
Description: !Sub 'RuleGroup for ${AWS::StackName}'
Name: !Sub 'RuleGroup${AWS::StackName}'
# 実際の仕様に合わせて変更
Rules:
- Name: 'ExampleRule'
Priority: 1
Action:
Block: {}
Statement:
ByteMatchStatement:
SearchString: 'example'
FieldToMatch:
UriPath: {}
TextTransformations:
- Priority: 0
Type: NONE
PositionalConstraint: 'CONTAINS'
VisibilityConfig:
CloudWatchMetricsEnabled: true
MetricName: !Sub 'RuleMetric${AWS::StackName}'
SampledRequestsEnabled: true
# SessionManager - ParameterStore
SaveRuleGroupArnParameter:
Type: 'AWS::SSM::Parameter'
Properties:
Name: !Sub '/waf/rulegroup/arn/${AWS::StackName}'
Type: 'String'
Value: !GetAtt MyRuleGroup.Arn
WAFのRuleGroupsは生成されるが、WebACLには関連付けられていない
その処理は後述のLambda関数にて別途実施する
Lambda関数を作成・実行
import json
import boto3
waf_client = boto3.client('wafv2')
ssm_client = boto3.client('ssm')
ssm_path = '/waf/rulegroup/arn'
rule_group_name_filter = '(RuleGroupに共通する文字)'
web_acl_name = '(実際のWebACLの名前)'
web_acl_id = '(実際のWebACLのID)'
def lambda_handler(event, context):
response = ssm_client.get_parameters_by_path(
Path=ssm_path,
Recursive=True,
MaxResults=(実際の仕様に合わせて設定)
)
rule_group_arns = [param['Value'] for param in response['Parameters']]
existing_rule_groups = list_rule_groups(rule_group_name_filter)
update_web_acl(rule_group_arns, existing_rule_groups)
def list_rule_groups(name_filter=None):
try:
response = waf_client.list_rule_groups(Scope='REGIONAL')
rule_groups = response['RuleGroups']
if name_filter:
rule_groups = [rg for rg in rule_groups if name_filter in rg['Name']]
print("Filtered Rule Groups:")
for rg in rule_groups:
print(f"Name: {rg['Name']}, ARN: {rg['ARN']}, Id: {rg['Id']}, LockToken: {rg['LockToken']}")
return [{'ARN': rg['ARN'], 'Name': rg['Name'], 'Id': rg['Id'], 'LockToken': rg['LockToken']} for rg in rule_groups]
except Exception as e:
print(f"Error retrieving rule groups: {e}")
return []
def update_web_acl(rule_group_arns, existing_rule_groups):
response = waf_client.get_web_acl(
Name=web_acl_name,
Scope='REGIONAL',
Id=web_acl_id
)
web_acl = response['WebACL']
print(f"rules: {web_acl['Rules']}")
new_rules = [
{
'Name': f'RuleGroupReference{index}',
'Priority': index + 1,
'Statement': {
'RuleGroupReferenceStatement': {
'ARN': arn
}
},
'VisibilityConfig': {
'CloudWatchMetricsEnabled': True,
'MetricName': f'RuleGroupMetric{index}',
'SampledRequestsEnabled': True
},
'OverrideAction': {
'None': {}
}
}
for index, arn in enumerate(rule_group_arns)
]
rules_to_delete = [rule for rule in web_acl['Rules'] if rule['Statement']['RuleGroupReferenceStatement']['ARN'] not in rule_group_arns]
if rules_to_delete:
for rule in rules_to_delete:
web_acl['Rules'].remove(rule)
waf_client.update_web_acl(
Name=web_acl_name,
Scope='REGIONAL',
Id=web_acl_id,
DefaultAction=web_acl['DefaultAction'],
Rules=new_rules,
VisibilityConfig=web_acl['VisibilityConfig'],
LockToken=response['LockToken']
)
for existing_rule_group in existing_rule_groups:
arn = existing_rule_group['ARN']
if arn not in rule_group_arns:
try:
rule_group_name = existing_rule_group['Name']
rule_group_id = existing_rule_group['Id']
lock_token = existing_rule_group['LockToken']
response = waf_client.delete_rule_group(
Name=rule_group_name,
Scope='REGIONAL',
Id=rule_group_id,
LockToken=lock_token
)
print(f"Deleted rule group: {arn}")
except Exception as e:
print(f"Error deleting rule group {arn}: {e}")
おわりに
メリット
Lambda関数の実行時に、ParameterStoreから指定したパスに関連する パラメータの値を取得し、必要に応じてrule_group_arnsリストを更新します。
また、作成したLambdaのファイル内では、WebACLに関連付いていないルールグループを 事前に取得したルールグループのリストと照らし合わせて削除する関数も設定しています。
そのため、開発環境が増えた場合だけでなく、減った場合にも柔軟に対応できます。
お悩みご相談ください
インターネット上にこのような内容の記事が少なく、作業には少し手間取ってしまいましたが、 似た悩みを抱える方々の参考になれば幸いです。
その他、何かお悩みのことがございましたら、以下のフォームよりお気軽にご連絡ください。
https://www.divx.co.jp/contact
最後までご覧いただき、ありがとうございました!