msfukuiの日記

おおまさのみみはそらのみみ。

Lambda + CloudWatch Events + KMS で AWS コンソールへのサインインイベントを Slack に通知する(2020年春版)

今更ではあるのですが、最近、少しずつ AWS を個人で触っています。

クラウドワークスさんの約3年半前の以下の記事を今のタイミングで実装した際に、いくつか変更が必要なところがあったので、自分向けの備忘録として残しておこうと思います。

engineer.crowdworks.jp

そもそも、今だと目的を達成するためだけであればもっとシンプルな実装方法がある気はしますが、そこはあえて気にしない。

変更した選択条件

  • AWS Lambda は Python3 を選択

  • AWS CLI はバージョン2をセットアップ

[ ~]$ aws --version
aws-cli/2.0.8 Python/3.7.4 Darwin/17.7.0 botocore/2.0.0dev12

その他 AWS マネジメントコンソールの入力フォームにもいくつか違っている箇所はありましたが、戸惑うところはあまりなかったので、省略します。

AWS Lambda で curl は叩けなくなった

AWS Lambda から curl コマンドを叩くと command not found になる感じでした。

stackoverflow.com

AWS Lambda の環境に curl コマンドが含まれなくなったみたいです。

ということで、curl を実行している箇所(import の箇所と、notify メソッドの中身)を urllib.request で書き換えます。

具体的には、ここの箇所を、

import json
import commands
import urllib

...

def notify(message, channel, web_hook_url):
    payload = {
        "text": message,
        "channel": channel,
        "username": "AWSアカウントモニターBot",
        "icon_emoji": ":ghost:"
    }
    escaped_payload = urllib.quote_plus(json.dumps(payload).encode('utf-8'))
    curl_command = 'curl -s -X POST -d "payload=%s" %s' % (escaped_payload, web_hook_url)
    return commands.getoutput(curl_command)

こんな感じに書き換えます。

import json
import urllib.request
import urllib.parse

...


def notify(message, channel, web_hook_url):
    payload = {
        "text": message,
        "channel": channel,
        "username": "AWSアカウントモニターBot",
        "icon_emoji": ":ghost:"
    }
    encoded_payload = urllib.parse.urlencode({'payload': payload}).encode('utf-8')
    with urllib.request.urlopen(url=web_hook_url, data=encoded_payload) as f:
        return f.read().decode('utf-8')

AWS CLI で KMS で秘密情報を暗号化しようとすると Invalid base64 エラーになる

AWS CLI で KMS で Slack の Incoming Webhooks の文字列を暗号化しようとすると、こんな感じでエラーになります。

[ ~]$ aws kms encrypt --region us-east-1 --output text --query CiphertextBlob --key-id alias/lambda_encryption --plaintext //hooks.slack.com/services/********/xxxxxxxx/xxxxxxxx

Invalid base64: "//hooks.slack.com/services/********/xxxxxxxx/xxxxxxxx"

以下の AWS CLI の注意事項にありますが、セキュリティ上の懸念点の解消のためか、バージョン2から plaintext で渡す文字列は、デフォルトで base64 エンコードしてあげる必要があるみたいです。

docs.aws.amazon.com

base64 エンコードした文字列を指定するのもいいのですが、以下のように fileb://〜 でバイナリの形で標準入力から渡してあげると簡単です。

[ ~]$ aws kms encrypt --region us-east-1 --output text --query CiphertextBlob --key-id alias/lambda_encryption --plaintext fileb://<(echo "//hooks.slack.com/services/********/xxxxxxxx/xxxxxxxx")
AQICAHic78b********YsIjaYI=

Python3 なので str と bytes はそのままでは連結できない

KMS で暗号化した Slack の Incoming Webhooks の文字列を AWS Lambda 内で復号する際、以下のコードが示されていますが、そのままだと 'https:' と復号した文字列を連結する箇所で TypeError: can only concatenate str (not "bytes") to str で怒られます。

def get_web_hook_url(web_hook_url_encrypted):
    return 'https:' + decrypt(web_hook_url_encrypted)

def decrypt(encrypted):
    import boto3
    import base64
    return boto3.client('kms').decrypt(CiphertextBlob=base64.b64decode(encrypted))['Plaintext']

復号した文字列の型は str ではなく bytes のため怒られています。そのままでは連結できないため decode で str に変換して連結します。

def get_web_hook_url(web_hook_url_encrypted):
    return 'https:' + decrypt(web_hook_url_encrypted)

def decrypt(encrypted):
    import boto3
    import base64
    return (boto3.client('kms').decrypt(CiphertextBlob=base64.b64decode(encrypted))['Plaintext']).decode('utf-8')

まとめ

ということで、少し変更するだけで、とりあえず動かすことができました。しあわせです。