TerraformでECSコンテナとBedrockの間のPrivateLinkを確立する

こんにちは、エキサイト株式会社の平石です。

エキサイトホールディングス Advent Calendar 2024のシリーズ2, 19日目を担当いたします。

今回はAWS上のECSコンテナとBedrockの間のPrivateLinkを確立する方法をご紹介します。

AWSリソースの作成にはTerraformを使います。

はじめに

Amazon Bedrockとは

Amazon Bedrockは、AWS上で生成AIを利用できるようにするフルマネージドサービスです。
既存のモデルを容易に利用することもできますし、用途やデータに合わせてカスタマイズすることも可能です。

aws.amazon.com

PrivateLinkとは

Amazon BedrockはVPC上になく、ECSコンテナ環境で動作しているアプリケーションからBedrcokを利用する際にはNAT Gatewayを利用するなどしてインターネットを経由する必要があります。
しかし、インターネットを経由するとセキュリティの問題が生じます。

PrivateLinkは利用するAWSサービスがVPC上にあるかのように接続するための方法で、インターネットを経由しないプライベートな接続が可能になります。

PrivateLinkはVPCにアタッチするVPCエンドポイントというコンポーネントと接続先のAWSサービスを結びつけることで構築します。

環境

Terraform 1.10.3で動作確認をしています。

前提条件

AWSアカウントの作成、Terraformの環境構築、ECSコンテナのデプロイ、Bedrockのモデルへのアクセス許可は完了していることを前提としています。

実装

Security GroupとIAMポリシーを作成する

まずは、VPC エンドポイントにアタッチするSecurity Groupを作成します。

./vpc_endpoint/bedrock/main.tf

variable "vpc" {
  type = object({
    vpc_id             = string
    private_subnet_ids = list(string)
  })
}

variable "trusted_security_group_id" {
  type = string
}

module "security_group" {
  source      = "terraform-aws-modules/security-group/aws"
  version     = "5.1.0"
  name        = "bedrock-vpce-sg"
  description = "Security group for bedrock-vpce"
  vpc_id      = var.vpc.vpc_id
  ingress_with_source_security_group_id = [
    {
      from_port                = 443
      to_port                  = 443
      protocol                 = "tcp"
      source_security_group_id = var.trusted_security_group_id
    }
  ]
}

設定したSecurity GroupからのTCPプロトコルによる443ポートへの通信のみ許可します。 変数trusted_security_group_idには、ECSにアタッチしたSecurity GroupのIDを渡します。

次に、VPCエンドポイントにアタッチするポリシードキュメントを作成します。

./vpc_endpoint/bedrock/main.tf

variable "trusted_role_arns" {
  type = list(string)
}

........

data "aws_iam_policy_document" "bedrock_access_policy" {
  statement {
    actions = [
      "bedrock:InvokeModel",
      "bedrock:InvokeModelWithResponseStream",
    ]
    effect = "Allow"
    resources = [
      "arn:aws:bedrock:ap-northeast-1::foundation-model/anthropic.claude-*"
    ]
    principals {
      identifiers = var.trusted_role_arns
      type        = "AWS"
    }
  }
}

このポリシードキュメントでは、VPC エンドポイントにBedrockのInvokeModelInvokeModelWithResponseStreamを許可します。
Bedrockのモデルにアクセスして、生成AIからのレスポンスを得るためにはこのポリシーを設定すれば十分でしょう。
今回は、AnthropicのClaudeというモデルにアクセスできるようにしています。

VPC エンドポイントを作成する

これまでに作成したSecurity Groupやポリシーを利用してVPCエンドポイントを作成します。

./vpc_endpoint/bedrock/main.tf

resource "aws_vpc_endpoint" "bedrock_vpc_endpoint" {
  service_name        = "com.amazonaws.ap-northeast-1.bedrock-runtime"  // ①
  vpc_id              = var.vpc.vpc_id
  vpc_endpoint_type   = "Interface"  // ②
  subnet_ids          = var.vpc.private_subnet_ids  
  policy              = data.aws_iam_policy_document.bedrock_claude_policy.json  // ③
  security_group_ids  = [module.security_group.security_group_id]  // ④
  private_dns_enabled = true  // ⑤

  tags = {
    Name = "bedrock-vpce"
  }
}

output "bedrock_vpc_endpoint_id" {
  value = aws_vpc_endpoint.bedrock_vpc_endpoint.id
}

①では、VPC エンドポイントでVPCとPrivateLinkを確立するサービスを設定します。今回は東京リージョン(ap-northeast-1)のBedrockを設定しています。

②では、VPC エンドポイントのタイプを設定します。VPC エンドポイントのタイプには主にGatewayInterfaceがあります。
Gateway エンドポイントは無料で利用できますが、現在のところAmazon S3またはDynamoDBとの接続しかサポートされていません。 今回は、Bedrockとの接続を確立するためInterface エンドポイントに設定しています。

③, ④では、上で作成したSecurity Groupとポリシーをアタッチしています。

⑤では、指定したVPCにプライベートホストゾーンを関連付けるかどうかを設定します。
VPC内のサービスが自動でVPC エンドポイントを利用するために必要なため、trueに設定しましょう。

ECSタスクのロールを変更

次にECSタスクのロールにアタッチされているポリシーを変更します。

./ecs/xxxxx/main.tf

variable "bedrock_vpc_endpoint_id" {
  type = string
}


data "aws_iam_policy_document" "iam_policy_document" {
  statement {
    actions = [
      〜〜 略 〜〜
      "ec2:CreateNetworkInterface",
      "ec2:DescribeNetworkInterfaces",
      "ec2:DeleteNetworkInterface",
      "ec2:*VpcEndpoint*"
    ]
    resources = ["*"]
  }

  // BedrockへのアクセスをVPC エンドポイント経由のみに制限
  statement {
    effect = "Allow"
    actions = [
      "bedrock:InvokeModel",
      "bedrock:InvokeModelWithResponseStream"
    ]
    sid       = "AllowTask"
    resources = ["*"]
    condition {
      test     = "ForAnyValue:StringEquals"
      values   = [var.bedrock_vpc_endpoint_id]
      variable = "aws:SourceVpce"
    }
  }
}

前半部分では、ECSタスクからVPCエンドポイントを利用できるようなstatementを追加しています。

また、後半部分ではECSタスクからBedrockへのアクセスをVPC エンドポイント経由のみに制限し、意図せぬインターネットへのアクセスが発生しないようにしています。
ただし、このstatementはあくまでVPCエンドポイントからBedrockにアクセスできるようにしているだけなので、他に無条件でBedrockにアクセスを許可するようなstatementを記述している場合には削除しておきましょう。

作成した部品を組み合わせる

最後にこれまで記述したVPC エンドポイントを実際に作成します。

./main.tf

// Bedrockに接続するためのVPCエンドポイント
module "bedrock_vpc_endpoint" {
  source = "./vpc_endpoint/bedrock"
  vpc = {
    vpc_id             = <使用するVPCのIDを指定>
    name               = <使用するVPCの名前を指定>
    private_subnet_ids = <使用するVPC内のサブネットを指定(基本的にはECSが存在するサブネット)>
  }
  trusted_role_arns = [
    <ECS タスクロールのarn>
  ]
  trusted_security_group_id = <ECSに設定したSecurity GroupのID>
}

終わりに

今回はECSコンテナとBedrockの間のPrivateLinkを確立する方法をご紹介しました。

インターネットを経由しないことでセキュリティ強化につながるため、Bedrockを利用する場合には設定すると良いでしょう。

参考文献