EC2 + OpenClaw + Bedrock な環境で Kiro CLI をエージェントとして動かしてみた
はじめに
OpenClaw は多機能なセルフホスト型 AI エージェントプラットフォームで、CLI・Web UI・チャットチャネル連携に対応しています。
OpenClaw の評価環境として EC2 が使えるか検証するため、EC2 (Amazon Linux 2023) 上に OpenClaw を構築し、LLM に Bedrock の最新モデル Claude Sonnet 4.6 を指定、さらに Kiro CLI をワーカーエージェントとして登録して、CLI・Web の両方から動かしてみました。
この構成にした理由は3つあります。Bedrock は完全従量課金で API キー管理も不要、IAM ロールだけで認証が完結すること。EC2 1台で独立したサンドボックスが手に入り、不要になれば CloudFormation スタックごと削除するだけの使い捨て環境であること。そして Kiro CLI をエージェントとして利用し、Manager/Worker パターンが成立するかを検証したかったこと、です。
構成概要
本記事の検証環境は以下のバージョンで動作確認しています。
| コンポーネント | バージョン |
|---|---|
| Amazon Linux 2023 | 2023.10.20260216 |
| Node.js | v22.22.0 |
| OpenClaw | 2026.2.26 |
| Kiro CLI | 1.26.2 |
| AWS CLI | 2.33.15 |
EC2 (t4g.medium, AL2023 ARM64)
├── OpenClaw Gateway (systemd, localhost:18789)
│ ├── Bedrock API (Claude Sonnet 4.6)
│ └── kiro-agent skill → Kiro CLI 呼び出し
├── Kiro CLI (Builder ID認証, 定額)
│ └── AWS_PROFILE=kiro-readonly で ReadOnly操作
└── AWS CLI
OpenClaw がマネージャー、Kiro CLI がワーカーという Manager/Worker パターンです。
事前準備
Gateway トークンの作成
OpenClaw Gateway の認証に使うトークンを事前に生成しておきます。任意のランダム文字列で構いません。
openssl rand -hex 32
# 例: a1b2c3d4e5f6...(64文字の16進数文字列が出力されます)
このトークンは以下の3箇所で使われます。
- CloudFormation パラメータ (
GatewayToken): スタック作成時に指定。以下の2つに自動で反映されます- Gateway 起動時: systemd サービスの
--token引数としてサーバー側の認証に使用 - openclaw config set:
gateway.remote.tokenとしてクライアント側の認証に使用
- Gateway 起動時: systemd サービスの
- Web UI アクセス時:
http://localhost:18789/?token=<token>でブラウザからの認証に使用
Bedrock モデルの有効性チェック
利用するモデルが Bedrock で使えるか確認しておきます。Sonnet 4.6 が利用できない場合は、他のモデルを CloudFormation のパラメータとして指定してください。
# モデルの有効性チェック (maxTokens=1 で最小コスト)
aws bedrock-runtime converse \
--model-id "global.anthropic.claude-sonnet-4-6" \
--messages '[{"role":"user","content":[{"text":"hi"}]}]' \
--inference-config '{"maxTokens":1}' \
--region ap-northeast-1
CloudFormation テンプレート
CloudFormation テンプレート1枚で完結します。リソースは6個です。
| リソース | 用途 |
|---|---|
| EC2Role | SSM + Bedrock + AssumeRole→KiroReadOnly |
| KiroReadOnlyRole | ReadOnlyAccess (EC2Roleからのみ AssumeRole可) |
| InstanceProfile | EC2Role紐付け |
| SecurityGroup | SSH(22) を特定IPからのみ許可 |
| LaunchTemplate | t4g.medium, 20GB gp3, IMDSv2 |
| EC2Instance | UserData で全セットアップ |
IAM ロール分離
EC2 のデフォルトロールには SSM と Bedrock の権限のみ付与しています。EC2 操作などの AWS リソース読み取り権限は含まれていないため、OpenClaw が直接 AWS CLI を実行しても拒否されます。AWS リソースの読み取りが必要な場合は、AssumeRole で ReadOnlyAccess を持つ別ロールに切り替える設計です。ただし、これはプロンプトの指示(SKILL.md)に依存しており、IAM ポリシーによる厳密な強制ではありません。
openclaw-simple-ec2-role (EC2デフォルト):
✅ SSM, Bedrock → 許可
❌ EC2操作など → 拒否
kiro-readonly (AssumeRole経由):
✅ ReadOnlyAccess → 全AWS読み取り許可
本構成はあくまで個人検証用です。本番環境で採用する場合は、エージェント専用の IAM User 発行や実行環境の分離を検討することをおすすめします。
UserData の流れ
UserData では、ランタイム (Node.js 22) と OpenClaw・Kiro CLI のインストール、AWS プロファイル・OpenClaw 設定・スキル登録、systemd サービスの起動までを一括で行います。
以下、UserData の中で特に重要な設定ポイントを3つ解説します。
設定ポイント 1: openclaw.json の手動作成
OpenClaw には openclaw configure というセットアップウィザードがありますが、対話式のみで --non-interactive オプションがありません。UserData のような非対話環境では使えないため、JSON を手動作成し openclaw config set で個別に設定しています。
# CloudFormation UserData より抜粋
cat > /home/ec2-user/.openclaw/openclaw.json << 'OCCONF'
{ "gateway": { "mode": "local" } }
OCCONF
chown -R ec2-user:ec2-user /home/ec2-user/.openclaw
sudo -u ec2-user bash -c "export HOME=/home/ec2-user; openclaw config set agents.defaults.model.primary 'amazon-bedrock/${OpenClawModel}'"
sudo -u ec2-user bash -c "export HOME=/home/ec2-user; openclaw config set gateway.remote.token '${GatewayToken}'"
openclaw.json には最小限のキーだけ記述します。version や providers を含めると不正キーエラーになります。
設定ポイント 2: systemd の環境変数 (Bedrock 認証)
OpenClaw 内部の Bedrock 認証チェックは、AWS_PROFILE や AWS_ACCESS_KEY_ID などの環境変数のみを確認しています。EC2 Instance Profile (IMDS) は検出対象外のため、環境変数が未設定だと「No API key found for amazon-bedrock」エラーになります。
systemd サービスに AWS_PROFILE=default を設定することで、認証チェックを通過させます。実際の API 呼び出し時には AWS SDK が IMDS にフォールバックして認証情報を取得します。
# CloudFormation UserData より抜粋 (systemd サービス定義)
[Service]
User=ec2-user
Environment=HOME=/home/ec2-user
Environment=AWS_REGION=${AWS::Region}
Environment=AWS_PROFILE=default
Environment=PATH=/usr/local/bin:/usr/bin:/bin
ExecStart=/usr/bin/openclaw gateway --allow-unconfigured --bind loopback --port 18789 --token ${GatewayToken}
設定ポイント 3: Bedrock モデル ID は inference profile 形式
Bedrock のモデルを直接呼び出す場合、on-demand ID(anthropic.claude-sonnet-4-6)ではなく inference profile ID(global.anthropic.claude-sonnet-4-6)を使う必要があります。OpenClaw のモデル指定にもこの形式をそのまま渡します。
テンプレートの OpenClawModel パラメータで以下から選択できます。
| モデル | inference profile ID |
|---|---|
| Sonnet 4.6 | global.anthropic.claude-sonnet-4-6 |
| Haiku 4.5 | global.anthropic.claude-haiku-4-5-20251001-v1:0 |
| Haiku 3 | apac.anthropic.claude-3-haiku-20240307-v1:0 |
| Nova Pro | apac.amazon.nova-pro-v1:0 |
デプロイ
aws cloudformation create-stack \
--stack-name openclaw-native \
--template-body file://openclaw-simple-ec2.yaml \
--capabilities CAPABILITY_NAMED_IAM \
--parameters \
ParameterKey=GatewayToken,ParameterValue=<your-token> \
ParameterKey=OpenClawModel,ParameterValue=global.anthropic.claude-sonnet-4-6 \
--region ap-northeast-1
スタック作成完了後、Kiro CLI の Builder ID 認証を手動で行います。この手順は自動化できないため、インスタンス作成のたびに必要です。
ssh ec2-user@<public-ip>
kiro-cli login
ブラウザで表示される URL にアクセスし、Builder ID で認証を完了してください。
kiro-agent スキルの設計
OpenClaw の「スキル」機能で Kiro CLI をエージェントとして登録しています。UserData 内で ~/.openclaw/skills/kiro-agent/SKILL.md に以下を配置しています。
---
name: kiro-agent
description: "Delegate tasks to Kiro CLI (AWS Builder ID auth, flat-rate)."
metadata:
{ "openclaw": { "emoji": "🤖", "requires": { "anyBins": ["kiro-cli"] } } }
---
# Kiro CLI Agent
## Read-Only AWS Operations
bash pty:true command:"AWS_PROFILE=kiro-readonly kiro-cli chat --no-interactive --trust-all-tools 'your query'"
## Rules
1. Always use pty:true
2. For AWS operations, always prefix with AWS_PROFILE=kiro-readonly
3. Prefer Kiro for heavy/iterative tasks (flat-rate vs per-token)
ポイントは Rules セクションです。「AWS 操作時は AWS_PROFILE=kiro-readonly を使え」と明記することで、OpenClaw が Kiro に委譲する際に ReadOnly ロールが使われます。
動作確認
CLI から実行
OpenClaw 単体で Bedrock に直接問い合わせてみました。
$ openclaw agent --session-id test --message "What is 2+2?" --json
応答: "Four." — 2.5 秒で、Bedrock (Sonnet 4.6) 経由の動作を確認できました。
次に、kiro-agent スキル経由で Kiro CLI にファイル作成を委譲してみました。
$ openclaw agent --session-id test2 \
--message "Use kiro-agent to create /tmp/hello.txt containing Hello from Kiro" --json
16 秒で完了しました。OpenClaw が Bedrock で指示を解釈し、kiro-agent スキルを通じて Kiro CLI にファイル作成を委譲しました。
AWS ReadOnly 操作の委譲も試しました。
$ openclaw agent --session-id test3 \
--message "Use kiro-agent to list EC2 instances in ap-northeast-1" --json
EC2 インスタンスの一覧がテーブル形式で返ってきました。31 秒で完了でした。
IAM ロール分離の確認
OpenClaw に直接 AWS CLI を実行させると、EC2 ロールには EC2 操作権限がないため拒否されました。
UnauthorizedOperation: You are not authorized to perform this operation.
User: arn:aws:sts::xxxxxxxxxxxx:assumed-role/openclaw-simple-ec2-role/i-xxx
is not authorized to perform: ec2:DescribeInstances
kiro-agent 経由(AWS_PROFILE=kiro-readonly)でのみ AWS 操作が可能でした。意図通りの動作を確認できました。
Web UI から実行
今回は安全に接続する手段として SSH ポートフォワーディングを利用しました。ローカルの 18789 番ポートを EC2 に転送し、ブラウザからアクセスしました。
ssh -L 18789:localhost:18789 ec2-user@<public-ip>
トークン付き URL は、事前準備で生成した Gateway トークンを使って組み立てました。トークンなしで /chat にアクセスすると unauthorized: gateway token missing エラーになりました。
http://localhost:18789/chat?session=main&token=<GatewayToken の値>
OpenClaw にはリモート接続時の端末登録(デバイスペアリング)機能があります。今回は端末登録を避けるため、SSH ポートフォワーディング経由で localhost としてアクセスする構成にしました。
Web UI のセキュリティ構成
今回の構成では3層で保護されています。
- loopback バインド: Gateway は localhost のみリッスン。EC2 の外部から直接アクセス不可
- SSH 制限: SecurityGroup で特定 IP からのみ SSH を許可。トンネルを張れる人を限定
- Gateway トークン: トークンなしのリクエストは拒否
日本語でも指示できました。
kiro-agentを使って、ap-northeast-1のEC2インスタンス一覧を取得して

