AWS CIS Foundations Benchmark モニタリングをAWS CLIで設定してみた

先日、弊社よりinsightwatch(インサイトウォッチ)がリリースされました。チェック項目であるAWS CIS Foundations Benchmarkモニタリングの項目をAWS CLIで設定してみました。
2018.05.22

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、佐伯です。

先日、弊社よりinsightwatch(インサイトウォッチ)がリリースされました。insightwatchはAWS環境があるべきセキュリティ設定で正しく運用されているか監査するサービスです。詳しくは以下プレスリリースをご確認ください。

セキュリティ監査サービス「インサイトウォッチ」をリリース|プレスリリース|クラスメソッド

2018年5月時点ではAWS CIS Foundations Benchmarkのチェック項目に対応しています。insightwatchではチェックはしてくれますが、設定はユーザーで行う必要があります。ということで、今回は設定が少し手間なモニタリングの項目について、AWS CLIで設定してみました。

AWS CIS Foundations Benchmarkとは

AWS CIS Foundations BenchmarkとはAWSにおけるベストプラクティスに沿ったセキュリティ設定に関するガイドラインのようなものです。詳しくは以下のエントリを参照ください。

あなたのAWSセキュリティ監査状況を採点〜CISベンチマークを読んでみた

AWS CIS Foundations Benchmarkモニタリングのチェック項目

AWS CIS Foundations Benchmarkのモニタリングでは以下の項目をチェックしています。3.15以外はCloudWatchアラームによる通知設定がされていることをチェック項目としています。

  • 3.1 許可されていないAPIコールに対して、アラーム通知設定されていること
  • 3.2 MFAなしでのAWSマネジメントコンソールログインに対して、アラーム通知設定されていること
  • 3.3 rootアカウントの利用に対して、アラーム通知設定されていること
  • 3.4 IAMポリシーの変更に対して、アラーム通知設定されていること
  • 3.5 CloudTrail設定の変更に対して、アラーム通知設定されていること
  • 3.6 AWSマネジメントコンソールのログイン失敗に対して、アラーム通知設定されていること
  • 3.7 KMSマスターキーの無効化またはスケジュール削除に対して、アラーム通知設定されていること
  • 3.8 S3バケットポリシー変更に対して、アラーム通知設定されていること
  • 3.9 AWS Config設定の変更に対して、アラーム通知設定されていること
  • 3.10 Security Group設定の変更に対して、アラーム通知設定されていること
  • 3.11 Network ACL設定の変更に対して、アラーム通知設定されていること
  • 3.12 Internet Gateway設定の変更に対して、アラーム通知設定されていること
  • 3.13 Route Tabley設定の変更に対して、アラーム通知設定されていること
  • 3.14 VPC設定の変更に対して、アラーム通知設定されていること
  • 3.15 SNS TopicのSubscriberに適切な連絡先が設定されていること

AWS CIS Foundations Benchmarkモニタリングのアーキテクチャ

かなりざっくりですが以下様にCloudTrail, CloudWatch Logs, CloudWatchアラーム, Amazon SNSを使用して、アラーム通知設定を実現しています。

AWS CLIを使って設定してみた

3.15以外の項目について、AWS CLIで設定する方法を試してみました。コピペで使えればと思い、変数にリージョンやAWSアカウントID入れたり、ヒアドキュメントでJSONファイル作ったりしています。読みにくい点についてはご了承ください…。

また、モニタリングに必要なAWSリソースはリージョンごとに作成する形にしています。そのためリージョンごとにコマンドを実行するため、forループを多用しています。

CloudTrailを作成

既にCloudTrailを作成しており、S3バケットに保存している場合は本手順はスキップして頂いて構わないです。

# AWSアカウントIDとリージョンを変数に代入
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGIONS=($(aws ec2 describe-regions --query Regions[].RegionName --output text))

# S3バケットの作成
# us-east-1は`--create-bucket-configuration`オプションが不要なのでIF文で分岐させてます。
for REGION in ${REGIONS[@]}
do
  if [ ${REGION} = "us-east-1" ];then
    aws s3api create-bucket --bucket cloudtrail-${AWS_ACCOUNT_ID}-${REGION} \
      --acl private --region ${REGION}
  else
    aws s3api create-bucket --bucket cloudtrail-${AWS_ACCOUNT_ID}-${REGION} \
      --create-bucket-configuration LocationConstraint=${REGION} \
      --acl private --region ${REGION}
  fi
done

# S3バケットポリシー設定用のJSONファイルを作成
for REGION in ${REGIONS[@]}
do
tee s3bucket_policy_${REGION}.json << EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudtrail.amazonaws.com"
            },
            "Action": "s3:GetBucketAcl",
            "Resource": "arn:aws:s3:::cloudtrail-${AWS_ACCOUNT_ID}-${REGION}"
        },
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudtrail.amazonaws.com"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::cloudtrail-${AWS_ACCOUNT_ID}-${REGION}/AWSLogs/${AWS_ACCOUNT_ID}/*",
            "Condition": {
                "StringEquals": {
                    "s3:x-amz-acl": "bucket-owner-full-control"
                }
            }
        }
    ]
}
EOF
done

