AWS Chatbot を使って Slack から AWS CLI コマンドを実行する
2020-04-24AWS Chatbot では CloudWatch や Code シリーズなどの通知を整形してくれるという機能が注目されがちですが、 Slack から AWS CLI コマンドを実行することもできます。実行できる CLI コマンドにはいくつか制限もあるので、そのあたりも一緒に確認してみます。
目次
前提
AWS Chatbot と Slack のワークスペースの連携は済んでいるものとします。連携の手順については公式ドキュメントを参照するか、一つ前の記事で書いた内容を参考にしてください。
- Getting started with AWS Chatbot - AWS Chatbot
- AWS Chatbot を使って CloudWatch Alarm の通知を Slack に飛ばしてみた - michimani.net
チャンネルの設定
AWS Chatbot では、 Slack ワークスペース の中に複数の チャンネル を設定します。設定については 前回の記事 と同様なので詳細は割愛します。
今回、 Slack で AWS CLI コマンドを実行するためには、 AWS Chatbot のチャンネル設定で 適切にアクセス許可をする 必要があります。
コンソールの設定項目でいうと、この部分です。

ポリシーテンプレート から必要な権限を選択してアクセス許可を設定するのですが、デフォルトでは 通知のアクセス許可 のみが選択されています。この権限を付与することで、 Amazon CloudWatch からメトリクスグラフを取得できるようになります。
ポリシーテンプレートには 通知のアクセス許可 の他に、つぎのテンプレートが用意されています。
- 読み取り専用コマンドのアクセス許可
- Lambda 呼び出しコマンドのアクセス許可
- AWS サポートコマンドのアクセス許可
これらはすべて Slack から実行できる AWS CLI コマンドを制限するものです。それぞれの権限で実行できるコマンドについて、公式ドキュメントをもとに整理してみます。
読み取り専用コマンドのアクセス許可
AWS CLI コマンドのうち、 読み取り専用 のコマンドのみ実行可能となります。基本的にはすべてのサービスが対象となりますが、以下のサービスについては読み取り専用であっても実行不可だったりするものがあります。
サービス | 制限内容 |
---|---|
IAM | すべて実行不可 |
AWS KMS | すべて実行不可 |
AWS STS | すべて実行不可 |
Amazon Cognito | 読み取り専用のみ実行可 GetSigningCertificate は実行不可 |
Amazon EC2 | 読み取り専用のみ実行可 GetPasswordData は実行不可 |
Amazon ECR | 読み取り専用のみ実行可 GetAuthorizationToken は実行不可 |
GameLift | 読み取り専用のみ実行可 資格情報 (credentials) に関するコマンド及び GetInstanceAccess は実行不可 |
Amazon Lightsail | List および Read は実行可 キーペアに対するコマンド及び GetInstanceAccess は実行不可 |
Amazon Redshift | GetClusterCredentials は実行不可 |
Amazon S3 | 読み取り専用のみ実行可 GetBucketPolicy は実行不可 |
AWS Storage Gateway | 読み取り専用のみ実行可 DescribeChapCredentials は実行不可 |
読み取り専用のコマンドの中でも、セキュリティや重要な情報に関するコマンドは実行不可となっています。また、 IAM や KMS 、 STS といった重要なサービスに関してはすべてのコマンドが実行不可となっています。
IAM ポリシーとしては次のようになります。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": [
"iam:*",
"kms:*",
"sts:*",
"cognito-idp:GetSigningCertificate",
"ec2:GetPasswordData",
"ecr:GetAuthorizationToken",
"gamelift:RequestUploadCredentials",
"gamelift:GetInstanceAccess",
"lightsail:DownloadDefaultKeyPair",
"lightsail:GetInstanceAccessDetail",
"lightsail:GetKeyPair",
"lightsail:GetKeyPairs",
"redshift:GetClusterCredentials",
"s3:GetBucketPolicy",
"storagegateway:DescribeChapCredentials"
],
"Resource": [
"*"
]
}
]
}
ちなみに Amazon S3 に関するコマンドに関しては、 AWS CLI の s3api
のサブコマンドを使用して、 s3 list-buckets
のように呼び出します。 s3 ls
は使用できません。
Lambda 呼び出しコマンドのアクセス許可
その名の通り、 Lambda 関数を実行するためのコマンドを実行可能となります。具体的には次のコマンドです。
lambda invoke
lambda invoke-async
IAM ポリシーは次のようになります。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"lambda:invokeAsync",
"lambda:invokeFunction"
],
"Resource": [
"*"
]
}
]
}
AWS サポートコマンドのアクセス許可
AWS サポートに関するコマンドを実行可能となります。この権限が用意されている理由としては、例えばサポート担当の人がマネジメントコンソールにアクセスする必要なく、 Slack から AWS サポートに対してチケットを作成できるユースケースが考えられるからです。
IAM ポリシーとしては次のようになります。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"support:*"
],
"Resource": "*"
}
]
}
これらの権限はチャンネルごとに設定できるため、チャンネルにジョインしているユーザーに合わせて権限を変えることで、可能な操作を制限しつつ便利に AWS リソースを操作できそうです。
実際にコマンドを実行してみる
では実際にコマンドを実行してみます。今回は上で説明したポリシーテンプレートはすべて付与しているチャンネルで実行します。
まずは全体のヘルプを確認してみます。Slack チャンネル内での AWS CLI コマンドの実行は、普段の aws
の代わりに @aws
として実行します。
なので、ヘルプを確認する場合は @aws help
とします。

