Abuse通知のリージョン間転送をServerless Framework+CloudFormation構文で構築する
こんにちは!
結婚式にて、某ゲーム音楽を流しまくっていたのを参列していたオタク友達にバレて恥ずかしかったこまっちゃんです。
今回は、AWS人生を続ける上で重要なAbuse通知をIaCで攻略していきたいと思います。
最終的なアーキテクチャ
You must create an EventBridge rule for each Region that you want to receive AWS Health events for. If you don’t create a rule, you won’t receive events.
の通り、EventBridgeの送信ルールはマルチリージョンに展開する必要があります。
一方、EventBridgeはリージョン間転送が可能なので、受信ルールは東京(ap-norheast-1
)に展開し、slack通知に用いるLambdaも同じく東京に作ります。
Serverless Frameworkを用いて構築
では、さっそく実装していきましょう。以下の順番で解説していきます。
本記事の本題はイベントのリージョン間転送なので、①はスキップいただいてもOKです!
- Lambdaの実装
- EventBridge 受信側ルールの実装
- EventBridge 送信側ルールの実装
- デプロイ
serverless framework用ディレクトリの構成は以下の形です。
serverless
├── api # Lambda
│ ├── functions.yml
│ ├── iam.yml
│ └── serverless.yml
├── eventbridge-receiver # 受信側の設定
│ ├── eventbridge.yml
│ ├── iam.yml
│ └── serverless.yml
└── eventbridge-sender # 送信側の設定
├── eventbridge.yml
└── serverless.yml
1. Lambdaの実装
- 全体像
-
service: aws-health-to-backlog-api frameworkVersion: "^3.33.0" useDotenv: true custom: prune: automatic: true number: 3 stage: ${env:STAGE} prefix: aws-health-${self:custom.stage} go: baseDir: ../ binDir: .bin cgo: 0 cmd: GOARCH=amd64 GOOS=linux go build -tags lambda.norpc monorepo: false supportedRuntimes: ["provided.al2023"] buildProvidedRuntimeAsBootstrap: true plugins: - serverless-prune-plugin - serverless-plugin-split-stacks - serverless-go-plugin - serverless-function-outputs provider: # General settings name: aws stage: ${self:custom.stage} region: ap-northeast-1 stackName: ${self:custom.prefix}-cfs-api-${self:custom.project} deploymentMethod: direct # General function settings runtime: provided.al2023 memorySize: 128 timeout: 900 environment: STAGE: ${self:custom.stage} SLACK_WEBHOOK_URL: ${env:SLACK_WEBHOOK_URL} logRetentionInDays: 90 # IAM permissions iam: role: LambdaRole functions: - ${file(./functions.yml)} resources: - ${file(./iam.yml)}
Lambdaを定義
NoticeAbuseToSlack: handler: ../handler/slack/main.go name: ${self:custom.prefix}-lmd-slack-NotifyAbuseAlert package: individually: true patterns: - "!./../../handler/**" - ./../../handler/slack/**
Lambdaに付与するIAM Role
Resources: LambdaRole: Type: AWS::IAM::Role Properties: RoleName: ${self:custom.prefix}-rol-lmd-${self:custom.project} Description: Role allows logging and Lambda actions for Lambda AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: ${self:custom.prefix}-pol-lmd-${self:custom.project} PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: - "Fn::Join": - ":" - - "arn:aws:logs" - Ref: "AWS::Region" - Ref: "AWS::AccountId" - "log-group:/aws/lambda/*:*:*" - Effect: Allow Action: - lambda:* Resource: "*"
- LambdaはGo言語で書いていたので、プラグイン「serverless-go-plugin」を使用してzip化まで実行しています。
- EventBridgeの受信側で使用するため、プラグイン「serverless-function-outputs」を使用して、Lambdaの名称やArnをOutputに出力しています。
2. EventBridge 受信側ルールの実装
- 全体像
-
service: aws-health-eventbridge-receiver frameworkVersion: "^3.33.0" useDotenv: true custom: prune: automatic: true number: 3 stage: ${env:STAGE} region: ap-northeast-1 prefix: health-${self:custom.stage} lambdaAbuseToSlack: ${cf:${self:custom.prefix}-cfs-api-abuse.NoticeAbuseToSlackLambdaFunctionName} lambdaAbuseToSlackArn: ${cf:${self:custom.prefix}-cfs-api-abuse.NoticeAbuseToSlackLambdaFunctionArn} plugins: - serverless-prune-plugin - serverless-plugin-split-stacks provider: # General settings name: aws stage: ${env:STAGE} region: ${self:custom.region} stackName: ${self:custom.prefix}-cfs-evb-receiver-abuse deploymentMethod: direct resources: - ${file(./eventbridge.yml)} - ${file(./iam.yml)} - Outputs: AbuseEventReceiverBusArn: Value: !GetAtt AbuseEventReceiverBus.Arn EventBridgeCrossRegionIAMroleArn: Value: !GetAtt EventBridgeCrossRegionIAMrole.Arn
EventBridge Rule(CloudFormation記法)
Resources: AbuseEventReceiverBus: Type: AWS::Events::EventBus Properties: Name: ${self:custom.prefix}-evb-${self:custom.abuse}Bus AbuseEventReceiverRule: Type: AWS::Events::Rule Properties: Name: ${self:custom.prefix}-evb-abuseReceiverRule Description: "Receive ABUSE events from sender EventBridge" EventBusName: !Ref AbuseEventReceiverBus EventPattern: source: - aws.health detail-type: - AWS Health Abuse Event detail: eventTypeCategory: - issue service: - ABUSE State: ENABLED Targets: - Id: LambdaAbuseToSlack Arn: ${self:custom.lambdaAbuseToSlackArn} ResourceBasePermission: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: ${self:custom.lambdaAbuseToSlack} Principal: events.amazonaws.com SourceArn: !GetAtt AbuseEventReceiverRule.Arn
クロスリージョンでイベントを通知するためのIAM rule(CloudFormation記法)
Resources: EventBridgeCrossRegionIAMrole: Type: "AWS::IAM::Role" Properties: RoleName: ${self:custom.prefix}-rol-eventbridge-abuse AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: !Sub events.amazonaws.com Action: "sts:AssumeRole" Path: / Policies: - PolicyName: ${self:custom.prefix}-pol-eventbridge-abuse PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "events:PutEvents" Resource: !GetAtt AbuseEventReceiverBus.Arn
それぞれ解説していきます!
イベント受信用のカスタムイベントバス
東京リージョンには「送信用」「受信用」のEventBridgeが存在するため、両方ともdefaultバスになってしまわないよう、受信側はカスタムイベントバスを使用しています。
本来は、AWSサービスからの発出されるイベントはデフォルトバスを使用する形で使い分けるため、送信側はデフォルトバスで対応します。
(参考:default event bus vs custom event bus | AWS re:Post)
AbuseEventReceiverBus:
Type: AWS::Events::EventBus
Properties:
Name: ${self:custom.prefix}-evb-${self:custom.abuse}Bus
EventBridge 受信用ルール
イベントを受信するためのEventBridgeルールを作成します。
AbuseEventReceiverRule:
Type: AWS::Events::Rule
Properties:
Name: ${self:custom.prefix}-evb-abuseReceiverRule
Description: "Receive ABUSE events from sender EventBridge"
EventBusName: !Ref AbuseEventReceiverBus
EventPattern:
source:
- aws.health
detail-type:
- AWS Health Abuse Event
detail:
eventTypeCategory:
- issue
service:
- ABUSE
State: ENABLED
Targets:
- Id: LambdaAbuseToSlack
Arn: ${self:custom.lambdaAbuseToSlackArn}
EventBusName
:カスタムイベントバスを指定します。- EventPattern:Abuseイベントのみ通知するように絞り込んでいます。
EventBridgeのマネジメントコンソールで試していただくと、わかりやすいのでおすすめです(下図)。 - Targets:slackに通知するためのLambdaのarnをターゲットに設定します。
Id
:任意の文字列でOKです。
Lambdaへのリソースベースのパーミッション
コンソール上で作成する場合は自動で作成されるのですが、IaCの場合は手動で作成する必要があります。このパーミッションがないとLambdaの呼び出しをすることができません。
ResourceBasePermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: ${self:custom.lambdaAbuseToSlack}
Principal: events.amazonaws.com
SourceArn: !GetAtt AbuseEventReceiverRule.Arn
リージョン間転送(クロスリージョン)のためのIAM Role
送信側のEventBridgeイベントルールがターゲットを指定する際に必要な、IAMロールとIAMポリシーを作成します。
送信側のEventBridgeに対して付与することで、sts:AssumeRole
の信頼ポリシーと、受信側EventBridgeにイベントを送信することができるようになります。
EventBridgeCrossRegionIAMrole:
Type: "AWS::IAM::Role"
Properties:
RoleName: ${self:custom.prefix}-rol-eventbridge-abuse
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: !Sub events.amazonaws.com
Action: "sts:AssumeRole"
Path: /
Policies:
- PolicyName: ${self:custom.prefix}-pol-eventbridge-abuse
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- "events:PutEvents"
Resource: !GetAtt AbuseEventReceiverBus.Arn
3. EventBridge 送信側ルールの実装
- 全体像
-
service: aws-health-eventbridge-sender frameworkVersion: "^3.33.0" useDotenv: true custom: prune: automatic: true number: 3 stage: ${env:STAGE} region: ${opt:region} prefix: aws-health-${self:custom.stage} abuseEventReceiverBusArn: ${cf(ap-northeast-1):${self:custom.prefix}-cfs-evb-receiver-${self:custom.project}.AbuseEventReceiverBusArn} eventBridgeCrossRegionIAMroleArn: ${cf(ap-northeast-1):${self:custom.prefix}-cfs-evb-receiver-abuse.EventBridgeCrossRegionIAMroleArn} plugins: - serverless-prune-plugin - serverless-plugin-split-stacks provider: # General settings name: aws stage: ${self:custom.stage} region: ${self:custom.region} stackName: ${self:custom.prefix}-cfs-evb-sender-abuse deploymentMethod: direct resources: - ${file(./eventbridge.yml)}
EventBridge Rule(CloudFormation記法)
Resources: AbuseEventSenderRule: Type: AWS::Events::Rule Properties: Name: ${self:custom.prefix}-evb-abuseSenderRule Description: "Send ABUSE events to receiver EventBridge" EventPattern: source: - aws.health detail-type: - AWS Health Abuse Event detail: eventTypeCategory: - issue service: - ABUSE State: ENABLED Targets: - Id: AbuseEventReceiverBus Arn: ${self:custom.abuseEventReceiverBusArn} RoleArn: ${self:custom.eventBridgeCrossRegionIAMroleArn}
受信側EventBridgeが出力した値を使用しつつルールを実装していきます。
EventBridge 送信用ルール
AbuseEventSenderRule:
Type: AWS::Events::Rule
Properties:
Name: ${self:custom.prefix}-evb-abuseSenderRule
Description: "Send ABUSE events to receiver EventBridge"
EventPattern:
source:
- aws.health
detail-type:
- AWS Health Abuse Event
detail:
eventTypeCategory:
- issue
service:
- ABUSE
State: ENABLED
Targets:
- Id: AbuseEventReceiverBus
Arn: ${self:custom.abuseEventReceiverBusArn}
RoleArn: ${self:custom.eventBridgeCrossRegionIAMroleArn}
EventName(省略)
:defaultバスに受信されます。EventPattern
:受信側と同じです。受け口を広くする意味合いでは、送信側はもう少し緩くしてもいいかもしれません(Abuse以外の通知も受け取るようにできます)。Targets
:受信側のカスタムイベントバスをターゲットにしており、2にて作成したIAM Roleによってクロスリージョンでの転送を実現しています。Id
:任意の文字列でOKです。
4. デプロイ
以上でリソースの準備は終了です!最後にデプロイしていきます。
cd serverless/api && \
npx serverless@3.38.0 deploy --verbose --stage $STAGE --region $AWS_DEFAULT_REGION && \
cd ../eventbridge-receiver && \
npx serverless@3.38.0 deploy --verbose --stage $STAGE --region $AWS_DEFAULT_REGION && \
AWS_REGIONS=("us-east-1" "us-east-2" "us-west-1" "us-west-2" "ap-south-1" "ap-northeast-3" "ap-northeast-2" "ap-northeast-1" "ap-southeast-2" "ap-southeast-1" "ca-central-1" "eu-central-1" "eu-west-1" "eu-west-2" "eu-west-3" "eu-north-1" "sa-east-1")
# deploy to multi regions
cd ../eventbridge-sender && \
for region in "${AWS_REGIONS[@]}"; do
npx serverless@3.38.0 deploy --verbose --stage $STAGE --region $region
done
serverless frameworkは単体でマルチリージョンへのデプロイをサポートしていません。
そこで、やむを得ずデフォルトで有効化されているリージョンごとにforループでデプロイする形式をとっています。
デプロイする際は順番が非常に重要です。依存性を考慮し、Lambda → 受信側EventBridgeルール → 送信側EventBridgeルール の順にデプロイしてくださいね!
さいごに
いかがでしたでしょうか?
Abuse通知はめったにお目にかかれるものではありませんが、いざ出会ってしまえば此処で会ったが百年目。アカウントの命がかかっているので、いち早く気が付きたいところです。
ぜひ素敵なAWSライフのために、IaCでのデプロイに挑戦してみてください!