LoginSignup
5
7

More than 3 years have passed since last update.

【AWS】API GateWay + LambdaでSlakcアプリケーションの認証を行う方法

Last updated at Posted at 2020-01-21

SlackのEventをAPI GateWayで処理し、Lambdaを起動しています。
APIのアドレスが漏れた場合、攻撃を受ける可能性が有る為、API GateWayで何かしらの対策ができないかと思い記事を書こうと思いました。

Slackの認証方法について

以下の2種類があります。

  • リクエストのBody内の文字列を利用した認証
  • Signing Secretを用いた認証

リクエストのBody内の文字列を利用した認証

SlackのEvent APIについて

Eventが発生すると、下記のようなJSONが発行されます。

Slackのイベント
{
        "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を用いて認証を行います。

image.png

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の設定を行います。

下記の図の統合リクエストをクリックします。

image.png

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を作成します。

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を呼び出し、認証を行います。

lambda_function.py
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の環境変数に登録します。

image.png

まとめ

以上で認証については終了です。

今後、時間が有れば、API GateWayで、Tokenについて認証し、Lambdaで「Signing Secret」を利用し、認証する事ができるかも、試してみたいです。

5
7
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
5
7