LoginSignup
13
14

More than 3 years have passed since last update.

CloudWatch Logs のサブスクリプションフィルタを使って特定文字列を検知したログをEメール通知する

Last updated at Posted at 2021-02-03

はじめに

CloudWatch Logs へ保存したログを、サブスクリプションフィルタを使って特定の文字列を検知し、その内容を SNS を使ってEメール通知するということを行いました。
また、今回の設定は全て aws-cli を利用して行っています。(確認として管理コンソールの画像も載せてます)

構成

まず、クライアントから CloudWatch Logs へログを送ります。
すると サブスクリプションフィルタ の検知をトリガーに Lambda が実行されます。
実行された LambdaSNS を通してクライアントへ Eメール を送信します。
Untitled.png

設定

今回は aws-cli を利用して作成します。設定は構成図の番号の流れで行っていきます。

① SNSの設定

検知した際の通知先設定を行います。
まず、トピックの作成を行います。

トピックの作成
$ aws sns create-topic --name subscription-topic
{
    "TopicArn": "arn:aws:sns:ap-northeast-1:123456789012:subscription-topic"
}

作成したら次は通知先となるサブスクリプションの登録です。

サブスクリプションの登録
$ aws sns subscribe --topic-arn arn:aws:sns:ap-northeast-1:123456789012:subscription-topic --protocol email --notification-endpoint test@hogehoge.jp
{
    "SubscriptionArn": "pending confirmation"
}

登録先がEmailの場合、通知先となるメールアドレスに以下のような確認メールが届きます。
スクリーンショット 2021-01-28 11.43.17.png
このままだとステータスが 「PendingConfirmation」 となっていて送れる状態になっていません。

サブスクリプションステータスの確認
$ aws sns list-subscriptions-by-topic --topic-arn arn:aws:sns:ap-northeast-1:123456789012:subscription-topic 
{
    "Subscriptions": [
        {
            "SubscriptionArn": "PendingConfirmation",
            "Owner": "123456789012",
            "Protocol": "email",
            "Endpoint": "test@hogehoge.jp",
            "TopicArn": "arn:aws:sns:ap-northeast-1:123456789012:subscription-topic"
        }
    ]
}

スクリーンショット 2021-01-28 11.43.45.png

届いたメールの 「Confirm subscription」 のリンクをクリックして承認します。
スクリーンショット 2021-01-28 11.43.23.png
すると、先ほど 「PendingConfirmation」 となっていた SubscriptionArn がサブスクリプションのARNに置き換わっているのが確認できます。

サブスクリプションステータスの確認
$ aws sns list-subscriptions-by-topic --topic-arn arn:aws:sns:ap-northeast-1:123456789012:subscription-topic 
{
    "Subscriptions": [
        {
            "SubscriptionArn": "arn:aws:sns:ap-northeast-1:123456789012:subscription-topic:62a143c8-2b3f-4a43-9fc2-7486f7eaf862",
            "Owner": "123456789012",
            "Protocol": "email",
            "Endpoint": "test@hogehoge.jp",
            "TopicArn": "arn:aws:sns:ap-northeast-1:123456789012:subscription-topic"
        }
    ]
}

スクリーンショット 2021-01-28 11.43.58.png

これで送信可能な状態となりました。

② IAM の設定

ここでは Lambda が SNS を利用するためのIAMロールを作成します。
まず、Lambdaがログを出力するためのポリシーをjsonファイルに記載します。

ポリシーの作成
$ vim example-policy.json
example-policy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        }
    ]
}

定義したjsonファイルを利用してポリシー作成を行います。

ポリシーの作成
$ aws iam create-policy --policy-name subscription-policy --policy-document file://example-policy.json
{
    "Policy": {
        "PolicyName": "subscription-policy",
        "PolicyId": "ANPA2ZM2YJELWMNIB6FR5",
        "Arn": "arn:aws:iam::123456789012:policy/subscription-policy",
        "Path": "/",
        "DefaultVersionId": "v1",
        "AttachmentCount": 0,
        "PermissionsBoundaryUsageCount": 0,
        "IsAttachable": true,
        "CreateDate": "2021-01-27T13:04:30+00:00",
        "UpdateDate": "2021-01-27T13:04:30+00:00"
    }
}

次は、作成するロールが Lambda で利用できるように信頼ポリシーを作成します。

信頼ポリシーの作成
$ vim example-role-trust-policy.json

jsonファイルに以下の定義をします。

example-role-trust-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

それではロールの作成を行います。