# ライフサイクル設定用のJSONファイルを作成
# オブジェクトが作成されてから400日後に削除する設定としています。
tee s3bucket_lifecycle_policy.json << EOF
{
    "Rules": [
        {
            "Filter": {
                "Prefix": ""
            },
            "Status": "Enabled",
            "Expiration": {
                "Days": 400
            },
            "ID": "Expire-after-400-days"
        }
    ]
}
EOF

# S3バケットポリシーを設定
for REGION in ${REGIONS[@]}
do
  aws s3api put-bucket-policy --bucket cloudtrail-${AWS_ACCOUNT_ID}-${REGION} \
    --policy file://s3bucket_policy_${REGION}.json --region ${REGION}
done

# S3バケットライフサイクルポリシーを適用
for REGION in ${REGIONS[@]}
do
  aws s3api put-bucket-lifecycle-configuration  \
  --bucket cloudtrail-${AWS_ACCOUNT_ID}-${REGION} \
  --lifecycle-configuration file://s3bucket_lifecycle_policy.json \
  --region ${REGION}
done

# CloudTrailの作成
for REGION in ${REGIONS[@]}
do
  aws cloudtrail create-trail --name CloudTrail-${REGION} \
    --s3-bucket-name cloudtrail-${AWS_ACCOUNT_ID}-${REGION} \
    --enable-log-file-validation \
    --no-include-global-service-events \
    --region ${REGION}
  aws cloudtrail start-logging --name CloudTrail-${REGION} --region ${REGION}
done

CloudTrailのログイベントをCloudWatch Logsに送信

CloudWatch LogsとCloudWatchアラームを使用してモニタリングを行うので、CloudTrailのログイベントをCloudWatch Logsへ送信します。こちらも既にCloudTrailをCloudWatch Logsへ送信している場合、本手順はスキップして頂いて構わないです。

# AWSアカウントIDとリージョンを変数に代入
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGIONS=($(aws ec2 describe-regions --query Regions[].RegionName --output text))

# CloudWatch Logsで使用するIAMロールを作成
tee cloudtrail_assumerole_policy.json << EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudtrail.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

for REGION in ${REGIONS[@]}
do
tee cloudtrail_cloudwatch_policy_${REGION}.json << EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
     "Effect": "Allow",
      "Action": [
        "logs:CreateLogStream"
      ],
      "Resource": [
        "arn:aws:logs:${REGION}:${AWS_ACCOUNT_ID}:log-group:CloudTrail/DefaultLogGroup:log-stream:${AWS_ACCOUNT_ID}_CloudTrail_${REGION}*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:PutLogEvents"
      ],
      "Resource": [
        "arn:aws:logs:${REGION}:${AWS_ACCOUNT_ID}:log-group:CloudTrail/DefaultLogGroup:log-stream:${AWS_ACCOUNT_ID}_CloudTrail_${REGION}*"
      ]
    }
  ]
}
EOF
done

# IAMロールの作成とポリシーのアタッチ
for REGION in ${REGIONS[@]}
do
  aws iam create-role --role-name CloudTrail_CloudWatchLogs_Role_${REGION} \
    --assume-role-policy-document file://cloudtrail_assumerole_policy.json
  aws iam put-role-policy --role-name CloudTrail_CloudWatchLogs_Role_${REGION} \
    --policy-name CloudTrail_CloudWatchLogs_Policy \
    --policy-document file://cloudtrail_cloudwatch_policy_${REGION}.json
done

# CloudWatch Logsへの送信
# こちらもログ保持期間は400日としています。
for REGION in ${REGIONS[@]}
do
  aws logs create-log-group --log-group-name CloudTrail/DefaultLogGroup --region ${REGION}
  aws logs put-retention-policy --log-group-name CloudTrail/DefaultLogGroup --retention-in-days 400 --region ${REGION}
  aws cloudtrail update-trail --name CloudTrail-${REGION} \
    --cloud-watch-logs-log-group-arn "arn:aws:logs:${REGION}:${AWS_ACCOUNT_ID}:log-group:CloudTrail/DefaultLogGroup:*" \
    --cloud-watch-logs-role-arn "arn:aws:iam::${AWS_ACCOUNT_ID}:role/CloudTrail_CloudWatchLogs_Role_${REGION}" \
    --region ${REGION}
done

アラーム通知用のSNS Topicを作成

CloudWatchアラームの通知先に設定するSNS Topicを作成します。SNS Subscription設定後は確認のメールが送信されるので、メール本文の"Confirm subscription"のリンクをクリックし、承認してください。

# AWSアカウントIDとリージョンを変数に代入
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGIONS=($(aws ec2 describe-regions --query Regions[].RegionName --output text))

# SNS Topic, SNS Subscription設定
# 複数のメールアドレスに通知する場合は`aws sns subscribe`コマンドをコピペして複数のメールアドレスにサブスクライブします。
for REGION in ${REGIONS[@]}
do
  aws sns create-topic --name CISBenchmarkSnsTopic --region ${REGION}
  aws sns subscribe --topic-arn arn:aws:sns:${REGION}:${AWS_ACCOUNT_ID}:CISBenchmarkSnsTopic \
  --protocol email --notification-endpoint <Your Email Address> --region ${REGION}
done

項目ごとのCloudWatch LogsメトリクスフィルタとCloudWatchアラームの作成

3.1 許可されていないAPIコールに対するアラーム通知設定

