Evidence Collection Script for AWS Resources: AWS WAF

2 min read

Introduction

This is the fourth part of my attempt to create scripts for collecting evidence about AWS resources. AWS WAF is often enabled in multiple regions. In that case, collecting evidence region by region is tedious, so I created the script below.

This script has been confirmed to work only in CloudShell.

Script

#!/bin/bash

# Get a list of all regions
regions=$(aws ec2 describe-regions --query 'Regions[].RegionName' --output text)

for region in $regions; do
    echo -e "\n Region: $region"
    
    # Create folders for each region
    mkdir -p "$region"
    
    # Get a list of web ACLs for each region
    web_acl_arns=$(aws wafv2 list-web-acls --scope REGIONAL --region $region --query 'WebACLs[].ARN' --output text)

    if [ -z "$web_acl_arns" ]; then
        echo "No WAF Web ACLs found"
    else
        echo "  Web ACLs:"
        for web_acl_arn in $web_acl_arns; do
            # Get detailed information about the Web ACL
            web_acl=$(aws wafv2 get-web-acl --name $(echo $web_acl_arn | awk -F'/' '{print $(NF-1)}') --scope REGIONAL --id $(echo $web_acl_arn | awk -F'/' '{print $NF}') --region $region)

            echo "    - $web_acl_arn"
            echo "Name: $(echo $web_acl | jq -r '.WebACL.Name')"
            echo "      ID: $(echo $web_acl | jq -r '.WebACL.Id')"
            echo "Description: $(echo $web_acl | jq -r '.WebACL.Description')"
            
            # Show list of rules
            rules=$(echo $web_acl | jq -r '.WebACL.Rules[] | .Name')
            if [ -z "$rules" ]; then
                echo "No rules found"
            else
                echo "List of rules:"
                for rule in $rules; do
                    echo "        - $rule"
                done
            fi

            # Show default action
            default_action=$(echo $web_acl | jq -r '.WebACL.DefaultAction')
            echo "Default action: $(echo $default_action | jq -r 'keys[]')"

            # Show CAPTCHA settings
            captcha_config=$(echo $web_acl | jq -r '.WebACL.CaptchaConfig')
            if [ "$captcha_config" != "null" ]; then
                captcha_immunity_time=$(echo $captcha_config | jq -r '.ImmunityTimeProperty.ImmunityTime')
                if [ "$captcha_immunity_time" == "null" ]; then
                    captcha_immunity_time=300
                fi
            else
                captcha_immunity_time=300
            fi
            echo "Immunity time for CAPTCHA settings: $captcha_immunity_time"

            # Show challenge settings
            challenge_config=$(echo $web_acl | jq -r '.WebACL.ChallengeConfig')
            if [ "$challenge_config" != "null" ]; then
                challenge_immunity_time=$(echo $challenge_config | jq -r '.ImmunityTimeProperty.ImmunityTime')
                if [ "$challenge_immunity_time" == "null" ]; then
                    challenge_immunity_time=300
                fi
            else
                challenge_immunity_time=300
            fi
            echo "Immunity time for challenge settings: $challenge_immunity_time"
            
            # Show token domain list
            token_domain_list=$(echo $web_acl | jq -r '.WebACL.TokenDomains[]?')
            if [ -z "$token_domain_list" ]; then
                echo "No token domains found"
            else
                echo "Token domain list:"
                for domain in $token_domain_list; do
                    echo "      - $domain"
                done
            fi

            # Display a list of resources associated with the Web ACL
            resource_types=("APPLICATION_LOAD_BALANCER" "API_GATEWAY" "APPSYNC" "COGNITO_USER_POOL" "APP_RUNNER_SERVICE" "VERIFIED_ACCESS_INSTANCE")

            echo "Associated resources:"
            resource_found=false
            all_resources=()
            for resource_type in "${resource_types[@]}"; do
                associated_resources=$(aws wafv2 list-resources-for-web-acl --web-acl-arn $web_acl_arn --resource-type $resource_type --region $region --query 'ResourceArns[]' --output json)
                if [ "$(echo $associated_resources | jq '. | length')" -gt 0 ]; then
                    resource_found=true
                    all_resources+=" $resource_type\n$(echo $associated_resources | jq -c '.[]')\n\n"
                    echo "        $resource_type:"
                    for resource in $(echo $associated_resources | jq -r '.[]'); do
                        echo "          - $resource:"
                    done
                fi
            done

            if [ "$resource_found" = false ]; then
                echo "No associated resources found"
            fi
            
            # Get logging settings
            logging_configuration=$(aws wafv2 get-logging-configuration --resource-arn $web_acl_arn --region $region)
            
            if [ -z "$logging_configuration" ]; then
                echo "No logging configuration found"
            else
                echo "Logging configuration ARN: $(echo $logging_configuration | jq -r '.LoggingConfiguration.ResourceArn')"
                echo "Send logs to: $(echo $logging_configuration | jq -r '.LoggingConfiguration.LogDestinationConfigs[]?')"
                echo "Excluded fields: $(echo $logging_configuration | jq -r '.LoggingConfiguration.RedactedFields[]?')"
            fi

            # Get sampled request settings
            sampled_requests_enabled=$(echo $web_acl | jq -r '.WebACL.VisibilityConfig.SampledRequestsEnabled')
            echo "Sampled requests enabled: $sampled_requests_enabled"
            
            # Display the mapping between CloudWatch Metrics rule names and metric names
            echo "CloudWatch Metrics rule-to-metric mapping:"
            echo $web_acl | jq -r '.WebACL.Rules[] | "- Rule name: \(.Name), Metric name: \(.VisibilityConfig.MetricName)"'
            
            echo -e "\n"

            # File output
            echo "$web_acl" > "$region/get-web-acl_$(echo $web_acl_arn | awk -F'/' '{print $(NF-1)}').json"
            echo -e "$all_resources" >  "$region/list-resources-for-web-acl_$(echo $web_acl_arn | awk -F'/' '{print $(NF-1)}').json"
            echo "$logging_configuration" > "$region/get-logging-configuration_$(echo $web_acl_arn | awk -F'/' '{print $(NF-1)}').json"
        done
    fi

    # Get list of IP sets for each region
    ip_sets=$(aws wafv2 list-ip-sets --scope REGIONAL --region $region --query 'IPSets[].ARN' --output text)
    if [ -z "$ip_sets" ];then
        echo "No IP sets found"
    else
        echo "IP sets:"
        for ip_set in $ip_sets; do
            echo "    - $ip_set"
            ip_set_details=$(aws wafv2 get-ip-set --name $(echo $ip_set | awk -F'/' '{print $(NF-1)}') --scope REGIONAL --id $(echo $ip_set | awk -F'/' '{print $NF}') --region $region)
            echo "Name: $(echo $ip_set_details | jq -r '.IPSet.Name')"
            echo "Description: $(echo $ip_set_details | jq -r '.IPSet.Description')"
            echo "IP version: $(echo $ip_set_details | jq -r '.IPSet.IPAddressVersion')"
            echo "List of IPs: $(echo $ip_set_details | jq -r '.IPSet.Addresses | join(", ")')"

            echo -e "\n"

            # File output
            echo "$ip_set_details" > "$region/get-ip-set_$(echo $ip_set | awk -F'/' '{print $(NF-1)}').json"
        done
    fi

    # Get list of regular expression pattern sets for each region
    regex_pattern_sets=$(aws wafv2 list-regex-pattern-sets --scope REGIONAL --region $region --query 'RegexPatternSets[].ARN' --output text)
    if [ -z "$regex_pattern_sets" ];then
        echo "No regular expression pattern sets found"
    else
        echo "Regular expression pattern sets:"
        for regex_pattern_set in $regex_pattern_sets; do
            echo "    - $regex_pattern_set"
            regex_pattern_set_details=$(aws wafv2 get-regex-pattern-set --name $(echo $regex_pattern_set | awk -F'/' '{print $(NF-1)}') --scope REGIONAL --id $(echo $regex_pattern_set | awk -F'/' '{print $NF}') --region $region)
            echo "Name: $(echo $regex_pattern_set_details | jq -r '.RegexPatternSet.Name')"
            echo "Description: $(echo $regex_pattern_set_details | jq -r '.RegexPatternSet.Description')"
            echo "List of regular expression patterns:"
            echo $regex_pattern_set_details | jq -r '.RegexPatternSet.RegularExpressionList[] | "        - \(.RegexString)"'

            echo -e "\n"
            
            # File output
            echo "$regex_pattern_set_details" > "$region/get-regex-pattern-set_$(echo $regex_pattern_set | awk -F'/' '{print $(NF-1)}').json"
        done
    fi

    # Get list of rule groups for each region
    rule_groups=$(aws wafv2 list-rule-groups --scope REGIONAL --region $region --query 'RuleGroups[].ARN' --output text)
    if [ -z "$rule_groups" ];then
        echo "No rule groups found"
    else
        echo "Rule groups:"
        for rule_group in $rule_groups; do
            echo "    - $rule_group"
            rule_group_details=$(aws wafv2 get-rule-group --name $(echo $rule_group | awk -F'/' '{print $(NF-1)}') --scope REGIONAL --id $(echo $rule_group | awk -F'/' '{print $NF}') --region $region)
            echo "Name: $(echo $rule_group_details | jq -r '.RuleGroup.Name')"
            echo "Description: $(echo $rule_group_details | jq -r '.RuleGroup.Description')"
            echo "Capacity: $(echo $rule_group_details | jq -r '.RuleGroup.Capacity')"
            echo "List of rules:"
            echo $rule_group_details | jq -r '.RuleGroup.Rules[] | "        - \(.Name)"'

            echo -e "\n"

            # File output
            echo "$rule_group_details" > "$region/get-rule-group_$(echo $rule_group | awk -F'/' '{print $(NF-1)}').json"
        done
    fi
