DIVX テックブログ

catch-img

AWS CloudFormationとLambdaでWAFのルールを動的に管理する


目次[非表示]

  1. 1.はじめに
    1. 1.1.概要
    2. 1.2.こういう方におすすめ
    3. 1.3.各サービスの説明
      1. 1.3.1.CloudFormation
      2. 1.3.2.Lambda
      3. 1.3.3.WAF WebACL
      4. 1.3.4.WAF RuleGroup
  2. 2.手順
    1. 2.1.CfnテンプレートでWebACLを作成
    2. 2.2.別のCfnテンプレートでRuleGroupを作成
    3. 2.3.Lambda関数を作成・実行
  3. 3.おわりに
    1. 3.1.メリット
    2. 3.2.お悩みご相談ください

はじめに

概要

この記事では、AWSのCloudFormation(以下、Cfn)とLambdaを使用し、 WAF WebACL(以下、WebACL)に複数のルールグループを動的に関連付ける方法を紹介します。

こういう方におすすめ

  • 開発環境ごとに異なるルールを設けるが、WebACLは同じものを共用で使いたい
  • 開発環境(案件メンバーなど)の数が多く、手動で関連付けるのは煩わしい

各サービスの説明

CloudFormation

  • AWSリソースの管理を自動化するためのサービス
  • リソースをコードとして定義することで、AWS環境を一貫して構築、更新、削除することができる
  • Infrastructure as Code(IaC)アプローチにより、環境の一貫性が保たれ、リソースを管理しやすくなる
  • https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html

Lambda

  • サーバーレスコンピューティングサービスで、コードをサーバーを意識することなく実行できる環境を提供する
  • 特定のイベント(API Gatewayのリクエスト、S3のオブジェクト作成、CloudWatchのスケジュールイベントなど)に応じて自動的にトリガーされる関数を実行できる
  • インフラストラクチャの管理を大幅に削減し、拡張性の高いアプリケーションやプロセスを迅速に構築できる
  • https://docs.aws.amazon.com/lambda/latest/dg/welcome.html

WAF WebACL

  • AWS WAF(Web Application Firewall)の中で、ウェブアプリケーションへのトラフィックをフィルタリングするための複数のルールをまとめたもの
  • 特定のIPアドレスからのトラフィックの許可や拒否、特定のリクエストパターンの検査などを統合的に行うことができる
  • https://docs.aws.amazon.com/waf/latest/developerguide/web-acl.html

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') # または'CLOUDFRONT'
        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', # または'CLOUDFRONT'
        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)

    # update WebACL
    waf_client.update_web_acl(
        Name=web_acl_name,
        Scope='REGIONAL', # または'CLOUDFRONT'
        Id=web_acl_id,
        DefaultAction=web_acl['DefaultAction'],
        Rules=new_rules,
        VisibilityConfig=web_acl['VisibilityConfig'],
        LockToken=response['LockToken']
    )

    # delete rule groups
    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', # または'CLOUDFRONT'
                    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

最後までご覧いただき、ありがとうございました!

お気軽にご相談ください


ご不明な点はお気軽に
お問い合わせください

サービス資料や
お役立ち資料はこちら

DIVXブログ

テックブログ タグ一覧

採用ブログ タグ一覧

人気記事ランキング

GoTopイメージ