
Slack通知のボタンから、Lambda経由でAWS DevOps Agentの調査を起動してみた
こんにちは、製造ビジネステクノロジー部のはすとです。
前回は、CloudWatchアラームをきっかけにAWS DevOps Agentの調査を自動で始める構成を作りました。
動かしてみると便利なのですが、調査は実行時間に応じて課金されるため、アラームが鳴るたびに調査が始まると費用がかさみます。
そこで今回は「人がSlackの通知のボタンを押したときだけ調査する」形にできないかと考えました。
ただ、Slackにボタンを置くと、今度は「誰でも押せてしまうのでは」「ボタンはどんな権限で動くのか」というセキュリティ面が気になります。
本記事では、Amazon Q Developer in chat applications(旧AWS Chatbot)のカスタムアクションでボタンを作り、押すとLambda経由でDevOps Agentの調査を発火させる仕組みと、セキュリティ面を確かめた結果をまとめます。
検証環境
2026年10月時点の情報です。
- リージョン: 東京(ap-northeast-1)
- Slack: プライベートチャンネル。Amazon Qのアプリを招待済み
- AWS CDK: aws-cdk-lib 2.272.0
- Lambda: Node.js 22、
@aws-sdk/client-devops-agent3.1152.0 - 調査の対象: ECS FargateのCPU使用率のアラーム
仕組み
ボタンを押してから調査が始まるまでの流れは、次のようになります。