CloudWatchアラームの閾値は5分間に5回以上、許可されていないAPIコールがあった場合としています。

# AWSアカウントIDとリージョンを変数に代入
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGIONS=($(aws ec2 describe-regions --query Regions[].RegionName --output text))

# CloudWatch Logsメトリクスフィルタ作成
for REGION in ${REGIONS[@]}
do
  aws logs put-metric-filter --log-group-name CloudTrail/DefaultLogGroup \
    --filter-name "UnauthorizedAPICallEventFilter" \
    --metric-transformations metricName=UnauthorizedAPICallEventCount,metricNamespace=CISBenchmark,metricValue=1 \
    --filter-pattern '{ ($.errorCode = "*UnauthorizedOperation") || ($.errorCode = "AccessDenied*") }' \
    --region ${REGION}
done

# CloudWatchアラーム作成
for REGION in ${REGIONS[@]}
do
  aws cloudwatch put-metric-alarm --alarm-name CIS3.1_UnauthorizedActivity \
    --alarm-description "CloudWatch Logs: https://${REGION}.console.aws.amazon.com/cloudwatch/home?region=${REGION}#logStream:group=CloudTrail/DefaultLogGroup" \
    --metric-name UnauthorizedAPICallEventCount \
    --statistic Sum --period 300 --threshold 5 \
    --comparison-operator GreaterThanOrEqualToThreshold \
    --evaluation-periods 1 \
    --treat-missing-data notBreaching \
    --namespace CISBenchmark \
    --alarm-actions arn:aws:sns:${REGION}:${AWS_ACCOUNT_ID}:CISBenchmarkSnsTopic \
    --region ${REGION}
done

3.2 MFAなしでのAWSマネジメントコンソールログインに対するアラーム通知設定

CloudWatchアラームの閾値は5分間に1回以上、MFAなしでAWSマネジメントコンソールにログインがあった場合としています。

# AWSアカウントIDとリージョンを変数に代入
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGIONS=($(aws ec2 describe-regions --query Regions[].RegionName --output text))

# CloudWatch Logsメトリクスフィルタ作成
for REGION in ${REGIONS[@]}
do
  aws logs put-metric-filter --log-group-name CloudTrail/DefaultLogGroup \
    --filter-name NoMfaConsoleLoginsEventFilter \
    --metric-transformations metricName=NoMfaConsoleLoginEventCount,metricNamespace=CISBenchmark,metricValue=1 \
    --filter-pattern '{ ($.eventName = "ConsoleLogin") && ($.additionalEventData.MFAUsed != "Yes") }' \
    --region ${REGION}
done

# CloudWatchアラーム作成
for REGION in ${REGIONS[@]}
do
  aws cloudwatch put-metric-alarm --alarm-name CIS3.2_ConsoleSigninWithoutMFA \
    --alarm-description "CloudWatch Logs: https://${REGION}.console.aws.amazon.com/cloudwatch/home?region=${REGION}#logStream:group=CloudTrail/DefaultLogGroup" \
    --metric-name NoMfaConsoleLoginEventCount \
    --statistic Sum --period 300 --threshold 1 \
    --comparison-operator GreaterThanOrEqualToThreshold \
    --evaluation-periods 1 \
    --treat-missing-data notBreaching \
    --namespace CISBenchmark \
    --alarm-actions arn:aws:sns:${REGION}:${AWS_ACCOUNT_ID}:CISBenchmarkSnsTopic \
    --region ${REGION}
done

3.3 rootアカウントの利用に対するアラーム通知設定

CloudWatchアラームの閾値は5分間に1回以上、rootアカウントの利用があった場合としています。

# AWSアカウントIDとリージョンを変数に代入
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGIONS=($(aws ec2 describe-regions --query Regions[].RegionName --output text))

# CloudWatch Logsメトリクスフィルタ作成
for REGION in ${REGIONS[@]}
do
  aws logs put-metric-filter --log-group-name CloudTrail/DefaultLogGroup \
    --filter-name RootUserEventFilter \
    --metric-transformations metricName=RootUserEventCount,metricNamespace=CISBenchmark,metricValue=1 \
    --filter-pattern '{ $.userIdentity.type = "Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != "AwsServiceEvent" }' \
    --region ${REGION}
done

# CloudWatchアラーム作成
for REGION in ${REGIONS[@]}
do
  aws cloudwatch put-metric-alarm --alarm-name CIS3.3_RootUserActivity \
    --alarm-description "CloudWatch Logs: https://${REGION}.console.aws.amazon.com/cloudwatch/home?region=${REGION}#logStream:group=CloudTrail/DefaultLogGroup" \
    --metric-name RootUserEventCount \
    --statistic Sum --period 300 --threshold 1 \
    --comparison-operator GreaterThanOrEqualToThreshold \
    --evaluation-periods 1 \
    --treat-missing-data notBreaching \
    --namespace CISBenchmark \
    --alarm-actions arn:aws:sns:${REGION}:${AWS_ACCOUNT_ID}:CISBenchmarkSnsTopic \
    --region ${REGION}
done

3.4 IAMポリシーの変更に対するアラーム通知設定

CloudWatchアラームの閾値は5分間に1回以上、IAMポリシーの変更があった場合としています。

# AWSアカウントIDとリージョンを変数に代入
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGIONS=($(aws ec2 describe-regions --query Regions[].RegionName --output text))

