AWS DevOps Agentのマルチアカウント構成をTerraformでデプロイしてみた

AWS DevOps Agentのマルチアカウント構成をTerraformでデプロイしてみた

2026.09.08

公式ドキュメントを参考に、Terraformを使ってAWS DevOps Agentのマルチアカウントアクセス構成を構築してみました。

Getting started with AWS DevOps Agent using Terraform

構成図

devops-agent.drawio.png

Terraformコード

AWS DevOps Agentのエージェントスペース(プライマリアカウント側)と、マルチアカウント監視用のセカンダリアカウント側の設定をまとめて構築します。

現時点ではawsプロバイダーでDevOps Agentの設定ができないため、awsccプロバイダーも併用します。

main.tf
# AWS DevOps Agent Terraform Configuration

provider "awscc" {
  region  = var.aws_region
  profile = "your-primary-account-profile"
}

provider "aws" {
  region  = var.aws_region
  profile = "your-primary-account-profile"
}

# Provider alias for the secondary account
provider "aws" {
  alias   = "secondary"
  region  = var.aws_region
  profile = "your-secondary-account-profile"
}

# Data source to get current AWS account ID
data "aws_caller_identity" "current" {}

# Data source to get the secondary account ID (avoids requiring
# service_account_id to be typed manually in tfvars)
data "aws_caller_identity" "secondary" {
  provider = aws.secondary
}

# Data source to get current AWS region
data "aws_region" "current" {}
variables.tf
# Variables for AWS DevOps Agent Configuration

variable "aws_region" {
  description = "AWS region for DevOps Agent deployment"
  type        = string
  default     = "ap-northeast-1"
}

variable "agent_space_name" {
  description = "Name for the DevOps Agent Space"
  type        = string
  default     = "MyAgentSpace"
}

variable "agent_space_description" {
  description = "Description for the DevOps Agent Space"
  type        = string
  default     = "AgentSpace for monitoring my application"
}

variable "tags" {
  description = "Tags to apply to resources"
  type        = map(string)
  default = {
    Environment = "test"
    Project     = "aws-devops-agent"
  }
}

プライマリアカウント側のIAMロールは2つです。

  • DevOpsAgentRole-AgentSpace-*: DevOps Agentサービス自身がアカウント内のリソース(Lambda、CloudWatch等)を調査するためのロールです。AIDevOpsAgentAccessPolicyを付与します。DevOps Agentはリソース検出用に、Resource Explorerのサービスリンクロールも許可しています。
  • DevOpsAgentRole-WebappAdmin-*: コンソールのチャット画面(オペレーターアプリ)を使うためのロールです。AssumeRoleに加えsts:TagSessionを許可し、AIDevOpsOperatorAppAccessPolicyを付与します

信頼ポリシーはどちらも自アカウント・自エージェントスペースに限定しています。

iam.tf
# IAM Roles and Policies for AWS DevOps Agent

# Random suffix to ensure unique role names
resource "random_id" "suffix" {
  byte_length = 4
}

# Trust policy for DevOps Agent Space Role
data "aws_iam_policy_document" "devops_agentspace_trust" {
  statement {
    effect = "Allow"

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

    actions = ["sts:AssumeRole"]

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

    condition {
      test     = "ArnLike"
      variable = "aws:SourceArn"
      values   = ["arn:aws:aidevops:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:agentspace/*"]
    }
  }
}

# DevOps Agent Space Role
resource "aws_iam_role" "devops_agentspace" {
  name               = "DevOpsAgentRole-AgentSpace-${random_id.suffix.hex}"
  assume_role_policy = data.aws_iam_policy_document.devops_agentspace_trust.json

  tags = var.tags
}

# Attach AIDevOpsAgentAccessPolicy managed policy to Agent Space role
resource "aws_iam_role_policy_attachment" "devops_agentspace_access" {
  role       = aws_iam_role.devops_agentspace.name
  policy_arn = "arn:aws:iam::aws:policy/AIDevOpsAgentAccessPolicy"
}

# Inline policy for creating Resource Explorer service-linked role
data "aws_iam_policy_document" "devops_agentspace_inline" {
  statement {
    sid    = "AllowCreateServiceLinkedRoles"
    effect = "Allow"

    actions = [
      "iam:CreateServiceLinkedRole"
    ]

    resources = [
      "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/aws-service-role/resource-explorer-2.amazonaws.com/AWSServiceRoleForResourceExplorer"
    ]
  }
}

resource "aws_iam_role_policy" "devops_agentspace_inline" {
  name   = "AllowCreateServiceLinkedRoles"
  role   = aws_iam_role.devops_agentspace.id
  policy = data.aws_iam_policy_document.devops_agentspace_inline.json
}

# Trust policy for Operator App Role
data "aws_iam_policy_document" "devops_operator_trust" {
  statement {
    effect = "Allow"

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

    actions = ["sts:AssumeRole", "sts:TagSession"]

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

    condition {
      test     = "ArnLike"
      variable = "aws:SourceArn"
      values   = ["arn:aws:aidevops:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:agentspace/*"]
    }
  }
}

