CloudTrailのログからセキュリティ的に注意が必要そうなイベントをSlack通知してみた

## はじめに
AWS CloudTrailは、コンソールやCLIからなどのAPIコール(ざっくり言うとアクセスログ)を記録するサービスです。
これにより、「いつ」「誰が」「何を」したのか(証跡)が記録でき、監査やセキュリティ監視、セキュリティ問題発生時などでの調査に利用する事ができます。

しかし、Webサーバーのアクセスログなどと同様に、ログを残しておくだけでは、セキュリティ問題が発生する可能性があるイベントがあっても気付くことができないため、対応が後手に回ってしまう可能性があります。

そこで、CloudTrailのログをCloudWatch Logsに転送し、特定のイベントについてSlack通知するようにしてみました。

## CloudTrailの有効化
CloudTrailの有効化の方法は、以下などを参考にするとよいと思います。

https://www.insight-tec.com/tech-blog/aws/20211223_aws_cloudtrail/

この際、S3だけでなく、CloudWatch Logsにもイベントを転送するようにしておくと、特定のイベントについて、メールやSlackに通知がしやすくなります。

## 構成
構成は以下の通りです。
CloudWatch Logsに転送したCloudTrailのイベントのうち、通知したいイベントをメトリクスフィルタでひろい、そのアラームをSNS、Chatbot経由でSlackに通知します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2554521/4e54287a-c4fa-ea33-9629-3226c4e71dc3.png)

## KMS(CMK)の作成
SNS用のCMKを作成します。
SNSの暗号化は必須ではありませんが、CMKを作成し適用しておきます。
| 設定項目 | 設定値 |
|:-|:-|
| エイリアス | Security-Alarm-Key |
| キーのタイプ | 対象 |
| オリジン | AWS_KMS |
| キーの仕様 | SYMMETRIC_DEFAULT |
| キーの使用 | 暗号化および復号化 |
| キーローテーション | 毎年自動的にローテーション |

今回は、CloudWatchから通知を受け取るので、CloudWatchのサービス(`"Service": "cloudwatch.amazonaws.com"`)からCMKを使えるようPrincipalを設定します。
```json
{
    "Version": "2012-10-17",
    "Id": "key-consolepolicy-3",
    "Statement": [
        {
            "Sid": "Enable IAM User Permissions",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::XXXXXXXXXXXX:root"
            },
            "Action": "kms:*",
            "Resource": "*"
        },
        {
            "Sid": "Allow access for Key Administrators",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::XXXXXXXXXXXX:user/administrator"
            },
            "Action": [
                "kms:Create*",
                "kms:Describe*",
                "kms:Enable*",
                "kms:List*",
                "kms:Put*",
                "kms:Update*",
                "kms:Revoke*",
                "kms:Disable*",
                "kms:Get*",
                "kms:Delete*",
                "kms:TagResource",
                "kms:UntagResource",
                "kms:ScheduleKeyDeletion",
                "kms:CancelKeyDeletion"
            ],
            "Resource": "*"
        },
        {
            "Sid": "Allow use of the key",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::XXXXXXXXXXXX:root",
                    "arn:aws:iam::XXXXXXXXXXXX:user/administrator"
                ],
                "Service": "cloudwatch.amazonaws.com"
            },
            "Action": [
                "kms:Encrypt",
                "kms:Decrypt",
                "kms:ReEncrypt*",
                "kms:GenerateDataKey*",
                "kms:DescribeKey"
            ],
            "Resource": "*"
        },
        {
            "Sid": "Allow attachment of persistent resources",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::XXXXXXXXXXXX:root",
                    "arn:aws:iam::XXXXXXXXXXXX:user/administrator"
                ],
                "Service": "cloudwatch.amazonaws.com"
            },
            "Action": [
                "kms:CreateGrant",
                "kms:ListGrants",
                "kms:RevokeGrant"
            ],
            "Resource": "*",
            "Condition": {
                "Bool": {
                    "kms:GrantIsForAWSResource": "true"
                }
            }
        }
    ]
}
```

## SNSの作成
SNSトピックを作成します。
サブスクリプションは、Chatbot設定時に関連付けるので、ここではトピックの作成のみ行います。
| 設定項目 | 設定値 |
|:-|:-|
| 名前 | Security-Alarm-Notification |
| アクセスポリシー | デフォルト：トピックの所有者のみがトピックに発行またはサブスクライブできる |
| 配信再試行ポリシー | デフォルト値 |
| 配信ステータスのログ記録 | あり |
| 暗号化 | alias/Security-Alarm-Key |

## Slackチャンネルの作成
アラーム通知用のSlackチャンネルを作成します。
作成したSlackチャンネルのIDは、Chatbotの設定で必要になります。

※Slackのプライベートチャンネルに通知をする場合、AWS Chatbotアプリを通知するチャンネルに招待しておく必要があります。

