ServerlessでシャッとAWS Guard Dutyの驚異検出(重要度中以上)をSlackに通知する(JS)
昨今騒がれているアレとは違いますが中々セキュリティ監視って大変ですよね。
専任のセキュリティ管理者がいればいいですが兼任してたりで中々継続的に調べる事も難しい事が多々あると思います。
そこでそういうものはAWS Guard Dutyにお任せしましょう。
AWS Guard Dutyとは?
詳しい方にご説明は譲ります
【レポート】自動セキュリティ分析サービスGuardDutyのよくわかる解説 #reinvent # SID218
上のサマリーで一言でいうと
ログを機械学習に突っ込んで役に立つ脅威情報を使える状態で出すよ
です。
料金について
普段の業務では一般的な
- ELB
- EC2
- RDS
- S3
みたいな構成でサービスを組んでいてEC2インスタンス自体は約30台あって
料金は月3.25$でした。
AWS Guard Dutyの検出頻度だったりEC2の台数だったりVPCの数で料金は変わってくると思うので参考程度にお願いしますmm
AWS Guard DutyをONにするだけではそれを毎回見なくてはならないです
辛いですよね。
そこで自分が大好きなServerless Frameworkを使ってGuard Dutyが驚異(重要度中以上)を検知したらSlackに通知をしてみたいと思います。
Serverless Framework内部はCloudFormationで管理してくれるのでサービス停止忘れとかがないし、設定わかりやすいしドキュメントが丁寧なのでLambda使うならマジおすすめします。
環境
- MacOSX
- Node8.10
- Serverless 1.47.0
Gurad Dutyを有効化にする
【re:invent2017】新サービス Amazon GuardDutyを触ってみた
ココらへんを参考にしてください。
難しい事はありません。ボタンを押すだけです。
ServerlessでTemplateを作成する
今回はこんな感じのLambda Functionを作成します
- Function名
- guard-duty-watcher-dev-handler (開発)
- guard-duty-watcher-prod-handler (本番)
npm install -g serverless
serverless create --template aws-nodejs --path guard-duty-watcher
下記のようなディレクトリができると思います。
guard-duty-watcher
├── handler.js
├── package-lock.json
├── package.json
├── serverless.yml
@slack/clientとaws-sdkのインストール
SlackのWebHookURLをsecret managerに保管しているので使わない方はaws-sdkのインストールは不要です。
npm i @slack/client aws-sdk --save
serverless.ymlの修正
こんな感じです。
- secret managerを使っていない方はiamRoleStatementsの
- Effect: "Allow"
Action:
- "secretsmanager:*"
Resource:
- "*"
は不要です。
service: guard-duty-watcher
provider:
name: aws
runtime: nodejs8.10
region: ${self:custom.defaultRegion}
stage: ${opt:stage, self:custom.defaultStage}
timeout: 180
memorySize: 128
logRetentionInDays: 7
iamRoleStatements:
- Effect: "Allow"
Action:
- "cloudwatch:ListMetrics"
- "cloudwatch:GetMetricStatistics"
- "cloudwatch:Describe*"
Resource:
- "*"
- Effect: "Allow"
Action:
- "logs:*"
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
Resource:
- "*"
- Effect: "Allow"
Action:
- "secretsmanager:*"
Resource:
- "*"
custom:
defaultRegion: ap-northeast-1
functions:
handler:
handler: handler.handle
description: "GuardDurtyのセキュリティ脅威を通知します(重要度中以上のもの)"
environment:
SERVERLESS_ENV_STAGE: "${opt:stage, 'dev'}"
events:
- cloudwatchEvent:
event:
source:
- 'aws.guardduty'
detail-type:
- 'GuardDuty Finding'
handler.jsを書く
さらっと50行位なので素の感じで
'use strict';
const SlackClient = require('./src/slack/client');
module.exports.handle = async (event) => {
// 時間がLambdaとlocalで違うので設定する
process.env.TZ = 'Asia/Tokyo';
const slackClient = new SlackClient();
if (event.detail.severity < 3.9) {
return {
statusCode: 304,
message: 'guard-duty Notify Fire',
};
}
let slackChannel = 'sandbox';
let severityText = '中';
if (event.detail.severity > 3.9) {
if (process.env.SERVERLESS_ENV_STAGE != 'dev') slackChannel = 'warn';
const attachments = [];
// 脅威度中の場合
if (event.detail.severity <= 6.9) {
attachments.push({
color: "warning",
fields: [
{
"title": event.detail.title,
"value": event.detail.description,
"short": false
}
]
});
// 脅威度高の場合
} else if (event.detail.severity > 6.9) {
if (process.env.SERVERLESS_ENV_STAGE != 'dev') slackChannel = 'emergency';
severityText = '高';
attachments.push({
color: "danger",
fields: [
{
"title": event.detail.title,
"value": event.detail.description,
"short": false
}
]
});
}
const sendOptions = {
channel: slackChannel,
icon_emoji: ':guardduty:', # 適当に良しななアイコンを設定してください
text: `AWS Guard Durtyで重要度: 【${severityText}】以上セキュリティの驚異を検出しました。
https://ap-northeast-1.console.aws.amazon.com/guardduty/home?region=ap-northeast-1#/findings?macros=current
で驚異の確認をしてください
`,
attachments: attachments,
username: 'AWS Guard Durty Watcher'
};
await slackClient.send(sendOptions);
}
return {
statusCode: 200,
body: JSON.stringify({ message: 'guard-duty Notify Fire',input: event},null,2)
};
};
SlackClientを書く
mkdir src
touch src/slack/client.js
'use strict';
const { IncomingWebhook } = require('@slack/client'),
AWS = require('aws-sdk');
class SlackClient {
constructor() {
this._awsSecretManagerEndpoint = 'https://secretsmanager.ap-northeast-1.amazonaws.com';
this._awsSecretManagerWebhookURLSecretName = 'xxxx/xxxxxxx'; # 設定したsecret managerのkeyを設定してください
}
async send(options) {
const client = new AWS.SecretsManager({
endpoint: this._awsSecretManagerEndpoint,
region: 'ap-northeast-1'
});
try {
let response = await client.getSecretValue({ SecretId: this._awsSecretManagerWebhookURLSecretName }).promise();
const webhookURL = JSON.parse(response.SecretString).slackWebhookURL;
const slackClient = new IncomingWebhook(webhookURL);
response = await slackClient.send(options);
console.log(`slack response: ${response.text}`);
} catch (err) {
console.error(err);
}
}
}
module.exports = SlackClient;
SampleEventのjsonを追加する
実際のeventをエミュレーションするために必要です。
sample値はAWS Console CloudWatchの画面からサンプル値を取得できます
mkdir events
touch events/guard-durty-sample.json
{
"version": "0",
"id": "c8c4daa7-a20c-2f03-0070-b7393dd542ad",
"detail-type": "GuardDuty Finding",
"source": "aws.guardduty",
"account": "123456789012",
"time": "1970-01-01T00:00:00Z",
"region": "us-east-1",
"resources": [],
"detail": {
"schemaVersion": "2.0",
"accountId": "123456789012",
"region": "us-east-1",
"partition": "aws",
"id": "16afba5c5c43e07c9e3e5e2e544e95df",
"arn": "arn:aws:guardduty:us-east-1:123456789012:detector/123456789012/finding/16afba5c5c43e07c9e3e5e2e544e95df",
"type": "Canary:EC2/Stateless.IntegTest",
"resource": {
"resourceType": "Instance",
"instanceDetails": {
"instanceId": "i-05746eb48123455e0",
"instanceType": "t2.micro",
"launchTime": 1492735675000,
"productCodes": [],
"networkInterfaces": [
{
"ipv6Addresses": [],
"privateDnsName": "ip-0-0-0-0.us-east-1.compute.internal",
"privateIpAddress": "0.0.0.0",
"privateIpAddresses": [
{
"privateDnsName": "ip-0-0-0-0.us-east-1.compute.internal",
"privateIpAddress": "0.0.0.0"
}
],
"subnetId": "subnet-d58b7123",
"vpcId": "vpc-34865123",
"securityGroups": [
{
"groupName": "launch-wizard-1",
"groupId": "sg-9918a123"
}
],
"publicDnsName": "ec2-11-111-111-1.us-east-1.compute.amazonaws.com",
"publicIp": "11.111.111.1"
}
],
"tags": [
{
"key": "Name",
"value": "ssh-22-open"
}
],
"instanceState": "running",
"availabilityZone": "us-east-1b",
"imageId": "ami-4836a123",
"imageDescription": "Amazon Linux AMI 2017.03.0.20170417 x86_64 HVM GP2"
}
},
"service": {
"serviceName": "guardduty",
"detectorId": "3caf4e0aaa46ce4ccbcef949a8785353",
"action": {
"actionType": "NETWORK_CONNECTION",
"networkConnectionAction": {
"connectionDirection": "OUTBOUND",
"remoteIpDetails": {
"ipAddressV4": "0.0.0.0",
"organization": {
"asn": -1,
"isp": "GeneratedFindingISP",
"org": "GeneratedFindingORG"
},
"country": {
"countryName": "United States"
},
"city": {
"cityName": "GeneratedFindingCityName"
},
"geoLocation": {
"lat": 0,
"lon": 0
}
},
"remotePortDetails": {
"port": 22,
"portName": "SSH"
},
"localPortDetails": {
"port": 2000,
"portName": "Unknown"
},
"protocol": "TCP",
"blocked": false
}
},
"resourceRole": "TARGET",
"additionalInfo": {
"unusualProtocol": "UDP",
"threatListName": "GeneratedFindingCustomerListName",
"unusual": 22
},
"eventFirstSeen": "2017-10-31T23:16:23Z",
"eventLastSeen": "2017-10-31T23:16:23Z",
"archived": false,
"count": 1
},
"severity": 5,
"createdAt": "2017-10-31T23:16:23.824Z",
"updatedAt": "2017-10-31T23:16:23.824Z",
"title": "Canary:EC2/Stateless.IntegTest",
"description": "Canary:EC2/Stateless.IntegTest"
}
}
package.jsonにrun scriptを書く
npm run invoke:local:dev
でローカルでlambdaを実行できます
{
"name": "guraddurty-event-watcher",
"version": "1.0.0",
"description": "",
"main": "handler.js",
"scripts": {
"invoke:local:dev": "serverless invoke local --stage dev --function handler --type event --path events/guard-durty-sample.json",
"invoke:lambda:dev": "serverless invoke --stage dev --function handler",
"invoke:lambda:prod": "serverless invoke --stage prod --function handler",
"deploy:dev": "serverless deploy --stage dev",
"deploy:prod": "serverless deploy --stage prod",
"watch:log:dev": "serverless logs --stage dev --function handler --tail --no-color --startTime",
"watch:log:prod": "serverless logs --stage prod --function handler --tail --no-color --startTime"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@slack/client": "^5.0.1",
"aws-sdk": "^2.494.0"
},
"devDependencies": {
"serverless": "^1.47.0"
}
}
ローカルで試す
npm run invoke:local:dev
下記のような通知が出てきたらOKです
開発用Lambdaをデプロイ
内部的にCkoudFormationのCreate Stackを実行して必要なAWS Resourceをデプロイしてくれます(Serverlessが)
npm run deploy:dev
Guard dutyのテスト
に「結果サンプルの生成」のボタンがあるのでポチッとおします。
5分程すると結果画面にサンプルの驚異検出をしてくれます
下記のような感じです
Slack側
本番用のLambdaをDeployする
本番用のデプロイします。
npm run deploy:prod
出来上がりです。(これで5分おきにLambdaがキックされて新しい驚異があった場合に通知してくれます)
補足
Lambdaのデバッグ用にcloudwatchのlogをtailするrun scriptがありますので動かなかったらlogを覗いてください
npm run watch:log:dev
最後に
Guard Duty Lambda連携でお手軽Slack通知ができます。
みなさんも是非試してみて下さい。
Discussion