0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS DevOps AgentをTerraformでデプロイしてClaude CodeからMCPで呼び出してみた

0
Posted at

はじめに

個人の AWS アカウントを Terraform で管理しながら、RSS を取得して DynamoDB に記録し、通知を飛ばす仕組みを動かしています。開発は Claude Code で進めているのですが、terraform apply が終わったあとの確認が地味に面倒でした。DynamoDB にレコードが入っているか、SQS の DLQ にメッセージが溜まっていないか、Lambda のログにエラーが出ていないか。コードは Claude Code に書いてもらっているのに、結果の確認だけマネジメントコンソールを開いて自分でやる、という状態です。

「じゃあ Claude Code に aws CLI を叩かせればいいのでは」と考えて、そこで止まりました。ローカルにあるのは管理者権限のプロファイルで、それをそのまま渡すのは怖い。プロンプトに「読み取りしかしないで」と書くことはできますが、それは自制のお願いであって権限の担保ではありません。事故が起きたときに「そう指示したはずなんですが」では困ります。

最初は「読み取り専用の IAM ロールを自分で作り、そのロールで aws CLI を叩く自作エージェント」を作るつもりでした。ところが着手時に調べ直したら、AWS DevOps Agent が MCP リモートサーバに対応していることを知ります。こちらはエージェントの AWS 側権限が AWS 管理ポリシー付きの IAM ロールで決まるため、自作ロール + プロンプトによる自制よりも強い担保が得られます。方針を変えて、こちらで組むことにしました。

結果として、AWS DevOps Agent の Agent Space を Terraform で構築し、Claude Code から MCP(SigV4 認証)経由で呼び出す構成になりました。Claude 側の認証情報や指示に関係なく IAM レベルで読み取り専用が担保されるのがポイントです。

この記事で学べること

  • AWS DevOps Agent の Agent Space を Terraform(awscc provider)でデプロイする方法と、その際の注意点(タグ形式、IAM 伝搬待ち)
  • 権限を「Claude Code 側(呼び出し)」と「エージェント側(AWS リソースアクセス)」に分離する IAM ロール 3 本の設計
  • MCP エンドポイントの認証で、アクセストークンではなく SigV4 を選んだ理由(トークンの 60 日失効とローテーション問題)
  • Claude Code のサブエージェントで「aws CLI を使わない」をプロンプトではなくツール制限で担保する書き方
  • 実際にハマった点(locale の BCP 47 形式、エージェントのリージョン走査、読み取り専用ポリシーで答えられないこと)

想定読者

  • Claude Code などの AI エージェントに AWS の状態確認を任せたいが、権限の渡し方に不安がある方
  • AWS DevOps Agent を試したい、あるいは Terraform で構成管理したい方
  • MCP サーバの認証方式(アクセストークン / SigV4)の選び方で迷っている方

Terraform と IAM の基本的な読み書きができる前提で書いています。

この記事は Claude Code を使って執筆しています。ただし、記載している構成はすべて筆者自身が実際に構築・検証したものであり、内容は筆者が確認したうえで公開しています。技術選定の判断や意見も筆者自身のものです。

AWS DevOps Agent とは

AWS DevOps Agent は、AWS アカウント内のリソースを調査・分析してくれるマネージドな AI エージェントです。Agent Space という単位で作成し、監視対象のアカウントを関連付けて使います。

今回の構成で重要なのは次の 2 点です。

  • MCP リモートサーバを提供している(2026 年 6 月頃から)。Claude Code などの MCP クライアントから直接呼び出せる
  • Agent の AWS 側権限は、こちらが用意した IAM ロールで決まる。AWS 管理ポリシー AIDevOpsAgentAccessPolicy はほぼ読み取り専用(書き込みは cloudtrail:StartQuerysupport:CreateCase のみ)

はじめに書いたとおり、当初は自作エージェントで済ませるつもりでした。着手時に調べ直したことで MCP 対応を知り、方針を変えられたわけです。「自作する前に、同じ目的のマネージドサービスが増えていないか着手時に調べ直す」は今回の教訓の一つでした。

全体構成