## Chatbotの設定
チャットクライアントのセットアップが終わったら、Chatbotのチャネル設定をします。
| 設定項目 | 設定値 |
|:-|:-|
| 設定名 | CloudWatch-Security-Alarm |
| チャネルID | 通知するSlackチャンネルのチャンネルID |
| ログ記録レベル | オフ |
| ロールの設定 | チャネルIAMロール |
| チャネルロール | AWSChatbot-role |
| ガードレールのポリシー | CloudWatchReadOnlyAccess |
| マッピング済みのSNSトピック | Security-Alarm-Notification |

## CloudWatch Logsのメトリクスフィルターとアラームを設定
CloudWatch Logsに転送されたCloudTrailのログに対して、メトリクスフィルターとアラームの設定を行います。
今回は、Security Hubのセキュリティ基準「CIS AWS Foundations Benchmark v1.2.0」で推奨されているイベントに対して設定を行います。

https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-cis-controls.html#securityhub-cis-controls-3.1

設定の流れは、以下の通りです。
1. CloudWatchの画面で、ナビゲーションペインから「ロググループ」を選択し、ロググループの一覧から、CloudTrailのログが出力されているロググループを選択
1. 「メトリクスフィルター」タブを選択し、「メトリクスフィルターの作成」を選択してメトリクスフィルターを作成
1. メトリクスフィルターの一覧で、2で作成したメトリクスフィルターを選択し、「アラームの作成」を選択してアラームを作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2554521/207cf947-5e04-e9b8-a9a5-414f8194adf3.png)

#### メトリクスフィルターを作成
作成したメトリクスフィルターの設定は以下の通りです。

