웹 애플리케이션 방화벽(WAF) 시작하기
웹 애플리케이션 방화벽(WAF) 시작하기
이 코드 예제에서는 AWS WAF(웹 애플리케이션 방화벽)로 Web ACL 생성 → 문자열 매치 규칙 추가 → 관리형 규칙 추가 → 로깅 구성 → 리소스 정리까지를 다룹니다. IAM 권한을 갖춘 환경에서 aws wafv2 명령으로 웹 트래픽을 보호하는 규칙 체계를 만들어볼 수 있어요.
출처: 문서
본문
#!/bin/bash
# AWS WAF Getting Started Script
# This script creates a Web ACL with a string match rule and AWS Managed Rules,
# associates it with a CloudFront distribution, and then cleans up all resources.
set -euo pipefail
# Security: Restrict file permissions
umask 077
# Set up logging with secure file handling
LOG_FILE="waf-tutorial.log"
touch "$LOG_FILE"
chmod 600 "$LOG_FILE"
exec > >(tee -a "$LOG_FILE") 2>&1
echo "==================================================="
echo "AWS WAF Getting Started Tutorial"
echo "==================================================="
echo "This script will create AWS WAF resources and associate"
echo "them with a CloudFront distribution."
echo ""
# Maximum number of retries for operations
MAX_RETRIES=3
# Function to handle errors securely
handle_error() {
local error_msg="$1"
echo "ERROR: $error_msg" >&2
echo "Check the log file for details: $LOG_FILE" >&2
cleanup_resources
exit 1
}
# Function to validate AWS CLI JSON output
validate_json() {
local json_string="$1"
if ! echo "$json_string" | jq empty 2>/dev/null; then
return 1
fi
return 0
}
# Function to safely extract JSON values
extract_json_value() {
local json_string="$1"
local key_path="$2"
if ! validate_json "$json_string"; then
return 1
fi
echo "$json_string" | jq -r "$key_path" 2>/dev/null || return 1
}
# Function to clean up resources securely
cleanup_resources() {
echo ""
echo "==================================================="
echo "CLEANING UP RESOURCES"
echo "==================================================="
if [ -n "${DISTRIBUTION_ID:-}" ] && [ -n "${WEB_ACL_ARN:-}" ]; then
echo "Disassociating Web ACL from CloudFront distribution..."
local account_id
account_id=$(aws sts get-caller-identity --query Account --output text 2>/dev/null) || account_id=""
if [ -z "$account_id" ]; then
echo "Warning: Could not retrieve AWS account ID"
return
fi
local disassociate_result
disassociate_result=$(aws wafv2 disassociate-web-acl \
--resource-arn "arn:aws:cloudfront::${account_id}:distribution/${DISTRIBUTION_ID}" \
--region us-east-1 2>&1) || true
if echo "$disassociate_result" | grep -qi "error"; then
echo "Warning: Failed to disassociate Web ACL: $disassociate_result"
else
echo "Web ACL disassociated successfully."
fi
fi
if [ -n "${WEB_ACL_ID:-}" ] && [ -n "${WEB_ACL_NAME:-}" ]; then
echo "Deleting Web ACL..."
local get_result
get_result=$(aws wafv2 get-web-acl \
--name "$WEB_ACL_NAME" \
--scope CLOUDFRONT \
--id "$WEB_ACL_ID" \
--region us-east-1 2>&1) || true
if echo "$get_result" | grep -qi "error"; then
echo "Warning: Failed to get Web ACL for deletion: $get_result"
echo "You may need to manually delete the Web ACL using the AWS Console."
else
local latest_token
latest_token=$(extract_json_value "$get_result" '.WebACL.LockToken' 2>/dev/null) || latest_token=""
if [ -n "$latest_token" ]; then
local delete_result
delete_result=$(aws wafv2 delete-web-acl \
--name "$WEB_ACL_NAME" \
--scope CLOUDFRONT \
--id "$WEB_ACL_ID" \
--lock-token "$latest_token" \
--region us-east-1 2>&1) || true
if echo "$delete_result" | grep -qi "error"; then
echo "Warning: Failed to delete Web ACL: $delete_result"
echo "You may need to manually delete the Web ACL using the AWS Console."
else
echo "Web ACL deleted successfully."
fi
else
echo "Warning: Could not extract lock token for deletion. You may need to manually delete the Web ACL."
fi
fi
fi
echo "Cleanup process completed."
}
# Security: Trap EXIT to ensure cleanup on any exit
trap cleanup_resources EXIT
# Generate a random identifier for resource names using secure method
RANDOM_ID=$(openssl rand -hex 4) || handle_error "Failed to generate random ID"
WEB_ACL_NAME="MyWebACL-${RANDOM_ID}"
METRIC_NAME="MyWebACLMetrics-${RANDOM_ID}"
echo "Using Web ACL name: $WEB_ACL_NAME"
# Step 1: Create a Web ACL
echo ""
echo "==================================================="
echo "STEP 1: Creating Web ACL"
echo "==================================================="
local create_result
create_result=$(aws wafv2 create-web-acl \
--name "$WEB_ACL_NAME" \
--scope "CLOUDFRONT" \
--default-action Allow={} \
--visibility-config "SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=$METRIC_NAME" \
--tags Key=project,Value=doc-smith Key=tutorial,Value=aws-waf-gs \
--region us-east-1 2>&1) || handle_error "Failed to create Web ACL"
if ! validate_json "$create_result"; then
handle_error "Invalid JSON response from create-web-acl"
fi
# Extract Web ACL ID, ARN, and Lock Token from the response
WEB_ACL_ID=$(extract_json_value "$create_result" '.Summary.Id') || handle_error "Failed to extract Web ACL ID"
WEB_ACL_ARN=$(extract_json_value "$create_result" '.Summary.ARN') || handle_error "Failed to extract Web ACL ARN"
LOCK_TOKEN=$(extract_json_value "$create_result" '.Summary.LockToken') || handle_error "Failed to extract Lock Token"
echo "Web ACL created successfully with ID: $WEB_ACL_ID"
echo "Lock Token: [REDACTED]"
# Step 2: Add a String Match Rule
echo ""
echo "==================================================="
echo "STEP 2: Adding String Match Rule"
echo "==================================================="
for ((i=1; i<=MAX_RETRIES; i++)); do
echo "Attempt $i to add string match rule..."
# Get the latest lock token before updating
local get_result
get_result=$(aws wafv2 get-web-acl \
--name "$WEB_ACL_NAME" \
--scope CLOUDFRONT \
--id "$WEB_ACL_ID" \
--region us-east-1 2>&1) || true
if echo "$get_result" | grep -qi "error"; then
echo "Warning: Failed to get Web ACL for update: $get_result"
if [ "$i" -eq "$MAX_RETRIES" ]; then
handle_error "Failed to get Web ACL after $MAX_RETRIES attempts"
fi
sleep 2
continue
fi
if ! validate_json "$get_result"; then
echo "Warning: Invalid JSON response from get-web-acl"
if [ "$i" -eq "$MAX_RETRIES" ]; then
handle_error "Invalid JSON response after $MAX_RETRIES attempts"
fi
sleep 2
continue
fi
local latest_token
latest_token=$(extract_json_value "$get_result" '.WebACL.LockToken' 2>/dev/null) || true
if [ -z "$latest_token" ]; then
echo "Warning: Could not extract lock token for update"
if [ "$i" -eq "$MAX_RETRIES" ]; then
handle_error "Failed to extract lock token after $MAX_RETRIES attempts"
fi
sleep 2
continue
fi
local update_result
update_result=$(aws wafv2 update-web-acl \
--name "$WEB_ACL_NAME" \
--scope "CLOUDFRONT" \
--id "$WEB_ACL_ID" \
--lock-token "$latest_token" \
--default-action Allow={} \
--rules '[{
"Name": "UserAgentRule",
"Priority": 0,
"Statement": {
"ByteMatchStatement": {
"SearchString": "MyAgent",
"FieldToMatch": {
"SingleHeader": {
"Name": "user-agent"
}
},
"TextTransformations": [
{
"Priority": 0,
"Type": "NONE"
}
],
"PositionalConstraint": "EXACTLY"
}
},
"Action": {
"Count": {}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "UserAgentRuleMetric"
}
}]' \
--visibility-config "SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=$METRIC_NAME" \
--region us-east-1 2>&1) || true
if echo "$update_result" | grep -qi "WAFOptimisticLockException"; then
echo "Optimistic lock exception encountered. Will retry with new lock token."
if [ "$i" -eq "$MAX_RETRIES" ]; then
handle_error "Failed to add string match rule after $MAX_RETRIES attempts"
fi
sleep 2
continue
elif echo "$update_result" | grep -qi "error"; then
handle_error "Failed to add string match rule: $update_result"
else
echo "String match rule added successfully."
break
fi
done
# Step 3: Add AWS Managed Rules
echo ""
echo "==================================================="
echo "STEP 3: Adding AWS Managed Rules"
echo "==================================================="
for ((i=1; i<=MAX_RETRIES; i++)); do
echo "Attempt $i to add AWS Managed Rules..."
# Get the latest lock token before updating
local get_result
get_result=$(aws wafv2 get-web-acl \
--name "$WEB_ACL_NAME" \
--scope CLOUDFRONT \
--id "$WEB_ACL_ID" \
--region us-east-1 2>&1) || true
if echo "$get_result" | grep -qi "error"; then
echo "Warning: Failed to get Web ACL for update: $get_result"
if [ "$i" -eq "$MAX_RETRIES" ]; then
handle_error "Failed to get Web ACL after $MAX_RETRIES attempts"
fi
sleep 2
continue
fi
if ! validate_json "$get_result"; then
echo "Warning: Invalid JSON response from get-web-acl"
if [ "$i" -eq "$MAX_RETRIES" ]; then
handle_error "Invalid JSON response after $MAX_RETRIES attempts"
fi
sleep 2
continue
fi
local latest_token
latest_token=$(extract_json_value "$get_result" '.WebACL.LockToken' 2>/dev/null) || true
if [ -z "$latest_token" ]; then
echo "Warning: Could not extract lock token for update"
if [ "$i" -eq "$MAX_RETRIES" ]; then
handle_error "Failed to extract lock token after $MAX_RETRIES attempts"
fi
sleep 2
continue
fi
local update_result
update_result=$(aws wafv2 update-web-acl \
--name "$WEB_ACL_NAME" \
--scope "CLOUDFRONT" \
--id "$WEB_ACL_ID" \
--lock-token "$latest_token" \
--default-action Allow={} \
--rules '[{
"Name": "UserAgentRule",
"Priority": 0,
"Statement": {
"ByteMatchStatement": {
"SearchString": "MyAgent",
"FieldToMatch": {
"SingleHeader": {
"Name": "user-agent"
}
},
"TextTransformations": [
{
"Priority": 0,
"Type": "NONE"
}
],
"PositionalConstraint": "EXACTLY"
}
},
"Action": {
"Count": {}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "UserAgentRuleMetric"
}
},
{
"Name": "AWS-AWSManagedRulesCommonRuleSet",
"Priority": 1,
"Statement": {
"ManagedRuleGroupStatement": {
"VendorName": "AWS",
"Name": "AWSManagedRulesCommonRuleSet",
"ExcludedRules": []
}
},
"OverrideAction": {
"Count": {}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "AWS-AWSManagedRulesCommonRuleSet"
}
}]' \
--visibility-config "SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=$METRIC_NAME" \
--region us-east-1 2>&1) || true
if echo "$update_result" | grep -qi "WAFOptimisticLockException"; then
echo "Optimistic lock exception encountered. Will retry with new lock token."
if [ "$i" -eq "$MAX_RETRIES" ]; then
handle_error "Failed to add AWS Managed Rules after $MAX_RETRIES attempts"
fi
sleep 2
continue
elif echo "$update_result" | grep -qi "error"; then
handle_error "Failed to add AWS Managed Rules: $update_result"
else
echo "AWS Managed Rules added successfully."
break
fi
done
# Step 4: List CloudFront distributions
echo ""
echo "==================================================="
echo "STEP 4: Listing CloudFront Distributions"
echo "==================================================="
local cf_result
cf_result=$(aws cloudfront list-distributions --query "DistributionList.Items[*].{Id:Id,DomainName:DomainName}" --output table 2>&1) || cf_result=""
if echo "$cf_result" | grep -qi "error"; then
echo "Warning: Failed to list CloudFront distributions: $cf_result"
echo "Continuing without CloudFront association."
DISTRIBUTION_ID=""
else
echo "$cf_result"
# Auto-select first CloudFront distribution if available
echo ""
echo "==================================================="
echo "STEP 5: Associate Web ACL with CloudFront Distribution"
echo "==================================================="
local first_dist
first_dist=$(aws cloudfront list-distributions --query "DistributionList.Items[0].Id" --output text 2>&1) || first_dist=""
if [ -n "$first_dist" ] && [ "$first_dist" != "None" ] && ! echo "$first_dist" | grep -qi "error"; then
DISTRIBUTION_ID="$first_dist"
echo "Using CloudFront distribution: $DISTRIBUTION_ID"
local account_id
account_id=$(aws sts get-caller-identity --query Account --output text 2>/dev/null) || account_id=""
if [ -z "$account_id" ]; then
echo "Warning: Could not retrieve AWS account ID for association"
DISTRIBUTION_ID=""
else
local associate_result
associate_result=$(aws wafv2 associate-web-acl \
--web-acl-arn "$WEB_ACL_ARN" \
--resource-arn "arn:aws:cloudfront::${account_id}:distribution/${DISTRIBUTION_ID}" \
--region us-east-1 2>&1) || true
if echo "$associate_result" | grep -qi "error"; then
echo "Warning: Failed to associate Web ACL with CloudFront distribution: $associate_result"
echo "Continuing without CloudFront association."
DISTRIBUTION_ID=""
else
echo "Web ACL associated with CloudFront distribution successfully."
fi
fi
else
echo "No CloudFront distributions available. Skipping association."
DISTRIBUTION_ID=""
fi
fi
# Display summary of created resources
echo ""
echo "==================================================="
echo "RESOURCE SUMMARY"
echo "==================================================="
echo "Web ACL Name: $WEB_ACL_NAME"
echo "Web ACL ID: $WEB_ACL_ID"
echo "Web ACL ARN: $WEB_ACL_ARN"
if [ -n "${DISTRIBUTION_ID:-}" ]; then
echo "Associated CloudFront Distribution: $DISTRIBUTION_ID"
fi
echo ""
# Auto-confirm cleanup
echo "==================================================="
echo "CLEANUP CONFIRMATION"
echo "==================================================="
echo "Proceeding with automatic cleanup of all created resources..."
echo ""
echo "==================================================="
echo "Tutorial completed!"
echo "==================================================="
echo "Log file: $LOG_FILE"
참고: 이 예제의 전체 소스와 실행 방법은 GitHub의 샘플 개발자 튜토리얼 저장소에서 확인할 수 있어요.
더 알아보기 (Learn more)
관련 API 동작을 더 자세히 보려면 AWS CLI 명령 참조 문서를 확인하세요.