CLI と同じ結果が Web UI 上に表示されました。
まとめ
OpenClaw + Bedrock + Kiro CLI の Manager/Worker 構成を EC2 上に構築し、CLI・Web UI の両方から動作を確認しました。
今回の検証を通じて、Kiro CLI でもエージェントのワーカーとして十分に活用できることを確認できました。ただし CLI はワンショット実行のためセッション状態が残らないことや、Builder ID 認証がインスタンス作成のたびに手動で必要な点は制約として残ります。現在プレビュー版として提供されている Kiro autonomous agent は、コンテキストの永続化やサンドボックス環境でのビルド・テスト実行、GitHub 連携による PR 作成まで対応しており、同じ片方向の委譲でも活用範囲が大きく広がる可能性があります。GA になったら改めて試してみたいと思います。
参考情報: CloudFormation テンプレート
本記事で使用した CloudFormation テンプレートの全文です。create-stack でデプロイし、不要になれば delete-stack で撤去できます。
テンプレート全文
AWSTemplateFormatVersion: '2010-09-09'
Description: OpenClaw + Kiro CLI on EC2 (simple, no Docker)
Parameters:
ProjectName:
Type: String
Default: openclaw-simple
InstanceType:
Type: String
Default: t4g.medium
AllowedValues: [t4g.small, t4g.medium, t4g.large]
KeyPairName:
Type: AWS::EC2::KeyPair::KeyName
AllowedSSHCidr:
Type: String
Default: 172.0.0.1/32
Description: CIDR for SSH access
ImageId:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64
OpenClawModel:
Type: String
Default: global.anthropic.claude-sonnet-4-6
AllowedValues:
- global.anthropic.claude-sonnet-4-6
- global.anthropic.claude-haiku-4-5-20251001-v1:0
- apac.anthropic.claude-3-haiku-20240307-v1:0
- apac.amazon.nova-pro-v1:0
GatewayToken:
Type: String
NoEcho: true
MinLength: 16
Resources:
EC2Role:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub '${ProjectName}-ec2-role'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
Policies:
- PolicyName: bedrock-invoke
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- bedrock:InvokeModel
- bedrock:InvokeModelWithResponseStream
- bedrock:ListFoundationModels
- bedrock:ListInferenceProfiles
Resource: '*'
- PolicyName: AssumeKiroReadOnly
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: sts:AssumeRole
Resource: !GetAtt KiroReadOnlyRole.Arn
KiroReadOnlyRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub '${ProjectName}-kiro-readonly'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
Action: sts:AssumeRole
Condition:
ArnEquals:
aws:PrincipalArn: !Sub 'arn:aws:iam::${AWS::AccountId}:role/${ProjectName}-ec2-role'
ManagedPolicyArns:
- arn:aws:iam::aws:policy/ReadOnlyAccess
InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
InstanceProfileName: !Sub '${ProjectName}-instance-profile'
Roles:
- !Ref EC2Role
SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub '${ProjectName}-sg'
GroupDescription: SSH from allowed IP only
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: !Ref AllowedSSHCidr
Tags:
- Key: Name
Value: !Sub '${ProjectName}-sg'
LaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: !Sub '${ProjectName}-lt'
LaunchTemplateData:
InstanceType: !Ref InstanceType
KeyName: !Ref KeyPairName
IamInstanceProfile:
Arn: !GetAtt InstanceProfile.Arn
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
VolumeSize: 20
VolumeType: gp3
Encrypted: true
MetadataOptions:
HttpTokens: required
HttpEndpoint: enabled
TagSpecifications:
- ResourceType: instance
Tags:
- Key: Name
Value: !Sub '${ProjectName}'
EC2Instance:
Type: AWS::EC2::Instance
Properties:
LaunchTemplate:
LaunchTemplateId: !Ref LaunchTemplate
Version: !GetAtt LaunchTemplate.LatestVersionNumber
ImageId: !Ref ImageId
SecurityGroupIds:
- !Ref SecurityGroup
UserData:
Fn::Base64: !Sub |
#!/bin/bash
set -euo pipefail
exec > >(tee /var/log/openclaw-setup.log) 2>&1
echo "=== Setup started ==="
# Node.js 22 LTS + OpenClaw
dnf install -y nodejs22 unzip git
npm install -g openclaw@latest
# Kiro CLI
curl --proto '=https' --tlsv1.2 -sSf \
"https://desktop-release.q.us-east-1.amazonaws.com/latest/kirocli-aarch64-linux.zip" \
-o /tmp/kirocli.zip
cd /tmp && unzip -o kirocli.zip
sudo -u ec2-user /tmp/kirocli/install.sh --no-confirm
rm -rf /tmp/kirocli /tmp/kirocli.zip
ln -sf /home/ec2-user/.local/bin/kiro-cli /usr/local/bin/kiro-cli
ln -sf /home/ec2-user/.local/bin/kiro-cli-chat /usr/local/bin/kiro-cli-chat
ln -sf /home/ec2-user/.local/bin/kiro-cli-term /usr/local/bin/kiro-cli-term
# AWS profile for Kiro readonly
mkdir -p /home/ec2-user/.aws
cat > /home/ec2-user/.aws/config << AWSEOF
[profile kiro-readonly]
role_arn = ${KiroReadOnlyRole.Arn}
credential_source = Ec2InstanceMetadata
role_session_name = kiro-readonly-session
region = ${AWS::Region}
AWSEOF
chown -R ec2-user:ec2-user /home/ec2-user/.aws
# OpenClaw config (non-interactive)
export HOME=/home/ec2-user
export OPENCLAW_STATE_DIR=/home/ec2-user/.openclaw
mkdir -p /home/ec2-user/.openclaw/workspace /home/ec2-user/.openclaw/agents/main/sessions
cat > /home/ec2-user/.openclaw/openclaw.json << 'OCCONF'
{
"gateway": { "mode": "local" },
"agents": {
"defaults": {
"model": {}
}
}
}
OCCONF
chown -R ec2-user:ec2-user /home/ec2-user/.openclaw
sudo -u ec2-user bash -c "export HOME=/home/ec2-user; openclaw config set agents.defaults.model.primary 'amazon-bedrock/${OpenClawModel}'"
sudo -u ec2-user bash -c "export HOME=/home/ec2-user; openclaw config set gateway.remote.token '${GatewayToken}'"
# Kiro agent skill for OpenClaw
mkdir -p /home/ec2-user/.openclaw/skills/kiro-agent
cat > /home/ec2-user/.openclaw/skills/kiro-agent/SKILL.md << 'SKILLEOF'
---
name: kiro-agent
description: "Delegate tasks to Kiro CLI (AWS Builder ID auth, flat-rate). Use for coding tasks, file operations, and AWS read-only queries via kiro-readonly profile."
metadata:
{
"openclaw": { "emoji": "🤖", "requires": { "anyBins": ["kiro-cli"] } }
}
---
# Kiro CLI Agent
Delegate tasks to Kiro CLI on the host. Kiro uses AWS Builder ID authentication (flat-rate).
## Read-Only AWS Operations
```bash
bash pty:true command:"AWS_PROFILE=kiro-readonly kiro-cli chat --no-interactive --trust-all-tools 'your AWS query here'"
```
## Coding Tasks
```bash
bash pty:true workdir:~/project command:"kiro-cli chat --no-interactive --trust-all-tools 'your coding task here'"
```
## Rules
1. Always use pty:true
2. For AWS operations, always prefix with AWS_PROFILE=kiro-readonly
3. Prefer Kiro for heavy/iterative tasks (flat-rate vs per-token)
SKILLEOF
chown -R ec2-user:ec2-user /home/ec2-user/.openclaw/skills
# systemd service
cat > /etc/systemd/system/openclaw.service << 'SVCEOF'
[Unit]
Description=OpenClaw Gateway
After=network.target
[Service]
Type=simple
User=ec2-user
Environment=HOME=/home/ec2-user
Environment=OPENCLAW_STATE_DIR=/home/ec2-user/.openclaw
Environment=OPENCLAW_WORKSPACE_DIR=/home/ec2-user/.openclaw/workspace
Environment=PATH=/usr/local/bin:/usr/bin:/bin
Environment=AWS_REGION=${AWS::Region}
Environment=AWS_PROFILE=default
ExecStart=/usr/bin/openclaw gateway --allow-unconfigured --bind loopback --port 18789 --token ${GatewayToken}
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
SVCEOF
systemctl daemon-reload
systemctl enable --now openclaw
echo "=== Setup complete ==="
Outputs:
InstanceId:
Value: !Ref EC2Instance
SSMConnect:
Value: !Sub 'aws ssm start-session --target ${EC2Instance} --region ${AWS::Region}'
KiroLoginCommand:
Value: 'sudo -u ec2-user /home/ec2-user/.local/bin/kiro-cli login'








