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マルチアカウント環境で、ボタン一つでアカウント間のVPC Peeringを作れるようにした

0
Last updated at Posted at 2026-07-10

背景

コスト削減の一環として、非本番環境のVPCを毎日自動で削除・再作成する運用を行っています。
ただ、この運用にはひとつ厄介な点があります。VPCを作り直すたびにVPC IDが変わるため、アカウントをまたぐVPC Peeringも毎回作り直さなければならないことです。

同一アカウント内であれば比較的単純ですが、アカウントをまたぐPeeringでは、次の作業を分けて行う必要があります。

  1. リクエスターアカウント: VPC Peering Connectionを作成
  2. アクセプターアカウント: VPC Peering Connectionを承認
  3. 両アカウント: それぞれのルートテーブルに相手側VPCへの経路を追加

マネジメントコンソールを行き来しながら都度手作業で行うのは手間が大きく、作業漏れも起きやすくなります。そこで、CodeBuildを用いてワンクリックで安全に実行できる仕組みを構築しました。

本記事の手順は、リクエスター・アクセプター双方が同一リージョン(例: ap-northeast-1)に存在することを前提としています。

構成の概要

基本の考え方はシンプルです。リクエスター側アカウントでCodeBuildを動かし、そのジョブがアクセプター側アカウントのロールを一時的に借りることで、1回の実行で両アカウントの操作を完結させます。

[リクエスター側アカウント]                       [アクセプター側アカウント]
CodeBuild (DeployerRole)
│
├─ ① STS AssumeRole ──────────────────────────> AcceptorRole
├─ ② リクエスター側のVPC情報を取得
├─ ③ アクセプター側のVPC情報を取得 (--profile)
├─ ④ VPC Peeringを作成(リクエスト)
├─ ⑤ VPC Peeringを承認 (--profile) ──────────> AcceptVpcPeeringConnection
├─ ⑥ リクエスター側のルートテーブルに経路追加
└─ ⑦ アクセプター側のルートテーブルに経路追加 (--profile)

IAMロールの設計

必要以上の権限付与を避け、役割に応じた制限を意識してクロスアカウント用のIAMロールを設計します。
(※リクエスター側は動的に変わるVPC/ルートテーブルを操作する都合上、一部リソース制限をワイルドカードにしていますが、実運用に合わせて適宜タグ制限などを追加してください)

1. リクエスター側: VpcPeeringDeployerRole

CodeBuildのサービスロールです。自アカウントのVPC・ルートテーブルの操作権限に加え、アクセプター側の VpcPeeringAcceptorRole への sts:AssumeRole 権限を付与します。

インラインポリシー:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeVpcs",
        "ec2:DescribeRouteTables",
        "ec2:CreateVpcPeeringConnection",
        "ec2:CreateRoute"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
      "Resource": "arn:aws:iam::<アクセプター側アカウントID>:role/VpcPeeringAcceptorRole"
    }
  ]
}

2. アクセプター側: VpcPeeringAcceptorRole

リクエスター側の DeployerRole からの AssumeRole のみを受け付けるロールです。不用意な操作を防ぐため、ec2:CreateRoute には Condition を設定し、特定のNameタグを持つルートテーブルのみを操作できるよう制限しています。

インラインポリシー:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:AcceptVpcPeeringConnection",
        "ec2:DescribeVpcs",
        "ec2:DescribeRouteTables",
        "ec2:CreateTags"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ec2:CreateRoute"
      ],
      "Resource": "arn:aws:ec2:*:*:route-table/*",
      "Condition": {
        "StringLike": {
          "ec2:ResourceTag/Name": [
            "example-project/vpc/private-subnet-*"
          ]
        }
      }
    }
  ]
}

信頼関係 (Trust Relationship):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::<リクエスター側アカウントID>:role/VpcPeeringDeployerRole"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

事前準備 (CodeBuildの環境変数)