# CloudWatch Logsメトリクスフィルタ作成
for REGION in ${REGIONS[@]}
do
  aws logs put-metric-filter --log-group-name CloudTrail/DefaultLogGroup \
    --filter-name IAMPolicyChangeEventFilter \
    --metric-transformations metricName=IAMPolicyChangeEventCount,metricNamespace=CISBenchmark,metricValue=1 \
    --filter-pattern '{($.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)}' \
    --region ${REGION}
done

# CloudWatchアラーム作成
for REGION in ${REGIONS[@]}
do
  aws cloudwatch put-metric-alarm --alarm-name CIS3.4_IAMPolicyChanges \
    --alarm-description "CloudWatch Logs: https://${REGION}.console.aws.amazon.com/cloudwatch/home?region=${REGION}#logStream:group=CloudTrail/DefaultLogGroup" \
    --metric-name IAMPolicyChangeEventCount \
    --statistic Sum --period 300 --threshold 1 \
    --comparison-operator GreaterThanOrEqualToThreshold \
    --evaluation-periods 1 \
    --treat-missing-data notBreaching \
    --namespace CISBenchmark \
    --alarm-actions arn:aws:sns:${REGION}:${AWS_ACCOUNT_ID}:CISBenchmarkSnsTopic \
    --region ${REGION}
done

3.5 CloudTrail設定の変更に対するアラーム通知設定

CloudWatchアラームの閾値は5分間に1回以上、CloudTrailに変更があった場合としています。

# AWSアカウントIDとリージョンを変数に代入
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGIONS=($(aws ec2 describe-regions --query Regions[].RegionName --output text))

# CloudWatch Logsメトリクスフィルタ作成
for REGION in ${REGIONS[@]}
do
  aws logs put-metric-filter --log-group-name CloudTrail/DefaultLogGroup \
    --filter-name CloudTrailChangeEventFilter \
    --metric-transformations metricName=CloudTrailChangeEventCount,metricNamespace=CISBenchmark,metricValue=1 \
    --filter-pattern '{ ($.eventName = CreateTrail) || ($.eventName = UpdateTrail) || ($.eventName = DeleteTrail) || ($.eventName = StartLogging) || ($.eventName = StopLogging) }' \
    --region ${REGION}
done

# CloudWatchアラーム作成
for REGION in ${REGIONS[@]}
do
  aws cloudwatch put-metric-alarm --alarm-name CIS3.5_CloudTrailChanges \
    --alarm-description "CloudWatch Logs: https://${REGION}.console.aws.amazon.com/cloudwatch/home?region=${REGION}#logStream:group=CloudTrail/DefaultLogGroup" \
    --metric-name CloudTrailChangeEventCount \
    --statistic Sum --period 300 --threshold 1 \
    --comparison-operator GreaterThanOrEqualToThreshold \
    --evaluation-periods 1 \
    --treat-missing-data notBreaching \
    --namespace CISBenchmark \
    --alarm-actions arn:aws:sns:${REGION}:${AWS_ACCOUNT_ID}:CISBenchmarkSnsTopic \
    --region ${REGION}
done

3.6 AWSマネジメントコンソールのログイン失敗に対するアラーム通知設定

CloudWatchアラームの閾値は5分間に3回以上、AWSマネジメントコンソールのログイン失敗があった場合としています。

# AWSアカウントIDとリージョンを変数に代入
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGIONS=($(aws ec2 describe-regions --query Regions[].RegionName --output text))

# CloudWatch Logsメトリクスフィルタ作成
for REGION in ${REGIONS[@]}
do
  aws logs put-metric-filter --log-group-name CloudTrail/DefaultLogGroup \
    --filter-name FailedConsoleLoginEventFilter \
    --metric-transformations metricName=FailedConsoleLoginEventCount,metricNamespace=CISBenchmark,metricValue=1 \
    --filter-pattern '{ ($.eventName = ConsoleLogin) && ($.errorMessage = "Failed authentication") }' \
    --region ${REGION}
done

# CloudWatchアラーム作成
for REGION in ${REGIONS[@]}
do
  aws cloudwatch put-metric-alarm --alarm-name CIS3.6_ConsoleLoginFailures \
    --alarm-description "CloudWatch Logs: https://${REGION}.console.aws.amazon.com/cloudwatch/home?region=${REGION}#logStream:group=CloudTrail/DefaultLogGroup" \
    --metric-name FailedConsoleLoginEventCount \
    --statistic Sum --period 300 --threshold 3 \
    --comparison-operator GreaterThanOrEqualToThreshold \
    --evaluation-periods 1 \
    --treat-missing-data notBreaching \
    --namespace CISBenchmark \
    --alarm-actions arn:aws:sns:${REGION}:${AWS_ACCOUNT_ID}:CISBenchmarkSnsTopic \
    --region ${REGION}
done

3.7 KMSマスターキーの無効化またはスケジュール削除に対するアラーム通知設定

CloudWatchアラームの閾値は5分間に1回以上、KMSマスターキーの無効化またはスケジュール削除があった場合としています。

# AWSアカウントIDとリージョンを変数に代入
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGIONS=($(aws ec2 describe-regions --query Regions[].RegionName --output text))