# DevOps Operator App Role
resource "aws_iam_role" "devops_operator" {
  name               = "DevOpsAgentRole-WebappAdmin-${random_id.suffix.hex}"
  assume_role_policy = data.aws_iam_policy_document.devops_operator_trust.json

  tags = var.tags
}

# Attach AIDevOpsOperatorAppAccessPolicy managed policy to Operator App role
resource "aws_iam_role_policy_attachment" "devops_operator_access" {
  role       = aws_iam_role.devops_operator.name
  policy_arn = "arn:aws:iam::aws:policy/AIDevOpsOperatorAppAccessPolicy"
}

セカンダリアカウント側には、クロスアカウントIAMロールDevOpsAgentRole-SecondaryAccount-TFと、調査対象のサンプルのエコー用Lambda関数echo-service-tfを作ります。信頼ポリシーは、プライマリアカウントのエージェントスペースARNをaws:SourceArn条件として指定して限定します。

service-account.tf
# Service Account Resources
# Deploys into the secondary account for cross-account monitoring.
# This configuration always deploys cross-account, so these resources are unconditional.

# Secondary account role (trusted by the Agent Space in the monitoring account)
resource "aws_iam_role" "secondary_account" {
  provider = aws.secondary

  name               = "DevOpsAgentRole-SecondaryAccount-TF"
  assume_role_policy = data.aws_iam_policy_document.secondary_account_trust.json
  description        = "Secondary account role for DevOps Agent Space cross-account access"

  tags = var.tags
}

data "aws_iam_policy_document" "secondary_account_trust" {
  statement {
    effect = "Allow"

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

    actions = ["sts:AssumeRole"]

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

    condition {
      test     = "StringEquals"
      variable = "aws:SourceArn"
      values   = [awscc_devopsagent_agent_space.main.arn]
    }
  }
}

# Attach AIDevOpsAgentAccessPolicy managed policy
resource "aws_iam_role_policy_attachment" "secondary_account_access" {
  provider = aws.secondary

  role       = aws_iam_role.secondary_account.name
  policy_arn = "arn:aws:iam::aws:policy/AIDevOpsAgentAccessPolicy"
}

# Inline policy for creating Resource Explorer service-linked role
data "aws_iam_policy_document" "secondary_account_inline" {
  statement {
    sid    = "AllowCreateServiceLinkedRoles"
    effect = "Allow"

    actions = [
      "iam:CreateServiceLinkedRole"
    ]

    resources = [
      "arn:aws:iam::${data.aws_caller_identity.secondary.account_id}:role/aws-service-role/resource-explorer-2.amazonaws.com/AWSServiceRoleForResourceExplorer"
    ]
  }
}

resource "aws_iam_role_policy" "secondary_account_inline" {
  provider = aws.secondary

  name   = "AllowCreateServiceLinkedRoles"
  role   = aws_iam_role.secondary_account.id
  policy = data.aws_iam_policy_document.secondary_account_inline.json
}

# Echo Lambda function (simple example service)
resource "aws_lambda_function" "echo_service" {
  provider = aws.secondary

  function_name = "echo-service-tf"
  description   = "Simple echo service that returns the input event"
  runtime       = "nodejs20.x"
  handler       = "index.handler"
  timeout       = 30
  memory_size   = 128

  filename         = data.archive_file.echo_lambda.output_path
  source_code_hash = data.archive_file.echo_lambda.output_base64sha256

  role = aws_iam_role.echo_service_role.arn

  tags = var.tags
}

data "archive_file" "echo_lambda" {
  type        = "zip"
  output_path = "${path.module}/echo-service.zip"

  source {
    content  = <<-JS
exports.handler = async (event) => {
  console.log('Received event:', JSON.stringify(event, null, 2));

  return {
    statusCode: 200,
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      message: 'Echo service response',
      echo: event,
      timestamp: new Date().toISOString()
    })
  };
};
JS
    filename = "index.js"
  }
}

# Lambda execution role
resource "aws_iam_role" "echo_service_role" {
  provider = aws.secondary

  name               = "echo-service-tf-role"
  assume_role_policy = data.aws_iam_policy_document.lambda_trust.json

  tags = var.tags
}

data "aws_iam_policy_document" "lambda_trust" {
  statement {
    effect = "Allow"

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

    actions = ["sts:AssumeRole"]
  }
}

