🐙
[Python]SAMでWAFの特定ルールを削除、作成するLambdaをデプロイしてみた
要件
-
アプリ審査時にWAFの地域制限のルールを外したい。
この作業を開発チームで完結させたいため、Terraform等のインフラ寄りのツールではなく、
開発側に馴染みのあるLambda関数を用いて実現したい。
-
現状は開発チームとSRE間で適宜コミュニケーションを取りながら対応しているが、
非同期でも共通認識を持てるようにするために処理完了後にSlack通知する。
前提
- SAM CLIをインストール済みであること。
- Pythonの基本的な文法を押さえていること。
- IAMロールの作成を行うので、AWS CLI or Terraformを扱える状態にあること。
(本記事ではTerraformで作成しています。)
1. SAMプロジェクトを作成する
- プロジェクトに必要なファイル群を作成したいディレクトリへ移動し以下コマンドを実行。
sam init
- インタラクティブな設定の説明
You can preselect a particular runtime or package type when using the `sam init` experience.
Call `sam init --help` to learn more.
Which template source would you like to use?
1 - AWS Quick Start Templates
2 - Custom Template Location
Choice: 1 # 特別な要件が無ければマネージドのテンプレートを選択。
Choose an AWS Quick Start application template
1 - Hello World Example
2 - Data processing
3 - Hello World Example with Powertools for AWS Lambda
4 - Multi-step workflow
5 - Scheduled task
6 - Standalone function
7 - Serverless API
8 - Infrastructure event management
9 - Lambda Response Streaming
10 - Serverless Connector Hello World Example
11 - Multi-step workflow with Connectors
12 - GraphQLApi Hello World Example
13 - Full Stack
14 - Lambda EFS example
15 - DynamoDB Example
16 - Machine Learning
Template: 1 # 作りたいものに近いテンプレートを選択。
Use the most popular runtime and package type? (Python and zip) [y/N]: y # 最も一般的なランタイム(Python)とパッケージタイプ(zip)を使用するかどうか。
Would you like to enable X-Ray tracing on the function(s) in your application? [y/N]: N # Lambda関数でAWS X-Rayトレースを有効にするかどうか。
X-Ray will incur an additional cost. View https://aws.amazon.com/xray/pricing/ for more details
Would you like to enable monitoring using CloudWatch Application Insights?
For more info, please view https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-application-insights.html [y/N]: y # CloudWatch Application Insightsを有効にするかどうか。
AppInsights monitoring may incur additional cost. View https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/appinsights-what-is.html#appinsights-pricing for more details
Would you like to set Structured Logging in JSON format on your Lambda functions? [y/N]: y # Lambda関数でJSON形式の構造化ログを有効にするかどうか。
Structured Logging in JSON format might incur an additional cost. View https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs.html#monitoring-cloudwatchlogs-pricing for more details
Project name [sam-app]: {your_project_name} # プロジェクトの名前を指定する。
-----------------------
Generating application:
-----------------------
Name: waf-delete-create
Runtime: python3.9
Architectures: x86_64
Dependency Manager: pip
Application Template: hello-world
Output Directory: .
Configuration file: waf-delete-create/samconfig.toml
Next steps can be found in the README file at waf-delete-create/README.md
Commands you can use next
=========================
[*] Create pipeline: cd waf-delete-create && sam pipeline init --bootstrap
[*] Validate SAM template: cd waf-delete-create && sam validate
[*] Test Function in the Cloud: cd waf-delete-create && sam sync --stack-name {stack-name} --watch
2.Lambdaの実行権限を作成する。
- Lambgda関数を実行するためのIAMロールの作成
data "aws_caller_identity" "current" {}
data "aws_region" "default" {
name = "ap-northeast-1"
}
resource "aws_iam_role" "lambda_execute_waf" {
name = "lambda-execute-waf"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow",
Principal = {
Service = "lambda.amazonaws.com"
},
Action = "sts:AssumeRole"
}
]
})
}
data "aws_iam_policy_document" "lambda_execute_waf" {
statement {
effect = "Allow"
actions = [
"wafv2:CreateRule",
"wafv2:DeleteRule",
"wafv2:UpdateWebACL",
"wafv2:GetWebACL",
"wafv2:ListWebACLs"
]
resources = ["arn:aws:wafv2:us-east-1:${data.aws_caller_identity.current.account_id}:global/webacl/*/*"]
}
statement {
effect = "Allow"
actions = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
resources = ["arn:aws:logs:${data.aws_region.default.name}:${data.aws_caller_identity.current.account_id}:log-group:*:*"]
}
}
resource "aws_iam_role_policy" "lambda_execute_waf" {
name = aws_iam_role.lambda_execute_waf.name
role = aws_iam_role.lambda_execute_waf.name
policy = data.aws_iam_policy_document.lambda_execute_waf.json
}
3.SAMテンプレートの作成
- ディレクトリ構造
tree
.
├── README.md
├── __init__.py
├── events
│ ├── event.json
│ └── get_web_acl.json
├── lambda_function
│ ├── waf_delete.py
│ └── waf_recreate.py
├── samconfig.toml
├── template.yaml
└── tests
├── __init__.py
├── integration
│ ├── __init__.py
│ └── test_api_gateway.py
├── requirements.txt
└── unit
├── __init__.py
└── test_handler.py
- IAMロールも含め、
template.yml
を編集する。
template.yml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
Sample SAM Template for waf rule create and delete
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 3
MemorySize: 128
# You can add LoggingConfig parameters such as the Logformat, Log Group, and SystemLogLevel or ApplicationLogLevel. Learn more here https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html#sam-function-loggingconfig.
LoggingConfig:
LogFormat: JSON
Resources:
DeleteWAFRuleFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
Handler: delete_waf_rule.lambda_handler
Runtime: python3.12
CodeUri: lambda_function/
Role: 'arn:aws:iam::{アカウントID}:role/lambda-execute-waf'
Architectures:
- x86_64
CreateWAFRuleFunction:
Type: AWS::Serverless::Function
Properties:
Handler: create_waf_rule.lambda_handler
Runtime: python3.12
CodeUri: lambda_function/
Role: 'arn:aws:iam::{アカウントID}:role/lambda-execute-waf'
Architectures:
- x86_64
- この時点でビルドが可能か確認しておく。(後でエラーが発生した際に切り分けやすくするため。)
sam build --use-container
# 中略
Commands you can use next
=========================
[*] Validate SAM template: sam validate
[*] Invoke Function: sam local invoke
[*] Test Function in the Cloud: sam sync --stack-name {{stack-name}} --watch
[*] Deploy: sam deploy --guided
3.Lambda関数の作成
- 実務の環境に調整する必要はあるが、特定のWAFルールを削除、作成する関数を適用。
WAFルールの削除
import boto3
waf = boto3.client('wafv2','us-east-1')
def lambda_handler(event, context):
web_acl_name = event['WebACL']['Name']
rule_name_to_delete = event['WebACL']['Rules'][0]['Name']
web_acl_id = event['WebACL']['Id']
scope = event['WebACL']['Scope']
# Get the Web ACL
web_acl = waf.get_web_acl(
Name = web_acl_name,
Scope = scope,
Id = web_acl_id,
)
# Filter out the rule to delete
updated_rules = [rule for rule in web_acl['WebACL']['Rules'] if rule['Name'] != rule_name_to_delete]
try:
# Update the Web ACL without the deleted rule
response = waf.update_web_acl(
Name= web_acl_name,
Scope= scope,
Id= web_acl_id,
DefaultAction= web_acl['WebACL']['DefaultAction'],
Rules= updated_rules,
VisibilityConfig= web_acl['WebACL']['VisibilityConfig'],
LockToken= web_acl['LockToken']
)
except Exception as e:
print(f"Error Delete Web ACL Rule: {e}")
raise e
return response
WAFルールの再作成
import boto3
waf = boto3.client('wafv2','us-east-1')
def lambda_handler(event, context):
web_acl_name = event['WebACL']['Name']
web_acl_id = event['WebACL']['Id']
scope = event['WebACL']['Scope']
recreate_rule_name = event['WebACL']['Rules'][0]['Name']
statement = event['WebACL']['Rules'][0]['Statement']
priority = event['WebACL']['Rules'][0]['Priority']
# Get the Web ACL
web_acl = waf.get_web_acl(
Name = web_acl_name,
Scope = scope,
Id = web_acl_id
)
# Add the new rule
new_rule = {
'Name': recreate_rule_name,
'Priority': priority,
'Action': {'Block': {}},
'Statement': statement,
'VisibilityConfig': {
'SampledRequestsEnabled': True,
'CloudWatchMetricsEnabled': True,
'MetricName': web_acl_name
}
}
updated_rules = web_acl['WebACL']['Rules'] + [new_rule]
try:
# Update the Web ACL with the new rule
response = waf.update_web_acl(
Name = web_acl_name,
Scope = scope,
Id = web_acl_id,
DefaultAction = web_acl['WebACL']['DefaultAction'],
Rules = updated_rules,
VisibilityConfig = web_acl['WebACL']['VisibilityConfig'],
LockToken = web_acl['LockToken']
)
except Exception as e:
print(f"Error Recreate Web ACL Rule: {e}")
raise e
return response
eventとして渡すデータ
get_web_acl.json
{
"WebACL": {
"Name": "example-web-acl",
"Id": "{string}",
"Scope": "CLOUDFRONT",
"ARN": "arn:aws:wafv2:us-east-1:{アカウントID}:global/webacl/example-web-acl/{WAFのID}",
"DefaultAction": {
"Allow": {}
},
"Description": "ACL for allowing specific regions",
"Rules": [
{
"Name": "CountOtherRegions",
"Priority": 1,
"Statement": {
"NotStatement": {
"Statement": {
"GeoMatchStatement": {
"CountryCodes": [
"JP",
"US"
]
}
}
}
},
"Action": {
"Block": {}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "countOtherRegions"
}
}
],
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "exampleWebACL"
},
"Capacity": 1,
"ManagedByFirewallManager": false,
"LabelNamespace": "awswaf:{アカウントID}:webacl:example-web-acl:"
},
"LockToken": "{string}"
}
- ローカルでのテスト実施
sam local invoke DeleteWAFRuleFunction --event get_web_acl.json
sam local invoke CreateWAFRuleFunction --event get_web_acl.json
4.AWSへデプロイの実施
- デプロイする際に対話的に設定する。
sam deploy --guided
Setting default arguments for 'sam deploy'
=========================================
Stack Name [waf-rule]:
AWS Region [ap-northeast-1]:
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [Y/n]: Y
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]: Y
#Preserves the state of previously provisioned resources when an operation fails
Disable rollback [y/N]: y
Save arguments to configuration file [Y/n]: Y
SAM configuration file [samconfig.toml]:
SAM configuration environment [default]:
- デプロイ後に手動で呼び出す際のコマンド
sam remote invoke DeleteWAFRuleFunction --event-file get_web_acl.json --stack-name waf-rule --region ap-northeast-1
sam remote invoke CreateWAFRuleFunction --event-file get_web_acl.json --stack-name waf-rule --region ap-northeast-1
参考
Discussion