この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
CX事業本部の佐藤智樹です。
今回は、EventBridgeのクイックスタートでSaaS(GitHub、Stripe、Twilio)とAWSサービスの連携が簡単に作れるようになったので紹介します。全体の流れはほぼ共通ですがSaaSごとにシークレットの登録などは異なるため、本記事ではGitHubとの連携を中心的に紹介します。
具体的な構成
CloudFormationによってSaaSの Webhook に対応するLambdaとFunction URLsが生成でき、EventBridgeへの連携が簡単にできるようになります。(画像はAWSコンソールのEventBridgeクイックスタートより引用)
やってみる
それでは実際に動かしてみます。細かい手順は以下に記載されています。
今回はGitHubの例で実際にどのような動きになるのか確認してみます。
GitHub上でシークレットの作成
以下の手順を元に、GitHubのシークレットを作成します。
リポジトリの設定から「Secrets」->「Actions」を選択して、以下の画像のように「New repository secrets」を選択して作成します。
シークレット名はルールを守るように名付けすれば大丈夫です。小文字は全て大文字に変換されます。値はuuidgen
コマンドで生成したUUIDなど推察されにくいもので設定します。
ここで生成したSecretの値はメモしておきます。
AWS側でIncomming Webhookの作成
EventBridgeのクイックスタートを開くと以下のように「Lambda fURL を使用したインバウンドウェブフック」という表記がでるので、「使用を開始する」を押下します。
画面をスクロールすると、「CloudFormation スタックテンプレート」に「GitHub」があるので「設定」を押下します。(ここでStripeやTwilioを選択すればそれぞれのSaaS用のテンプレートが使えます)
Webhookの設定画面に移行します。「ステップ 1: イベントバスを選択する」を今回はdefaultのままで変更せず、「ステップ 2: CloudFormation を使用してセットアップする」から「新しいGitHubウェブフック」を押下します。
すると警告画面が出ます。パブリックに公開されるURLが生成されるためセキュリティチームへの確認などが推奨されます。問題なければチェックを入れて、「確認」を押下します。
スタックの展開画面に遷移します。公開されたテンプレートのURLが表示されます。またパラメータにGithubWebhookSecretが書かれているのでここに前の手順で取得したシークレットを設定します。
設定後、チェックなどを確認後つけて「スタックの作成」を押下します。
スタック作成後、以下の「FunctionUrlEndpoint」にIncoming Webhook用のURLが生成されるのでメモします。
展開されるテンプレートとLambdaのコードの中身
展開されるテンプレートは現状以下のようになっています。GitHubのSecretsはパラメータに指定できますが、ちゃんとNoEcho
で残らないよう設定されています。リソースとしては、SecretManager、Lambda、CloudWatch Alarmが生成されています。SecretManagerはGitHub Webhookのシークレット保存用、CloudWatch AlarmはLambda URLに過剰なアクセスがあるかの監視、Lambdaは処理の本体になってます。
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
github-webhook
Amazon EventBridge Inbound webhooks using lambda fURLs CFNs Template.
Parameters:
GithubWebhookSecret:
Type: String
Description: Github webhook secret
NoEcho: true
AllowedPattern: ".+"
EventBusName:
Type: String
Description: EventBridge event bus name
Default: default
LambdaInvocationThreshold:
Type: String
Description: Innovation Alarm Threshold for number of events in a 5 minute period.
Default: 2000
Resources:
WebhookSecretsManager:
Type: AWS::SecretsManager::Secret
Properties:
Name: !Sub WebhookSecret-${AWS::StackName}
Description: Secrets Manager for storing Webhook Secret
SecretString: !Ref GithubWebhookSecret
LambdaInvocationsAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmDescription: !Sub Alarm for ${AWS::StackName} - InboundWebhook Lambda for traffic spikes
AlarmName: !Sub InboundWebhook-Lambda-Invocation-Alarm-${AWS::StackName}
MetricName: Invocations
Namespace: AWS/Lambda
Statistic: Sum
Period: "300"
EvaluationPeriods: "2"
Threshold: !Ref LambdaInvocationThreshold
Dimensions:
- Name: FunctionName
Value: !Ref WebhookFunction
ComparisonOperator: GreaterThanThreshold
WebhookFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
DependsOn: WebhookSecretsManager
Properties:
FunctionName: !Sub [
"InboundWebhook-Lambda-${ID}",
ID: !Select [2, !Split ["/", !Ref AWS::StackId]],
] # Append the stack UUID
CodeUri:
Bucket: !Sub 'eventbridge-inbound-webhook-templates-prod-${AWS::Region}'
Key: 'lambda-templates/github-lambdasrc.zip'
Handler: app.lambda_handler
Runtime: python3.7
ReservedConcurrentExecutions: 10
Environment:
Variables:
GITHUB_WEBHOOK_SECRET_ARN: !Ref WebhookSecretsManager
EVENT_BUS_NAME: !Ref EventBusName
MemorySize: 128
Timeout: 100
FunctionUrlConfig:
AuthType: NONE
Policies:
- EventBridgePutEventsPolicy:
EventBusName: !Ref EventBusName
- Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- secretsmanager:DescribeSecret
- secretsmanager:GetSecretValue
Resource: !Ref WebhookSecretsManager
Outputs:
FunctionUrlEndpoint:
Description: "Webhhook Function URL Endpoint"
Value: !GetAtt WebhookFunctionUrl.FunctionUrl
LambdaFunctionName:
Value: !Ref WebhookFunction
LambdaFunctionARN:
Description: Lambda function ARN.
Value: !GetAtt WebhookFunction.Arn
Lambdaに関しても、CodeUri
のURLからコードが確認できます。少し長くなるのでapp.handler
と実際のリクエストの整形の部分のみ記載します。コード量は多くないですが、細かいツールが増えると保守が大変なので今回のクイックスタートに任せたり、このコードの内容を参考に独自で拡張して作ることも可能かと思います。
app.py
~~
def lambda_handler(event, _context):
"""Webhook function"""
headers = event.get('headers')
# Input validation
try:
json_payload = get_json_payload(event=event)
except ValueError as err:
print_error(f'400 Bad Request - {err}', headers)
return {'statusCode': 400}
except BaseException as err: # Unexpected Error
print_error('500 Internal Server Error\n' +
f'Unexpected error: {err}, {type(err)}', headers)
return {'statusCode': 500}
detail_type = headers.get('x-github-event', 'github-webhook-lambda')
try:
if not contains_valid_signature(event=event):
print_error('401 Unauthorized - Invalid Signature', headers)
return {'statusCode': 401}
response = forward_event(json_payload, detail_type)
if response['FailedEntryCount'] > 0:
print_error('500 Internal Server Error - Failed to forward message to EventBridge\n' +
str(response['Entries'][0]), headers)
return {'statusCode': 500}
return {'statusCode': 202}
except BaseException as err: # Unexpected Error
print_error('500 Internal Server Error\n' +
f'Unexpected error: {err}, {type(err)}', headers)
return {'statusCode': 500}
~~
def forward_event(payload, detail_type):
"""Forward event to EventBridge"""
return event_bridge_client.put_events(
Entries=[
{
'Source': 'github.com',
'DetailType': detail_type,
'Detail': payload,
'EventBusName': event_bus_name
},
]
)
GitHub Webhook の設定
話を画面の設定に戻します。GitHubを開いてWebhookを設定します。公式の情報だと以下が参考になります。
作成したGitHubのシークレットとFunctionのURLを設定します。設定後今回は、issueに反応してWebhookを送信するように設定してみます。
適当なissueをGitHubのリポジトリ内で作成してみます。
LambdaのCloudWatch Dashboardを確認するとLambdaが実行されたことが分かります。Webhook作成時に1回実行されるので合計2回Lambdaが実行されてます。イベントの内容は異なるので反応させたいイベントをEventBridgeのルールで設定すれば大丈夫です。
EventBridgeまで通知がされているのか確認するため、EventBridgeのルールを作成してます。今回はEventBridgeにルールに該当するものがあればイベントをSNS経由でメールに送信するよう設定します。先にSNSの画面でトピックを作成してください。
作成後、「サブスクリプションの設定」からプロトコルをEメールで選択し、エンドポイントに受信可能なメールアドレスを設定します。設定後、SNSからメールアドレス宛に「AWS Notification - Subscription Confirmation」という件名のメールが来るので、「Confirm subscription」を押下して許可設定を行います。 これで受信の準備は完了です。
次はEventBridgeのルールの「ルールを作成」から以下のように適当なルール名をつけます。
イベントソースを「その他」で設定し、「サンプルイベント」に以下を設定してみます。
{
"version": "0",
"id": "d85hoge-fugo-3712-abxx-1234567ee4e9",
"detail-type": "issues",
"source": "github.com",
"account": "123456789012",
"time": "2019-12-05T16:50:53Z",
"region": "ap-northeast-1",
"resources": [],
"detail": {}
}
次にイベントパターンで「ご自身名前を入力」を選択し以下を設定します。こちらが実際にイベントを引っ掛ける際のルールになります。テストパターンのボタンから設定がうまくいっているか確認できます。
{
"source": ["github.com"]
}
例ですが以下のようなイベントにすればissueのオープンのイベントに対してマッチングしてターゲットの実行ができます。
{
"source": ["github.com"],
"detail-type": ["issues"],
"detail": {
"action": ["opened"]
}
}
ターゲットタイプにSNSを選択し、先ほど作成したトピックを設定します。ターゲットを変更すればCodePiplineやLambdaの実行などができます。後の設定はデフォルトのままで完了まで進めます。
上記で設定は完了です。再度GitHub上でissueを作成すると以下のようなイベントがメールに送信されてきます。
{
"version": "0",
"id": "d85hoge-ebae-1694-76d4-1234567ee4e9",
"detail-type": "issues",
"source": "github.com",
"account": "123456789012",
"time": "2022-08-12T14:36:07Z",
"region": "ap-northeast-1",
"resources": [],
"detail": {
"action": "opened",
"issue": {
"url": ""
...
}
}
}
おまけ:今までのGitHubとCodePipeline
個人的になぜこのアップデートが良かったのかについて記載してます。AWSとGitHubの連携は、ソースをビルド/デプロイするためCodeBuildとCodePipelineとの連携がメインになってたかと考えます。ここではCodePipelineとの関係に注目します。CodePipelineとGitHubを接続するためにはGitHub認証のv2かv1を使う必要があります。v2では基本的なイベントであるマージやプッシュなどによるイベントフックには対応していますが、細かいイベント(PRの作成、タグ作成など)によるパイプラインの実行ができませんでした。
v1の場合は、設定することである程度細かいイベントによる実行はできました。しかしながら、モノレポなどで特定のディレクトリの変更にだけ反応させたいなどさらに細かいイベントに対応したい場合は対応できませんでした。なので、以下のようにAPI GatewayにGitHubのWebhookを送る方法がブログに書かれていました。
ただ細かい実装方法やCloudFormationやCDKのテンプレートは公開されていないので実現するのは少し手間でした。今回のクイックスタートを使うとここを始めるためのハードルが下がると期待できます。またEventBridgeと連携することで、単にCodePipelineの実行だけでなく他のサービスも合わせた独自のパイプラインを組むこともできます。
所感
GitHubにメインで注目した内容になりました。CodePipelineなどを使わなければならない環境では、複雑なWebhookが多少作りやすくなり良いアップデートかと思いました。個人的にはEventBridgeのルール作成の方が細かくエラーが出なく難しいのでこちらもテンプレートがあるとありがたいかなと感じます。余談ですがLambda FunctionsのURL機能は、 Lambda fURLと略すみたいです。
Lambda fURLは公開設定になるので、セキュリティポリシーに合わせて使用を検討してください。おそらくですが、認証部分だけ変更すればLambdaの内容は基本使いまわせるかと思うので試してみてください。どなたかの参考になれば幸いです。