ボタンの正体は、Amazon Qのカスタムアクションです。
通知にボタンを付けておき、押されたらAWS CLIのコマンドを実行する仕組みで、コマンドには通知の値を変数として埋め込めます。
ここでは、アラーム名を入れたlambda invokeを実行します。
呼ばれたLambdaは、アラーム名が許可リストにあるか、そのアラームが今ALARMかを確かめてから、DevOps AgentのCreateBacklogTask APIで調査を作ります。
Webhookは使わないため、トークンの保管もEventBridgeのルールも要りません。
公式ドキュメントには、カスタムアクションの権限について次のように書かれています。
No additional permissions are needed to configure or run custom actions. When your channel members choose the custom action button, the action target is invoked using the configured IAM permissions in your channel configuration.
(カスタムアクションの設定や実行に追加の権限は要りません。チャンネルのメンバーがボタンを選ぶと、チャンネル設定に構成されたIAMの権限でアクションの対象が呼び出されます)
Custom actions using Amazon Q Developer in chat applications
つまり、ボタンを押したときに使われるのは、チャンネル設定に付けたIAMロール(チャンネルロール)の権限です。
押した人のAWSの権限ではありません。
なお、Amazon QのCLI実行からdevops-agentのコマンドを直接呼べれば一番簡単なのですが、試したところInternal Failureになり使えませんでした。
Lambdaを挟んでいるのはそのためです。
作ったもの
CDKで作ったものは次の4つです。
| 作るもの | 役割 |
|---|---|
| Lambdaとそのロール | 許可リストと状態の確認、CreateBacklogTaskの呼び出し |
| チャンネルロールとガードレール | ボタンが使う権限と、チャンネルから使える権限の上限 |
| カスタムアクション | 通知に出すボタンと、押したときに実行するコマンド |
| Amazon Qのチャンネル設定 | SNSトピックの購読、チャンネルロール、ガードレール、ボタンの登録 |
Lambda
clientTokenに「アラーム名と時刻(1時間単位)」を入れているため、同じアラームのボタンを同じ1時間の間に何度押しても、調査は1件になります。
戻り値の文字列は、Amazon QがそのままSlackのスレッドに投稿します。
import { CloudWatchClient, DescribeAlarmsCommand } from "@aws-sdk/client-cloudwatch";
import { DevOpsAgentClient, CreateBacklogTaskCommand } from "@aws-sdk/client-devops-agent";
const cloudwatch = new CloudWatchClient({});
const devopsAgent = new DevOpsAgentClient({});
const agentSpaceId = process.env.AGENT_SPACE_ID;
const allowedAlarms = (process.env.ALLOWED_ALARMS ?? "").split(",").filter(Boolean);
export const handler = async (event) => {
const alarmName = event?.alarmName;
// 許可リスト: 対象のアラーム以外は調査を作らない
if (!allowedAlarms.includes(alarmName)) {
return `\`${alarmName}\` は調査の対象ではありません。`;
}
// 今 ALARM のものだけ調査する(OK に戻った通知から押しても始めない)
const { MetricAlarms = [] } = await cloudwatch.send(new DescribeAlarmsCommand({ AlarmNames: [alarmName] }));
const alarm = MetricAlarms[0];
if (alarm?.StateValue !== "ALARM") {
return `\`${alarmName}\` は今 ${alarm?.StateValue} なので調査を始めません。`;
}
// 同じアラームで同じ 1 時間の間に何度押しても、調査は 1 件にする(clientToken は冪等キー)
const hour = new Date().toISOString().slice(0, 13).replace(/[-T:]/g, "");
const clientToken = `${alarmName}-${hour}`.slice(0, 64);
const { task } = await devopsAgent.send(
new CreateBacklogTaskCommand({
agentSpaceId,
clientToken,
taskType: "INVESTIGATION",
priority: "MEDIUM",
title: `Slack から調査依頼: ${alarmName}`,
description: `Slack のアラーム通知のボタンから調査の依頼がありました。対象は CloudWatch アラーム ${alarmName} です。アラームの理由: ${alarm.StateReason}。...`,
}),
);
return `調査を作りました: ${task.taskId}(状態 ${task.status})`;
};
Lambdaのロールには、aidevops:CreateBacklogTask(このAgent Spaceだけ)とcloudwatch:DescribeAlarmsを付けます。
const investigateFn = new NodejsFunction(this, 'InvestigateFunction', {
entry: 'lambda/investigate/index.mjs',
runtime: lambda.Runtime.NODEJS_22_X,
environment: {
AGENT_SPACE_ID: agentSpace.attrAgentSpaceId,
ALLOWED_ALARMS: cpuAlarm.alarmName, // カンマ区切りで複数指定できる
},
});
investigateFn.addToRolePolicy(
new iam.PolicyStatement({
actions: ['aidevops:CreateBacklogTask'],
resources: [`arn:aws:aidevops:${this.region}:${this.account}:agentspace/${agentSpace.attrAgentSpaceId}`],
}),
);
investigateFn.addToRolePolicy(new iam.PolicyStatement({ actions: ['cloudwatch:DescribeAlarms'], resources: ['*'] }));
チャンネルロールとガードレール
チャンネルロールに付けた権限は、通知の表示に使うCloudWatchの読み取りと、このLambda 1つのlambda:InvokeFunctionだけです。
ガードレールにも同じ内容のポリシーを指定しています。
const channelPermissions = [
new iam.PolicyStatement({
actions: ['lambda:InvokeFunction'],
resources: [investigateFn.functionArn], // この Lambda だけ呼べる
}),
new iam.PolicyStatement({
actions: ['cloudwatch:Describe*', 'cloudwatch:Get*', 'cloudwatch:List*'],
resources: ['*'],
}),
];
const channelRole = new iam.Role(this, 'ChatbotChannelRole', {
assumedBy: new iam.ServicePrincipal('chatbot.amazonaws.com', {
conditions: { StringEquals: { 'aws:SourceAccount': this.account } },
}),
inlinePolicies: { ButtonOnly: new iam.PolicyDocument({ statements: channelPermissions }) },
});
// ガードレール: チャンネルから使える権限の上限。既定のままだと AdministratorAccess になる
const guardrail = new iam.ManagedPolicy(this, 'ChatbotGuardrail', { statements: channelPermissions });
カスタムアクションとチャンネル設定
カスタムアクションはAWS::Chatbot::CustomActionで作ります。
Slackの画面からも作れて、その場合は「Lambda action」という種類を選び、関数名とpayloadを入力するだけでボタンができます。
ただ、Slackの画面から作ったアクションはlist-custom-actionsに出てこず、CloudFormationのAWS::Chatbot::CustomActionにも「Lambda action」という種類はありません。
CloudFormationの定義にあるのはCLIのコマンド文字列(CommandText)だけです。
CDKで管理したいため、CLIアクションとしてlambda invokeのコマンドを書いています。
$AlarmNameは、通知の値に置き換わる変数です。
criteriaで、ALARMになったときの通知にだけボタンを出すようにしています。
const investigateAction = new chatbot.CfnCustomAction(this, 'InvestigateLambdaCustomAction', {
actionName: 'devops-agent-cpu-demo-request-investigation',
definition: {
// 出力先のファイル名(位置引数)を付けると Amazon Q が解釈できないため付けない
commandText: `lambda invoke --function-name ${investigateFn.functionName} --payload '{"alarmName":"$AlarmName"}' --region ${this.region}`,
},
attachments: [
{
notificationType: 'CloudWatch',
buttonText: 'DevOps Agent に調査依頼',
// 通知の中身から JMESPath で値を取り出す
variables: { AlarmName: 'event.AlarmName', State: 'event.NewStateValue' },
// ALARM の通知にだけ表示する
criteria: [
{ variableName: 'AlarmName', operator: 'HAS_VALUE' },
{ variableName: 'State', operator: 'EQUALS', value: 'ALARM' },
],
},
],
});
new chatbot.CfnSlackChannelConfiguration(this, 'SlackChannelConfiguration', {
configurationName: 'devops-agent-cpu-demo-alarm',
slackWorkspaceId,
slackChannelId,
snsTopicArns: [alarmTopic.topicArn], // アラームの通知先の SNS トピック
iamRoleArn: channelRole.roleArn,
guardrailPolicies: [guardrail.managedPolicyArn],
customizationResourceArns: [investigateAction.attrCustomActionArn],
});
押してみた
アラームをALARMにすると、Slackの通知に「DevOps Agent に調査依頼」のボタンが付きました。

ボタンを押すと、すぐには実行されず、確認のダイアログが出ます。
実行するコマンドの全文と、実行先のアカウントが表示されます。

Runを押すと、スレッドに実行結果が投稿されました。
Lambdaの戻り値がそのままPayloadとして表示され、DevOps Agentに調査が作られています。
続けてもう一度押しても、同じタスクIDが返り、調査は増えませんでした。

アラームがOKに戻った通知にはボタンが出ていません。
criteriaで指定した表示の条件は、そのとおりに働いています。
誰が押せるのか
チャンネル設定には、チャンネルロールを使う方法と、メンバーごとにユーザーロールを選ばせる方法があります。
公式ドキュメントでは、チャンネルロールは次のように説明されています。
A channel role gives all channel members the same permissions.
(チャンネルロールは、チャンネルの全メンバーに同じ権限を与えます)
Understanding Amazon Q Developer in chat applications permissions
今回の構成はチャンネルロールのため、チャンネルにいる人なら誰でもボタンを押せます。
AWSのアカウントを持っているかどうかは関係ありません。
「誰が押せるか」は、Slackのチャンネルに誰を入れるかで決まります。
表示条件は、起動の制限にならない
ここからが、本記事で確かめたかったところです。
ボタンに「ALARMの通知にだけ表示する」という条件を付けたため、対象外のアラームや、OKに戻った通知からは調査を起動できないはずだと考えると思います。
私もそう思っていました。
実際には、この条件は表示の条件にすぎません。
確認のダイアログには「Change variables」のボタンがあり、押した人がその場でアラーム名を書き換えられます。
書き換えると、実行するコマンドの中身もその名前に変わります。