# CloudWatch Logsメトリクスフィルタ作成
for REGION in ${REGIONS[@]}
do
  aws logs put-metric-filter --log-group-name CloudTrail/DefaultLogGroup \
    --filter-name KmsDisabledOrScheduledDeletionEventFilter \
    --metric-transformations metricName=KmsDisabledOrScheduledDeletionEventCount,metricNamespace=CISBenchmark,metricValue=1 \
    --filter-pattern '{($.eventSource = kms.amazonaws.com) && (($.eventName=DisableKey)||($.eventName=ScheduleKeyDeletion))}' \
    --region ${REGION}
done

# CloudWatchアラーム作成
for REGION in ${REGIONS[@]}
do
  aws cloudwatch put-metric-alarm --alarm-name CIS3.7_KmsDisabledOrScheduledDeletion \
    --alarm-description "CloudWatch Logs: https://${REGION}.console.aws.amazon.com/cloudwatch/home?region=${REGION}#logStream:group=CloudTrail/DefaultLogGroup" \
    --metric-name KmsDisabledOrScheduledDeletionEventCount \
    --statistic Sum --period 300 --threshold 1 \
    --comparison-operator GreaterThanOrEqualToThreshold \
    --evaluation-periods 1 \
    --treat-missing-data notBreaching \
    --namespace CISBenchmark \
    --alarm-actions arn:aws:sns:${REGION}:${AWS_ACCOUNT_ID}:CISBenchmarkSnsTopic \
    --region ${REGION}
done

3.8 S3バケットポリシー変更に対するアラーム通知設定

CloudWatchアラームの閾値は5分間に1回以上、S3バケットポリシー変更があった場合としています。

# AWSアカウントIDとリージョンを変数に代入
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGIONS=($(aws ec2 describe-regions --query Regions[].RegionName --output text))

# CloudWatch Logsメトリクスフィルタ作成
for REGION in ${REGIONS[@]}
do
  aws logs put-metric-filter --log-group-name CloudTrail/DefaultLogGroup \
    --filter-name S3BucketPolicyChangeEventFilter \
    --metric-transformations metricName=S3BucketPolicyChangeEventCount,metricNamespace=CISBenchmark,metricValue=1 \
    --filter-pattern '{ ($.eventSource = s3.amazonaws.com) && (($.eventName = PutBucketAcl) || ($.eventName = PutBucketPolicy) || ($.eventName = PutBucketCors) || ($.eventName = PutBucketLifecycle) || ($.eventName = PutBucketReplication) || ($.eventName = DeleteBucketPolicy) || ($.eventName = DeleteBucketCors) || ($.eventName = DeleteBucketLifecycle) || ($.eventName = DeleteBucketReplication)) }' \
    --region ${REGION}
done

# CloudWatchアラーム作成
for REGION in ${REGIONS[@]}
do
  aws cloudwatch put-metric-alarm --alarm-name CIS3.8_S3BucketPolicyChanges \
    --alarm-description "CloudWatch Logs: https://${REGION}.console.aws.amazon.com/cloudwatch/home?region=${REGION}#logStream:group=CloudTrail/DefaultLogGroup" \
    --metric-name S3BucketPolicyChangeEventCount \
    --statistic Sum --period 300 --threshold 1 \
    --comparison-operator GreaterThanOrEqualToThreshold \
    --evaluation-periods 1 \
    --treat-missing-data notBreaching \
    --namespace CISBenchmark \
    --alarm-actions arn:aws:sns:${REGION}:${AWS_ACCOUNT_ID}:CISBenchmarkSnsTopic \
    --region ${REGION}
done

3.9 AWS Config変更に対するアラーム通知設定

CloudWatchアラームの閾値は5分間に1回以上、AWS Configの変更があった場合としています。

# AWSアカウントIDとリージョンを変数に代入
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGIONS=($(aws ec2 describe-regions --query Regions[].RegionName --output text))

# CloudWatch Logsメトリクスフィルタ作成
for REGION in ${REGIONS[@]}
do
  aws logs put-metric-filter --log-group-name CloudTrail/DefaultLogGroup \
    --filter-name AWSConfigChangeEventFilter \
    --metric-transformations metricName=AWSConfigChangeEventCount,metricNamespace=CISBenchmark,metricValue=1 \
    --filter-pattern '{($.eventSource = config.amazonaws.com) && (($.eventName=StopConfigurationRecorder)||($.eventName=DeleteDeliveryChannel)||($.eventName=PutDeliveryChannel)||($.eventName=PutConfigurationRecorder))}' \
    --region ${REGION}
done

# CloudWatchアラーム作成
for REGION in ${REGIONS[@]}
do
  aws cloudwatch put-metric-alarm --alarm-name CIS3.9_AWSConfigChanges \
    --alarm-description "CloudWatch Logs: https://${REGION}.console.aws.amazon.com/cloudwatch/home?region=${REGION}#logStream:group=CloudTrail/DefaultLogGroup" \
    --metric-name AWSConfigChangeEventCount \
    --statistic Sum --period 300 --threshold 1 \
    --comparison-operator GreaterThanOrEqualToThreshold \
    --evaluation-periods 1 \
    --treat-missing-data notBreaching \
    --namespace CISBenchmark \
    --alarm-actions arn:aws:sns:${REGION}:${AWS_ACCOUNT_ID}:CISBenchmarkSnsTopic \
    --region ${REGION}
