slackのslash commandの処理をaws lambdaで実装してみました
TRANSCRIPT
SlackのSlash commandの処理をAWS Lambdaで実装してみました株式会社サイタスマネジメント
米田真治
1
2016
/4/2
2JA
WS-
UG
アーキテクチャ専門支部クラウドネイティブ
分科会
CDP議論会
#8
自己紹介•米田真治 (こめだ しんじ)
� 株式会社サイタスマネジメント CTO� 運用エンジニア
• 経歴� 学生時代にUNIXに出会う� Internetにつながる環境がきた� いろんなプログラムをビルドしているうちに、開発・構築の楽しさに目覚める
� 学科のシステム管理を経験
� システム運用のおもしろさを覚える
� 就職してISPのサーバ構築・運用に携わる� 2000年6月サイタスマネジメント創業
2016
/04/
20OSS運用管理勉強会
2
全体像
3
2016
/4/2
2JA
WS-
UG
アーキテクチャ専門支部クラウドネイティブ
分科会
CDP議論会
#8
利用したblueprint• slack-echo-command-python
Slack 側は Custom Integrations の Slash Commands を使用します。
• cloudwatch-alarm-to-slack-pythonevent['Records'][0]['Sns']['Message']からメッセージを取り出します。Incoming Webhooksでなく response_url を使って応答を返すように修正しました。
4
2016
/4/2
2JA
WS-
UG
アーキテクチャ専門支部クラウドネイティブ
分科会
CDP議論会
#8
シナリオSlack → API Gateway → Lambda
Slack 側では Slash Commands を使用します。Lambda 関数の処理では 3 秒以内にSlack 側へ応答を返す必要があります。時間がかかる処理を行うために、Lambda で受信したコマンドは、SNS を経由して別の Lambda 関数をイベント起動します。
5
2016
/4/2
2JA
WS-
UG
アーキテクチャ専門支部クラウドネイティブ
分科会
CDP議論会
#8
シナリオLambda → SNS → Lambda → SlackSNS イベントから起動される Lambda 関数では STS を利用して、Slash Command を発行したユーザのロールを引き受けてAPI コマンドを実行します。
{"user_name": "komeda", "command": "/lambda", "channel": "genral", "command_text": "ec2 start i-‐xxxxxxxx", "response_url": "https://...."
}
6
2016
/4/2
2JA
WS-
UG
アーキテクチャ専門支部クラウドネイティブ
分科会
CDP議論会
#8
API Gatewayの設定
7
2016
/4/2
2JA
WS-
UG
アーキテクチャ専門支部クラウドネイティブ
分科会
CDP議論会
#8
lambda-bot関数概要def lambda_handler(event, context):
req_body = event['body']params = parse_qs(req_body)トークンチェック引数取りだしarg = command_text.split(' ')
if arg[0] in ['ec2', 's3']:sns = boto3.client('sns')topic_arn = sns.create_topic(Name='sns-‐lambda')['TopicArn']message={"user_name": user, "command": command, "channel": channel, "command_text": com
mand_text, "response_url": response_url}message=json.dumps(message)message=json.dumps({'default': message, 'lambda': message})response = sns.publish(
TopicArn=topic_arn,Subject='/lambda',MessageStructure='json',Message=message
)return { "text": "%s %s¥nroger" % (command, command_text) }
elif arg[0] == 'help':return { "text": "ec2 [console|start|stop instance-‐id]¥ns3 [usage bucket]" }
else:return { "text": "%s invoked %s in %s with the following text: %s" % (user, command, ch
annel, command_text) }
8
2016
/4/2
2JA
WS-
UG
アーキテクチャ専門支部クラウドネイティブ
分科会
CDP議論会
#8
notify-to-slack関数概要def lambda_handler(event, context):
message = event['Records'][0]['Sns']['Message']logger.info("Event: " + str(message))if True:
message = json.loads(message)引数取りだしarg = command_text.split(' ')slack_message = {
'channel': '@%s' % user_name, 'response_type': 'ephemeral', # or 'in_channel''isDelayedResponse': 'true','text': "%s %s" % (command, command_text)
}if arg[0] == 'ec2':
slack_message['text'] = ec2_command(arg, user_name, response_url)send_response(response_url, slack_message)
elif arg[0] == 's3':slack_message['text'] = s3_command(arg, user_name, response_url)send_response(response_url, slack_message)
else:slack_message = { 'text': "command failed" }send_response(response_url, slack_message)
9
2016
/4/2
2JA
WS-
UG
アーキテクチャ専門支部クラウドネイティブ
分科会
CDP議論会
#8
STSでの権限の引き受けポリシー名とロール名をハードコードしています。ほかに良い方法があれば…
iam = boto3.client('iam')response = iam.get_user(UserName=user_name)if response.has_key('User'):
uid = response['User']['Arn'].split(':')[4]if uid and boto3.resource('iam').UserPolicy(user_name, 'ec2-‐full-‐access'):
response = iam.get_role(RoleName='virginia-‐ec2-‐delegate')if response.has_key('Role'):
sts = boto3.client('sts')assumedRoleObject = sts.assume_role(RoleArn="arn:aws:iam::%s:role/%s" %
(uid, 'virginia-‐ec2-‐delegate'), RoleSessionName='session')credentials = assumedRoleObject['Credentials']ec2 = boto3.resource(
'ec2',aws_access_key_id=credentials['AccessKeyId'],aws_secret_access_key=credentials['SecretAccessKey'],aws_session_token = credentials['SessionToken'],)
10
2016
/4/2
2JA
WS-
UG
アーキテクチャ専門支部クラウドネイティブ
分科会
CDP議論会
#8
response_urlに応答を返すdef send_response(response_url, message):
req = Request(response_url)req.add_header('Content-‐Type', 'application/json')try:
response = urlopen(req, json.dumps(message))response.read()
except HTTPError as e:logger.error("Request failed: %d %s", e.code, e.reason)
except URLError as e:logger.error("Server connection failed: %s", e.reason)
11
2016
/4/2
2JA
WS-
UG
アーキテクチャ専門支部クラウドネイティブ
分科会
CDP議論会
#8
課題• 認証
� いまのところ、Slackのユーザ名をAWSのユーザ名に対応づけて扱っています。
� コマンド実行のときは、できればユーザ認証をしたい。
� MFAできればよいけれど… …� Cognitoの新機能「User Pools」を使えばMFAできるの?
• STS� ポリシー名とロール名をハードコードしているところ、ユーザが持っている権限をチェックできた方がよい。
� 実用上はハードコードでもかまわない?
12
2016
/4/2
2JA
WS-
UG
アーキテクチャ専門支部クラウドネイティブ
分科会
CDP議論会
#8