確認の流れは次のとおりです。

  1. Claude Code のサブエージェント aws-readonly が MCP ツール(chat など)で質問を送る
  2. ローカルの mcp-proxy-for-aws が呼び出し用ロール(Invoker)の認証情報で SigV4 署名し、DevOps Agent の MCP エンドポイントへ中継する
  3. Agent が Agent Space 用ロール(読み取り専用)を assume して AWS リソースを調査し、回答を返す

ポイントは、Claude Code 側の権限(Invoker ロール)と Agent の AWS 側権限(AgentSpace ロール)が分離されていることです。Invoker ロールにできるのは「Agent とチャットして結果を読む」だけで、AWS リソースへ直接アクセスする権限はありません。

Terraform 実装

Agent Space は awscc provider で作る

Agent Space と Association は aws provider にリソースが無く、Cloud Control API 経由の awscc provider で作ります。

resource "awscc_devopsagent_agent_space" "this" {
  name        = var.agent_space_name
  description = var.agent_space_description
  locale      = "ja-JP" # 応答言語。BCP 47 形式

  operator_app = {
    iam = {
      operator_app_role_arn = aws_iam_role.operator_app.arn
    }
  }

  tags = local.awscc_tags

  depends_on = [time_sleep.wait_for_iam_propagation]
}

# 自アカウントを monitor として関連付ける。全リージョンのリソースが調査対象になる
resource "awscc_devopsagent_association" "this_account" {
  agent_space_id = awscc_devopsagent_agent_space.this.agent_space_id
  service_id     = "aws"

  configuration = {
    aws = {
      account_id         = local.account_id
      account_type       = "monitor"
      assumable_role_arn = aws_iam_role.agent_space.arn
      resources          = []
    }
  }
}

awscc provider 特有の注意点が 2 つあります。

タグの形式が違う。 awscc provider には default_tags が無く、タグは {key, value} のリスト形式です。aws provider 用の共通タグ map を変換して渡します。

locals {
  common_tags = {
    ManagedBy = "terraform"
    Project   = "my-aws"
  }

  # awscc provider のタグは {key, value} のリスト形式
  awscc_tags = [for k, v in local.common_tags : { key = k, value = v }]
}

IAM ロールの伝搬待ちが要る。 IAM ロール作成直後に Agent Space を作ると、信頼ポリシーの検証に失敗することがあります。time_sleep で 30 秒待ってから作成するようにしました。

resource "time_sleep" "wait_for_iam_propagation" {
  create_duration = "30s"

  depends_on = [
    aws_iam_role_policy_attachment.agent_space,
    aws_iam_role_policy.agent_space_slr,
    aws_iam_role_policy_attachment.operator_app,
  ]
}

IAM ロールは 3 本

ロール 誰が assume するか 権限
AgentSpace DevOps Agent サービス AIDevOpsAgentAccessPolicy(ほぼ読み取り専用)+ Resource Explorer の SLR 作成
OperatorApp DevOps Agent サービス AIDevOpsOperatorAppAccessPolicy(Web アプリ用)
Invoker ローカルの管理者プロファイル チャット / 参照系の aidevops アクションのみ

AgentSpace ロールと OperatorApp ロールの信頼ポリシーは、aidevops.amazonaws.com だけが引き受けられる形にした上で、aws:SourceAccountaws:SourceArn で「このアカウントの Agent Space から」に絞り、混乱した代理(confused deputy)を防ぎます。

data "aws_iam_policy_document" "agent_space_trust" {
  statement {
    effect  = "Allow"
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["aidevops.amazonaws.com"]
    }

    condition {
      test     = "StringEquals"
      variable = "aws:SourceAccount"
      values   = [local.account_id]
    }

    condition {
      test     = "ArnLike"
      variable = "aws:SourceArn"
      # 作成前は ID が確定しないのでワイルドカード
      values   = ["arn:${local.partition}:aidevops:${var.region}:${local.account_id}:agentspace/*"]
    }
  }
}

Claude Code が使う Invoker ロールは、チャットと結果参照に必要な aidevops アクションだけを、この Agent Space の ARN に限定して許可します。アクセストークン管理や Agent Space の変更は含めません。