done

How to use

Run the script with the command below.

sh get-awswaf.sh

Use redirection if you want to save the script execution log as a file.

sh get-awswaf.sh > get-awswaf.logging

Output

The standard output looks like this. You can check the settings at a glance.


Region: ap-northeast-1
  Web ACLs:
    - arn:aws:wafv2:ap-northeast-1:XXXXXXXXXXXX:regional/webacl/XXXXXXXXXXXXXXXXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXX
      Name: XXXXXXXXXXXXXXXXXXXXXXXXX
      ID: XXXXXXXXXXXXXXXXXXXXXXXXX
      Description:
      List of rules:
        - XXXXXXXXXXXXXXXXXXXXXXXXX
      Default action: Allow
      CAPTCHA setting immunity time: 300
      Challenge setting immunity time: 300
      No token domains found
      No associated resources found
      Logging configuration ARN: arn:aws:wafv2:ap-northeast-1:XXXXXXXXXXXX:regional/webacl/XXXXXXXXXXXXXXXXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXX
        Send logs to: arn:aws:s3:::XXXXXXXXXXXXXXXXXXXXXXXX
        Excluded fields:
      Sampled requests enabled: true
      CloudWatch Metrics rule-to-metric mapping:
        - Rule name: XXXXXXXXXXXXXXXXXXXXXXX, Metric name: XXXXXXXXXXXXXXXXXXXXXXXXX


