プライベートサブネット内の EC2 インスタンスが、Internet Gateway / NAT Gateway を使わずに、Interface 型 VPC エンドポイント経由で AWS サービスへ接続する構成です。
今回は例として Secrets Manager に接続する構成にします。Secrets Manager、SSM、ECR、CloudWatch Logs、KMS、STS などは Interface 型 VPC エンドポイントでよく使います。
Gateway 型 VPC エンドポイントについては以下で詳しく解説しています。
目次
Interface 型 VPC エンドポイントとは
Interface 型 VPC エンドポイントは、ざっくり言うと、VPC 内に AWS サービス接続用の ENI を作り、その ENI のプライベート IP アドレス宛てに通信する仕組みです。
Interface 型 VPC エンドポイントは PrivateLink を利用して AWS サービスなどに接続する仕組みです。
Gateway 型 VPC エンドポイントは、ルートテーブルに
S3宛て → Gateway Endpoint
のようなルートを追加する方式でした。
一方、Interface 型はルートテーブルではなく、サブネット内に
エンドポイントENI
が作成されます。
イメージとしてはこうです。
EC2
↓
Secrets Managerの通常DNS名
例: secretsmanager.ap-northeast-1.amazonaws.com
↓
VPC内ではエンドポイントENIのプライベートIPに名前解決される
↓
Interface型VPCエンドポイント
↓
AWS PrivateLink
↓
Secrets Manager
EC2 から見ると普通に AWS サービスへアクセスしているように見えますが、実際の通信は VPC 内のエンドポイント ENI を経由します。
Interface 型 VPC エンドポイントと Gateway 型 VPC エンドポイント比較表
| 項目 | Interface型 | Gateway型 |
|---|---|---|
| 代表サービス | Secrets Manager、SSM、ECR、CloudWatch Logs、KMS、STSなど | S3、DynamoDB |
| 実体 | サブネット内のENI | ルートテーブルのターゲット |
| 通信先 | DNSでエンドポイントENIへ向ける | ルートテーブルで制御 |
| セキュリティグループ | あり | なし |
| AWS PrivateLink | 使う | 使わない |
| 料金 | 時間課金+データ処理量課金あり | S3/DynamoDB Gateway Endpointは追加料金なし |
| オンプレミスからの利用 | Direct Connect / VPN経由で利用可能なケースあり | 基本不可 |
Gateway 型 VPC エンドポイントは PrivateLink を使わず、S3 と DynamoDB 向けの仕組みです。また、S3 の Gateway 型 VPC エンドポイント追加料金なしですが、Interface 型 VPC エンドポイントは追加コストがあります。
構成
今回は以下のような構成です。
AWS
└── VPC 10.0.0.0/16
└── Availability Zone A
└── Private Subnet 10.0.1.0/24
├── EC2
│ └── Secrets Managerへアクセス
│
└── Interface型VPCエンドポイント
├── Endpoint ENI
├── Private IP: 10.0.1.x
└── Security Group付き
AWS PrivateLink
└── AWS Secrets Manager
ポイントは、EC2 と Interface 型エンドポイントが同じプライベートサブネット内にあることです。
Interface 型エンドポイントは指定したサブネットごとに ENI を作成します。高可用性にしたい場合は、複数 AZ の プライベートサブネットにエンドポイントを作成します。
構成図

