EventBridge API Destinationsで、AWSからTeamsにS3イベントを通知

# はじめに
EventBridge API Destinationsを使えば、AWSから外部のWebhookに通知できることを知ったため実際にやってみました。

ケースとして、S3のPutイベントをTeamsに送信する環境を、コンソールとCloudFormationの両方で構築してみました。

# 参考

https://dev.classmethod.jp/articles/eventbridge-teams/

# 構成
今回の内容で、以下のものが作られます。

- S3バケット
- EventBridge 
    - EventBridge Rule
    - EventBridge API Destinations
        - コンソールの`統合-APIの送信先-[APIの送信先]タブ`から確認可能
    - EventBridge Connection
        - コンソールの`統合-APIの送信先-[接続]タブ`から確認可能
- IAM Role
- Secrets Managerのシークレット


構成図は以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/3919feaa-551e-b825-4073-47fd96e7dd4f.png)


# やったこと
## コンソール
### 作成
#### S3バケット作成
コンソールから作成するだけです。
EventBridgeへの通知が必要なので、設定をオンにする必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/efcaaf32-5fcb-9709-8c9b-e9c6b2ff576b.png)

#### EventBridge Rule作成

`Amazon EventBridge > ルール`からルールを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/a9fb24c7-414e-b34c-cb4a-842311ebe00b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/6e785b4e-b521-a77f-7be3-c09a6eba657a.png)

イベントパターンにS3の`Object Created`を指定して、先ほど作成したバケットを指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/08de650e-17ec-4245-edb5-b6158d51f835.png)

ターゲットに以下を指定していきます。

- ターゲットタイプ:EventBridge APIの宛先
- API送信先:新しいAPI送信先を作成
- 名前:[任意の名前]
- API送信先エンドポイント:[TeamsのWebhookのURL]
- HTTPメソッド:POST

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/d9ac05bb-3759-a748-9f17-006df086e367.png)

- 接続:新しい接続を作成
- 接続名:[任意の名前]
- 送信先タイプ:その他

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/78eee854-6bf7-47c7-0b9f-db455640b249.png)

- 認証タイプ:APIキー
- APIキー
    - ヘッダー名:Content-Type
    - 値:application/json
        - これらがSecret Mnagerで扱われることになります
- 実行ロール:この特定のリソースについて新しいロールを作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/2bff71e2-f537-451f-449e-f71ec81907b4.png)

- ターゲット入力を設定:入力トランスフォーマー
    - 「入力トランスフォーマーを設定」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/7079b26b-15e0-305c-13b2-d59726cd952a.png)

- ターゲット入力トランスフォーマー
    - 入力パス:`{"key": "$.detail.object.key"}`
    - テンプレート:`{ "text":"ファイル **<key>** が置かれました"}`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/210d56c6-1c47-152b-b936-c9eff2414661.png)

以降の設定はデフォルトのまま変更せず、ルールを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/1a7fdefb-04fe-c75e-5c63-2d51b9748968.png)

正常に作成できた後は、対象のS3バケットにファイルを置くと、Teamsに通知が行きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/c29bc5d7-fd77-e3e6-6c77-106caf461697.png)

CloudFormationを作成する場合は、テンプレートを確認して参考にするのがよいと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/c34826d9-60a7-60c9-f6ca-bdff6ed77921.png)


### 削除
ルール作成時に自動で作成されているものがあるので、残らず消す必要があります。

#### EventBridge Rule
`パス > ルール`から、対象のルールを選択して削除します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/e49ff6c3-2705-2301-b011-cce1b29e99f4.png)

#### EventBridge API destinations
`パス > ルール`から、[APIの送信先]タブを開き、対象の名前を選択して削除します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/aaa097fb-deca-705c-8c13-4093fce42516.png)

#### EventBridge API Connection
`パス > ルール`から、[接続]タブを開き、対象の名前を選択して削除します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/6f582304-25d2-6dd1-5c80-48748f7b4d26.png)

- 「認証を解除」しなくても削除できます。
- Secret Managerからも、ここを削除する際に一緒に削除されます。

#### IAM Role
自動作成した場合`Amazon_EventBridge_Invoke_Api_Destination_{数値列}`で作成されているはずなので、それを指定して削除します。

#### S3バケット
バケットの中を空にした後、削除してください。

## CloudFormation
これまで説明した構成を、CloudFormationで記述すると以下のようになります。
TeamsのWebhookURLをパラメータとして指定できるようにしています。

<details><summary>クリックで表示</summary><div>

```yaml
AWSTemplateFormatVersion: 2010-09-09

Parameters:
  TeamsWebhookUrl:
    Type: String

Resources:
  TargetBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketEncryption:
        ServerSideEncryptionConfiguration:
        - ServerSideEncryptionByDefault:
            SSEAlgorithm: AES256
          BucketKeyEnabled: false
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      NotificationConfiguration:
        EventBridgeConfiguration:
          EventBridgeEnabled: true

  TeamsConnection:
    Type: AWS::Events::Connection
    Properties:
      AuthorizationType: API_KEY
      Description: Connection to Teams webhook
      AuthParameters:
        ApiKeyAuthParameters:
          ApiKeyName: "Content-Type"
          ApiKeyValue: "application/json"

  TeamsDestination:
    Type: AWS::Events::ApiDestination
    Properties:
      ConnectionArn: !GetAtt TeamsConnection.Arn
      HttpMethod: POST
      InvocationEndpoint: !Ref TeamsWebhookUrl
      InvocationRateLimitPerSecond: 10

  TeamsRuleRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - events.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: '/service-role/'
      Policies:
        - PolicyName: "Amazon_EventBridge_Invoke_Api_Destination"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - events:InvokeApiDestination
                Resource: !GetAtt TeamsDestination.Arn

  TeamsRule:
    Type: AWS::Events::Rule
    Properties:
      State: DISABLED
      EventBusName: default
      EventPattern:
        source:
          - aws.s3
        detail-type:
          - Object Created
        detail:
          bucket:
            name:
            - !Ref TargetBucket
      Targets:
        - Id: teams-destination
          Arn: !GetAtt TeamsDestination.Arn
          RoleArn: !GetAtt TeamsRuleRole.Arn
          InputTransformer:
            InputPathsMap:
              key: $.detail.object.key
            InputTemplate: |-
              {
                "text":"ファイル **<key>** が置かれました"
              }
```

</div></details>

# おわりに
今までSNS->Lambdaを連携してTeamsに通知していましたが、これを使えばLambdaのスクリプトの管理が不要で通知できます。

ですがSecrets Managerを使うため、こちらの構成のほうが利用料がかかります。しかもTeamsが送信先の場合は暗号化する情報が`Content-Type:application/json`なので割高感が拭えず、こちらの構成の採用を見送りました。

検討される方はコスト面も忘れずに評価してください。