michimani.net

AWS Chatbot を使って Slack から AWS CLI コマンドを実行する

2020-04-24

AWS Chatbot では CloudWatch や Code シリーズなどの通知を整形してくれるという機能が注目されがちですが、 Slack から AWS CLI コマンドを実行することもできます。実行できる CLI コマンドにはいくつか制限もあるので、そのあたりも一緒に確認してみます。

目次

前提

AWS Chatbot と Slack のワークスペースの連携は済んでいるものとします。連携の手順については公式ドキュメントを参照するか、一つ前の記事で書いた内容を参考にしてください。

チャンネルの設定

AWS Chatbot では、 Slack ワークスペース の中に複数の チャンネル を設定します。設定については 前回の記事 と同様なので詳細は割愛します。

今回、 Slack で AWS CLI コマンドを実行するためには、 AWS Chatbot のチャンネル設定で 適切にアクセス許可をする 必要があります。

コンソールの設定項目でいうと、この部分です。

AWS Chatbot channel configuration - Permission setting

ポリシーテンプレート から必要な権限を選択してアクセス許可を設定するのですが、デフォルトでは 通知のアクセス許可 のみが選択されています。この権限を付与することで、 Amazon CloudWatch からメトリクスグラフを取得できるようになります。

ポリシーテンプレートには 通知のアクセス許可 の他に、つぎのテンプレートが用意されています。

これらはすべて 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 関数を実行するためのコマンドを実行可能となります。具体的には次のコマンドです。

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 とします。

running AWS CLI command from Slack | help

こんな感じで応答が返ってきます。これぞ chat bot っていう感じですね。

読み取り専用コマンド

読み取り専用のコマンドの例として、 Lambda 関数の一覧を取得してみます。実行するのは @aws lambda list-functions です。

running AWS CLI command from Slack | lambda list-functions

Lambda 関数のリストが返ってきました。

では、書き込みのコマンドを実行するとどうなるでしょうか。試しに s3 create-bucket コマンドを実行してみます。

running AWS CLI command from Slack | s3 create-bucket

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

running AWS CLI command from Slack | s3 create-bucket help

ちなみに、チャンネルの 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}')
running AWS CLI command from Slack | lambda invoke

本当に実行するかコマンドの確認があり、 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 から呼び出してみます。

running AWS CLI command from Slack | lambda invoke via incoming-webhook

chatbot-function は呼び出されませんでした。

AWS サポートコマンド

実際にサポートチケットを作成するわけにはいかないので、ヘルプを確認して実行可能なことを確認します。

running AWS CLI command from Slack | support create-case

まとめ

AWS Chatbot を使って Slack から AWS CLI コマンドを実行してみた話でした。
サポートに関するコマンドだけを実行できる権限というのは、サポート担当の非エンジニアの方が Slack からサポートチケットを作成できるので、ハードルが少し低くなるかなと思いました。

また、 Lambda 関数を呼び出すことができるということは出来ることの幅がかなり広がるので、アイデア次第では面白い・便利な使い方ができるかもしれません。たとえば、特定の CodePipeline を実行する Lambda を作成しておけば、マネジメントコンソールにログインできなくても、 PC でターミナルを操作することができなくても、スマホに Slack アプリさえ入っていれば寝転びながらでもパイプラインを実行することができます。

他にも使い方は色々ありそうなので、これからたくさんの情報が出てくることにワクワクしています。


comments powered by Disqus