Configで検知した異常をSNSとLambdaを使ってSlackへ通知してみた

AWSでサービスを動かしているとSlackと連携して通知をする機会がしばしばあると思う.社内で,AWSのConfigで検知した異常をSlackに通知してほしいと言われたので,Simple Notification Service(以降SNS)とLambdaを使って実現した.今回はその方法を書いていく.

はじめに

弊社は自社サービスが多く,アカウントはサービス単位で発行され,開発エンジニアはそれぞれのアカウントで環境を構築している.今回は,各アカウントで設定されたConfigからSNSとLambdaを経由して会社共有のSlackに通知する.SNSとLambdaは各アカウントごとに作成するのはコストの面でよくないので,インフラチームのアカウントで1つだけ作成する方針にしている.ということで今回は以下のような設計で構築した.

f:id:a-mochan:20191019231622p:plain
Configの検知をSlackへ通知するまでの流れ

なお,Slackへ通知する際に必要なIncoming Webhookの設定はこの記事では割愛する.公式を参考にしていただくとスムーズに設定できるかと思う.

slack.com

手順

Configを作成

Configとは,AWSリソースの設定を評価,監査,審査できるサービスである.Configではリソースを評価するためのルールを設定する.今回設定するルールは,インバウンドのsshを設定しているセキュリティグループでIPをフルオープンにしていないかチェックするrestricted-sshにする.ルールは自由に選んでよいが,通知テストをする上では一時的にルールを破る必要があるのでそれに適したルールを設定するのがよいと思う.また,設定項目にAmazon SNS トピックがあるが,SNSトピックの設定をした後でないとarnの入力ができないため一旦このままにしておく.その他はデフォルトのまま.

SNSを作成

SNSとは簡単に言うと「何らかの通知を送信したい人(またはシステム)から、受信したい人(またはシステム)へ通知を送る仕組み」を提供するマネージドサービスである.SNSでは「送信したい人」を発行者と呼び,「受信したい人」を購読者と呼ぶ.そして,双方を繋げる中間役をトピックという.SNSを利用する際はまずトピックを作成し,そのトピックと通信できる発行者・購読者を設定することで,アクセスを制御することができる.今回の場合,発行者は各サービスのConfigで,購読者はLambdaとなるように設定する.トピック作成時に出てくるアクセスポリシーの設定では異なるアカウントからの発行を許可するよう設定しておく.

f:id:a-mochan:20191018221154p:plain
トピック作成時におけるアクセスポリシーの設定

Configに通知先としてSNSを設定

トピックの設定が終わるとarnが発行されるので,それをConfigに設定する.ConfigとSNSは別アカウントにあるため別のアカウントからトピックを選択を選び,トピックのarnを入力.

f:id:a-mochan:20191013143322p:plain
Config上でSNSを設定

Config用のロールを作成

Configにアタッチされているポリシーにもトピックのarnを設定しなければならない.Configを作成する際ロールを新規で作成すると,AWSServiceRoleForConfigというロールが割り当てられる.そのロールにはSNSへの発行(Publish)を許可するポリシーが設定されていないので,SNSに発行(Publish)可能なロールを新たに作成してConfigにアタッチする必要がある.まずはロールを作成.

f:id:a-mochan:20191017234355p:plain
Configにアタッチするロールを作成
次に,以下のようなポリシーを作成し上記のロールにアタッチ.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "sns:Publish",
            "Resource": "arn:aws:sns:hogehoge"
        }
    ]
}

このロールをConfigから選択する.

f:id:a-mochan:20191018221504p:plain
Configからロールを選択
これでConfigからSNSまでの通知の設定は完了.

Lambdaを作成

SNSから通知を受け取り,Slackへ通知するためのLambdaを作成する.Python3.7で以下のように記述した.

import json
import os
import urllib.request

SLACK_WEBHOOK_URL = os.environ['SLACK_WEBHOOK_URL']

def lambda_handler(event, context):
    # cloudwatchにログを吐くため用のデバッグ文
    print(event['Records'][0]['Sns']['Message'])
    message = json.loads(event['Records'][0]['Sns']['Message'])
    title = message['configRuleName']
    detail = message['awsAccountId'] + 'アカウントで' + message['configRuleName'] + 'の異常が検知されました'
    
    post_slack(title, detail)
    
def post_slack(title: str, detail: str) -> None:
    payload = {
        'attachments': [
            {
                'color': '#D50200',
                'pretext': title,
                'text': detail
            }
        ]
    }
    
    request = urllib.request.Request(
        SLACK_WEBHOOK_URL,
        data=json.dumps(payload).encode('utf-8'), 
        method="POST"
    )
    try:
        with urllib.request.urlopen(request) as response:
            print(response.read())
    except urllib.error.HTTPError as err:
        print(err.code)

やっていることは単純で,ConfigからSNSを経由して受け取った情報から,Slackで表示する文字列を生成してSlackへ投稿するだけである.SLACK_WEBHOOK_URLの部分はIncoming Webhookの設定で吐き出されるチャンネルのWebhook先のURLである.冒頭でも言及した通り,SlackのIncoming Webhookの設定方法はここでは割愛する.さらに,SNSからの通知をトリガーにLambdaを動作させたいのでトリガーに先ほど作成したSNSを設定する.

f:id:a-mochan:20191018204158p:plain
Lambdaにトリガーを設定

SNSの購読者にLambdaを設定

Lambdaを作成した際にarnが発行されるので,それを使いSNSにLambdaを購読者として設定する.Lambdaでトリガーを設定するとここは自動で設定されているかもしれない.これでSNSに発行されたConfigの情報をLambdaで受け取ることができる.

f:id:a-mochan:20191016214259p:plain
SNSにLambdaを購読者として設定

テスト

ここまでで通知の設定は完了した.試しにConfigが設定してあるセキュリティグループのインバウンドを任意の場所からsshできるよう設定してみると,ちょっと経ってからSlackに通知が届く.正直どのタイミングでConfigがSNSに通知するのかよく分かっていない.

f:id:a-mochan:20191017214742p:plain
通知されたSlackの画面
また以下のようにコンソールから,ConfigとSNS間,SNSとLambda間,LambdaとSlack間で結合テストができる.
f:id:a-mochan:20191017220732p:plain
ConfigからSNSへ通知するテスト
f:id:a-mochan:20191017220810p:plain
SNSからLambdaへ通知するテスト
f:id:a-mochan:20191017220643p:plain
LambdaからSlackへ通知するテスト

はまったところ

SNSへ発行可能なConfig用のロールを作成しなければならないことに気づかずはまってしまった.ConfigからSNSへの通知テストは通ったので,ConfigとSNS間はあまり疑っていなかった.しかし,それはコンソールにログインしているユーザのロールにSNSへの発行が許可されていて,コンソール上からは通知テストがうまくいっているというだけだった.ここで学んだことは,AWSだとどのサービスが動いていて,どのロールが適用されているかを理解し,アクセス先はもとよりアクセス元のACLもしっかり考慮しないといけないこと.

感想

  • 少しだけコードも書いたし,はまりもしたが,基本ポチポチだけでSlack通知までいけたので楽チンだった
  • デバッグが難しいのでCloudWatchに吐き出せるログは吐き出すようにしとくと良いかもしれない
  • AWS Chatbotを使ったslack通知なんてのも出てきているので試してみたい

aws.amazon.com