TerraformでAWS ChatbotとAmazon SNSを設定していて、詰まった話

AWS

2024.2.16

Topics

はじめに

こんにちは、Shunです。

AWS Chatbotをコード化する際に、Amazon SNSで詰まってしまったので、その体験を共有します。

私自身、かなり苦労したため、これが誰かの助けになれば幸いです。

想定読者

  • Terraformを使用してAWS Chatbotを作成していて、通知が届かない方
  • Amazon SNS関連で問題に直面している方
  • リソースをTerraformで管理したいと考えている方

本記事で紹介する内容

  • Amazon SNS設定時の注意事項
  • Terraform記述時のポイント

本記事で紹介しない内容

  • AWS Chatbotの具体的な設定方法
  • Amazon SNSなどのサービス詳細
  • Terraformの基本記法

実施していたこと

以下の構成をTerraformで記述していました。

エラーは発生せず、環境は正常にapplyされました。

しかし、CloudWatch Alarmの状態を変更してChatbotへの通知を試みたところ、通知が届きませんでした。

以前、コンソールで同様の設定を行った経験があるため、何が間違っているのかがわかりませんでした。。

確認した事項

AWS Chatbotの設定確認

まず、Slackへの通知機能を持つChatbotが正しく機能するかを確認しました。

すぐにSlackへの通知が届き、Chatbotの権限や対象チャネルの設定に問題はないことが分かりました。

Amazon CloudWatch Alarmの設定確認

CloudWatch Alarmの設定を確認しましたが、今回のアラームはEventBridgeのルールによって捉えられるため、設定されていないことが正常です。

Amazon EventBridgeの設定確認

EventBridgeの設定も問題ないことを確認しました。

「イベントパターン」は適切なCloudWatch Alarmを指しており、問題はありませんでした。

「ターゲット」も問題なさそうです。

Amazon SNSの設定確認

SNSについては、コンソールで作成する際には名前の入力のみで済むため、最後に確認しました。

関連する「アクセスポリシー」を見てみると、

以下のような権限が設定されています。

  {
    "Version": "2008-10-17",
    "Id": "__default_policy_ID",
    "Statement": [
      {
        "Sid": "__default_statement_ID",
        "Effect": "Allow",
        "Principal": {
          "AWS": "*"
        },
        "Action": [
          "SNS:Publish",
          "SNS:RemovePermission",
          "SNS:SetTopicAttributes",
          "SNS:DeleteTopic",
          "SNS:ListSubscriptionsByTopic",
          "SNS:GetTopicAttributes",
          "SNS:AddPermission",
          "SNS:Subscribe"
        ],
        "Resource": "arn:aws:sns:ap-northeast-1:xxxxxxxxxx:xxxxxxxxxxx",
        "Condition": {
          "StringEquals": {
            "AWS:SourceOwner": "xxxxxxxxxx"
          }
        }
      }
    ]
  }

これとは別に、コンソールから作成したSNSトピックの「アクセスポリシー」も確認してみると、

  {
    "Version": "2008-10-17",
    "Id": "__default_policy_ID",
    "Statement": [
      {
        "Sid": "__default_statement_ID",
        "Effect": "Allow",
        "Principal": {
          "AWS": "*"
        },
        "Action": [
          "SNS:Publish",
          "SNS:RemovePermission",
          "SNS:SetTopicAttributes",
          "SNS:DeleteTopic",
          "SNS:ListSubscriptionsByTopic",
          "SNS:GetTopicAttributes",
          "SNS:AddPermission",
          "SNS:Subscribe"
        ],
        "Resource": "arn:aws:sns:ap-northeast-1:xxxxxxxxxx:xxxxxxxxxxx",
        "Condition": {
          "StringEquals": {
            "AWS:SourceOwner": "xxxxxxxxxx"
          }
        }
      },
      {
        "Sid": "xxxxxxxxxxxxxxxxxxxxxxxxx",
        "Effect": "Allow",
        "Principal": {
          "Service": "events.amazonaws.com"
        },
        "Action": "sns:Publish",
        "Resource": "arn:aws:sns:ap-northeast-1:xxxxxxxxxxx:xxxxxxx"
      }
    ]
  }

記憶にない設定が追加されていることが判明しました。具体的には、"events.amazonaws.com"への"sns:Publish"権限が新たに付与されていました。

この追加された設定が、Slackへの通知が届かなかった原因であることが明らかになりました。

つまり、

SNSの「アクセスポリシー」内で、EventBridgeからのアクセスに”sns:Publish”の権限を明示的に付与

する必要があったのです。

検証

先述した設定がどのように行われたのか検証を行いました。まず、Terraformを用いて以下の設定を適用しました。

resource "aws_sns_topic" "test" {
  name = "test"
}

resource "aws_cloudwatch_event_rule" "test" {
  event_bus_name = "default"
  event_pattern  = file("alarm_event.json")
  is_enabled     = true
  name           = "test"
}

resource "aws_cloudwatch_event_target" "test" {
  arn  = aws_sns_topic.test.arn
  rule = aws_cloudwatch_event_rule.test.id

  input_transformer {
    input_paths = {
      "account" : "$.account",
      "alarm" : "$.detail.alarmName",
      "description" : "$.detail.configuration.description",
      "instance" : "$.detail.configuration.metrics[0].metricStat.metric.dimensions.InstanceId",
      "metrics_name" : "$.detail.configuration.metrics[0].metricStat.metric.name",
      "state" : "$.detail.state.value",
      "timestamp" : "$.detail.state.timestamp"
    }
    input_template = file("alarm_template.json")
  }
}