--- Omitted ---


  IP sets:
    - arn:aws:wafv2:ap-northeast-1:XXXXXXXXXXXX:regional/ipset/XXXXXXXXXXXXXXXXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXX
      Name: XXXXXXXXXXXXXXXXXXXXXXXXX
      Description: XXXXXXXXXX
      IP version: IPV4
      List of IPs: XX.XX.XXX.XXX/XX, XXX.XXX.XX.XXX/XX


--- Omitted ---


  Regular expression pattern sets:
    - arn:aws:wafv2:ap-northeast-1:XXXXXXXXXXXX:regional/regexpatternset/XXXXXXXXXXXXXXXXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXX
      Name: XXXXXXXXXXXXXXXXXXXXXXXXX
      Description:
      List of regular expression patterns:
        - .*\.(css|pdf|jpg|gif|txt|png|ico|js|jpeg|jpe)


--- Omitted ---


  Rule groups:
    - arn:aws:wafv2:ap-northeast-1:XXXXXXXXXXXX:regional/rulegroup/XXXXXXXXXXXXXXXXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXX
      Name: XXXXXXXXXXXXXXXXXXXXXXXXX
      Description: XXXXXXXXXXXXXXXXXXXXXXX
      Capacity: 100
      List of rules:
        - XXXXXXXXXXXXXXXXXXXXXXXXX