resource "aws_iam_role_policy_attachment" "echo_service_basic" {
  provider = aws.secondary

  role       = aws_iam_role.echo_service_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

最後に、エージェントスペースとプライマリ・セカンダリ両アカウントの関連付けです。

devops-agent.tf
# AWS DevOps Agent Resources

# Create the Agent Space with Operator App
resource "awscc_devopsagent_agent_space" "main" {
  name        = var.agent_space_name
  description = var.agent_space_description
  operator_app = {
    iam = {
      operator_app_role_arn = aws_iam_role.devops_operator.arn
    }
  }
}

# Associate the primary AWS account for monitoring
resource "awscc_devopsagent_association" "primary_aws_account" {
  agent_space_id = awscc_devopsagent_agent_space.main.id
  service_id     = "aws"
  configuration = {
    aws = {
      assumable_role_arn = aws_iam_role.devops_agentspace.arn
      account_id         = data.aws_caller_identity.current.account_id
      account_type       = "monitor"
    }
  }
  depends_on = [
    awscc_devopsagent_agent_space.main
  ]
}

# Associate the secondary account for cross-account monitoring
resource "awscc_devopsagent_association" "secondary_aws_account" {
  agent_space_id = awscc_devopsagent_agent_space.main.id
  service_id     = "aws"
  configuration = {
    source_aws = {
      assumable_role_arn = aws_iam_role.secondary_account.arn
      account_id         = data.aws_caller_identity.secondary.account_id
      account_type       = "source"
    }
  }
  depends_on = [
    awscc_devopsagent_association.primary_aws_account
  ]
}

terraform applyでデプロイします。

デプロイ後、CLIでエージェントスペースの作成を確認できます。

aws devops-agent get-agent-space \
  --agent-space-id <AGENT_SPACE_ID> \
  --region ap-northeast-1 \
  --profile <プライマリアカウントのプロファイ>

関連付けの検証

terraform apply後、list-associationsで状態を確認します。セカンダリアカウント側の関連付けがpending-confirmationのまま止まっています。

aws devops-agent list-associations \
  --agent-space-id <AGENT_SPACE_ID> \
  --region ap-northeast-1 \
  --profile <プライマリアカウントのプロファイ>
list-associations(抜粋、一部フィールドを省略)
{
    "associations": [
        {
            "status": "valid",
            "associationId": "<ASSOCIATION_ID_PRIMARY>",
            "serviceId": "aws",
            "configuration": {
                "aws": {
                    "assumableRoleArn": "arn:aws:iam::<PRIMARY_ACCOUNT_ID>:role/DevOpsAgentRole-AgentSpace-xxxxxxxx",
                    "accountId": "<PRIMARY_ACCOUNT_ID>",
                    "accountType": "monitor"
                }
            }
        },
        {
            "status": "pending-confirmation",
            "associationId": "<ASSOCIATION_ID_SECONDARY>",
            "serviceId": "aws",
            "configuration": {
                "sourceAws": {
                    "accountId": "<SECONDARY_ACCOUNT_ID>",
                    "accountType": "source",
                    "assumableRoleArn": "arn:aws:iam::<SECONDARY_ACCOUNT_ID>:role/DevOpsAgentRole-SecondaryAccount-TF"
                }
            }
        }
    ]
}

validにするには、次のコマンドを実行する必要があります。

aws devops-agent validate-aws-associations \
  --agent-space-id <AGENT_SPACE_ID> \
  --region ap-northeast-1 \
  --profile <プライマリアカウントのプロファイ>

数秒後、再度list-associationsで確認するとstatus: "valid"に変わりました。

validate-aws-associations実行後のget-association(抜粋)
{
    "association": {
        "status": "valid",
        "associationId": "<ASSOCIATION_ID_SECONDARY>",
        "serviceId": "aws",
        "configuration": {
            "sourceAws": {
                "accountId": "<SECONDARY_ACCOUNT_ID>",
                "accountType": "source",
                "assumableRoleArn": "arn:aws:iam::<SECONDARY_ACCOUNT_ID>:role/DevOpsAgentRole-SecondaryAccount-TF"
            }
        }
    }
}

動作確認

association状態がvalidになっただけでは、関連付けが成立していることしか分かりません。実際にマルチアカウントの調査権限が機能しているか確認します。DevOps Agentのコンソール(オペレーターアプリ)のチャットで、セカンダリアカウント側のLambda関数について質問してみました。

セカンダリアカウント(<SECONDARY_ACCOUNT_ID>)のecho-service-tfというLambda関数について教えて

devops-chat_png-2.png

devops-chat_png-3.png

33個のツールを使って調べた結果、正しいARN(アカウントIDを含む)・ランタイム・実行ロール名まで回答に含まれていました。加えて、直近24時間の実行回数(2回)とエラー件数(0件)も正確でした。実行回数の2回は、検証中に自分が実際に呼び出していた回数と一致しています。Agentがセカンダリアカウント側のLambda設定・CloudWatch指標まで実際に横断して読みに行けることを確認できました。

おわりに

terraform applyだけでは関連付けがvalidにならず、validate-aws-associationsを別途実行する必要があります。CIやモジュール化まで考えるなら、apply後にこのCLI呼び出しを挟む構成も考えられます。

この記事をシェアする

関連記事