ロールの作成
$ aws iam create-role --role-name subscription-role --assume-role-policy-document file://example-role-trust-policy.json
{
    "Role": {
        "Path": "/",
        "RoleName": "subscription-role",
        "RoleId": "AROA2ZM2YJEL2EULM52BI",
        "Arn": "arn:aws:iam::123456789012:role/subscription-role",
        "CreateDate": "2021-01-27T13:05:27+00:00",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "Service": "lambda.amazonaws.com"
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        }
    }
}

以下のようにロールが作成されます。
スクリーンショット 2021-01-27 22.31.51.png
スクリーンショット 2021-01-27 22.14.57.png
次に先ほど作成したポリシーをロールヘアタッチします。

ロールへのアタッチ
$ aws iam attach-role-policy --role-name subscription-role --policy-arn "arn:aws:iam::123456789012:policy/subscription-policy"

次にSNSへのアクセス権限を付与します。今回は既存のAWS管理ポリシーを利用します。

ロールへのアタッチ
% aws iam attach-role-policy --role-name subscription-role --policy-arn "arn:aws:iam::aws:policy/AmazonSNSFullAccess" 

以下の2つのポリシーがアタッチされていることが確認できます。
スクリーンショット 2021-01-27 22.27.22.png

③ Lambda 関数の作成

サブスクリプションフィルタで利用するLambdaの作成を行います。

ソースコードの作成
$ vim subscription.py

以下のコードを用意します。

subscription.py
import base64
import json
import zlib
import datetime
import os
import boto3
from botocore.exceptions import ClientError

print('Loading function')

def lambda_handler(event, context):
    data = zlib.decompress(base64.b64decode(event['awslogs']['data']), 16+zlib.MAX_WBITS)
    data_json = json.loads(data)
    log_json = json.loads(json.dumps(data_json["logEvents"][0], ensure_ascii=False))

    print(log_json)

    try:
        sns = boto3.client('sns')

        #SNS Publish
        publishResponse = sns.publish(
            TopicArn = os.environ['SNS_TOPIC_ARN'],
            Message = log_json['message'],
            Subject = os.environ['ALARM_SUBJECT']
        )

    except Exception as e:
        print(e)

zipファイル化します。

zipファイル化
$ zip subscription.zip subscription.py

それではLambda関数を作成します。

Lambda関数の作成
$ aws lambda create-function --function-name subtest --role arn:aws:iam::123456789012:role/subscription-role --runtime python3.7 --handler subscription.lambda_handler --zip-file fileb://subscription.zip
{
    "FunctionName": "subtest",
    "FunctionArn": "arn:aws:lambda:ap-northeast-1:123456789012:function:subtest",
    "Runtime": "python3.7",
    "Role": "arn:aws:iam::123456789012:role/subscription-role",
    "Handler": "subscription.lambda_handler",
    "CodeSize": 581,
    "Description": "",
    "Timeout": 3,
    "MemorySize": 128,
    "LastModified": "2021-01-29T13:47:14.979+0000",
    "CodeSha256": "tSwPi+21KmRkigZ7LupkcTlpla4eVmnIMR9FwI5TMAI=",
    "Version": "$LATEST",
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "fb42511e-0e72-458b-b725-22ebbf32c375",
    "State": "Active",
    "LastUpdateStatus": "Successful"
}

作成したら環境変数の設定を行います。

環境変数の設定
$ aws lambda update-function-configuration --function-name subtest --environment Variables='{SNS_TOPIC_ARN="arn:aws:sns:ap-northeast-1:123456789012:subscription-topic",ALARM_SUBJECT="TEST"}'
{
    "FunctionName": "subtest",
    "FunctionArn": "arn:aws:lambda:ap-northeast-1:123456789012:function:subtest",
    "Runtime": "python3.7",
    "Role": "arn:aws:iam::123456789012:role/subscription-role",
    "Handler": "subscription.lambda_handler",
    "CodeSize": 525,
    "Description": "",
    "Timeout": 3,
    "MemorySize": 128,
    "LastModified": "2021-01-31T13:52:09.511+0000",
    "CodeSha256": "yHLp4MvKbGfdizOKxRKOlKKwj+zCY1FJpBQWZMizuCg=",
    "Version": "$LATEST",
    "Environment": {
        "Variables": {
            "SNS_TOPIC_ARN": "arn:aws:sns:ap-northeast-1:123456789012:subscription-topic",
            "ALARM_SUBJECT": "TEST"
        }
    },
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "19d21114-bf2c-415c-9d6e-996acbe6895a",
    "State": "Active",
    "LastUpdateStatus": "Successful"
}

④ CloudWatch Logs の設定

ログの保存先となるロググループを作成します。

ロググループの作成
$ aws logs create-log-group --log-group-name test-subscriptionfilter

スクリーンショット 2021-01-24 22.23.49.png

