Codepipeline上で承認を行っていた
今回、社内から要望が上がってきたので備忘録として記載しておく。これまではCodepipelineで本番環境にDeployする前段階で以下のような「承認」を経てデプロイしていた。
ところがAWSのコンソールに入るまでに「二段階認証を行うのは面倒」ということでSlack上に「承認」を行うボタンを表示させ、そこをポチったら承認フローが完了するようにした。
ただし、slackのchannnelは「管理者」のみのプライベートにして無闇矢鱈に誰でもポチポチできるような仕組みは排除するようにした。
仕組みを考える
codepipleline上のApprovalから直接何かしらの手順で投げれれば良かったが投げれないため
① SNS通知させた。
②・③ その通知をlambdaのトリガーとしてslackに通知させる
④・⑤・⑥ slackで承認ボタンを押下した際にAPIGatewayにPOSTさせてlambdaを発動させえ最終的にCodepipelineの「承認」を実施する
①のSNS通知から作る
まずは、SNSのトピックから作成した。ここは特に難しい部分はない。
次にCodepipelineの承認の設定。ここは作成したSNSトピックを指定するだけ。
Slackの設定を行う
最初に Slackアプリの設定でアプリの設定を行う。
このあたりをポチポチやっていく。どこのchannelにあてがうのか?とか設定があるが感覚でわかる。
次にTokenを取得する。
最後に赤枠の中の「Vertification Token」を取得
lambdaの作成
ようやく下準備ができたところでlambdaを作成する。
lambdaの環境変数を設定する。今回は、値の部分は暗号化をしていないが暗号化しておいたほうがベター。
自分のMACで実施した作業ログ(ライブラリの導入)
$ mkdir data
$ docker run -it -v $PWD/data:/data --rm python:3.7 /bin/bash
$ cd /data
$ pip3 install slackclient -t ./
スクリプト作成
今回はライブラリに記載のあった以下のapi_callを使おうかと思う。
import logging
import json
import os
import boto3
import traceback
from slack import WebClient
from slack.errors import SlackApiError
def lambda_handler(event, context):
token = os.environ["SLACK_API_TOKEN"]
channel_id = os.environ["channel_id"]
client = WebClient(token=token)
message = event["Records"][0]["Sns"]["Message"]
data = json.loads(message)
token = data["approval"]["token"]
codepipeline_name = data["approval"]["pipelineName"]
if codepipeline_name == "ml-age-api":
msg_text = "```承認してもよろしいでしょうか?```"
attachments_json = [
{
"fallback": "Upgrade your Slack client to use messages like these.",
"color": "#258ab5",
"attachment_type": "default",
"callback_id": "the_greatest_war",
"actions": [
{
"name": "ok",
"text": "承認",
"value": token + ',' + codepipeline_name,
"style": "primary",
"type": "button",
"confirm": {
"title": "承認しますか?",
"text": "本当によろしいですか?",
"ok_text": "OK",
"dismiss_text": "Cancel"
}
},
{
"name": "cancel",
"text": "キャンセル",
"style": "danger",
"value": token + ',' + codepipeline_name,
"type": "button"
}
]
}
]
try:
response = client.chat_postMessage(
channel=channel_id,
text=msg_text,
attachments=attachments_json
)
assert response["ok"]
except Exception:
print(traceback.format_exc())
ここで、ポイントとなる部分は下記の部分である。
"value": token + ',' + codepipeline_name,
「承認」をする際に必要な put_approval_result
を見ていただくと分かる通り pipelineName
と token
が必要になる。よってここで送ってしまえと思いこのようにした。
さらにここには理由があり複数のCodepipelineが動いているためlambdaをその都度作りたくなく出来れば使いまわしをしたかったためである。
承認用のlambdaの作成
上記は、slackにボタンを送信するためのlambdaで今回はAPIGatewayから「承認」または「却下」を受け取ってCodepipelineにわたすためのlambdaである。
# coding: utf-8
import json
import urllib.request
import boto3
stage_name = "Approval"
action_name = "Approval"
def lambda_handler(event, context):
client = boto3.client("codepipeline")
msg = event["body"] # httpでpostされたpayloadの格納
if not msg:
return {
'statusCode': 403,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
'body': "Forbidden"
}
msg_decode = urllib.parse.unquote(msg)
msg_replace = msg_decode.replace("payload=", '')
new_msg = json.loads(msg_replace)
token = new_msg["actions"][0]["value"]
approval_data = token.split(',')
pipeline_name = approval_data[1]
token = approval_data[0]
if new_msg["actions"][0]["name"] == "ok":
client.put_approval_result(
pipelineName=pipeline_name,
stageName=stage_name,
result={
'summary': 'Approval',
'status': 'Approved',
},
token=token,
actionName=action_name
)
return {
'statusCode': 200,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
'body': "```承認されました```"
}
else:
client.put_approval_result(
pipelineName=pipeline_name,
stageName=stage_name,
result={
'summary': 'Rejected',
'status': 'Rejected',
},
token=token,
actionName=action_name
)
return {
'statusCode': 200,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
'body': "```却下されました```"
}
下記の2つはCodepipeline上のStageの名前とActionの名前
stage_name = "Approval"
action_name = "Approval"
次にこのlambdaに
「AWSCodePipelineApproverAccess」
を付与する
##APIGatewayの設定
APIGatewayはPostメソッドで作成を行う。統合リクエストでLambda-Proxyを選択して先に作った。APIGatewayから受け取るLambdaに指定する。
あとは、APIをデプロイして終了。ここでデプロイしたURLを控える。
Slackの設定
httpsでAPIGatewayにPOSTするために以下の設定を行う。
実行してみる
こんな感じで出て「承認」と「キャンセル」をポチって確認終了。
投稿時のエラー
承認をslack投稿時に以下のようなエラーが出る場合がある
{ "ok": false, "error": "not_in_channel" }
この場合、slackの投稿したいchannelで
/invite @ボット名
とすればchannelにボットが追加されて投稿ができるようになる。