NLB + S3 Interface Endpoint で固定 IP アクセスが技術的に不可能ではないことを確認してみた
はじめに
S3 の IP アドレスレンジは数百規模で変動します。オンプレミスの FW に IP ホワイトリストとして登録・維持するのは現実的ではありません。
VPN や Direct Connect が使えないが、FW の都合で接続先 IP の指定が必須——という環境を想定し、NLB に EIP を付与して固定 IP を確保し、バックエンドの S3 Interface Endpoint に TCP passthrough で転送する構成を試しました。
検証内容
構成とメカニズム
全体の通信フローは以下のとおりです。
DNS / hosts 設定:
bucket.vpce-xxxxxxxxxx.s3.ap-northeast-1.vpce.amazonaws.com → NLB の EIP
オンプレサーバー
│ aws s3 cp --endpoint-url https://bucket.vpce-xxxxxxxxxx.s3.ap-northeast-1.vpce.amazonaws.com
│
▼ (名前解決で NLB の EIP に到達)
NLB (EIP = 固定 IP) ← FW にはこの IP だけ登録
│ TCP passthrough (443)(TLS に関与しない)
▼
S3 Interface Endpoint (ENI)
│ PrivateLink 経由
▼
S3 バケット
この構成が動く理由は3つあります。
- NLB は TCP passthrough のため TLS に関与せず、S3 Interface Endpoint の証明書がクライアントにそのまま返る
- 証明書の SAN に
bucket.vpce-xxxxxxxxxx.s3.ap-northeast-1.vpce.amazonaws.comが含まれているため、endpoint-url で指定したホスト名での SSL 検証が通過する - AWS CLI は Host ヘッダを含めて SigV4 署名を計算する。DNS の向き先だけ NLB に変えても Host ヘッダは S3 Endpoint 向けの名前のままなので、S3 側の署名検証も成功する
つまり「DNS だけ NLB に向けて、TLS と署名はエンドツーエンドで S3 Endpoint と直接やりとりしている」状態です。
なお、bucket.vpce-xxxxxxxxxx.s3.ap-northeast-1.vpce.amazonaws.com の先頭 bucket は S3 Interface Endpoint が持つ bucket 用 DNS ラベルであり、S3 バケット名ではありません。今回の検証では、実際のバケット名はリクエストパスに含まれていました。
Host: bucket.vpce-xxxxxxxxxx.s3.ap-northeast-1.vpce.amazonaws.com
PUT /example-bucket/test.txt
検証用 CloudFormation テンプレート
CloudFormation テンプレート全文
AWSTemplateFormatVersion: "2010-09-09"
Description: "NLB (EIP) + S3 Interface Endpoint - On-premises fixed IP access to S3"
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/24
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: s3-fixed-ip
Subnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.0.0/25
AvailabilityZone: ap-northeast-1a
Tags:
- Key: Name
Value: s3-fixed-ip-subnet
EndpointSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow HTTPS from VPC
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 10.0.0.0/24
S3Endpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPC
ServiceName: com.amazonaws.ap-northeast-1.s3
VpcEndpointType: Interface
SubnetIds:
- !Ref Subnet
SecurityGroupIds:
- !Ref EndpointSG
PrivateDnsEnabled: false
EIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
Tags:
- Key: Name
Value: s3-fixed-ip-nlb
NLB:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: s3-fixed-ip-nlb
Scheme: internet-facing
Type: network
# SecurityGroups: 本テンプレートでは省略。実利用を検討する場合はオンプレの送信元 IP に絞った SG を付与すること
SubnetMappings:
- SubnetId: !Ref Subnet
AllocationId: !GetAtt EIP.AllocationId
IGW:
Type: AWS::EC2::InternetGateway
IGWAttach:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref IGW
RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Route:
Type: AWS::EC2::Route
DependsOn: IGWAttach
Properties:
RouteTableId: !Ref RouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref IGW
SubnetRouteTableAssoc:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref Subnet
RouteTableId: !Ref RouteTable
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: tg-s3-endpoint
VpcId: !Ref VPC
Protocol: TCP
Port: 443
TargetType: ip
HealthCheckProtocol: TCP
HealthCheckPort: "443"
Listener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref NLB
Port: 443
Protocol: TCP
DefaultActions:
- Type: forward
TargetGroupArn: !Ref TargetGroup
S3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "s3-fixed-ip-${AWS::AccountId}"
Outputs:
EIPAddress:
Description: "Fixed IP for on-premises firewall whitelist"
Value: !Ref EIP
NLBDNSName:
Value: !GetAtt NLB.DNSName
S3EndpointId:
Value: !Ref S3Endpoint
S3BucketName:
Value: !Ref S3Bucket
TargetGroupArn:
Description: "Register S3 Endpoint ENI IP after stack creation"
Value: !Ref TargetGroup
スタック作成後の手動手順
今回の CloudFormation テンプレートでは、S3 Interface Endpoint の ENI Private IP を取得して NLB TargetGroup に登録する処理は含めていません。スタック作成後に以下の手動操作が必要です。
# S3 Interface Endpoint の ENI IP を取得
ENDPOINT_ID="<S3EndpointId の出力値>"
ENI_ID=$(aws ec2 describe-vpc-endpoints --vpc-endpoint-ids $ENDPOINT_ID \
--query 'VpcEndpoints[0].NetworkInterfaceIds[0]' --output text \
--region ap-northeast-1)
ENI_IP=$(aws ec2 describe-network-interfaces --network-interface-ids $ENI_ID \
--query 'NetworkInterfaces[0].PrivateIpAddress' --output text \
--region ap-northeast-1)
echo "ENI IP: $ENI_IP"
# TargetGroup に登録
TG_ARN="<TargetGroupArn の出力値>"
aws elbv2 register-targets --target-group-arn $TG_ARN \
--targets Id=$ENI_IP,Port=443 --region ap-northeast-1
# ヘルスチェック確認
aws elbv2 describe-target-health --target-group-arn $TG_ARN --region ap-northeast-1
動作確認
3つのパターンで接続を試し、どの条件で成功するかを確認しました。
テスト1: NLB DNS 名を endpoint-url に直接指定
aws s3 cp /tmp/test.txt s3://example-bucket/test.txt \
--endpoint-url https://nlb-example-xxxx.elb.ap-northeast-1.amazonaws.com \
--region ap-northeast-1
結果は SSL validation failed でした。
テスト2: NLB DNS 名 + --no-verify-ssl
aws s3 cp /tmp/test.txt s3://example-bucket/test.txt \
--endpoint-url https://nlb-example-xxxx.elb.ap-northeast-1.amazonaws.com \
--no-verify-ssl --region ap-northeast-1
SSL 検証をスキップしても NoSuchBucket で失敗しました。
テスト3: S3 Interface Endpoint の bucket 用 DNS 名 + hosts で NLB IP に名前解決 ✅
# hosts 設定(オンプレ DNS またはサーバーの /etc/hosts に追加)
echo "<NLB_EIP> bucket.vpce-xxxxxxxxxx.s3.ap-northeast-1.vpce.amazonaws.com" >> /etc/hosts
# アップロード
aws s3 cp /tmp/test.txt s3://example-bucket/test.txt \
--endpoint-url https://bucket.vpce-xxxxxxxxxx.s3.ap-northeast-1.vpce.amazonaws.com \
--region ap-northeast-1
upload: ../../tmp/test.txt to s3://example-bucket/test.txt
検証結果まとめ
| # | endpoint-url ホスト名 | 結果 | 原因 |
|---|---|---|---|
| 1 | NLB DNS 名 | ❌ SSL 失敗 | 証明書 SAN にホスト名が含まれない |
| 2 | NLB DNS 名 + SSL 検証無効 | ❌ NoSuchBucket | Host ヘッダが S3 Endpoint 用でない |
| 3 | S3 Endpoint の bucket 用 DNS 名 | ✅ 成功 | SSL 検証・署名ともに正常 |
注意事項・Tips
TargetGroup への ENI IP 登録は CloudFormation 外で実施しています。Interface Endpoint の ENI IP は Endpoint の存続期間中は基本的に変わりませんが、Endpoint の再作成やサブネット変更時には再登録が必要です。
hosts に S3 Interface Endpoint の bucket 用 DNS 名を1行登録するだけで、複数の異なるバケットにアクセスできました。今回の検証では、AWS CLI が --endpoint-url 指定時にバケット名をリクエストパスに含める形式でリクエストを組み立てたため、通信先ホスト名は bucket.vpce-... のままでした。
default.s3.addressing_style を明示している環境では異なる挙動になる可能性があるため、必要に応じて aws --debug で Host ヘッダとリクエストパスを確認してください。
echo "<NLB_EIP> bucket.vpce-xxxxxxxxxx.s3.ap-northeast-1.vpce.amazonaws.com" >> /etc/hosts
aws s3 cp file1.csv s3://bucket-a/file1.csv \
--endpoint-url https://bucket.vpce-xxxxxxxxxx.s3.ap-northeast-1.vpce.amazonaws.com
aws s3 cp file2.csv s3://bucket-b/file2.csv \
--endpoint-url https://bucket.vpce-xxxxxxxxxx.s3.ap-northeast-1.vpce.amazonaws.com
本検証テンプレートでは NLB の Security Group および VPC Endpoint Policy を省略しています。Security Group なしで作成した NLB には後から Security Group を関連付けられないため、実利用を検討する場合はオンプレ送信元 IP に絞った SG と、バケット・アクションを制限する VPC Endpoint Policy を追加してデプロイしてください。
まとめ
今回の NLB + S3 Interface Endpoint 構成は、FW の IP ホワイトリスト制約がある環境での暫定策として技術的に成立することを確認しました。ただし以下の制約があります。
- オンプレ側で hosts ファイルまたは DNS 設定の変更が必須
- NLB TargetGroup への ENI IP 登録が手動(Endpoint の再作成・構成変更時には再登録が必要)
- NLB + Interface Endpoint の月額コストが継続的に発生する(2026年5月時点、東京リージョン・1 AZ 構成で月額 $30〜50 程度。データ処理料金別途)
FW 側で S3 のドメイン(*.s3.ap-northeast-1.amazonaws.com 等)を許可できる場合や、ドメイン許可のプロキシが利用できる場合は、そちらの利用をおすすめします。
参考リンク