data "aws_iam_policy_document" "invoker" {
  statement {
    sid    = "ChatWithAgentSpace"
    effect = "Allow"
    actions = [
      "aidevops:GetAgentSpace",
      "aidevops:CreateChat",
      "aidevops:SendMessage",
      "aidevops:ListChats",
      "aidevops:ListPendingMessages",
      "aidevops:InvokeAgent",
      "aidevops:ListExecutions",
      "aidevops:ListJournalRecords",
      "aidevops:ListRecommendations",
      "aidevops:GetRecommendation",
      # ...
    ]
    resources = [
      awscc_devopsagent_agent_space.this.arn,
      "${awscc_devopsagent_agent_space.this.arn}/*",
    ]
  }

  # Agent Space に紐づかない参照系(Resource は * が必要)
  statement {
    sid       = "ListAgentSpaces"
    effect    = "Allow"
    actions   = ["aidevops:ListAgentSpaces", "aidevops:GetAccountUsage"]
    resources = ["*"]
  }
}

チャットや実行履歴などサブリソースの ARN 形式はドキュメントに明記が無く、Resource を絞りすぎると AccessDenied になる懸念がありました。agentspace/<id>agentspace/<id>/* の両方を許可し、apply 後に実際の chat 呼び出しで AccessDenied が出ないことを確認しています。plan や validate では分からないため、「マージ後に実 API で検証する」を Issue のチェックリストに入れておくのが安全です。

認証はアクセストークンではなく SigV4

DevOps Agent の MCP エンドポイントへの認証は、アクセストークン(Bearer)と SigV4 の 2 通りがあります。最初はアクセストークンで進めていたのですが、トークンが最長 60 日で失効し、ローテーションが必要という点が引っかかりました。RotateAccessToken を自動化しても、新しいトークンをローカルへ配布するのに結局 AWS 認証情報が要るため、自動化しきれません。

そこで mcp-proxy-for-aws を使った SigV4 認証に切り替えました。ローカルの AWS 認証情報(Invoker ロールを assume するプロファイル)でリクエストに署名する方式です。proxy は毎リクエストで認証情報を読み直すため、STS セッションの期限切れも意識する必要がなく、ローテーション作業がゼロになります。監査も CloudTrail の IAM ロールセッションに一本化されます。

~/.aws/config に呼び出し用プロファイルを 1 回だけ設定します。

[profile devops-agent]
role_arn          = <Invoker ロールの ARN>
source_profile    = <管理者プロファイル名>
region            = ap-northeast-1
role_session_name = claude-code

.mcp.json はリポジトリにコミットし、プロファイル名などの個人差分は ${VAR:-default} 記法の環境変数で吸収します。

{
  "mcpServers": {
    "aws-devops-agent": {
      "type": "stdio",
      "command": "uvx",
      "args": [
        "mcp-proxy-for-aws@1.6.4",
        "https://connect.aidevops.ap-northeast-1.api.aws/mcp",
        "--service", "aidevops",
        "--region", "ap-northeast-1",
        "--profile", "${DEVOPS_AGENT_PROFILE:-devops-agent}",
        "--read-timeout", "120"
      ],
      "timeout": 120000
    }
  }
}

Claude Code 側: aws-readonly サブエージェント

AWS の状態確認は、専用のサブエージェント .claude/agents/aws-readonly.md 経由で行うようにしました。ポイントは 「aws CLI を使わない」をプロンプトの自制ではなくツール制限で担保することです。frontmatter の tools に Bash を含めず、DevOps Agent の MCP ツールと読み取り系ツールだけを許可します。

---
name: aws-readonly
description: AWS の現在の状態を AWS DevOps Agent に問い合わせて確認する読み取り専用エージェント。
tools: Read, Grep, Glob, mcp__aws-devops-agent__list_agent_spaces, mcp__aws-devops-agent__chat, mcp__aws-devops-agent__send_message, ...
---

運用上の工夫を 3 つ挙げます。

最初に list_agent_spaces で Agent Space を特定する。 SigV4 モードではツール呼び出しに agent_space_id が必要なため、エージェントの手順に「最初に list_agent_spaces を呼んで ID を取り、以降の呼び出しに渡す」を入れています。

質問はローカル文脈を添えて 1 回にまとめる。 リソース名を推測で渡さず、先に Terraform コードを読んで具体名を特定してから質問を組み立てます。

[Local Context]
構成管理: Terraform
DynamoDB table: whatsnew-rss-history
SQS DLQ: whatsnew-dlq
Log group: /aws/lambda/whatsnew-notifier

[Question]
1. whatsnew-rss-history の ItemCount と最終更新時刻を教えて
2. whatsnew-dlq の ApproximateNumberOfMessages は 0 か
3. /aws/lambda/whatsnew-notifier の直近 1 時間に ERROR を含むログはあるか

言い回しで応答時間をコントロールする。 「List / Show / 確認して」系は速く返ってきますが、「Investigate / 根本原因を調べて」系は深い調査(5〜8 分)に入り、時間と料金がかかります。深い調査はユーザーが明示的に求めたときだけ使うルールにしました。

実際に、RSS を DynamoDB に記録して通知する仕組みの確認を依頼したところ、テーブルの ItemCount 221 / ACTIVE、DLQ の滞留 0、直近 1 時間のエラーログなし、まで一度の質問で返ってきました。

aws-readonly エージェントに DynamoDB テーブルのレコード数を尋ねた結果

aws-readonly エージェントがバックグラウンドで DevOps Agent に問い合わせ、55 秒で ItemCount / TableSizeBytes / TableStatus / リージョンを返してきました。

つまずいた点

locale は BCP 47 形式(ja_JP はエラー)

Agent Space の localeja_JP と書いていたら、CloudFormation の AWS::DevOpsAgent::AgentSpaceLocale^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$(ハイフン区切りの BCP 47)で、アンダースコア区切りは不正でした。ja-JP に修正し、variable に同じ正規表現の validation を追加しています。

variable "agent_locale" {
  type    = string
  default = "ja-JP"

  validation {
    condition     = var.agent_locale == null || can(regex("^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$", var.agent_locale))
    error_message = "agent_locale は \"ja-JP\" のような BCP 47 形式で指定すること。"
  }
}

注意したいのは、awscc provider のドキュメントには型しか書かれておらず、値の制約は CloudFormation リソースリファレンス側にしか載っていない点です。terraform validate では通ってしまうので、apply 前に CloudFormation 側のドキュメントを見ておかないと CI の apply で初めて落ちます。

Agent のリージョン走査

ローカル文脈で region ap-northeast-1 を渡しても、Agent は最初に us-east-1 でロググループを探してから他リージョンを走査することがありました。最終的には見つかり回答は正しかったのですが、質問テンプレートでリソースごとにリージョンを明記するのが良さそうです。

権限上の制約を知っておく

AIDevOpsAgentAccessPolicy は読み取り専用とはいえ、すべてが読めるわけではありません。答えられないことを把握しておかないと、エージェントが「確認できない」理由に迷います。

確認したいこと ポリシーの状況 代替
DynamoDB の正確な件数・項目 dynamodb:Scan/Query/GetItem なし DescribeTable の ItemCount(約 6 時間遅れの概算)
DLQ メッセージの中身 sqs:ReceiveMessage なし GetQueueAttributes で滞留数だけ確認
SSM SecureString の値 kms:Decrypt なし 存在と String 型の値は確認可
S3 オブジェクトの中身 s3:GetObject なし バケット設定・ポリシーの確認まで

この制約表はサブエージェントの定義にも書いておき、「この方法では確認できない」と代替案付きで明言させるようにしています。

まとめ

  • AWS DevOps Agent の Agent Space を Terraform(awscc provider)で構築し、Claude Code から MCP 経由で AWS の状態確認ができるようにした
  • Agent の AWS 側権限は AWS 管理ポリシー AIDevOpsAgentAccessPolicy 付きの IAM ロールで決まるため、プロンプトの自制ではなく IAM レベルで読み取り専用を担保できる
  • 認証はアクセストークン(60 日で失効)ではなく、mcp-proxy-for-aws による SigV4 + 最小権限の呼び出し用ロールにすることで、ローテーション作業をゼロにした
  • Claude Code 側はサブエージェントのツール allowlist から Bash を外し、「aws CLI を使わない」もツール制限で担保した

「自作する前にマネージドサービスを調べ直す」「失効する資格情報を運用に入れない」あたりは、この構成に限らず使える教訓だと思います。Claude Code に AWS を触らせたいが権限が気になる、という方の参考になれば幸いです。

参考資料

AWS DevOps Agent

Terraform / CloudFormation

MCP / Claude Code

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?