こんな感じで応答が返ってきます。これぞ chat bot っていう感じですね。
読み取り専用コマンド
読み取り専用のコマンドの例として、 Lambda 関数の一覧を取得してみます。実行するのは @aws lambda list-functions
です。

Lambda 関数のリストが返ってきました。
では、書き込みのコマンドを実行するとどうなるでしょうか。試しに s3 create-bucket
コマンドを実行してみます。

it isn’t enabled と言われて実行不可でした。ちなみに、実行不可なコマンドに関してはヘルプを見ることもできません。

ちなみに、チャンネルの IAM ロールにはポリシーテンプレートを使用せずに、既存の IAM ロールをアタッチすることも可能です。その際、たとえば S3 への FullAccess をもつ IAM ロールをアタッチしたとしても、 Slack からのコマンド実行でバケット作成などの書き込み操作ができるようになるわけではありません。
Lambda 呼び出しコマンド
Lambda 呼び出しコマンド lambda invoke
を実行してみます。実行する Lambda 関数としては、 context
の一部を Slack にポストするだけの関数を用意しました。関数名は chatbot-function とします。
import json
import urllib.parse
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
SLACK_WEBHOOK_URL = 'https://hooks.slack.com/************'
def lambda_handler(event, context):
print_context_attrs = [
'aws_request_id',
'function_name',
'function_version',
'memory_limit_in_mb',
'client_context',
]
context_data = {}
for attr in print_context_attrs:
if attr == 'client_context':
client_context = context.client_context
attr_value = {}
if client_context is not None:
for client_context_attr in dir(client_context):
attr_value[client_context_attr] = getattr(client_context, client_context_attr)
else:
attr_value = getattr(context, attr)
context_data[attr] = attr_value
context_str = json.dumps(context_data, indent=2, ensure_ascii=False)
message = f'```\n{context_str}\n```'
post_to_slack(message)
def post_to_slack(message):
req = Request(SLACK_WEBHOOK_URL, json.dumps(
{'text': message}).encode('utf-8'))
try:
response = urlopen(req)
response.read()
except HTTPError as e:
print(f'Request failed: {e.code} {e.reason}')
except URLError as e:
print(f'Server connection failed: {e.reason}')

本当に実行するかコマンドの確認があり、 Yes を押すと実行されます。
チャンネルが一緒なので Lambda からのポストが間に入ってますが、実行結果も AWS Chatbot から返ってきています。
ここで、 Incoming WebHook のポストメッセージが AWS CLI のコマンドになっていたらどうなるのか?とふと思い、やってみました。同じ関数だとループしたとき怖いので、次のような別の Lambda 関数を作成しました。関数名は chatbot-pre-function とします。
import json
import urllib.parse
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
SLACK_WEBHOOK_URL = 'https://hooks.slack.com/services/********'
def lambda_handler(event, context):
message = '@aws lambda invoke --function-name chatbot-function --region ap-northeast-1'
post_to_slack(message)
def post_to_slack(message):
req = Request(SLACK_WEBHOOK_URL, json.dumps(
{'text': message}).encode('utf-8'))
try:
response = urlopen(req)
response.read()
except HTTPError as e:
print(f'Request failed: {e.code} {e.reason}')
except URLError as e:
print(f'Server connection failed: {e.reason}')
この関数を Slack から呼び出してみます。

chatbot-function は呼び出されませんでした。
AWS サポートコマンド
実際にサポートチケットを作成するわけにはいかないので、ヘルプを確認して実行可能なことを確認します。

まとめ
AWS Chatbot を使って Slack から AWS CLI コマンドを実行してみた話でした。
サポートに関するコマンドだけを実行できる権限というのは、サポート担当の非エンジニアの方が Slack からサポートチケットを作成できるので、ハードルが少し低くなるかなと思いました。
また、 Lambda 関数を呼び出すことができるということは出来ることの幅がかなり広がるので、アイデア次第では面白い・便利な使い方ができるかもしれません。たとえば、特定の CodePipeline を実行する Lambda を作成しておけば、マネジメントコンソールにログインできなくても、 PC でターミナルを操作することができなくても、スマホに Slack アプリさえ入っていれば寝転びながらでもパイプラインを実行することができます。
他にも使い方は色々ありそうなので、これからたくさんの情報が出てくることにワクワクしています。
comments powered by Disqus