やはりこの設定では、SNSの「アクセスポリシー」にEventBridgeからのアクセス権限が自動的に付与されませんでした。

次に、上記の設定をコンソールから実施します。(表示部分以外は、既定値です。)

「アクセスポリシー」を確認します。

    {
      "Version": "2008-10-17",
      "Id": "__default_policy_ID",
      "Statement": [
        {
          "Sid": "__default_statement_ID",
          "Effect": "Allow",
          "Principal": {
            "AWS": "*"
          },
          "Action": [
            "SNS:Publish",
            "SNS:RemovePermission",
            "SNS:SetTopicAttributes",
            "SNS:DeleteTopic",
            "SNS:ListSubscriptionsByTopic",
            "SNS:GetTopicAttributes",
            "SNS:AddPermission",
            "SNS:Subscribe"
          ],
          "Resource": "arn:aws:sns:ap-northeast-1:xxxxxxxxxxx:test",
          "Condition": {
            "StringEquals": {
              "AWS:SourceOwner": "xxxxxxxxxxxx"
            }
          }
        }

やはりアクセスポリシーは付与されていません。

続いて、EventBridgeを設定します。Terraformの設定と同様の「イベントパターン」と「ターゲット」を選択し、作成します。

そして、もう一度SNSの「アクセスポリシー」を確認してみると、

    {
      "Version": "2008-10-17",
      "Id": "__default_policy_ID",
      "Statement": [
        {
          "Sid": "__default_statement_ID",
          "Effect": "Allow",
          "Principal": {
            "AWS": "*"
          },
          "Action": [
            "SNS:Publish",
            "SNS:RemovePermission",
            "SNS:SetTopicAttributes",
            "SNS:DeleteTopic",
            "SNS:ListSubscriptionsByTopic",
            "SNS:GetTopicAttributes",
            "SNS:AddPermission",
            "SNS:Subscribe"
          ],
          "Resource": "arn:aws:sns:ap-northeast-1:xxxxxxxxxxx:test",
          "Condition": {
            "StringEquals": {
              "AWS:SourceOwner": "xxxxxxxxxxxx"
            }
          }
        },
        {
          "Sid": "xxxxxxxxxxxxxxxxxxxxxxxxx",
          "Effect": "Allow",
          "Principal": {
            "Service": "events.amazonaws.com"
          },
          "Action": "sns:Publish",
          "Resource": "arn:aws:sns:ap-northeast-1:xxxxxxxxxxx:xxxxxxx"
        }
      ]
    }

やっぱり増えてるんです。。

このことから、

コンソールから操作を行うと、EventBridgeからのアクセスに対して、自動でSNSのアクセスポリシーに権限が付与

されることが確認できました。

解決策

問題の解決策として、Terraformによる実装でSNSの「アクセスポリシー」にEventBridgeからのアクセスを明示的に付与する必要がありました。

resource "aws_sns_topic" "test" {
  name = "test"
}

resource "aws_sns_topic_policy" "test" {
  arn    = aws_sns_topic.test.arn
  policy = <<POLICY
{
    "Version": "2008-10-17",
    "Id": "__default_policy_ID",
    "Statement": [
      {
        "Sid": "__default_statement_ID",
        "Effect": "Allow",
        "Principal": {
          "AWS": "*"
        },
        "Action": [
          "SNS:Publish",
          "SNS:RemovePermission",
          "SNS:SetTopicAttributes",
          "SNS:DeleteTopic",
          "SNS:ListSubscriptionsByTopic",
          "SNS:GetTopicAttributes",
          "SNS:AddPermission",
          "SNS:Subscribe"
        ],
        "Resource": "arn:aws:sns:ap-northeast-1:xxxxxxxx:xxxxxxxxx",
        "Condition": {
          "StringEquals": {
            "AWS:SourceOwner": "xxxxxxxxx"
          }
        }
      },
      {
        "Sid": "xxxxxxxxx",
        "Effect": "Allow",
        "Principal": {
          "Service": "events.amazonaws.com"
        },
        "Action": "sns:Publish",
        "Resource": "arn:aws:sns:ap-northeast-1:xxxxxx:xxxxxxxx"
      }
    ]
}
POLICY
}

resource "aws_cloudwatch_event_rule" "test" {
  event_bus_name = "default"
  event_pattern  = file("alarm_event.json")
  is_enabled     = true
  name           = "test"
}

resource "aws_cloudwatch_event_target" "test" {
  arn  = aws_sns_topic.test.arn
  rule = aws_cloudwatch_event_rule.test.id

  input_transformer {
    input_paths = {
      "account" : "$.account",
      "alarm" : "$.detail.alarmName",
      "description" : "$.detail.configuration.description",
      "instance" : "$.detail.configuration.metrics[0].metricStat.metric.dimensions.InstanceId",
      "metrics_name" : "$.detail.configuration.metrics[0].metricStat.metric.name",
      "state" : "$.detail.state.value",
      "timestamp" : "$.detail.state.timestamp"
    }
    input_template = file("alarm_template.json")
  }
}

このコードを適用した結果、正常にSlackに通知が送信されるようになりました。

まとめ

コンソールからリソースを作成すると、設定値が自動で補完されたり、設定が裏側で追加されるなど、便利な機能が多いことを改めて確認しました。しかし、サービスに対する深い理解を得るためには、コード化による設定も有効な手段だと感じました。

この記事が何らかの形で役立てば幸いです。

最後まで読んでいただきありがとうございました。

Shun

好きな言葉は生ビール199円です。日本ビール協会認定1冠、AWS12冠、Google Cloud11冠

Recommends

こちらもおすすめ

Special Topics

注目記事はこちら