done

3.10 セキュリティグループ変更に対するアラーム通知設定

CloudWatchアラームの閾値は5分間に1回以上、セキュリティグループの変更があった場合としています。

# AWSアカウントIDとリージョンを変数に代入
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGIONS=($(aws ec2 describe-regions --query Regions[].RegionName --output text))

# CloudWatch Logsメトリクスフィルタ作成
for REGION in ${REGIONS[@]}
do
  aws logs put-metric-filter --log-group-name CloudTrail/DefaultLogGroup \
    --filter-name SecurityGroupChangeEventFilter \
    --metric-transformations metricName=SecurityGroupChangeEventCount,metricNamespace=CISBenchmark,metricValue=1 \
    --filter-pattern '{ ($.eventName = AuthorizeSecurityGroupIngress) || ($.eventName = AuthorizeSecurityGroupEgress) || ($.eventName = RevokeSecurityGroupIngress) || ($.eventName = RevokeSecurityGroupEgress) || ($.eventName = CreateSecurityGroup) || ($.eventName = DeleteSecurityGroup)}' \
    --region ${REGION}
done

# CloudWatchアラーム作成
for REGION in ${REGIONS[@]}
do
  aws cloudwatch put-metric-alarm --alarm-name CIS3.10_SecurityGroupChanges \
    --alarm-description "CloudWatch Logs: https://${REGION}.console.aws.amazon.com/cloudwatch/home?region=${REGION}#logStream:group=CloudTrail/DefaultLogGroup" \
    --metric-name SecurityGroupChangeEventCount \
    --statistic Sum --period 300 --threshold 1 \
    --comparison-operator GreaterThanOrEqualToThreshold \
    --evaluation-periods 1 \
    --namespace CISBenchmark \
    --treat-missing-data notBreaching \
    --alarm-actions arn:aws:sns:${REGION}:${AWS_ACCOUNT_ID}:CISBenchmarkSnsTopic \
    --region ${REGION}
done

3.11 ネットワークACL変更に対するアラーム通知設定

CloudWatchアラームの閾値は5分間に1回以上、ネットワークACLの変更があった場合としています。

# AWSアカウントIDとリージョンを変数に代入
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGIONS=($(aws ec2 describe-regions --query Regions[].RegionName --output text))

# CloudWatch Logsメトリクスフィルタ作成
for REGION in ${REGIONS[@]}
do
  aws logs put-metric-filter --log-group-name CloudTrail/DefaultLogGroup \
    --filter-name NACLChangeEventFilter \
    --metric-transformations metricName=NACLChangeEventCount,metricNamespace=CISBenchmark,metricValue=1 \
    --filter-pattern '{ ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry) || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation) }' \
    --region ${REGION}
done

# CloudWatchアラーム作成
for REGION in ${REGIONS[@]}
do
  aws cloudwatch put-metric-alarm --alarm-name CIS3.11_NACLChanges \
    --alarm-description "CloudWatch Logs: https://${REGION}.console.aws.amazon.com/cloudwatch/home?region=${REGION}#logStream:group=CloudTrail/DefaultLogGroup" \
    --metric-name NACLChangeEventCount \
    --statistic Sum --period 300 --threshold 1 \
    --comparison-operator GreaterThanOrEqualToThreshold \
    --evaluation-periods 1 \
    --namespace CISBenchmark \
    --treat-missing-data notBreaching \
    --alarm-actions arn:aws:sns:${REGION}:${AWS_ACCOUNT_ID}:CISBenchmarkSnsTopic \
    --region ${REGION}
done

3.12 インターネットゲートウェイ変更に対するアラーム通知設定

CloudWatchアラームの閾値は5分間に1回以上、インターネットゲートウェイの変更があった場合としています。

# AWSアカウントIDとリージョンを変数に代入
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGIONS=($(aws ec2 describe-regions --query Regions[].RegionName --output text))

# CloudWatch Logsメトリクスフィルタ作成
for REGION in ${REGIONS[@]}
do
  aws logs put-metric-filter --log-group-name CloudTrail/DefaultLogGroup \
    --filter-name NetworkGatewayChangeEventFilter \
    --metric-transformations metricName=NetworkGatewayChangeEventCount,metricNamespace=CISBenchmark,metricValue=1 \
    --filter-pattern '{ ($.eventName = CreateCustomerGateway) || ($.eventName = DeleteCustomerGateway) || ($.eventName = AttachInternetGateway) || ($.eventName = CreateInternetGateway) || ($.eventName = DeleteInternetGateway) || ($.eventName = DetachInternetGateway) }' \
    --region ${REGION}
done

# CloudWatchアラーム作成
for REGION in ${REGIONS[@]}
do
  aws cloudwatch put-metric-alarm --alarm-name CIS3.12_NetworkGatewayChanges \
    --alarm-description "CloudWatch Logs: https://${REGION}.console.aws.amazon.com/cloudwatch/home?region=${REGION}#logStream:group=CloudTrail/DefaultLogGroup" \
    --metric-name NetworkGatewayChangeEventCount \
    --statistic Sum --period 300 --threshold 1 \
    --comparison-operator GreaterThanOrEqualToThreshold \
    --evaluation-periods 1 \
    --namespace CISBenchmark \
    --treat-missing-data notBreaching \
    --alarm-actions arn:aws:sns:${REGION}:${AWS_ACCOUNT_ID}:CISBenchmarkSnsTopic \
    --region ${REGION}
