パラメータストアを使って安全なLambda関数を作成する
はじめに
こんばんは、菅野です。 Lambda関数を作っていると、外部に知られたくない文字列を使いたい事があります。 そんな時にパラメータストアを使うと、ソースに埋め込む必要があるのは「名前」だけで「秘密にしたい値」はプログラム側で一切保持しなくて済むようになります。 今回はパラメータストアに保存してあるAPIキーとRoomIDを使って、Chatworkへメッセージを送信する汎用的なLambda関数を作成してみました。
パラメータについて
今回必要なパラメータは以下の3つとなります。
- ChatworkのURL
- ChatworkのAPIキー
- ChatworkのRoomID
この中で機密性の高いものはAPIキーとRoomIDの二つなので、これらは暗号化しておきます。 URLはソースに埋め込んでも問題無いのですが、他のLambda関数でも使えるのでパラメータ化しておきます。 そうすればURLが変わった時にパラメータだけ変更すれば全てのLambda関数の修正が完了します。
パラメータの準備
それではパラメータを作成します。 例として暗号化しておきたいAPIキーを作成してみましょう。名前を付けてタイプを「安全な文字列」にするだけです。
同様にRoomIDのパラメータも作成します。
ChatworkのURLは暗号化しなくてもいいので、タイプは「文字列」でOKです。
Lambda関数用のIAMポリシーとIAMロールの準備
今回作成するLambda関数に必要な権限はパラメータストアからのパラメータ取得です。 必要な権限は以下の二つとなります。
- ssm:GetParameters
- sts:AssumeRole
このポリシーを使う、Lambda関数用のIAMロールも作ります。
Lambda関数で使うライブラリの準備
今回のLambda関数はChatworkへhttpsでPOSTする必要があるので、requestsライブラリをzipで用意しておきます。
$ mkdir requests $ cd requests $ pip install requests -t . $ zip -r requests.zip *
Lambda関数の作成
- 「一から作成」を選択
- 名前を付けます
- ランタイムはPython3.6を選択
- 先ほど作成したIAMロールを選択
- コードエントリタイプは「.ZIPファイルをアップロード」を選択
- 先ほど作成したrequests.zipをアップロード
- 右上の「保存」ボタンをクリック
「lambda_function.py」ファイルが無いと注意されます。アップロードしたZIPファイルの中に含まれていないので当然です。
本来は「lambda_function.py」とライブラリをまとめてZIPファイルにしてアップロードするのですが、今回は「lambda_function.py」をマネジメントコンソール上で作成します。
- 「File」をクリック
- 「New File」をクリック
- もう一度「File」をクリック
- 「Save As」をクリック
- ファイル名に「lambda_function.py」と入力
- 一番上のフォルダを選択
- 「Save」ボタンをクリック
これで関数を作成できます。以下のコードをコピーして「lambda_function.py」に貼り付けて保存してください。
from __future__ import print_function import json import boto3 import requests # 初期設定 ssm = boto3.client( 'ssm' ) chatwork_url_param_name = 'chatwork-url' chatwork_room_param_name = '' chatwork_key_param_name = '' # メイン関数 def lambda_handler( event, context ): global chatwork_room_param_name global chatwork_key_param_name # 渡されたパラメータストアの名前を保存 try: chatwork_room_param_name = event[ 'room' ] chatwork_key_param_name = event[ 'key' ] except KeyError as e: return { "error": "param_exists_error", "param_name": str( e ).replace( "'", '' ) } # パラメータの名前から復号化したパラメータを取得 ssm_response = ssm.get_parameters( Names = [ chatwork_url_param_name, chatwork_room_param_name, chatwork_key_param_name ], WithDecryption = True ) # パラメータを格納する配列を準備 params = {} # 復号化したパラメータを配列に格納 for param in ssm_response[ 'Parameters' ]: params[ param['Name'] ] = param['Value'] # パラメータストアに存在しないパラメータ名を指定されていたら終了 if len( ssm_response[ 'InvalidParameters' ] ) > 0: return { "error": "param_name_error", "param_name": ', '.join( ssm_response[ 'InvalidParameters' ] ) } # chatwork へメッセージを送信 return post_to_chatwork( event, params ) # chatwork へメッセージを送信 def post_to_chatwork( event, params ): # 送信する情報を準備 post_params = { "body": event[ 'message' ] } token_header = { "X-ChatWorkToken": params[ chatwork_key_param_name ] } # chatwork へメッセージを送信 try: https_response = requests.post( # 引数1:URL params[ chatwork_url_param_name ] + '/rooms/' + params[ chatwork_room_param_name ] + '/messages', # 引数2:POST パラメータ data=post_params, # 引数3:ヘッダ(key) headers=token_header ) # 200 以外は例外を発生 https_response.raise_for_status() except Exception as e: return { "error": "requests_post_error", "message": str( e ) } # レスポンスを辞書型に変換 https_response_dict = json.loads( https_response.text ) # 終了 return { "error": "", "message_id": https_response_dict[ 'message_id' ] }
パラメータを取得する部分の説明
今回、3つのパラメータをまとめて取得しています。
# パラメータの名前から復号化したパラメータを取得 ssm_response = ssm.get_parameters( Names = [ chatwork_url_param_name, chatwork_room_param_name, chatwork_key_param_name ], WithDecryption = True )
- Namesというリストにパラメータ名をカンマ区切りで入れて渡しています。
- APIキーとRoomIDは暗号化されていますので、復号化されたものを取得できるように「WithDecryption = True」を指定します。
テスト実行
今回作成したLambda関数はAPIキーとRoomIDのパラメータ名、Chatworkへ送る文字列をこの関数へ渡す事で「event」変数にそれらの値が入った状態で動作します。 右上の「テストイベントの選択」からテスト実行するのに必要なパラメータを作成しましょう。
「テストイベントの設定」をクリックすると以下のダイアログが表示されます。
- 任意のイベント名を入力
- パラメータに以下の内容を入力して下にある「作成」ボタンをクリック
{ "room": "chatwork-room-test", "key": "chatwork-key-cm", "message": "aaa\nbbb" }
右上で今作成したテストイベント「test」を選択して「テスト」ボタンをクリックしてみます。 Chatworkへのメッセージ送信が成功して、message_idが帰ってきました。
さいごに
今更感がありますが、パラメータストアを利用してみました。 今まではLambda関数ではなく、呼び出す方のプログラムに秘密の情報を埋めていましたが、パラメータストアを使えば呼び出し側もLambda関数側もどちらにも書かなくて済みますので安心してソースコードを管理できます。 皆さんもパラメータストアを活用して安全なプログラムを作成していきましょう!
参考ページ
以下のページを参考にしました。 ありがとうございました。 Boto 3 Docs 1.7.4 documentation Systems Manager パラメータについて - AWS Systems Manager 【AWS】Lambdaでpipしたいと思ったときにすべきこと - Qiita PythonモジュールRequestsのHTTPステータスコードについて 【Python入門】JSONをパースする方法 - Qiita