--- Omitted ---

A directory will be created for each region as shown below.

[cloudshell-user@ip-XX-XXX-XXX-XXX ~]$ ls -l
total 136
drwxr-xr-x. 2 cloudshell-user cloudshell-user  4096 Jun  7 01:55 ap-northeast-1
drwxr-xr-x. 2 cloudshell-user cloudshell-user  4096 Jun  7 01:55 ap-northeast-2
drwxr-xr-x. 2 cloudshell-user cloudshell-user  4096 Jun  7 01:55 ap-northeast-3
drwxr-xr-x. 2 cloudshell-user cloudshell-user  4096 Jun  7 01:54 ap-south-1
drwxr-xr-x. 2 cloudshell-user cloudshell-user  4096 Jun  7 01:55 ap-southeast-1
drwxr-xr-x. 2 cloudshell-user cloudshell-user  4096 Jun  7 01:56 ap-southeast-2
drwxr-xr-x. 2 cloudshell-user cloudshell-user  4096 Jun  7 01:55 ca-central-1
drwxr-xr-x. 2 cloudshell-user cloudshell-user  4096 Jun  7 01:56 eu-central-1
drwxr-xr-x. 2 cloudshell-user cloudshell-user  4096 Jun  7 01:54 eu-north-1
drwxr-xr-x. 2 cloudshell-user cloudshell-user  4096 Jun  7 01:54 eu-west-1
drwxr-xr-x. 2 cloudshell-user cloudshell-user  4096 Jun  7 01:54 eu-west-2
drwxr-xr-x. 2 cloudshell-user cloudshell-user  4096 Jun  7 01:54 eu-west-3
-rw-r--r--. 1 cloudshell-user cloudshell-user 20617 Jun  7 01:56 get-awswaf.log
-rw-r--r--. 1 cloudshell-user cloudshell-user  9535 Jun  7 01:54 get-awswaf.sh
-rw-r--r--. 1 cloudshell-user cloudshell-user 29389 Jun  7 02:00 get-awswaf.zip
drwxr-xr-x. 2 cloudshell-user cloudshell-user  4096 Jun  7 01:55 sa-east-1
drwxr-xr-x. 2 cloudshell-user cloudshell-user  4096 Jun  7 01:56 us-east-1
drwxr-xr-x. 2 cloudshell-user cloudshell-user  4096 Jun  7 01:56 us-east-2
drwxr-xr-x. 2 cloudshell-user cloudshell-user  4096 Jun  7 01:56 us-west-1
drwxr-xr-x. 2 cloudshell-user cloudshell-user  4096 Jun  7 01:56 us-west-2
[cloudshell-user@ip-XX-XXX-XXX-XXX ~]$ 

JSON will be saved in the region directory as shown below.

[cloudshell-user@ip-XX-XXX-XXX-XXX ~]$ ls -l ap-northeast-1/
total 160
-rw-r--r--. 1 cloudshell-user cloudshell-user  3567 Jun  7 01:55 get-ip-set_XXXXXXXXXXXXXXXXXXXXXXXXX.json
-rw-r--r--. 1 cloudshell-user cloudshell-user   424 Jun  7 01:55 get-logging-configuration_XXXXXXXXXXXXXXXXXXXXXXXXX.json
-rw-r--r--. 1 cloudshell-user cloudshell-user   666 Jun  7 01:55 get-regex-pattern-set_XXXXXXXXXXXXXXXXXXXXXXXXX.json
-rw-r--r--. 1 cloudshell-user cloudshell-user  2712 Jun  7 01:55 get-rule-group_XXXXXXXXXXXXXXXXXXXXXXXXX.json
-rw-r--r--. 1 cloudshell-user cloudshell-user  5493 Jun  7 01:55 get-web-acl_XXXXXXXXXXXXXXXXXXXXXXXXX.json
-rw-r--r--. 1 cloudshell-user cloudshell-user     1 Jun  7 01:55 list-resources-for-web-acl_XXXXXXXXXXXXXXXXXXXXXXXXX.json
[cloudshell-user@ip-XX-XXX-XXX-XXX ~]$