done

3.13 ルートテーブル変更に対するアラーム通知設定

CloudWatchアラームの閾値は5分間に1回以上、ルートテーブルの変更があった場合としています。

# AWSアカウントIDとリージョンを変数に代入
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGIONS=($(aws ec2 describe-regions --query Regions[].RegionName --output text))

# CloudWatch Logsメトリクスフィルタ作成
for REGION in ${REGIONS[@]}
do
  aws logs put-metric-filter --log-group-name CloudTrail/DefaultLogGroup \
    --filter-name RouteTableChangeEventFilter \
    --metric-transformations metricName=RouteTableChangeEventCount,metricNamespace=CISBenchmark,metricValue=1 \
    --filter-pattern '{ ($.eventName = CreateRoute) || ($.eventName = CreateRouteTable) || ($.eventName = ReplaceRoute) || ($.eventName = ReplaceRouteTableAssociation) || ($.eventName = DeleteRouteTable) || ($.eventName = DeleteRoute) || ($.eventName =DisassociateRouteTable) }' \
    --region ${REGION}
done

# CloudWatchアラーム作成
for REGION in ${REGIONS[@]}
do
  aws cloudwatch put-metric-alarm --alarm-name CIS3.13_RouteTableChanges \
    --alarm-description "CloudWatch Logs: https://${REGION}.console.aws.amazon.com/cloudwatch/home?region=${REGION}#logStream:group=CloudTrail/DefaultLogGroup" \
    --metric-name RouteTableChangeEventCount \
    --statistic Sum --period 300 --threshold 1 \
    --comparison-operator GreaterThanOrEqualToThreshold \
    --evaluation-periods 1 \
    --namespace CISBenchmark \
    --treat-missing-data notBreaching \
    --alarm-actions arn:aws:sns:${REGION}:${AWS_ACCOUNT_ID}:CISBenchmarkSnsTopic \
    --region ${REGION}
done

3.14 VPC変更に対するアラーム通知設定

CloudWatchアラームの閾値は5分間に1回以上、VPCの変更があった場合としています。

# AWSアカウントIDとリージョンを変数に代入
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGIONS=($(aws ec2 describe-regions --query Regions[].RegionName --output text))

# CloudWatch Logsメトリクスフィルタ作成
for REGION in ${REGIONS[@]}
do
  aws logs put-metric-filter --log-group-name CloudTrail/DefaultLogGroup \
    --filter-name VPCChangeEventFilter \
    --metric-transformations metricName=VPCChangeEventCount,metricNamespace=CISBenchmark,metricValue=1 \
    --filter-pattern '{ ($.eventName = CreateVpc) || ($.eventName = DeleteVpc) || ($.eventName = ModifyVpcAttribute) || ($.eventName = AcceptVpcPeeringConnection) || ($.eventName = CreateVpcPeeringConnection) || ($.eventName = DeleteVpcPeeringConnection) || ($.eventName = RejectVpcPeeringConnection) || ($.eventName = AttachClassicLinkVpc) || ($.eventName = DetachClassicLinkVpc) || ($.eventName = DisableVpcClassicLink) || ($.eventName = EnableVpcClassicLink) }' \
    --region ${REGION}
done

# CloudWatchアラーム作成
for REGION in ${REGIONS[@]}
do
  aws cloudwatch put-metric-alarm --alarm-name CIS3.14_VPCChanges \
    --alarm-description "CloudWatch Logs: https://${REGION}.console.aws.amazon.com/cloudwatch/home?region=${REGION}#logStream:group=CloudTrail/DefaultLogGroup" \
    --metric-name VPCChangeEventCount \
    --statistic Sum --period 300 --threshold 1 \
    --comparison-operator GreaterThanOrEqualToThreshold \
    --evaluation-periods 1 \
    --namespace CISBenchmark \
    --treat-missing-data notBreaching \
    --alarm-actions arn:aws:sns:${REGION}:${AWS_ACCOUNT_ID}:CISBenchmarkSnsTopic \
    --region ${REGION}
done

アラーム通知例

以下はMFA設定がされていないIAMユーザーでAWSマネジメントコンソールにログインした際のメール内容です。

You are receiving this email because your Amazon CloudWatch Alarm "CIS3.2_ConsoleSigninWithoutMFA" in the US East (N. Virginia) region has entered the ALARM state, because "Threshold Crossed: 1 out of the last 1 datapoints [1.0 (22/05/18 04:41:00)] was greater than or equal to the threshold (1.0) (minimum 1 datapoint for OK -> ALARM transition)." at "Tuesday 22 May, 2018 04:46:38 UTC".

View this alarm in the AWS Management Console:
https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#s=Alarms&alarm=CIS3.2_ConsoleSigninWithoutMFA

Alarm Details:
- Name:                       CIS3.2_ConsoleSigninWithoutMFA
- Description:                CloudWatch Logs: https://us-east-1.console.aws.amazon.com/cloudwatch/home?region=us-east-1#logStream:group=CloudTrail/DefaultLogGroup
- State Change:               OK -> ALARM
- Reason for State Change:    Threshold Crossed: 1 out of the last 1 datapoints [1.0 (22/05/18 04:41:00)] was greater than or equal to the threshold (1.0) (minimum 1 datapoint for OK -> ALARM transition).
- Timestamp:                  Tuesday 22 May, 2018 04:46:38 UTC
- AWS Account:                012345678910