CodeBuildプロジェクトを作成する際は、以下の環境変数(Environment variables)を事前に設定してください。

変数名 説明
ACCEPTOR_ACCOUNT_ID アクセプター側のAWSアカウントID 123456789012
PEERING_NAME 作成するVPC Peering Connectionに付与するNameタグ example-peering
REQUESTER_VPC_NAME リクエスター側VPCのNameタグ example-requester-project/vpc
ACCEPTOR_VPC_NAME アクセプター側VPCのNameタグ example-acceptor-project/vpc

CodeBuildの構築スクリプト(buildspec.yml)

CodeBuildで実行する処理の全容です。すでに存在する場合は処理をスキップし、何度実行してもエラーにならない安全な設計にしています。

version: 0.2

phases:
  pre_build:
    commands:
      # AWS CLIのデフォルトリージョンを設定(環境に合わせて変更してください)
      - export AWS_DEFAULT_REGION="ap-northeast-1"

      # 1. アクセプター側のロールを引き受けて一時認証情報を取得
      - ROLE_ARN="arn:aws:iam::${ACCEPTOR_ACCOUNT_ID}:role/VpcPeeringAcceptorRole"
      - CRED=$(aws sts assume-role --role-arn "${ROLE_ARN}" --role-session-name "CrossAccountPeeringSession")
      
      # 2. アクセプター側操作用のAWS CLIプロファイル(acceptor-profile)を作成
      - aws configure set aws_access_key_id $(echo $CRED | jq -r '.Credentials.AccessKeyId') --profile acceptor-profile
      - aws configure set aws_secret_access_key $(echo $CRED | jq -r '.Credentials.SecretAccessKey') --profile acceptor-profile
      - aws configure set aws_session_token $(echo $CRED | jq -r '.Credentials.SessionToken') --profile acceptor-profile
      
      # 3. 意図したアカウントに切り替わっているかダブルチェック
      - CALLER_ACCOUNT_ID=$(aws sts get-caller-identity --profile acceptor-profile --query 'Account' --output text)
      - |
        if [ "$CALLER_ACCOUNT_ID" = "$ACCEPTOR_ACCOUNT_ID" ]; then
          echo "Success: acceptor-profile points to expected account: $CALLER_ACCOUNT_ID"
        else
          echo "Error: Account mismatch. expected=$ACCEPTOR_ACCOUNT_ID actual=$CALLER_ACCOUNT_ID"
          exit 1
        fi

  build:
    commands:
      # 二重実行の防止: 既存のPeeringが存在すれば終了
      - |
        active_peering_ids=$(aws ec2 describe-vpc-peering-connections \
          --filters "Name=tag:Name,Values=${PEERING_NAME}" \
          --query "VpcPeeringConnections[?contains(['active','provisioning','pending-acceptance','initiating-request'], Status.Code)].VpcPeeringConnectionId" \
          --output text)
        if [ -n "$active_peering_ids" ]; then
          echo "VPC peering already exists: $active_peering_ids. Exiting."
          exit 1
        fi

      # 両アカウントのVPC情報(IDとCIDR)を取得
      - REQUESTER_VPC_ID=$(aws ec2 describe-vpcs --filters "Name=tag:Name,Values=${REQUESTER_VPC_NAME}" --query "Vpcs[0].VpcId" --output text)
      - REQUESTER_VPC_CIDR=$(aws ec2 describe-vpcs --filters "Name=tag:Name,Values=${REQUESTER_VPC_NAME}" --query "Vpcs[0].CidrBlock" --output text)
      - ACCEPTOR_VPC_ID=$(aws ec2 describe-vpcs --filters "Name=tag:Name,Values=${ACCEPTOR_VPC_NAME}" --query "Vpcs[0].VpcId" --output text --profile acceptor-profile)
      - ACCEPTOR_VPC_CIDR=$(aws ec2 describe-vpcs --filters "Name=tag:Name,Values=${ACCEPTOR_VPC_NAME}" --query "Vpcs[0].CidrBlock" --output text --profile acceptor-profile)

      # VPC Peeringの作成(リクエスト)
      - |
        PEERING_ID=$(aws ec2 create-vpc-peering-connection \
          --vpc-id ${REQUESTER_VPC_ID} \
          --peer-vpc-id ${ACCEPTOR_VPC_ID} \
          --peer-owner-id ${ACCEPTOR_ACCOUNT_ID} \
          --tag-specifications "ResourceType=vpc-peering-connection,Tags=[{Key=Name,Value=${PEERING_NAME}}]" \
          --query 'VpcPeeringConnection.VpcPeeringConnectionId' \
          --output text)

      # VPC Peeringの承認(アクセプター側でAccept)
      - aws ec2 accept-vpc-peering-connection --vpc-peering-connection-id ${PEERING_ID} --profile acceptor-profile

      # リクエスター側のルートテーブル(プライベートサブネット)に対象経路を追加
      - |
        REQUESTER_ROUTE_TABLE_IDS=$(aws ec2 describe-route-tables \
          --filters "Name=vpc-id,Values=${REQUESTER_VPC_ID}" "Name=tag:Name,Values=${REQUESTER_VPC_NAME}/private-subnet-*" \
          --query "RouteTables[].RouteTableId" --output text)
        for rt_id in $REQUESTER_ROUTE_TABLE_IDS; do
          aws ec2 create-route --route-table-id $rt_id --destination-cidr-block $ACCEPTOR_VPC_CIDR --vpc-peering-connection-id $PEERING_ID
          echo "Route created in Requester route table $rt_id to Acceptor VPC CIDR $ACCEPTOR_VPC_CIDR via peering connection $PEERING_ID"
        done

      # アクセプター側のルートテーブル(プライベートサブネット)に対象経路を追加
      - |
        ACCEPTOR_ROUTE_TABLE_IDS=$(aws ec2 describe-route-tables \
          --filters "Name=vpc-id,Values=${ACCEPTOR_VPC_ID}" "Name=tag:Name,Values=${ACCEPTOR_VPC_NAME}/private-subnet-*" \
          --query "RouteTables[].RouteTableId" --output text --profile acceptor-profile)
        for rt_id in $ACCEPTOR_ROUTE_TABLE_IDS; do
          aws ec2 create-route --route-table-id $rt_id --destination-cidr-block $REQUESTER_VPC_CIDR --vpc-peering-connection-id $PEERING_ID --profile acceptor-profile
          echo "Route created in Acceptor route table $rt_id to Requester VPC CIDR $REQUESTER_VPC_CIDR via peering connection $PEERING_ID"
        done

まとめ

本仕組みを導入したことで、毎日VPCがスクラップ&ビルドされる環境であっても、開発者はCodeBuildの 「ビルドを開始」ボタンを押すだけ で、安全かつ瞬時にアカウント間のネットワーク経路を復旧できるようになりました。

今回の運用のポイントは以下の通りです。

  • クロスアカウントの自動化: aws sts assume-role で取得した一時認証情報をAWS CLIのプロファイルに切り替えることで、単一のスクリプトから複数アカウントを安全に操作可能
  • セキュリティの考慮: アクセプター側の権限は Condition タグを用いて操作可能なルートテーブルを厳密に絞り込み、不用意なルート追加を防ぐ(※リクエスター側も運用の要件に応じて、同様のタグ制限を入れるとより安全です)
  • 安全な設計: 事前の存在チェックを入れることで、意図しない二重実行によるエラーやルートの重複登録を防ぐ
  • シンプルなデプロイ構成: リクエスター側にCodeBuild、アクセプター側にIAMロールをそれぞれ通常のCDKスタックとして個別にデプロイするだけで実現可能

マルチアカウント環境でのネットワーク自動化を検討している方の参考になれば幸いです。

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?