はじめに
長袖と半袖の人が行き交う季節になりましたが皆様いかがお過ごしでしょうか?
初めまして、ヤプリで1ヶ月間SREでインターンをさせていただいた静岡大学の鈴木(@otyamura)と申します。
最近はポケモンユナイトでゴールを守っている鈴木ですが、インターンではヤプリのインフラを守る業務に携わらせていただきました。
(攻撃は最大の防御の図)
行ったこと
インターン期間中に行わせていただいたことは主に以下の三つです。
- GuardDutyのSlack通知の改善
- GuardDutyのS3 Export設定
- GuardDutyのログをNew Relicに取り込む
使用技術
また、本記事で使用した主な技術は以下の通りです
- Amazon GuardDuty
- Amazon EventBridge
- AWS Chatbot
- AWS Cloudformation
- New Relic
背景
今までGuardDutyのFindingsの通知はDatadogから送信しており、以下の画像のように重要度の情報のみでした。
Findingsの詳細を見るためにはAWSへアクセスしてGuardDutyを見てどのFindingsか探して…といった手順を踏んでおり、飛んできた通知のみを見ればわかるようにしたいよね、ということで改善に取り組みました。
Slackへの通知は以下の二つのNew Relicにログを取り込んでalertを出す方法とChatbotを用いる方法の二つを実装し、良い方を採用するということになりました。
結果
最終的にEventBridge + SNS + Chatbotを用いて以下のような通知にしました。
今までのよりも圧倒的に情報量が増えていることがわかると思います。
また、情報量だけでなくFindingsのseverityによって送信先チャンネルを分けており、早急に対処が必要かどうかもすぐに判断できるようになりました。
New Relicはどうだったのか
New Relicからの通知は以下のようになりました。
New Relicからの通知はグラフが描写されるため、ログの件数をトリガーにしている場合などはとても見やすい通知です。しかしながら、Findingsをトリガーにした場合はあまり向いていないことがわかったためChatbotの方式を採用することにしました。
今回はNew RelicにFindingsのログを取り込むことでダッシュボードなどに活かせると判断し、ログ取り込みも行うことにしました。
実装方法
EventBridge + SNS + Chatbot
こちらの記事を参考にCloudformationのテンプレートを作成しました。
ポイントとしては、脅威度によって送信するチャンネルを変更できるようにした点です。
テンプレート内のSeverityThresholdという値によって変更ができ、デフォルトの7ではFindingsの脅威度が高以上か未満かで分かれるようになっています。
このテンプレートを、Stacksetを用いてGuardDutyが有効な全リージョンにEventBridgeのルールとSNSの通知設定を展開します。
AWSTemplateFormatVersion: '2010-09-09' Parameters: SeverityThreshold: Type: Number Default: 7 Resources: # SNSトピック SnsTopicOverThreshold: Type: AWS::SNS::Topic Properties: TopicName: guardduty-over-threshold # SNSトピックポリシー EventTopicPolicyOverThreshold: Type: AWS::SNS::TopicPolicy Properties: PolicyDocument: Statement: - Effect: "Allow" Principal: Service: "events.amazonaws.com" Action: "sns:Publish" Resource: "*" Topics: - !Ref SnsTopicOverThreshold # EventBridgeルール EventBridgeRuleOverThreshold: Type: AWS::Events::Rule Properties: Name: guardduty-over-threshold-rule EventPattern: !Sub | { "source": ["aws.guardduty"], "detail-type": ["GuardDuty Finding"], "detail": { "severity": [ {"numeric": [">=", ${SeverityThreshold}]} ] } } Targets: - Arn: !Ref SnsTopicOverThreshold Id: sns-topic-over-threshold # 脅威度が低いもの # SNSトピック SnsTopicUnderThreshold: Type: AWS::SNS::Topic Properties: TopicName: guardduty-under-threshold # SNSトピックポリシー EventTopicPolicyUnderThreshold: Type: AWS::SNS::TopicPolicy Properties: PolicyDocument: Statement: - Effect: "Allow" Principal: Service: "events.amazonaws.com" Action: "sns:Publish" Resource: "*" Topics: - !Ref SnsTopicUnderThreshold # EventBridgeルール EventBridgeRuleUnderThreshold: Type: AWS::Events::Rule Properties: Name: guardduty-under-threshold-rule EventPattern: !Sub | { "source": ["aws.guardduty"], "detail-type": ["GuardDuty Finding"], "detail": { "severity": [ {"numeric": ["<", ${SeverityThreshold}]} ] } } Targets: - Arn: !Ref SnsTopicUnderThreshold Id: sns-topic-under-threshold
その後、以下のCFnテンプレートからChatbotを作成します。
SlackWorkspaceIdとSlackChannelIdの取得方法はこちらの記事を参考にしました。
AWSTemplateFormatVersion: '2010-09-09' Parameters: SnsTopicNameOverThreshold: Type: String Default: guardduty-over-threshold SnsTopicNameUnderThreshold: Type: String Default: guardduty-under-threshold SlackWorkspaceId: Type: String Default: xxxxxxxx ChannelId1: Type: String Default: xxxxxxxx ChannelId2: Type: String Default: xxxxxxxx Resources: # Chatbot用 IAMロール ChatbotIamRole: Type: AWS::IAM::Role Properties: RoleName: ChatbotIamRole AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: "sts:AssumeRole" Principal: Service: "chatbot.amazonaws.com" Policies: - PolicyName: AWS-Chatbot-NotificationsOnly-Policy PolicyDocument: Version: "2012-10-17" Statement: - Action: - "cloudwatch:Describe*" - "cloudwatch:Get*" - "cloudwatch:List*" Effect: "Allow" Resource: "*" # Chatbot 設定 # 脅威度が高いもの ChatbotConfigurationOverThreshold: Type: AWS::Chatbot::SlackChannelConfiguration Properties: ConfigurationName: slack-channel-1 IamRoleArn: !GetAtt ChatbotIamRole.Arn LoggingLevel: NONE SlackChannelId: !Ref ChannelId1 SlackWorkspaceId: !Ref SlackWorkspaceId SnsTopicArns: - !Sub 'arn:aws:sns:ap-northeast-1:${AWS::AccountId}:${SnsTopicNameOverThreshold}' - !Sub 'arn:aws:sns:ap-northeast-2:${AWS::AccountId}:${SnsTopicNameOverThreshold}' - !Sub 'arn:aws:sns:ap-south-1:${AWS::AccountId}:${SnsTopicNameOverThreshold}' - !Sub 'arn:aws:sns:ap-southeast-1:${AWS::AccountId}:${SnsTopicNameOverThreshold}' - !Sub 'arn:aws:sns:ap-southeast-2:${AWS::AccountId}:${SnsTopicNameOverThreshold}' - !Sub 'arn:aws:sns:ca-central-1:${AWS::AccountId}:${SnsTopicNameOverThreshold}' - !Sub 'arn:aws:sns:eu-central-1:${AWS::AccountId}:${SnsTopicNameOverThreshold}' - !Sub 'arn:aws:sns:eu-north-1:${AWS::AccountId}:${SnsTopicNameOverThreshold}' - !Sub 'arn:aws:sns:eu-west-1:${AWS::AccountId}:${SnsTopicNameOverThreshold}' - !Sub 'arn:aws:sns:eu-west-2:${AWS::AccountId}:${SnsTopicNameOverThreshold}' - !Sub 'arn:aws:sns:eu-west-3:${AWS::AccountId}:${SnsTopicNameOverThreshold}' - !Sub 'arn:aws:sns:sa-east-1:${AWS::AccountId}:${SnsTopicNameOverThreshold}' - !Sub 'arn:aws:sns:us-east-1:${AWS::AccountId}:${SnsTopicNameOverThreshold}' - !Sub 'arn:aws:sns:us-east-2:${AWS::AccountId}:${SnsTopicNameOverThreshold}' - !Sub 'arn:aws:sns:us-west-1:${AWS::AccountId}:${SnsTopicNameOverThreshold}' - !Sub 'arn:aws:sns:us-west-2:${AWS::AccountId}:${SnsTopicNameOverThreshold}' # 脅威度が低いもの ChatbotConfigurationUnderThreshold: Type: AWS::Chatbot::SlackChannelConfiguration Properties: ConfigurationName: slack-channel-2 IamRoleArn: !GetAtt ChatbotIamRole.Arn LoggingLevel: NONE SlackChannelId: !Ref ChannelId2 SlackWorkspaceId: !Ref SlackWorkspaceId SnsTopicArns: - !Sub 'arn:aws:sns:ap-northeast-1:${AWS::AccountId}:${SnsTopicNameUnderThreshold}' - !Sub 'arn:aws:sns:ap-northeast-2:${AWS::AccountId}:${SnsTopicNameUnderThreshold}' - !Sub 'arn:aws:sns:ap-south-1:${AWS::AccountId}:${SnsTopicNameUnderThreshold}' - !Sub 'arn:aws:sns:ap-southeast-1:${AWS::AccountId}:${SnsTopicNameUnderThreshold}' - !Sub 'arn:aws:sns:ap-southeast-2:${AWS::AccountId}:${SnsTopicNameUnderThreshold}' - !Sub 'arn:aws:sns:ca-central-1:${AWS::AccountId}:${SnsTopicNameUnderThreshold}' - !Sub 'arn:aws:sns:eu-central-1:${AWS::AccountId}:${SnsTopicNameUnderThreshold}' - !Sub 'arn:aws:sns:eu-north-1:${AWS::AccountId}:${SnsTopicNameUnderThreshold}' - !Sub 'arn:aws:sns:eu-west-1:${AWS::AccountId}:${SnsTopicNameUnderThreshold}' - !Sub 'arn:aws:sns:eu-west-2:${AWS::AccountId}:${SnsTopicNameUnderThreshold}' - !Sub 'arn:aws:sns:eu-west-3:${AWS::AccountId}:${SnsTopicNameUnderThreshold}' - !Sub 'arn:aws:sns:sa-east-1:${AWS::AccountId}:${SnsTopicNameUnderThreshold}' - !Sub 'arn:aws:sns:us-east-1:${AWS::AccountId}:${SnsTopicNameUnderThreshold}' - !Sub 'arn:aws:sns:us-east-2:${AWS::AccountId}:${SnsTopicNameUnderThreshold}' - !Sub 'arn:aws:sns:us-west-1:${AWS::AccountId}:${SnsTopicNameUnderThreshold}' - !Sub 'arn:aws:sns:us-west-2:${AWS::AccountId}:${SnsTopicNameUnderThreshold}'
以上で設定が完了です。
New Relicへのログ取り込み
S3 export設定
New Relicへログを取り込むためには、あらかじめGuardDutyのログをS3へ保管するように設定する必要があります。
マネジメントコンソールからの設定は、手間がかかるためこちらの記事を参考にさせていただき、シェルスクリプトでS3への保管設定を行いました。
各リージョンの検知結果を一つのS3バケットに保存することができたら、Lambdaを設定します。
Lambda設定
Serverless Application Repositoryから"カスタムIAMロールまたはリソースポリシーを作成するアプリを表示する"にチェックをつけNewRelic-log-ingestion-s3
を検索します。
デプロイの画面でNRLicenseKeyという環境変数に、New Relicで発行したLicense keyを入力します。
NRLogTypeではログのフォーマット指定ができますが、GuardDutyの結果はjson形式のため指定せずに解釈を行なってくれます。
デプロイを行った後はトリガーを設定します。
S3を選択し、GuardDutyの結果が集約されるバケットを選択します。
トリガーを設定したら、S3の読み取り権限とKMSの復号権限のポリシーを以下のロールに与えます。
- S3のバケットポリシーのStatementに以下の記述を追加します。
{ "Sid": "Allow GetObject from lambda", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::account-id:role/serverlessrepo-NewRelic-l-NewRelicLogIngestionFunc-xxxxxxxxx" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::your-bucket-name/*" }
- KMSの復号ポリシーを作成し、ロールに割り当てます。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowDecryptFromLambda", "Effect": "Allow", "Action": "kms:Decrypt", "Resource": "arn:aws:kms:ap-northeast-1:account-id:key/xxxxxxxxxxxx" } ] }
以上で、New Relicにログが取り込まれます。
通知サンプル生成のTips
GuardDutyの通知のテストを行いたいときにサンプルを生成しますが、以下の画面からサンプル生成を行うと大量のサンプルが生成されて使い勝手が悪いです。
そこで、通知のテストを行うときに便利な方法を紹介します。
通知のサンプルを指定して作成する方法
以下のスクリプトを用いることで、重要度が高と低のサンプルを生成することが可能です。
--finding-typesに渡すものによって生成されるサンプルを変更することができ、どのような種類があるのかはこちらをご確認ください。
#!/bin/sh REGION=ap-northeast-1 PROFILE_NAME=your-profile-name id=`aws guardduty list-detectors --query 'DetectorIds' --region $REGION --output text --profile $PROFILE_NAME` echo $id aws guardduty create-sample-findings \ --detector-id $id \ --finding-types UnauthorizedAccess:EC2/TorClient Recon:EC2/PortProbeUnprotectedPort \ --profile $PROFILE_NAME
おわりに
インシデントは発生しないことが一番ですがいつかは発生してしまうことでもあるので、発生した後の処理を迅速に行うための環境を整備することも重要です。
本記事ではインシデントの処理を行いやすくするために、Chatbotを用いたGuardDutyの検知結果通知やそれらをNew Relicに取り込むといったことをしました。
今回のインターンで実施したことが役に立つ日が来てほしいと思いつつも、インシデントは発生しないのが一番なので通知が来ないことを願ってたり願ってなかったりしています:smile: