
Lambda + IAMロールで Kiro CLI の use_aws ツールを安全に動かしてみた
Kiro CLI 2.0でヘッドレスモードが追加されました。KIRO_API_KEY環境変数と--no-interactive・--trust-all-toolsフラグで対話なし実行できます。
Kiro CLIはuse_awsという専用ツールを備えており、内部でawsコマンドを実行してAWS APIを操作します。Lambda上で動かせばイベント駆動でAWS操作を自動化でき、操作範囲はIAMロールで制御できるのがポイントです。
公式CI/CD例(GitHub Actions)では毎回curl | bashでインストールしますが、Lambdaでは毎回ダウンロードが走りコールドスタート約40秒。これをDocker化で短縮できるか検証しました。
Lambdaへのデプロイ方式として3パターンを検討しました。
| 方式 | コールドスタート | ウォーム | ECR必要 | デプロイ |
|---|---|---|---|---|
| /tmp都度インストール | 41秒 | 3.5秒 | ❌ | zip |
| Docker (ECR) | 22秒 | 3.2秒 | ✅ | イメージ |
| Lambda Layer | 不可(452MB > 上限250MB) | - | - | - |
※ 測定条件: メモリ512MB / ARM64 / us-east-1 / kiro-cli 2.3.0 / プロンプト「What is 2+2?」
検証内容
Dockerfile
ベースイメージはpublic.ecr.aws/lambda/python:3.12です。ポイントは以下の通り。
- Kiro CLIをビルド時にインストールし、/usr/local/bin へコピー。Lambda実行環境は非rootなので /root/.local/bin には直接アクセスできません
- ENV PATHはビルド時のインストールスクリプト用。Lambda実行時はhandler.pyからフルパス指定で呼び出すため、実行時のPATH依存はありません
- KIRO_CLI_SKIP_SETUP=1でインストールスクリプトの初期セットアップ(対話的な設定)をスキップ
- AWS CLI v2もインストール。use_awsツールが内部でawsコマンドをspawnするため必須です
Dockerfile全文
FROM public.ecr.aws/lambda/python:3.12
ENV PATH="/root/.local/bin:${PATH}"
RUN dnf install -y tar gzip unzip && \
curl -fsSL https://cli.kiro.dev/install | KIRO_CLI_SKIP_SETUP=1 bash && \
cp /root/.local/bin/kiro-cli* /usr/local/bin/ && \
curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o /tmp/awscliv2.zip && \
unzip -q /tmp/awscliv2.zip -d /tmp && /tmp/aws/install && rm -rf /tmp/aws /tmp/awscliv2.zip && \
dnf clean all
RUN pip install boto3
COPY handler.py ${LAMBDA_TASK_ROOT}/
CMD ["handler.handler"]
handler.py
Lambda関数のエントリポイントです。
- SSM Parameter Store(SecureString)からKiro APIキーを取得し、ウォーム時はキャッシュを利用
HOME=/tmpを設定。kiro-cliは初回起動時にSQLiteDBを作成するため書き込み可能なパスが必要(/var/taskは読み取り専用)
--trust-all-toolsは全ツール実行を許可するフラグですが、Lambdaではuse_awsが実行するAWS APIコールがIAMロールの権限に縛られます。今回はec2:DescribeRegionsとec2:DescribeAvailabilityZonesのみ許可しており、仮にエージェントがEC2インスタンスを起動しようとしてもPermission Deniedになります。用途別にLambdaを分ければ、ロール単位で最小権限を実現できます。
APIキーの発行手順は前回の記事を参照してください。
handler.py全文
import subprocess
import json
import os
import time
import boto3
ssm = boto3.client("ssm", region_name=os.environ.get("AWS_REGION", "us-east-1"))
_api_key_cache = None
def get_api_key():
global _api_key_cache
if not _api_key_cache:
_api_key_cache = os.environ.get("KIRO_API_KEY")
if not _api_key_cache:
resp = ssm.get_parameter(Name="/kiro/headless/api-key", WithDecryption=True)
_api_key_cache = resp["Parameter"]["Value"]
return _api_key_cache
def handler(event, context):
timings = {}
t0 = time.time()
prompt = event.get("prompt", "")
if not prompt or len(prompt.strip()) < 10:
return {"statusCode": 400, "error": "Prompt too short (min 10 chars)"}
api_key = get_api_key()
env = {**os.environ, "KIRO_API_KEY": api_key, "HOME": "/tmp"}
kiro_bin = "/usr/local/bin/kiro-cli"
cmd = [kiro_bin, "chat", "--no-interactive", "--trust-all-tools"]
# model = event.get("model") # e.g. "claude-sonnet-4.6"
# if model:
# cmd += ["--model", model]
cmd.append(prompt)
start = time.time()
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=280,
env=env,
)
timings["kiro_execution"] = round(time.time() - start, 2)
timings["total"] = round(time.time() - t0, 2)
if result.returncode != 0:
return {
"statusCode": 500,
"error": result.stderr.strip() or f"kiro-cli exited with code {result.returncode}",
"timings": timings,
}
return {
"statusCode": 200,
"prompt": prompt,
"result": result.stdout.strip(),
"timings": timings,
}
/tmp方式(参考)
Docker方式との比較対象として、/tmp に都度インストールする方式も検証しました。Python標準ライブラリのみ(urllib + zipfile)でKiro CLIとAWS CLIをダウンロード・展開します。ECR不要でPoC向きですが、コールドスタートが41秒かかり、ネットワーク依存もあるため本番運用にはDocker方式を推奨します。
handler_tmp.py全文
import subprocess
import json
import os
import time
import zipfile
import urllib.request
import boto3
ssm = boto3.client("ssm", region_name=os.environ.get("AWS_REGION", "us-east-1"))
_api_key_cache = None
KIRO_BIN = "/tmp/.local/bin/kiro-cli"
AWS_CLI_BIN = "/tmp/aws-bin/aws"
def get_api_key():
global _api_key_cache
if not _api_key_cache:
_api_key_cache = os.environ.get("KIRO_API_KEY")
if not _api_key_cache:
resp = ssm.get_parameter(Name="/kiro/headless/api-key", WithDecryption=True)
_api_key_cache = resp["Parameter"]["Value"]
return _api_key_cache
def download_and_extract(url, dest_dir):
"""Download zip and extract to dest_dir."""
zip_path = f"/tmp/{os.path.basename(dest_dir)}.zip"
urllib.request.urlretrieve(url, zip_path)
with zipfile.ZipFile(zip_path, "r") as zf:
zf.extractall(dest_dir)
os.remove(zip_path)
def install_kiro():
if os.path.exists(KIRO_BIN):
return 0
start = time.time()
arch = "aarch64" if os.uname().machine == "aarch64" else "x86_64"
url = f"https://desktop-release.q.us-east-1.amazonaws.com/latest/kirocli-{arch}-linux.zip"
download_and_extract(url, "/tmp/kirocli-pkg")
os.makedirs("/tmp/.local/bin", exist_ok=True)
for name in ["kiro-cli", "kiro-cli-chat", "kiro-cli-term"]:
src = f"/tmp/kirocli-pkg/kirocli/bin/{name}"
dst = f"/tmp/.local/bin/{name}"
if os.path.exists(src):
subprocess.run(["cp", src, dst], check=True)
os.chmod(dst, 0o755)
subprocess.run(["rm", "-rf", "/tmp/kirocli-pkg"], capture_output=True)
return round(time.time() - start, 2)
def install_aws_cli():
if os.path.exists(AWS_CLI_BIN):
return 0
start = time.time()
arch = "aarch64" if os.uname().machine == "aarch64" else "x86_64"
url = f"https://awscli.amazonaws.com/awscli-exe-linux-{arch}.zip"
download_and_extract(url, "/tmp/aws-install")
installer = "/tmp/aws-install/aws/install"
os.chmod(installer, 0o755)
subprocess.run(
[installer, "--install-dir", "/tmp/aws-cli", "--bin-dir", "/tmp/aws-bin"],
check=True, capture_output=True, text=True,
)
subprocess.run(["rm", "-rf", "/tmp/aws-install"], capture_output=True)
return round(time.time() - start, 2)
def handler(event, context):
timings = {}
t0 = time.time()
try:
timings["kiro_install"] = install_kiro()
except Exception as e:
return {"statusCode": 500, "error": f"kiro install failed: {e}", "timings": timings}
try:
timings["aws_cli_install"] = install_aws_cli()
except Exception as e:
return {"statusCode": 500, "error": f"aws cli install failed: {e}", "timings": timings}
timings["total_install"] = round(time.time() - t0, 2)
prompt = event.get("prompt", "")
if not prompt or len(prompt.strip()) < 10:
return {"statusCode": 400, "error": "Prompt too short (min 10 chars)", "timings": timings}
api_key = get_api_key()
env = {
**os.environ,
"KIRO_API_KEY": api_key,
"HOME": "/tmp",
"PATH": f"/tmp/.local/bin:/tmp/aws-bin:{os.environ.get('PATH', '')}",
}
start = time.time()
result = subprocess.run(
[KIRO_BIN, "chat", "--no-interactive", "--trust-all-tools", prompt],
capture_output=True, text=True, timeout=280, env=env,
)
timings["kiro_execution"] = round(time.time() - start, 2)
timings["total"] = round(time.time() - t0, 2)
if result.returncode != 0:
return {
"statusCode": 500,
"error": result.stderr.strip() or f"exit code {result.returncode}",
"timings": timings,
}
return {
"statusCode": 200,
"prompt": prompt,
"result": result.stdout.strip(),
"timings": timings,
}
CloudFormation + CodeBuild
S3やGitHub連携なしで、テンプレート2枚だけで環境構築を完結させる設計です。
| スタック | 内容 |
|---|---|
| Stack1 | ECRリポジトリ + CodeBuildプロジェクト |
| Stack2 | Lambda関数(Stack1でイメージpush後にデプロイ) |
CodeBuildはNO_SOURCEタイプで、buildspec内にDockerfileとhandler.pyをbase64埋め込みしています。ARM64ビルド環境(aws/codebuild/amazonlinux-aarch64-standard:3.0)を使用します。
stack1-ecr-codebuild.yaml全文
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Kiro Headless Lambda - Stack 1: ECR + CodeBuild'
Resources:
EcrRepository:
Type: AWS::ECR::Repository
Properties:
RepositoryName: kiro-headless-lambda
ImageScanningConfiguration:
ScanOnPush: true
LifecyclePolicy:
LifecyclePolicyText: |
{"rules":[{"rulePriority":1,"selection":{"tagStatus":"untagged","countType":"imageCountMoreThan","countNumber":3},"action":{"type":"expire"}}]}
CodeBuildRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: codebuild.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: CodeBuildPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: '*'
- Effect: Allow
Action: ecr:GetAuthorizationToken
Resource: '*'
- Effect: Allow
Action:
- ecr:BatchCheckLayerAvailability
- ecr:GetDownloadUrlForLayer
- ecr:BatchGetImage
- ecr:PutImage
- ecr:InitiateLayerUpload
- ecr:UploadLayerPart
- ecr:CompleteLayerUpload
Resource: !GetAtt EcrRepository.Arn
CodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
Name: kiro-headless-lambda-build
ServiceRole: !GetAtt CodeBuildRole.Arn
Artifacts:
Type: NO_ARTIFACTS
Environment:
Type: ARM_CONTAINER
Image: aws/codebuild/amazonlinux-aarch64-standard:3.0
ComputeType: BUILD_GENERAL1_SMALL
PrivilegedMode: true
EnvironmentVariables:
- Name: ECR_REPO_URI
Value: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${EcrRepository}'
Source:
Type: NO_SOURCE
BuildSpec: !Sub |
version: 0.2
phases:
pre_build:
commands:
- aws ecr get-login-password | docker login --username AWS --password-stdin "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com"
build:
commands:
- echo "RlJPTSBwdWJsaWMuZWNyLmF3cy9sYW1iZGEvcHl0aG9uOjMuMTIKCkVOViBQQVRIPSIvcm9vdC8ubG9jYWwvYmluOiR7UEFUSH0iCgpSVU4gZG5mIGluc3RhbGwgLXkgdGFyIGd6aXAgdW56aXAgJiYgXAogICAgY3VybCAtZnNTTCBodHRwczovL2NsaS5raXJvLmRldi9pbnN0YWxsIHwgS0lST19DTElfU0tJUF9TRVRVUD0xIGJhc2ggJiYgXAogICAgY3AgL3Jvb3QvLmxvY2FsL2Jpbi9raXJvLWNsaSogL3Vzci9sb2NhbC9iaW4vICYmIFwKICAgIGN1cmwgImh0dHBzOi8vYXdzY2xpLmFtYXpvbmF3cy5jb20vYXdzY2xpLWV4ZS1saW51eC1hYXJjaDY0LnppcCIgLW8gL3RtcC9hd3NjbGl2Mi56aXAgJiYgXAogICAgdW56aXAgLXEgL3RtcC9hd3NjbGl2Mi56aXAgLWQgL3RtcCAmJiAvdG1wL2F3cy9pbnN0YWxsICYmIHJtIC1yZiAvdG1wL2F3cyAvdG1wL2F3c2NsaXYyLnppcCAmJiBcCiAgICBkbmYgY2xlYW4gYWxsCgpSVU4gcGlwIGluc3RhbGwgYm90bzMKCkNPUFkgaGFuZGxlci5weSAke0xBTUJEQV9UQVNLX1JPT1R9LwoKQ01EIFsiaGFuZGxlci5oYW5kbGVyIl0K" | base64 -d > Dockerfile
- echo "aW1wb3J0IHN1YnByb2Nlc3MKaW1wb3J0IGpzb24KaW1wb3J0IG9zCmltcG9ydCB0aW1lCgppbXBvcnQgYm90bzMKCnNzbSA9IGJvdG8zLmNsaWVudCgic3NtIiwgcmVnaW9uX25hbWU9b3MuZW52aXJvbi5nZXQoIkFXU19SRUdJT04iLCAidXMtZWFzdC0xIikpCl9hcGlfa2V5X2NhY2hlID0gTm9uZQoKCmRlZiBnZXRfYXBpX2tleSgpOgogICAgZ2xvYmFsIF9hcGlfa2V5X2NhY2hlCiAgICBpZiBub3QgX2FwaV9rZXlfY2FjaGU6CiAgICAgICAgX2FwaV9rZXlfY2FjaGUgPSBvcy5lbnZpcm9uLmdldCgiS0lST19BUElfS0VZIikKICAgICAgICBpZiBub3QgX2FwaV9rZXlfY2FjaGU6CiAgICAgICAgICAgIHJlc3AgPSBzc20uZ2V0X3BhcmFtZXRlcihOYW1lPSIva2lyby9oZWFkbGVzcy9hcGkta2V5IiwgV2l0aERlY3J5cHRpb249VHJ1ZSkKICAgICAgICAgICAgX2FwaV9rZXlfY2FjaGUgPSByZXNwWyJQYXJhbWV0ZXIiXVsiVmFsdWUiXQogICAgcmV0dXJuIF9hcGlfa2V5X2NhY2hlCgoKZGVmIGhhbmRsZXIoZXZlbnQsIGNvbnRleHQpOgogICAgdGltaW5ncyA9IHt9CiAgICB0MCA9IHRpbWUudGltZSgpCgogICAgcHJvbXB0ID0gZXZlbnQuZ2V0KCJwcm9tcHQiLCAiIikKICAgIGlmIG5vdCBwcm9tcHQgb3IgbGVuKHByb21wdC5zdHJpcCgpKSA8IDEwOgogICAgICAgIHJldHVybiB7InN0YXR1c0NvZGUiOiA0MDAsICJlcnJvciI6ICJQcm9tcHQgdG9vIHNob3J0IChtaW4gMTAgY2hhcnMpIn0KCiAgICBhcGlfa2V5ID0gZ2V0X2FwaV9rZXkoKQogICAgZW52ID0geyoqb3MuZW52aXJvbiwgIktJUk9fQVBJX0tFWSI6IGFwaV9rZXksICJIT01FIjogIi90bXAifQogICAga2lyb19iaW4gPSAiL3Vzci9sb2NhbC9iaW4va2lyby1jbGkiCgogICAgc3RhcnQgPSB0aW1lLnRpbWUoKQogICAgcmVzdWx0ID0gc3VicHJvY2Vzcy5ydW4oCiAgICAgICAgW2tpcm9fYmluLCAiY2hhdCIsICItLW5vLWludGVyYWN0aXZlIiwgIi0tdHJ1c3QtYWxsLXRvb2xzIiwgcHJvbXB0XSwKICAgICAgICBjYXB0dXJlX291dHB1dD1UcnVlLAogICAgICAgIHRleHQ9VHJ1ZSwKICAgICAgICB0aW1lb3V0PTI4MCwKICAgICAgICBlbnY9ZW52LAogICAgKQogICAgdGltaW5nc1sia2lyb19leGVjdXRpb24iXSA9IHJvdW5kKHRpbWUudGltZSgpIC0gc3RhcnQsIDIpCiAgICB0aW1pbmdzWyJ0b3RhbCJdID0gcm91bmQodGltZS50aW1lKCkgLSB0MCwgMikKCiAgICBpZiByZXN1bHQucmV0dXJuY29kZSAhPSAwOgogICAgICAgIHJldHVybiB7CiAgICAgICAgICAgICJzdGF0dXNDb2RlIjogNTAwLAogICAgICAgICAgICAiZXJyb3IiOiByZXN1bHQuc3RkZXJyLnN0cmlwKCkgb3IgZiJraXJvLWNsaSBleGl0ZWQgd2l0aCBjb2RlIHtyZXN1bHQucmV0dXJuY29kZX0iLAogICAgICAgICAgICAidGltaW5ncyI6IHRpbWluZ3MsCiAgICAgICAgfQoKICAgIHJldHVybiB7CiAgICAgICAgInN0YXR1c0NvZGUiOiAyMDAsCiAgICAgICAgInByb21wdCI6IHByb21wdCwKICAgICAgICAicmVzdWx0IjogcmVzdWx0LnN0ZG91dC5zdHJpcCgpLAogICAgICAgICJ0aW1pbmdzIjogdGltaW5ncywKICAgIH0K" | base64 -d > handler.py
- docker build --platform linux/arm64 -t "$ECR_REPO_URI:latest" .
post_build:
commands:
- docker push "$ECR_REPO_URI:latest"
Outputs:
EcrRepositoryUri:
Value: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${EcrRepository}'
Export:
Name: KiroHeadlessEcrUri
CodeBuildProjectName:
Value: !Ref CodeBuildProject
stack2-lambda.yaml全文
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Kiro Headless Lambda - Stack 2: Lambda Function'
Parameters:
ImageUri:
Type: String
Description: ECR Image URI (from Stack 1 CodeBuild output)
SsmParameterName:
Type: String
Default: /kiro/headless/api-key
Description: handler.py内のパラメータ名と一致させること
Resources:
LambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: KiroHeadlessPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: KiroApiKey
Effect: Allow
Action: ssm:GetParameter
Resource: !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter${SsmParameterName}'
- Sid: KmsDecrypt
Effect: Allow
Action: kms:Decrypt
Resource: '*'
- Sid: SafeReadOnly
Effect: Allow
Action:
- ec2:DescribeRegions
- ec2:DescribeAvailabilityZones
Resource: '*'
KiroLambda:
Type: AWS::Lambda::Function
Properties:
FunctionName: kiro-headless
PackageType: Image
Code:
ImageUri: !Ref ImageUri
Architectures:
- arm64
Role: !GetAtt LambdaRole.Arn
Timeout: 360
MemorySize: 512
Outputs:
LambdaFunction:
Value: !Ref KiroLambda
LambdaArn:
Value: !GetAtt KiroLambda.Arn
デプロイ手順
APIキー未取得の方は前回の記事でKiro APIキーを発行してください。
# 1. SSMにAPIキー保存
aws ssm put-parameter \
--name "/kiro/headless/api-key" \
--type SecureString \
--value "YOUR_KIRO_API_KEY" \
--overwrite \
--region us-east-1
# 2. スタック1デプロイ (ECR + CodeBuild)
aws cloudformation deploy \
--template-file stack1-ecr-codebuild.yaml \
--stack-name KiroHeadlessStack1 \
--capabilities CAPABILITY_IAM \
--region us-east-1
# 3. CodeBuildでイメージビルド(約2分)
aws codebuild start-build \
--project-name kiro-headless-lambda-build \
--region us-east-1
# ビルド完了を待機(SUCCEEDEDになるまでループ)
BUILD_ID=$(aws codebuild list-builds-for-project \
--project-name kiro-headless-lambda-build \
--region us-east-1 \
--query 'ids[0]' --output text)
while true; do
STATUS=$(aws codebuild batch-get-builds \
--ids "$BUILD_ID" \
--region us-east-1 \
--query 'builds[0].buildStatus' --output text)
echo "Build status: $STATUS"
[ "$STATUS" = "SUCCEEDED" ] && break
[ "$STATUS" = "FAILED" ] && echo "Build failed!" && exit 1
sleep 15
done
# 4. スタック2デプロイ (Lambda)
ECR_URI=$(aws cloudformation describe-stacks \
--stack-name KiroHeadlessStack1 \
--region us-east-1 \
--query 'Stacks[0].Outputs[?OutputKey==`EcrRepositoryUri`].OutputValue' --output text)
aws cloudformation deploy \
--template-file stack2-lambda.yaml \
--stack-name KiroHeadlessStack2 \
--capabilities CAPABILITY_IAM \
--parameter-overrides "ImageUri=${ECR_URI}:latest" \
--region us-east-1
動作確認
許可された操作: リージョン一覧取得
aws lambda invoke \
--function-name kiro-headless \
--region us-east-1 \
--payload '{"prompt": "アジアパシフィックのAWSリージョンを一覧して"}' \
--cli-read-timeout 360 \
result.json && cat result.json | sed 's/\x1b\[[0-9;]*m//g'
出力にはANSIカラーコードが含まれるため、sedで除去しています。
実行結果の例です。use_awsツールがec2:DescribeRegionsを呼び出し、日本語で応答しています。
{
"statusCode": 200,
"prompt": "アジアパシフィックのAWSリージョンを一覧して",
"result": "アジアパシフィックのAWSリージョン一覧:\n\n| リージョン名 | リージョンコード |\n|---|---|\n| アジアパシフィック (東京) | ap-northeast-1 |\n| アジアパシフィック (大阪) | ap-northeast-3 |\n| アジアパシフィック (ソウル) | ap-northeast-2 |\n| アジアパシフィック (ムンバイ) | ap-south-1 |\n| アジアパシフィック (ハイデラバード) | ap-south-2 |\n| アジアパシフィック (シンガポール) | ap-southeast-1 |\n| アジアパシフィック (シドニー) | ap-southeast-2 |\n| アジアパシフィック (ジャカルタ) | ap-southeast-3 |\n| アジアパシフィック (メルボルン) | ap-southeast-4 |\n| アジアパシフィック (香港) | ap-east-1 |\n| アジアパシフィック (マレーシア) | ap-southeast-5 |\n| アジアパシフィック (タイ) | ap-southeast-7 |",
"timings": {
"kiro_execution": 8.94,
"total": 9.05
}
}
コールドスタート時はLambda側のINIT Duration(約22秒)が加算されます。CloudWatch Logsでは以下のように記録されます。
REPORT Duration: 18920.00 ms Billed Duration: 19521 ms Memory Size: 512 MB Max Memory Used: 358 MB Init Duration: 22045.12 ms
ウォーム状態ではtimings.totalの値がそのままレスポンス時間です。
拒否される操作: EC2インスタンス一覧取得
IAMロールの効果を確認するため、許可していない操作を試みます。
aws lambda invoke \
--function-name kiro-headless \
--region us-east-1 \
--payload '{"prompt": "us-east-1のEC2インスタンス一覧を取得して表示して"}' \
--cli-read-timeout 360 \
result.json && cat result.json
{
"statusCode": 200,
"prompt": "us-east-1のEC2インスタンス一覧を取得して表示して",
"result": "現在の実行環境(IAMロール KiroHeadlessStack2-LambdaRole)には ec2:DescribeInstances の権限が付与されていないため、EC2インスタンス一覧を取得できませんでした。\n\n対処方法:\n- このIAMロールに ec2:DescribeInstances 権限を追加する\n- または、適切な権限を持つAWSプロファイルを指定して再実行する",
"timings": {
"kiro_execution": 28.77,
"total": 28.77
}
}
ec2:DescribeInstancesは許可していないため、use_awsツールが実行してもIAMロールがブロックします。エージェントは権限不足を検知して対処方法まで案内してくれます。--trust-all-toolsで全ツールの実行自体は許可しつつ、実際のAWS操作はIAMポリシーで最小権限に制御できています。
なお、EC2インスタンス起動のような高リスク操作はIAM以前にエージェント自身のセーフティガードレールが確認を求めます。IAMロール + エージェントのガードレールの多重防御が効いている形です。
まとめ
--trust-all-toolsを使いつつ、IAMロールでec2:Describe*のみに絞ることで、use_awsツールを安全にLambdaで実行できました。Docker化によりコールドスタートも/tmp方式の41秒から22秒に短縮。CloudFormation+CodeBuildでテンプレート化したので、AWSアカウントとKiro APIキーがあれば数分で再現可能です。
AWS MCPサーバー経由のAWS操作も選択肢ですが、Kiroのuse_awsツールはAWS CLIの全操作をカバーしており、Lambda+IAMロールとの組み合わせで権限制御もシンプルです。ぜひお試しください。








