SlackのEventをAPI GateWayで処理し、Lambdaを起動しています。
APIのアドレスが漏れた場合、攻撃を受ける可能性が有る為、API GateWayで何かしらの対策ができないかと思い記事を書こうと思いました。
Slackの認証方法について
以下の2種類があります。
- リクエストのBody内の文字列を利用した認証
- Signing Secretを用いた認証
リクエストのBody内の文字列を利用した認証
SlackのEvent APIについて
Eventが発生すると、下記のようなJSONが発行されます。
{
"token": "XXYYZZ",
"team_id": "TXXXXXXXX",
"api_app_id": "AXXXXXXXXX",
"event": {
"type": "name_of_event",
"event_ts": "1234567890.123456",
"user": "UXXXXXXX1",
...
},
"type": "event_callback",
"authed_users": [
"UXXXXXXX1",
"UXXXXXXX2"
],
"event_id": "Ev08MFMKH6",
"event_time": 1234567890
}
token、team、そしてapi_app_idフィールドでは、妥当性、起源、およびリクエストの送信先を識別するのに役立ちます。
と公式サイトに記載されています。
tokenの内容で認証を行う方法
Slackアプリの「App Credentials」のページ内に記載されている、Verification Token
を用いて認証を行います。
Verification Token
をLambdaの環境変数「VERIFICATION_TOKEN_APPTEST_WS」に設定して利用します。
eventの内容は、Slackから来るPOSTのBodyが格納されています。
# TOKEN認証
if event['token'] != os.environ['VERIFICATION_TOKEN_APPTEST_WS']:
return {
'statusCode': 200,
'body': json.dumps('Error'),
'text' : 'Token不整合'
}
※同様に、App_IDでも認証をする事が可能です。
Signing Secretを用いた認証を行う方法
Verifying requests from Slack の情報を参考に認証を設定していきます。
API GateWayの設定
SlackのBody以外の内容を受け取れるようにする為に、API GateWayの設定を行います。
下記の図の統合リクエスト
をクリックします。
Lambda プロキシ統合の使用
のチェックを有効にします。
「Lambda プロキシ統合の使用」を有効化について
「Lambda プロキシ統合の使用」を有効化する事で、これまで、API GateWayが整形していたHTTPヘッダーの情報をきちんとLambda関数で受け取る事ができるようになります。
例)
・「Lambda プロキシ統合の使用」を無効化した状態
→ HTTPヘッダーの情報が切り捨てられ、Lambdaのeventに渡される内容は、Bodyの内容のみ
{ Message: 'Hello World!' }
・「Lambda プロキシ統合の使用」を有効化した状態
→ HTTPヘッダーの情報も含めてLambdaのeventに渡される
※ 注意! eventの内容はJSONではなく、文字列で渡される為、注意が必要です。
{
resource: '/mook',
path: '/mook',
httpMethod: 'POST',
headers: { Accept: ' application/json' },
queryStringParameters: { param1: 'value1' },
pathParameters: null,
stageVariables: null,
requestContext: {
accountId: 'xxxxxxxxxxxx',
resourceId: 'xxxxxx',
stage: 'test-invoke-stage',
requestId: 'test-invoke-request',
identity: {
cognitoIdentityPoolId: null,
accountId: 'xxxxxxxxxxxx',
cognitoIdentityId: null,
caller: 'xxxxxxxxxxxx',
apiKey: 'test-invoke-api-key',
sourceIp: 'test-invoke-source-ip',
cognitoAuthenticationType: null,
cognitoAuthenticationProvider: null,
userArn: 'arn:aws:iam::xxxxxxxxxxxx:root',
userAgent: 'Apache-HttpClient/4.5.x (Java/1.8.0_102)',
user: 'xxxxxxxxxxxx'
},
resourcePath: '/mook',
httpMethod: 'POST',
apiId: 'xxxxxxxxxx'
},
body: '{"Message": "Hello World"}'
}
Lambdaで認証の処理を作成
API GateWayでは認証の処理を実装する事ができないので、Lambdaに処理を実装します。
認証用の処理を記載したPythonファイルslack_signing.py
を作成します。
# -*- coding: utf-8 -*-
""" Slackの認証を行う処理 """
import hmac
import hashlib
import datetime
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def __generate_hmac_signature(timestamp, body, secretkey):
""" hmacの生成処理 """
secretkey_bytes = bytes(secretkey, 'UTF-8')
message = "v0:{}:{}".format(timestamp, body)
message_bytes = bytes(message, 'UTF-8')
return hmac.new(secretkey_bytes, message_bytes, hashlib.sha256).hexdigest()
def is_valid_event(event, secretkey):
""" 認証処理 """
if "X-Slack-Request-Timestamp" not in event["headers"] \
or "X-Slack-Signature" not in event["headers"]:
print('API GateWayで「Lambda プロキシ統合の使用」が選択されているかチェックしてください。')
return False
request_timestamp = int(event["headers"]["X-Slack-Request-Timestamp"])
now_timestamp = int(datetime.datetime.now().timestamp())
if abs(request_timestamp - now_timestamp) > (60 * 5):
return False
expected_hash = __generate_hmac_signature(
event["headers"]["X-Slack-Request-Timestamp"],
event["body"],
secretkey
)
expected = "v0={}".format(expected_hash)
actual = event["headers"]["X-Slack-Signature"]
logger.debug("Expected HMAC signature {} : {}".format(type(expected), expected))
logger.debug("Actual HMAC signature {} : {}".format(type(actual), actual))
return hmac.compare_digest(expected, actual)
Lambdaのハンドラがあるソースで、is_valid_event
を呼び出し、認証を行います。
import json
import logging
import os
from slack_signing import is_valid_event
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event: dict, context):
# EventのVerify用
logger.info(json.dumps(event))
if "challenge" in event:
return event.get("challenge")
# Signing処理の呼び出し
secretkey = os.environ['SLACK_API_SIGNING_SECRET']
if is_valid_event(event, secretkey) is False:
logger.error('×System Error!')
return {
'statusCode': 200,
'body': json.dumps('OK')
}
# Bodyの内容は、文字列なので、ここでJSONに変換
body = json.loads(event['body'])
# 処理をここに書く
return {
'statusCode': 200,
'body': json.dumps('OK')
}
SLACK_API_SIGNING_SECRET を利用するために、環境変数に登録します。
下記の図のShow
ボタンを押して「Signing Secret」を表示し、Lambdaの環境変数に登録します。
まとめ
以上で認証については終了です。
今後、時間が有れば、API GateWayで、Tokenについて認証し、Lambdaで「Signing Secret」を利用し、認証する事ができるかも、試してみたいです。