通信の流れ
Secrets Manager へアクセスする例で考えます。EC2 上のアプリケーションや AWS CLI が、以下の Secrets Manager のエンドポイントへアクセスします。
https://secretsmanager.ap-northeast-1.amazonaws.com
このとき、Interface 型 VPC エンドポイントで private_dns_enabled = true にしていると、VPC 内ではこの DNS 名がエンドポイント ENI のプライベート IP に解決されます。
private_dns_enabled (
Optional[bool] ) – 指定された VPC にプライベート ホスト ゾーンを関連付けるかどうか。これにより、デフォルトの DNS ホスト名を使用してサービスにリクエストを送信できます。デフォルト:IInterfaceVpcEndpointService インスタンスによって設定されます。IInterfaceVpcEndpointService インスタンスによって定義されていない場合は true です。
つまり、EC2から見ると、
secretsmanager.ap-northeast-1.amazonaws.com
へアクセスしているつもりですが、実際には
10.0.1.x
のような VPC 内のプライベート IP に通信しています。
その通信が Interface 型 VPC エンドポイントを通り、AWS PrivateLink 経由で Secrets Manager へ届きます。
PrivateLink とは
PrivateLink は、Interface 型 VPC エンドポイントの裏側で使われている仕組みです。
一言でいうと、VPC 内のリソースから、AWS サービス・他の VPC のサービス・SaaS 事業者のサービスへ、インターネットを経由せずにプライベート接続するための AWS の仕組みです。
PrivateLink を使うには、サービスにアクセスする必要があるサブネットに VPC エンドポイントを作成し、それによって ENI が作られます。そして、その ENI がサービスへのトラフィックの入口になります。
結論:PrivateLinkは「裏側の専用通路」と言える
Interface 型 VPC エンドポイントでは、VPC 内に エンドポイント ENI が作られます。
Private Subnet
├── EC2
└── Interface VPC Endpoint
└── Endpoint ENI
└── Private IP: 10.0.1.x
EC2 は、この ENI のプライベート IP に通信します。その先で、AWS PrivateLink が AWS サービスへつないでくれます。
EC2
↓
Endpoint ENI
↓
AWS PrivateLink
↓
AWSサービス
つまり、EC2 から見えるのは VPC 内のプライベート IP までです。その先の AWS サービス側への接続を裏で担当しているのが AWS PrivateLink です。
Secrets Manager へアクセスする場合
たとえば、EC2 から Secrets Manager へアクセスする場合です。
通常であれば、EC2 は以下のような AWS サービスのエンドポイントへアクセスします。
secretsmanager.ap-northeast-1.amazonaws.com
↓ DNS
10.0.1.10
そして EC2 は、10.0.1.10 に HTTPS 通信します。
EC2
↓ HTTPS:443
10.0.1.10
↓
Interface VPC Endpoint
↓
AWS PrivateLink
↓
Secrets Manager
重要なのは、EC2 が直接 Secrets Manage rの実体に向かっているわけではないことです。
EC2 はあくまで、VPC 内に作られた エンドポイントENI に通信しています。
PrivateLink がやっていること
PrivateLink がない場合、プライベートサブネットの EC2 からAWS サービスへアクセスするには、一般的には NAT Gateway などを使います。
EC2
↓
NAT Gateway
↓
インターネット側のAWSサービスエンドポイント
↓
AWSサービス
この場合、通信先は AWS サービスですが、経路としてはパブリックな AWS サービスエンドポイントへ向かいます。
一方、PrivateLink を使うとこうなります。
EC2
↓
Interface VPC Endpoint
↓
AWS PrivateLink
↓
AWSサービス
この構成では、EC2 にパブリック IP は不要です。NAT Gateway も不要です。Internet Gateway も不要です。
PrivateLink を使った Interface 型 VPC エンドポイントでは、Internet Gateway、NAT デバイス、VPN 接続、Direct Connect 接続なしにサービスへプライベートアクセスでき、VPC 内のインスタンスにパブリック IP は不要と説明されています。
PrivateLinkの登場人物
PrivateLink を理解するときは、登場人物を分けると分かりやすいです。
1. サービスコンシューマー
サービスを使う側です。
今回の例では、EC2 がある VPC 側です。
EC2があるVPC
この VPC に Interface 型 VPC エンドポイントを作ります。
Interface 型 VPC エンドポイント
使う側の VPC に作る接続口です。
Interface VPC Endpoint
└── Endpoint ENI
└── Private IP
EC2はこのENIに通信します。
Interface 型 VPC エンドポイントを作ると、指定したサブネットにエンドポイントネットワークインターフェイスが作成されます。この ENI が対象サービスへのトラフィックのエントリポイントになります。
エンドポイントサービス
接続される側のサービスです。
たとえば AWS サービスなら、以下が挙げられます。
Secrets Manager
SSM
ECR
CloudWatch Logs
KMS
STS
AWS サービス以外にも、自分で作ったサービスや、他社 SaaS のサービスを PrivateLink で公開することもできます。AWS サービスだけでなく、AWS の顧客やパートナーが所有するサービスにも PrivateLink で接続できます。
AWS サービスに接続する場合
AWS サービスに接続する場合は、以下の流れになります。
自分のVPC
└── EC2
↓
Interface VPC Endpoint
↓
AWS PrivateLink
↓
AWSサービス
Secrets Manager なら Terraform ではこう書きます。
resource "aws_vpc_endpoint" "secretsmanager" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.ap-northeast-1.secretsmanager"
vpc_endpoint_type = "Interface"
subnet_ids = [aws_subnet.private_a.id]
security_group_ids = [aws_security_group.vpce.id]
private_dns_enabled = true
}
セキュリティグループの考え方
Interface 型 VPC エンドポイントには、セキュリティグループを付けられます。
ここが Gateway 型との大きな違いです。
Interface 型エンドポイントに関連付けられたセキュリティグループのルールによって、VPC 内リソースからエンドポイント ENI への通信を制御すると説明されています。
今回の構成では、以下のように考えます。
EC2のSecurity Group
outbound 443 → Endpoint Security Group
EndpointのSecurity Group
inbound 443 ← EC2のSecurity Group
つまり、EC2 からエンドポイント ENI の443番ポートへ通信できるようにするという設定が必要です。
AWS サービスへの API アクセスは基本的に HTTPS なので、通常は TCP 443 を許可します。
エンドポイントポリシーの考え方
Interface 型 VPC エンドポイントにも、Gateway 型と同じように エンドポイントポリシー を設定できます。
たとえば EC2 から Secrets Manager の Secret を取得する場合、最低限見るポイントは3つあります。
1. EC2のIAMロール
→ secretsmanager:GetSecretValue を許可しているか
2. VPCエンドポイントポリシー
→ そのSecretへのアクセスを許可しているか
3. Secret側のリソースポリシー
→ クロスアカウント等の場合、EC2ロールを許可しているか
同一アカウントで通常利用するだけなら、Secret側のリソースポリシーは不要なことも多いです。
ただし、エンドポイントポリシーで絞る場合は、
このVPCエンドポイント経由では、このSecretだけ取得可能
のように制限できます。
Terraformコード
今回は、以下を作ります。
- VPC
- Private Subnet
- EC2 用 Security Group
- Interface Endpoint 用 Security Group
- EC2 IAM ロール
- EC2 インスタンス
- Secrets Manager 用 Interface VPC Endpoint
- テスト用 Secret
- エンドポイントポリシー
なお、Terraformの aws_vpc_endpoint では、Interface 型の場合に security_group_ids を関連付けられます。
構成図
上図の構成図と同じ構成図です。

