SNSトピックを暗号化してもBudgetsから通知したい！

## 背景
ある日、AWSからメールが届きました。
```
You requested that we notify you regarding the status of your <cost-all-services-manage> budget on the following SNS topic: <arn:aws:sns:ap-northeast-1:111111111111:xxxxx_topic>.

Unfortunately, we are unable to successfully publish to this SNS topic at this time. Please ensure that AWS Budgets has been added to the list of services that are allowed to publish to this SNS topic. You can find additional information on how to add AWS Budgets to your SNS topic permissions here. You can also log in to your AWS Budgets Dashboard to check the status of your budget.

Please note that if you do not adjust your SNS topic permissions, it is likely that budget notifications will not be sent to your SNS topic subscribers.
```

どうやら権限不足によりBudgetsが検知したアラートをSNSトピックに配信できなかったようです。
以前までは問題なくアラートが飛んでいたようなのですが今回から何故か飛ばなくなりました。

この事象の原因を調べ、対策を考えていきます。

## 調査
権限まわりが原因ということで真っ先にSNSのアクセスポリシーを確認しましたが、
`budgets.amazonaws.com`がAllowされていますし、ARNに誤りもなく、問題があるようには見えませんでした。

改めて[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/cost-management/latest/userguide/budgets-sns-policy.html)を確認してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397203/8478bde7-4184-9fe9-b556-6de4e4473d19.png)

ここで、「暗号化...そういえばちょっと前にSNSトピックを暗号化するみたいな話をしていたような...」ということを思い出しました。

実際にSNSトピックを確認すると、マネージド鍵で暗号化していました。
これが原因でBudgetsがSNSトピックに到達できなくなっていた可能性が高いですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397203/41a9e0aa-adf6-2120-8f3b-4740b7f6a376.png)

試しに、暗号化したSNSトピックと暗号化していないSNSトピックをそれぞれ用意して、メッセージが飛ばないことを確認していきます。
[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/cost-management/latest/userguide/budgets-sns-policy.html)に沿って、Budgetsからのアクセスポを許可したSNSトピックを用意します。

- 暗号化したSNSトピック : `shimazu_test_dev_cost_alert_topic`
- 暗号化していないSNSトピック : `shimazu_test_dev_cost_alert_topic_no_encryption`

次に、Budgetsに設定していきます。
暗号化したSNSトピック : `shimazu_test_dev_cost_alert_topic`を設定しようとしたところエラーメッセージが出て弾かれました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397203/6d0b6887-ffc8-5d88-5cc6-46ef4d752ba0.png)

暗号化していないSNSトピック : `shimazu_test_dev_cost_alert_topic_no_encryption`は設定できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397203/c6339e28-a094-a970-95c1-c5c11c054eb1.png)

ここで以前から使っていたSNSトピックに設定し直そうとすると、エラーメッセージが出て弾かれました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397203/959b9404-bee4-a9fb-0952-31c1385a60e4.png)

ここまでの検証で、暗号化したSNSトピックはそもそもBudgetsには設定できないこと、一度暗号化していないSNSトピックをBudgetsに設定してから暗号化することはできてしまう（ただし、BudgetsはSNSトピックに到達できない）ことがわかりました。

## 対策
対策としては２つ考えられます。

1. SNSトピックの暗号化を無効にする
1. Budgetsが復号化を行えるような鍵を作成し、その鍵でSNSトピックを暗号化する

### 1. SNSトピックの暗号化を無効にする
これは単純な話です。
暗号化したSNSトピックが使えないのであれば、暗号化を無効化すればよいのです。
これはSNSトピックを作り直すことなく暗号化を無効にできます。

しかし、セキュリティの観点では暗号化しておいたほうが良いような気がしますし、会社のポリシーにも準拠できそうです。
そんなときは2.の対策が考えられます。