さらに、チャンネルのメンバーは@Amazon Q lambda invoke ...とコマンドを手で打つこともできます。
どちらの場合も、Amazon Qはチャンネルロールの権限でLambdaを呼びます。
IAMの権限は「この関数を呼べるか」しか見ていないため、ここでは止まりません。
止めてくれるのはLambdaです。
許可リストにない名前で呼ぶと、調査を作らずに拒否の文を返します。

同じように、アラームがOKに戻ったあとに押しても「今 OK なので調査を始めません」が返ります。
ボタンの表示条件や変数の初期値は、起動を制限する仕組みではありません。
どのアラームの調査を起動してよいかは、ボタンの外側(今回はLambda)で決める必要があります。
気をつけること
検証して分かった、設計で気をつける点です。
ガードレールを既定のままにしない
チャンネル設定のガードレールは、既定ではAdministratorAccessです。
ガードレールはチャンネルロールとユーザーロールの両方に優先する上限であるため、ここが広いままだと、将来チャンネルロールに権限を足したときに、そのままチャンネルの全員が使えるようになります。
チャンネルロールと同じ最小のポリシーを指定しましょう。
押した人の記録は、監査ログに残らない
Amazon Qは、実行したコマンドの監査ログをCloudWatch Logs(バージニア北部の/aws/chatbot/<設定名>)に残します。
手打ちしたコマンドは、preparedとapprovedの2行が、SlackのユーザーIDとコマンド全文付きで残っていました。
一方、ボタンを押した時刻には、このログにcommandの行がありませんでした。
カスタムアクションの実行は、この検証では監査ログに残りませんでした。
CreateBacklogTaskは管理イベントのためCloudTrailに残りますが、主体はLambdaのロールです。
Amazon QがLambdaに渡すeventは{"alarmName": "..."}だけで、押した人の情報は入っていませんでした。
誰が押したかを残したい場合は、Amazon Qの監査ログの保持期間を確かめたうえで、Slackのスレッドの投稿(I ran the commandの投稿者)を手がかりにすることになります。
1日の調査時間の上限は別に要る
clientTokenでまとめられるのは、同じアラームを同じ1時間の間に押した分だけです。
時間を置いて押せば新しい調査が始まり、調査は実行時間に応じて課金されるため、押した回数に応じて費用はかかります。
1日の調査時間の合計に上限を付けたい場合は、その判定もLambdaに足す等の対応が必要です。
まとめ
Slackの通知のボタンから調査を起動する仕組みは、カスタムアクションと小さなLambdaで作れました。
一方で、ボタン自体には何かを制限する力がありません。
ボタンを押したときに使われるのはチャンネルロールの権限で、チャンネルにいる人なら誰でも押せます。
そのため、チャンネルロールに付ける権限は、ボタンに必要な最小限にとどめる必要があります。
同じように、ボタンを「ALARMの通知にだけ表示する」ようにしても、調査を起動できる範囲は絞れません。
表示条件が決めるのはボタンを出すかどうかだけで、確認のダイアログからアラーム名を書き換えることも、同じコマンドを手で打つこともできます。
つまり各責任は、誰が押せるかはSlackのチャンネル側にあり、何の権限で動くかはチャンネルロールで、どのアラームを調査してよいかはLambdaで決まります。
今回はLambdaに許可リストと状態の確認を置いたため、書き換えても手で打っても、対象外なら調査は始まりません。
同じようにSlackからDevOps Agentを起動したい方の参考になれば幸いです。
参考
- Custom actions using Amazon Q Developer in chat applications
- Understanding Amazon Q Developer in chat applications permissions
- Accessing Amazon CloudWatch Logs for Amazon Q Developer in chat applications
- AWS::Chatbot::CustomAction - AWS CloudFormation
- CreateBacklogTask - AWS DevOps Agent API Reference