main.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
data "aws_region" "current" {}
data "aws_caller_identity" "current" {}
# 最新のAmazon Linux 2023 AMIを取得
data "aws_ami" "al2023" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
filter {
name = "architecture"
values = ["x86_64"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
########################################
# VPC
########################################
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "interface-endpoint-vpc"
}
}
########################################
# Private Subnet
########################################
resource "aws_subnet" "private_a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "ap-northeast-1a"
map_public_ip_on_launch = false
tags = {
Name = "private-subnet-a"
}
}
########################################
# Route Table
# NAT Gateway / Internet Gateway は作らない
########################################
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
tags = {
Name = "private-route-table"
}
}
resource "aws_route_table_association" "private_a" {
subnet_id = aws_subnet.private_a.id
route_table_id = aws_route_table.private.id
}
########################################
# Security Group for EC2
########################################
resource "aws_security_group" "ec2" {
name = "ec2-sg"
description = "Security group for private EC2"
vpc_id = aws_vpc.main.id
# 今回は外部からSSHしない前提のため、inboundはなし
egress {
description = "Allow HTTPS to VPC endpoint"
from_port = 443
to_port = 443
protocol = "tcp"
security_groups = [aws_security_group.vpce.id]
}
tags = {
Name = "ec2-sg"
}
}
########################################
# Security Group for Interface VPC Endpoint
########################################
resource "aws_security_group" "vpce" {
name = "secretsmanager-vpce-sg"
description = "Security group for Secrets Manager interface endpoint"
vpc_id = aws_vpc.main.id
ingress {
description = "Allow HTTPS from EC2"
from_port = 443
to_port = 443
protocol = "tcp"
security_groups = [aws_security_group.ec2.id]
}
egress {
description = "Allow all outbound from endpoint ENI"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "secretsmanager-vpce-sg"
}
}
########################################
# Test Secret
########################################
resource "aws_secretsmanager_secret" "sample" {
name = "sample/interface-endpoint-secret"
tags = {
Name = "sample-interface-endpoint-secret"
}
}
resource "aws_secretsmanager_secret_version" "sample" {
secret_id = aws_secretsmanager_secret.sample.id
secret_string = jsonencode({
username = "test-user"
password = "test-password"
})
}
########################################
# IAM Role for EC2
########################################
resource "aws_iam_role" "ec2" {
name = "interface-endpoint-ec2-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
Action = "sts:AssumeRole"
}
]
})
}
resource "aws_iam_policy" "ec2_secretsmanager" {
name = "interface-endpoint-ec2-secretsmanager-policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowGetSampleSecret"
Effect = "Allow"
Action = [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
]
Resource = aws_secretsmanager_secret.sample.arn
}
]
})
}
resource "aws_iam_role_policy_attachment" "ec2_secretsmanager" {
role = aws_iam_role.ec2.name
policy_arn = aws_iam_policy.ec2_secretsmanager.arn
}
resource "aws_iam_instance_profile" "ec2" {
name = "interface-endpoint-ec2-instance-profile"
role = aws_iam_role.ec2.name
}
########################################
# EC2
########################################
resource "aws_instance" "private_ec2" {
ami = data.aws_ami.al2023.id
instance_type = "t3.micro"
subnet_id = aws_subnet.private_a.id
vpc_security_group_ids = [aws_security_group.ec2.id]
iam_instance_profile = aws_iam_instance_profile.ec2.name
associate_public_ip_address = false
tags = {
Name = "private-ec2-for-interface-endpoint"
}
}
########################################
# Interface VPC Endpoint for Secrets Manager
########################################
resource "aws_vpc_endpoint" "secretsmanager" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${data.aws_region.current.name}.secretsmanager"
vpc_endpoint_type = "Interface"
subnet_ids = [aws_subnet.private_a.id]
security_group_ids = [aws_security_group.vpce.id]
private_dns_enabled = true
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowGetSpecificSecretViaEndpoint"
Effect = "Allow"
Principal = "*"
Action = [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
]
Resource = aws_secretsmanager_secret.sample.arn
}
]
})
tags = {
Name = "secretsmanager-interface-endpoint"
}
}
このTerraformで作られるリソース
このコードを適用すると、プライベートサブネット内に EC2 が作成されます。
ただし、この EC2 には以下がありません。
Internet Gateway なし
NAT Gateway なし
Public IP なし
そのため、通常のインターネット経由では Secrets Manager へ到達できません。
しかし、Secrets Manager 用の Interface 型 VPC エンドポイントを作っているため、EC2 から Secrets Manager への API 通信は可能になります。
private_dns_enabled = true が重要
ここが Interface 型 VPC エンドポイントの重要ポイントです。
private_dns_enabled = true
これを有効にすると、EC2 から通常の AWS サービス名へアクセスしたときに、VPC 内では Interface 型 VPC エンドポイントのプライベート IP へ名前解決されます。
たとえば、通常は以下の名前にアクセスします。
secretsmanager.ap-northeast-1.amazonaws.com
private_dns_enabled = true の場合、VPC 内ではこの DNS 名がエンドポイント ENI のプライベート IP へ解決されます。
そのため、アプリケーション側の接続先 URL を変えなくてもよくなります。これは非常に大きなメリットです。
Gateway型とInterface型の通信の違い
Gateway 型は、ルートテーブルで S3 や DynamoDB への通信をエンドポイントに向けます。
EC2
↓
ルートテーブルを見る
↓
S3宛てならGateway Endpointへ
↓
S3
Interface 型は、DNS でエンドポイントENIに向けます。
EC2
↓
AWSサービス名を名前解決
↓
エンドポイントENIのプライベートIPが返る
↓
Interface EndpointへHTTPS通信
↓
AWS PrivateLink
↓
AWSサービス
以下のように覚えると分かりやすいです。
Gateway型 = ルートテーブル型
Interface型 = ENI + DNS + Security Group型
設計ポイント
1. サービスごとにエンドポイントが必要
Interface 型は基本的に、
Secrets Manager用エンドポイント
SSM用エンドポイント
ECR用エンドポイント
CloudWatch Logs用エンドポイント
のように、サービスごとに作ります。
2. セキュリティグループで通信元を絞る
Interface 型 VPC エンドポイントにはセキュリティグループを付けられるため、
EC2のSecurity Groupからのみ443を許可
のようにできます。
これは非常に分かりやすい制御です。
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
security_groups = [aws_security_group.ec2.id]
}
この設定により、指定したEC2 Security GroupからのHTTPS通信だけを許可できます。
3. エンドポイントポリシーでアクセス先を絞る
セキュリティグループはネットワークレベルの制御です。
一方、エンドポイントポリシーは AWS API レベルの制御です。
たとえば、今回の例では、
このVPCエンドポイント経由では、特定のSecretだけ取得可能
にしています。
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowGetSpecificSecretViaEndpoint"
Effect = "Allow"
Principal = "*"
Action = [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
]
Resource = aws_secretsmanager_secret.sample.arn
}
]
})
ただし、これだけではEC2がSecretを取得できるわけではありません。
EC2のIAMロールにも許可が必要です。
IAMロールで許可
かつ
エンドポイントポリシーで許可
の両方が必要です。
4. private_dns_enabled を有効にする
すでに上述していますが、通常はこれを有効にします。
private_dns_enabled = true
これにより、アプリケーション側は通常の AWS サービスエンドポイントをそのまま使えます。
無効にすると、VPC エンドポイント固有の DNS 名を指定する必要が出てきて、アプリケーション側の設定が面倒になります。
