こんにちは、SCSKの石原です。
皆様はAWSコンソールサインインやIAM操作の通知が欲しくなったことありますでしょうか。
AWS OrganizationsやAWS Configを活用して、組織として中央統制の管理になっていれば問題はないですが、必ずしもそのような管理がされているわけではありません。代表的な例として、AWS認定試験の受験勉強のため利用した個人所有のAWSアカウントが挙げられます。
今回は「AWSコンソールサインイン」と「IAM操作」の通知機能をさくっと実装する例を紹介します。すぐにプロビジョニングできるように 可能な限り、AWS CloudFormation テンプレートを付けさせていただきたいと思います。
AWSコンソールサインイン通知
今回はCloudTrailの証跡をCloudWatchLogsの出力したのち、サブスクリプションフィルターを利用してAWSコンソールサインインのイベントを通知します。この実装では、CloudWatchLogsに集約したのち検知するため数分程度遅れて通知されることになります。
実装イメージは下記の通りです。
CloudTrailの設定
AWSコンソールサインインイベントはリージョン毎に異なりますので、まずCloudTrailにコンソールサインインの記録を集約することになります。CloudTrailでは全てのリージョンに適用される証跡が作成できますので、そちらをご利用になると良いかと思います。
続いて、証跡をCloudWatchLogsに出力する設定をします。
Lambda・SNSの設定
SNSにパブリッシュするLambda関数を作成し、SNSからEmailに送信できるようにトピックとサブスクリプションを作成します。
SNSのサブスクリプションについては通知先を追加する可能性があるのでネスト構成としました。
テンプレートは以下の通りで、親スタック用と子スタック用の2つあります。
1名のみに通知するのであれば、親スタックに追記していただいても問題ありません。
親スタック
AWSTemplateFormatVersion: "2010-09-09"
Description: "aws-console-login-cloudtrail-event-notify"
Parameters:
LowerStackSnsSubscriptionYamlUrl:
Type: String
Default: "Your LowerStackSnsSubscriptionYamlUrl"
NotifyEmailAddress:
Type: String
EventType:
Type: String
Default: "AwsConsoleSignIn"
Resources:
# =====================================
# SNS
# =====================================
ConsoleLoginNotifyTopic:
Type: AWS::SNS::Topic
Properties:
DisplayName: "ConsoleLoginNotify"
TopicName: !Sub "Topic-${AWS::AccountId}-${EventType}"
LowerStackSubscription:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Ref LowerStackSnsSubscriptionYamlUrl
Parameters:
NotifyEndpoint: !Ref NotifyEmailAddress
TopicArn: !Ref ConsoleLoginNotifyTopic
# =====================================
# Lambda
# =====================================
LambdaIAMRole:
Type: "AWS::IAM::Role"
Properties:
Path: "/"
RoleName: !Sub "Lambda2snsRole"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- "lambda.amazonaws.com"
Action:
- "sts:AssumeRole"
MaxSessionDuration: 3600
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSNSFullAccess
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
Description: "Allows Lambda To SNS."
AWSLambdaFunction:
Type: "AWS::Lambda::Function"
Properties:
FunctionName: !Sub "func-${AWS::AccountId}-${EventType}"
Handler: index.handler
Role: !GetAtt LambdaIAMRole.Arn
Timeout: 60
Runtime: python3.8
Environment:
Variables:
ALARM_SUBJECT: !Sub "${AWS::AccountId}-${EventType}"
SNS_TOPIC_ARN: !Ref ConsoleLoginNotifyTopic
Code:
ZipFile: |
import base64
import json
import zlib
import datetime
import os
import boto3
from botocore.exceptions import ClientError
def handler(event, context):
data = zlib.decompress(base64.b64decode(event['awslogs']['data']), 16+zlib.MAX_WBITS)
data_json = json.loads(data)
log_entire_json = json.loads(json.dumps(data_json["logEvents"], ensure_ascii=False))
log_entire_len = len(log_entire_json)
for i in range(log_entire_len):
log_json = json.loads(json.dumps(data_json["logEvents"][i], ensure_ascii=False))
try:
sns = boto3.client('sns')
publishResponse = sns.publish(
TopicArn = os.environ['SNS_TOPIC_ARN'],
Message = log_json['message'],
Subject = os.environ['ALARM_SUBJECT']
)
except Exception as e:
print(e)
Outputs:
ConsoleLoginNotifyTopicARN:
Description: "SNS Topic ARN"
Value: !Ref ConsoleLoginNotifyTopic
子スタック
ネストするコード「LowerStackSnsSubscription」は下記の通りです。
AWSTemplateFormatVersion: "2010-09-09"
Description: "LowerStackSnsSubscriptionYaml"
Parameters:
NotifyEndpoint:
Type: String
TopicArn:
Type: String
Resources:
Subscription:
Type: AWS::SNS::Subscription
Properties:
Endpoint: !Ref NotifyEndpoint
Protocol: email
TopicArn: !Ref TopicArn
ダウンロードはこちらからどうぞ
CloudwatchLogsのサブスクリプションフィルター設定
CloudwatchLogsのサブスクリプションフィルターを利用して、特定の出力があった場合に先ほどCFnテンプレートで作成したLambda関数へログを送信しましょう。
CloudTrailの証跡を出力しているCloudWatchLogsのロググループを選択して、サブスクリプションフィルターを作成します。設定内容は下記の通りです
Lambda関数 | func-[アカウントID]-AwsConsoleSignIn ※デフォルトの場合 |
ログの形式 | Amazon CloudTrail |
フィルターパターン | { $.eventType = “AwsConsoleSignIn” } |
サブスクリプションフィルター名 | 任意 |
以上でAWSコンソールサインインイベント通知の設定が完了です。この設定では下記の図に示すサインインイベントを検知します。IAMユーザにサインインだけでなく、ジャンプアカウントからのAssumeRoleやAWS SSO経由のアクセスにも対応しています。
IAM操作通知
続いてIAM操作の通知を設定します。
今回はCloudWatchのメトリクスフィルターとアラームを利用して通知を行います。この実装ではユーザが何の操作をしたかまでは通知されません。内容まで通知したい場合は、サブスクリプションフィルターが2つまで設定できますので、上述のサインインイベントのサブスクリプションフィルターを利用した実装をご検討いただければと思います。
対象とするIAM操作は以下の通りです。監視が必要なAPIがある場合は、CFnを修正してご使用ください。
AddUserToGroup |
AttachUserPolicy |
CreateAccessKey |
CreateAccessKey |
CreateLoginProfile |
CreatePolicyVersion |
CreateRole |
CreateUser |
PutUserPolicy |
CloudWatch・SNSの設定
CloudTrailからCloudWatchLogsへの出力は、「AWSコンソールサインイン」の設定で実施していますので、CloudWatchとSNSの設定をCFnテンプレートを利用して設定します。
AWSTemplateFormatVersion: "2010-09-09"
Description: "aws-cloudtrail-event-metricsfilter-iam"
Parameters:
LowerStackSnsSubscriptionYamlUrl:
Type: String
Default: "Your LowerStackSnsSubscriptionYamlUrl"
NotifyEmailAddress:
Type: String
EventType:
Type: String
Default: "AwsIAMEvent"
CloudWatchLogsGroup:
Type: String
Resources:
# =====================================
# SNS
# =====================================
EventTypeNotifyTopic:
Type: AWS::SNS::Topic
Properties:
DisplayName: !Sub ${EventType}-${AWS::AccountId}
TopicName: !Sub "Topic-${AWS::AccountId}-${EventType}"
LowerStackSubscription:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Ref LowerStackSnsSubscriptionYamlUrl
Parameters:
NotifyEndpoint: !Ref NotifyEmailAddress
TopicArn: !Ref EventTypeNotifyTopic
# =====================================
# Cloudwatch
# =====================================
MetricsFilterIAMWarn:
Type: AWS::Logs::MetricFilter
Properties:
FilterPattern: "{($.eventName=CreateAccessKey)||($.eventName=CreateUser)||($.eventName=CreateLoginProfile)||($.eventName=AddUserToGroup)||($.eventName=PutUserPolicy)||($.eventName=AttachUserPolicy)||($.eventName=AttachUserPolicy)||($.eventName=CreateAccessKey)||($.eventName=CreateRole)||($.eventName=CreatePolicyVersion)}"
LogGroupName: !Ref CloudWatchLogsGroup
MetricTransformations:
-
DefaultValue: 0
MetricName: !Sub ${CloudWatchLogsGroup}/Warn
MetricNamespace: "IAMEvent"
MetricValue: "1"
AlarmIAMWarn:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmActions:
- !Ref EventTypeNotifyTopic
AlarmDescription: "IAM Event"
AlarmName: "AlarmIAMWarn"
ComparisonOperator: GreaterThanOrEqualToThreshold
DatapointsToAlarm: 1
EvaluationPeriods: 1
MetricName: !Sub ${CloudWatchLogsGroup}/Warn
Namespace: "IAMEvent"
Period: 300
Statistic: Maximum
Threshold: 1
TreatMissingData: notBreaching
Outputs:
EventTypeNotifyTopicARN:
Description: "SNS Topic ARN"
Value: !Ref EventTypeNotifyTopic
これで実装は完了です。IAM操作に気付けるようになりましたね。
終わりに
今回は実施しなかった別の実装方法もありますので、一言コメントとともに記載しておきます。
【AWSコンソールサインイン通知】
EventBridgeでの実装 | コンソールサインインイベントが必ずしもus-east-1で発生するわけではない。すべてのリージョンに設定する必要があるので見送り |
【IAM操作通知】
サブスクリプションフィルターでの実装 | 設定上限が2つなので、拡張性がないことから見送り |
EventBridgeでの実装 | 公式の回答がありましたので見送り(https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudformation-iam-event-monitoring/) |
AWS Config | 今回対象としていた、組織で管理されていないアカウントではAWS Configが有効になっていない可能性が高いので見送り |