次にログストリームを作成します。

ログストリームの作成
$ aws logs create-log-stream --log-group-name test-subscriptionfilter --log-stream-name test-stream

スクリーンショット 2021-01-24 22.24.40.png

ログ送信を送るのに creationTime の情報が必要になるので確認します。

ログストリームの確認
$ aws logs describe-log-streams --log-group-name test-subscriptionfilter
{
    "logStreams": [
        {
            "logStreamName": "test-stream",
            "creationTime": 1611494667587,
            "arn": "arn:aws:logs:ap-northeast-1:123456789012:log-group:test-subscriptionfilter:log-stream:test-stream",
            "storedBytes": 0
        }
    ]
}

ログの送信テストをします。
その際、 timestamp は確認した値を入れます。

ログの送信
$ aws logs put-log-events --log-group-name test-subscriptionfilter --log-stream-name test-stream --log-events timestamp=1611494667587,message="This is Test"
{
    "nextSequenceToken": "49614471546104629625117997422005678002374743753152879794"
}

スクリーンショット 2021-01-24 22.25.59.png

msessage で指定したThis is Testが保存されているのが確認できました。
これでログ保存先の用意は完了です。

2回目以降のログ送信はコマンド実行した際の nextSequenceToken の値が必要になります。
後でも確認できますが面倒でなければメモしておくとスムーズかもしれないです。

それではいよいよサブスクリプションフィルタの作成を行います。

サブスクリプションフィルタの作成
$ aws logs put-subscription-filter --log-group-name test-subscriptionfilter --filter-name subscription-filter --filter-pattern test --destination-arn arn:aws:lambda:ap-northeast-1:123456789012:function:subtest

An error occurred (InvalidParameterException) when calling the PutSubscriptionFilter operation: Could not execute the lambda function. Make sure you have given CloudWatch Logs permission to execute your function.

エラーが発生してしまいました。。。どうやら CloudWatch Logs が Lambda を実行できないそうです。
コンソールからサブスクリプションフィルタを作成する場合は気にする必要ないのですが、cliで設定する場合は事前にLambda側でパーミッションの追加をしておく必要があります。

パーミッションの追加
$ aws lambda add-permission --function-name subtest --statement-id "lambdapermission" --principal "logs.ap-northeast-1.amazonaws.com" --action "lambda:InvokeFunction" --source-arn "arn:aws:logs:ap-northeast-1:123456789012:log-group:*:*" --source-account 123456789012
{
    "Statement": "{\"Sid\":\"lambdapermission\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"logs.ap-northeast-1.amazonaws.com\"},\"Action\":\"lambda:InvokeFunction\",\"Resource\":\"arn:aws:lambda:ap-northeast-1:123456789012:function:subtest\",\"Condition\":{\"StringEquals\":{\"AWS:SourceAccount\":\"123456789012\"},\"ArnLike\":{\"AWS:SourceArn\":\"arn:aws:logs:ap-northeast-1:123456789012:log-group:*:*\"}}}"
}

ということで、改めてサブスクリプションフィルタの作成を行います。

サブスクリプションフィルタの作成
$ aws logs put-subscription-filter --log-group-name test-subscriptionfilter --filter-name subscription-filter --filter-pattern Test --destination-arn arn:aws:lambda:ap-northeast-1:123456789012:function:subtest

無事作成できました。

テスト

それでは実際にサブスクリプションフィルタの情報がメールで通知されるか試してみます。

ログの送信
$ aws logs put-log-events --log-group-name test-subscriptionfilter --log-stream-name test-stream --log-events timestamp=1611453737087,message="This is Test" --sequence-token 49614471546104629625117997422005678002374743753152879794
{
    "nextSequenceToken": "49614471546104629625118392286855075160199013121852005554"
}

もし put-log-events で以下のようなエラーが発生した場合、 --sequence-token の値をエラー内の記載されている値(ここだと49614471546104629625118452176870929254188121824069508274)に置き換えて改めて送信してみてください。

送信エラー
An error occurred (InvalidSequenceTokenException) when calling the PutLogEvents operation: The given sequenceToken is invalid. The next expected sequenceToken is: 49614471546104629625118452176870929254188121824069508274

メールが届けば成功です。

スクリーンショット 2021-01-31 23.02.14.png

先ほど Lambda のところで update-function-configuration で設定した ALARM_SUBJECT の値が件名で表示され、本文には put-log-events で送信した message の内容が表示されます。

おわりに

メトリクスフィルターの方が簡単に設定できますが、文字列を検知したことの通知しかできません。
ログ本文を通知内容に含めたい場合はサブスクリプションフィルターを利用することで解決できます。

参考URL

13
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
14