### 2. Budgetsが復号化を行えるような鍵を作成し、その鍵でSNSトピックを暗号化する。 
 [CloudTrail の通知に暗号化された SNS Topic を利用した場合に発生する Policy エラーに対応する](https://blog.serverworks.co.jp/config-rules-sns-encrypted-kms)ではCloudTrailの通知先に暗号化したSNSトピックを設定できない事例が紹介されていました。
おそらくBudgetsに関しても同様で、Budgetsが暗号化したSNSトピックに到達できない理由は、復号化が行えないからだと考えられます。

マネージドキーのキーポリシーを見ればBudgetsが復号化を許可されていないことがわかります。


<details>
<summary>マネージド鍵`aws/sns`のキーポリシー</summary>
```
{
    "Version": "2012-10-17",
    "Id": "auto-sns-2",
    "Statement": [
        {
            "Sid": "Allow access through SNS for all principals in the account that are authorized to use SNS",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": [
                "kms:Decrypt",
                "kms:GenerateDataKey*",
                "kms:CreateGrant",
                "kms:ListGrants",
                "kms:DescribeKey"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "kms:CallerAccount": "111111111111",
                    "kms:ViaService": "sns.ap-northeast-1.amazonaws.com"
                }
            }
        },
        {
            "Sid": "Allow direct access to key metadata to the account",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::111111111111:root"
            },
            "Action": [
                "kms:Describe*",
                "kms:Get*",
                "kms:List*",
                "kms:RevokeGrant"
            ],
            "Resource": "*"
        },
        {
            "Sid": "Allow SNS to decrypt archived messages",
            "Effect": "Allow",
            "Principal": {
                "Service": "sns.amazonaws.com"
            },
            "Action": "kms:Decrypt",
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "aws:SourceAccount": "111111111111"
                },
                "ArnLike": {
                    "aws:SourceArn": "arn:*:sns:ap-northeast-1:111111111111:*"
                }
            }
        }
    ]
}
```
</details>

 
Budgetsによる復号化を許可するには鍵のポリシーを書き換える必要があります。
しかし、マネージド鍵のポリシーは編集不可なので、自分たちで管理できる鍵（CMK）を作ってBudgetsによる許可するポリシーを書いてやれば、BudgetsからSNSに到達できると考えられます。
例えば以下のようなポリシーを持つCMKを作ると良さそうです。
```
{
    "Version": "2012-10-17",
    "Id": "key-consolepolicy-1",
    "Statement": [
        {
            "Sid": "Enable IAM User Permissions",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::111111111111:root"
            },
            "Action": "kms:*",
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "budgets.amazonaws.com"
            },
            "Action": [
                "kms:GenerateDataKey*",
                "kms:Decrypt"
            ],
            "Resource": "*"
        }
    ]
}
```

実際にCMKを使って動作確認してみます。

## 検証
まずは通知の仕組みを作ります。
Budgets＋暗号化していないSNS＋Chatbotを組み合わせて、通知成功パターンを構築します。
以下のようなSNSをトピックを作成しChatbotの通知元に設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397203/54090211-6742-84a6-6ce2-f10a5075c06e.png)

[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/cost-management/latest/userguide/budgets-sns-policy.html)を参考にしてSNSをトピックにBudgetsからの配信を許可するポリシーを設定します。
```
{
  "Sid": "E.g., AWSBudgetsSNSPublishingPermissions",
  "Effect": "Allow",
  "Principal": {
    "Service": "budgets.amazonaws.com"
  },
  "Action": "SNS:Publish",
  "Resource": "your topic ARN",
   "Condition": {
        "StringEquals": {
          "aws:SourceAccount": "<account-id>"
        },
        "ArnLike": {
          "aws:SourceArn": "arn:aws:budgets::<account-id>:*"
        }
      }
}
```
次にBudgetsを作成します。
先にSNSのアクセスポリシーでBudgetsからの配信を許可しておかないとBudgetsにSNSトピックを設定できないので注意してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397203/3dd2ad74-a185-01de-2b55-b6d2c0635a21.png)

すぐに通知したいため、しきい値を現在の利用実績より低い金額に設定しました。
Budgetsの作成が完了するとすぐにアラートが上がります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397203/dfc5476e-ed95-d5dd-3305-ff6b290cd5f5.png)

期待通りSlackまで通知が届きました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397203/ea1acf47-2000-86a3-5a06-1b158d87f91d.png)

下準備は完了したので次はSNSを暗号化していきます。
まずは暗号化するためのCMKを作ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397203/338c1162-69d5-f438-622b-5e88491ea19d.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397203/d60f4b56-a132-7c65-f78a-f6f95f657332.png)

一度鍵を作成してから以下のようなキーポリシーを追記します。
```
{
    "Effect": "Allow",
    "Principal": {
        "Service": "budgets.amazonaws.com"
    },
    "Action": [
        "kms:GenerateDataKey*",
        "kms:Decrypt"
    ],
    "Resource": "*"
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397203/5cae6514-56e7-8a55-715a-64559d23a0b2.png)

作成したCMKを使って暗号化します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397203/8f048df6-cd66-81fb-c07e-3ff9911053ca.png)

もう一度アラートを発生させるとBudgetsが復号化できるようになったので
期待通りSlackに通知が届きました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397203/4689f26c-153b-527d-4486-69b256214499.png)

## まとめ
Budgetsの通知が飛ばなくなった原因調査と暗号化したSNSを使ってBudgetsの通知を行う方法を紹介しました。
今までCMKをつくるユースケースがあんまり思いつかなかったのですが、今回の検証でユースケースを一つ学ぶことができました。
Budgets以外にもSNSトピックを経由して通知を行う場合は今回のようにCMKを使うことが考えられます。
暗号化をやめるのか、CMKを使うのかは会社のポリシーとCMKのコストを考慮して決定するのが良いと考えます。まぁ基本的にエンタープライズなら暗号化一択かとは思いますが。

## 参考
- [CloudTrail の通知に暗号化された SNS Topic を利用した場合に発生する Policy エラーに対応する](https://blog.serverworks.co.jp/config-rules-sns-encrypted-kms)
- [公式ドキュメント](https://docs.aws.amazon.com/ja_jp/cost-management/latest/userguide/budgets-sns-policy.html)