14のメトリクスフィルターを作成していますが、メトリクス名前空間には、全て「CIS-Benchmark-LogMetrics」を設定しています(全てのメトリクスの名前空間を同じにする事で、メトリクスがグループ化される)。
また、フィルター名とメトリクス名は同じ名前にし、デフォルト値とUnitには何も指定していません。
| イベント通知のトリガー | フィルターパターン | メトリクス値 | フィルター名/メトリクス名 |
|:-|:-|:-|:-|
| 不正なAPIコールがあった場合 | \{\(\\$.errorCode="\*UnauthorizedOperation"\) \|\| \(\$.errorCode="AccessDenied\*"\)\} | 1 | CIS-3.1-UnauthorizedAPICalls |
| MFAなしでAWS管理コンソールにサインイン | \{ \(\\$.eventName = "ConsoleLogin"\) && \(\\$.additionalEventData.MFAUsed != "Yes"\) && \(\\$.userIdentity.type = "IAMUser"\) && \(\$.responseElements.ConsoleLogin = "Success"\) \} | 1 | CIS-3.2-ConsoleSigninWithoutMFA |
| rootユーザーの利用 | \{\\$.userIdentity.type="Root" && \\$.userIdentity.invokedBy NOT EXISTS && \\$.eventType !="AwsServiceEvent"\} | 1 | CIS-3.3-RootAccountUsage |
| IAM ポリシーの変更 | \{\(\\$.eventName=DeleteGroupPolicy\) \|\| \(\\$.eventName=DeleteRolePolicy) \|\| \(\\$.eventName=DeleteUserPolicy\) \|\| \(\\$.eventName=PutGroupPolicy\) \|\| \(\\$.eventName=PutRolePolicy\) \|\| \(\\$.eventName=PutUserPolicy\) \|\| \(\\$.eventName=CreatePolicy\) \|\| \(\\$.eventName=DeletePolicy\) \|\| \(\\$.eventName=CreatePolicyVersion\) \|\| \(\\$.eventName=DeletePolicyVersion\) \|\| \(\\$.eventName=AttachRolePolicy\) \|\| \(\\$.eventName=DetachRolePolicy\) \|\| \(\\$.eventName=AttachUserPolicy\) \|\| \(\\$.eventName=DetachUserPolicy\) \|\| \(\\$.eventName=AttachGroupPolicy\) \|\| \(\\$.eventName=DetachGroupPolicy\)\} | 1 | CIS-3.4-IAMPolicyChanges |
| CloudTrail設定の変更 | \{\(\\$.eventName=CreateTrail\) \|\| \(\\$.eventName=UpdateTrail\) \|\| \(\\$.eventName=DeleteTrail\) \|\| \(\\$.eventName=StartLogging\) \|\| \(\\$.eventName=StopLogging\)\} | 1 | CIS-3.5-CloudTrailChanges |
| AWS管理コンソールでの認証失敗 | \{\(\\$.eventName=ConsoleLogin\) && \(\\$.errorMessage="Failed authentication"\)\} | 1 | CIS-3.6-ConsoleAuthenticationFailure |
| CMKの無効化またはスケジュールされた削除 | \{\(\\$.eventSource=kms.amazonaws.com\) && \(\(\\$.eventName=DisableKey\) \|\| \(\\$.eventName=ScheduleKeyDeletion\)\)\} | 1 | CIS-3.7-DisableOrDeleteCMK |
| S3バケットポリシーの変更 | \{\(\\$.eventSource=s3.amazonaws.com\) && \(\(\\$.eventName=PutBucketAcl\) \|\| \(\\$.eventName=PutBucketPolicy\) \|\| \(\\$.eventName=PutBucketCors\) \|\| \(\\$.eventName=PutBucketLifecycle\) \|\| \(\\$.eventName=PutBucketReplication\) \|\| \(\\$.eventName=DeleteBucketPolicy\) \|\| \(\\$.eventName=DeleteBucketCors\) \|\| \(\\$.eventName=DeleteBucketLifecycle\) \|\| \(\\$.eventName=DeleteBucketReplication\)\)\} | 1 | CIS-3.8-S3BucketPolicyChanges |
| AWS Config設定の変更 | \{\(\\$.eventSource=config.amazonaws.com\) && \(\(\\$.eventName=StopConfigurationRecorder\) \|\| \(\\$.eventName=DeleteDeliveryChannel\) \|\| \(\\$.eventName=PutDeliveryChannel\) \|\| \(\\$.eventName=PutConfigurationRecorder\)\)\} | 1 | CIS-3.9-AWSConfigChanges |
| セキュリティグループの変更 | \{\(\\$.eventName=AuthorizeSecurityGroupIngress\) \|\| \(\\$.eventName=AuthorizeSecurityGroupEgress\) \|\| \(\\$.eventName=RevokeSecurityGroupIngress\) \|\| \(\\$.eventName=RevokeSecurityGroupEgress\) \|\| \(\\$.eventName=CreateSecurityGroup\) \|\| \(\\$.eventName=DeleteSecurityGroup\)\} | 1 | CIS-3.10-SecurityGroupChanges |
| ネットワークACLの変更 | \{\(\\$.eventName=CreateNetworkAcl\) \|\| \(\\$.eventName=CreateNetworkAclEntry\) \|\| \(\\$.eventName=DeleteNetworkAcl\) \|\| \(\\$.eventName=DeleteNetworkAclEntry\) \|\| \(\\$.eventName=ReplaceNetworkAclEntry\) \|\| \(\\$.eventName=ReplaceNetworkAclAssociation\)\} | 1 | CIS-3.11-NetworkACLChanges |
| ネットワークゲートウェイへの変更 | \{\(\\$.eventName=CreateCustomerGateway\) \|\| \(\\$.eventName=DeleteCustomerGateway\) \|\| \(\\$.eventName=AttachInternetGateway\) \|\| \(\\$.eventName=CreateInternetGateway\) \|\| \(\\$.eventName=DeleteInternetGateway\) \|\| \(\\$.eventName=DetachInternetGateway\)\} | 1 | CIS-3.12-NetworkGatewayChanges |
| ルートテーブルの変更 | \{\(\\$.eventName=CreateRoute\) \|\| \(\\$.eventName=CreateRouteTable\) \|\| \(\\$.eventName=ReplaceRoute\) \|\| \(\\$.eventName=ReplaceRouteTableAssociation\) \|\| \(\\$.eventName=DeleteRouteTable\) \|\| \(\\$.eventName=DeleteRoute\) \|\| \(\\$.eventName=DisassociateRouteTable\)\} | 1 | CIS-3.13-RouteTableChanges |
| VPCの変更 | \{\(\\$.eventName=CreateVpc\) \|\| \(\\$.eventName=DeleteVpc\) \|\| \(\\$.eventName=ModifyVpcAttribute\) \|\| \(\\$.eventName=AcceptVpcPeeringConnection\) \|\| \(\\$.eventName=CreateVpcPeeringConnection\) \|\| \(\\$.eventName=DeleteVpcPeeringConnection\) \|\| \(\\$.eventName=RejectVpcPeeringConnection\) \|\| \(\\$.eventName=AttachClassicLinkVpc\) \|\| \(\\$.eventName=DetachClassicLinkVpc\) \|\| \(\\$.eventName=DisableVpcClassicLink\) \|\| \(\\$.eventName=EnableVpcClassicLink\)\} | 1 | CIS-3.14-VPCChanges |

#### メトリクスフィルター毎にアラームを作成
作成したメトリクスフィルターと同じ14のアラームを作成していますが、アラーム名はメトリクス名と同じにしています。
また、各アラームの設定は全て以下のようにしています。
| 設定項目 | 設定値 |
|:-:|:-:|
| 統計 | 合計 |
| 期間 | 5分 |
| しきい値の種類 | 静的 |
| アラーム条件 | 以上 >= しきい値 |
| しきい値 | 1 |
| アラーム状態トリガー | アラーム状態 |
| 通知先SNSトピック | Security-Alarm-Notification |


## 設定の確認・検証
ここまで設定が終わったら、実際にSlackに通知がされるか確認します。
以下のような通知が来れば、設定完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2554521/67ebf5f7-463f-27f5-8cf8-4adf4b1e8f89.png)

## おわりに
CloudTrailのログから、セキュリティ的に注意が必要そうなイベントをSlack通知してみました。

なお、通知が来た後、具体的に何のリソースに対する変更があったのかを確認する場合は、あらかじめS3バケットに格納されているログをパーティション化しておき、該当時間帯のログをAthenaなどで解析するか、CloudTrail Lakeを構成し、SQLライクなクエリーで調査するなどが必要になるかと思います。

解析方法まで準備しておかないと、通知だけできても不十分ではありますが、まずは気付く事が大切ですね。