EC2 + OpenClaw + Bedrock な環境で Kiro CLI をエージェントとして動かしてみた

EC2 + OpenClaw + Bedrock な環境で Kiro CLI をエージェントとして動かしてみた

OpenClaw の評価環境を EC2 に構築し、Bedrock 経由で Kiro CLI をエージェントとして動かす手順を解説します。CloudFormation テンプレート1枚でデプロイでき、CLI・Web UI の両方から動作確認した結果をまとめました。
2026.03.01

はじめに

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 としてクライアント側の認証に使用
  • 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 には最小限のキーだけ記述します。versionproviders を含めると不正キーエラーになります。

設定ポイント 2: systemd の環境変数 (Bedrock 認証)

OpenClaw 内部の Bedrock 認証チェックは、AWS_PROFILEAWS_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層で保護されています。

  1. loopback バインド: Gateway は localhost のみリッスン。EC2 の外部から直接アクセス不可
  2. SSH 制限: SecurityGroup で特定 IP からのみ SSH を許可。トンネルを張れる人を限定
  3. Gateway トークン: トークンなしのリクエストは拒否

日本語でも指示できました。

kiro-agentを使って、ap-northeast-1のEC2インスタンス一覧を取得して

OpenClaw_Chat

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'

この記事をシェアする

FacebookHatena blogX

関連記事