Threshold:
- The alarm is in the ALARM state when the metric is GreaterThanOrEqualToThreshold 1.0 for 300 seconds.

Monitored Metric:
- MetricNamespace:                     CISBenchmark
- MetricName:                          NoMfaConsoleLoginEventCount
- Dimensions:
- Period:                              300 seconds
- Statistic:                           Sum
- Unit:                                not specified
- TreatMissingData:                    NonBreaching


State Change Actions:
- OK:
- ALARM: [arn:aws:sns:us-east-1:012345678910:CISBenchmarkSnsTopic]
- INSUFFICIENT_DATA:

通知受信後のアクションについて

上記はCloudWatchアラームデフォルトの通知内容です。詳細なイベントログの内容まで含まれていないので、いつ・どのユーザーがといった部分まではわかりません。

少し手間ではありますが、CloudWatch Logsメトリクスフィルタに設定したフィルタパターンをCloudWatch Logsロググループより検索することによって、詳細なイベントログ内容を確認することができます。

Descriptionに記載のURLより、CloudWatch LogsロググループのAWSマネジメントコンソールへアクセスします。[イベントの検索]をクリックし、イベントログビューワーよりCloudWatch Logsで設定したフィルタパターンを入力し、検索します。

該当するイベントログの詳細を確認し、いつ・どのユーザーがといった情報を確認できるかと思います。なお、CloudWatchアラームからの通知はCloudTrailのイベントログがCloudWatch Logsに送信されてから検知するため、通知時刻とイベント発生時刻は少し差異があります。

フィルタパターン一覧

3.1から3.14のフィルタパターンは以下の通りです。イベントログを検索する際ご利用ください。

3.1: { ($.errorCode = "*UnauthorizedOperation") || ($.errorCode = "AccessDenied*") }
3.2: { ($.eventName = "ConsoleLogin") && ($.additionalEventData.MFAUsed != "Yes") }
3.3: { $.userIdentity.type = "Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != "AwsServiceEvent" }
3.4: {($.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)}
3.5: { ($.eventName = CreateTrail) || ($.eventName = UpdateTrail) || ($.eventName = DeleteTrail) || ($.eventName = StartLogging) || ($.eventName = StopLogging) }
3.6: { ($.eventName = ConsoleLogin) && ($.errorMessage = "Failed authentication") }
3.7: {($.eventSource = kms.amazonaws.com) && (($.eventName=DisableKey)||($.eventName=ScheduleKeyDeletion))}
3.8: { ($.eventSource = s3.amazonaws.com) && (($.eventName = PutBucketAcl) || ($.eventName = PutBucketPolicy) || ($.eventName = PutBucketCors) || ($.eventName = PutBucketLifecycle) || ($.eventName = PutBucketReplication) || ($.eventName = DeleteBucketPolicy) || ($.eventName = DeleteBucketCors) || ($.eventName = DeleteBucketLifecycle) || ($.eventName = DeleteBucketReplication)) }
3.9: {($.eventSource = config.amazonaws.com) && (($.eventName=StopConfigurationRecorder)||($.eventName=DeleteDeliveryChannel)||($.eventName=PutDeliveryChannel)||($.eventName=PutConfigurationRecorder))}
3.10: { ($.eventName = AuthorizeSecurityGroupIngress) || ($.eventName = AuthorizeSecurityGroupEgress) || ($.eventName = RevokeSecurityGroupIngress) || ($.eventName = RevokeSecurityGroupEgress) || ($.eventName = CreateSecurityGroup) || ($.eventName = DeleteSecurityGroup)}
3.11: { ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry) || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation) }
3.12: { ($.eventName = CreateCustomerGateway) || ($.eventName = DeleteCustomerGateway) || ($.eventName = AttachInternetGateway) || ($.eventName = CreateInternetGateway) || ($.eventName = DeleteInternetGateway) || ($.eventName = DetachInternetGateway) }
3.13: { ($.eventName = CreateRoute) || ($.eventName = CreateRouteTable) || ($.eventName = ReplaceRoute) || ($.eventName = ReplaceRouteTableAssociation) || ($.eventName = DeleteRouteTable) || ($.eventName = DeleteRoute) || ($.eventName =DisassociateRouteTable) }
3.14: { ($.eventName = CreateVpc) || ($.eventName = DeleteVpc) || ($.eventName = ModifyVpcAttribute) || ($.eventName = AcceptVpcPeeringConnection) || ($.eventName = CreateVpcPeeringConnection) || ($.eventName = DeleteVpcPeeringConnection) || ($.eventName = RejectVpcPeeringConnection) || ($.eventName = AttachClassicLinkVpc) || ($.eventName = DetachClassicLinkVpc) || ($.eventName = DisableVpcClassicLink) || ($.eventName = EnableVpcClassicLink) }

まとめ

各AWSリソースの設定からアラーム通知受信後のアクションについて紹介させて頂きました。CloudWatchアラームの間隔や閾値などは実際の運用に合わせて適宜変更されてもよいかと思います。