NLB + S3 Interface Endpoint で固定 IP アクセスが技術的に不可能ではないことを確認してみた

NLB + S3 Interface Endpoint で固定 IP アクセスが技術的に不可能ではないことを確認してみた

オンプレミスの Firewallで IP ホワイトリスト管理をしている環境から S3 にアクセスする際、NLB(EIP)+ S3 Interface Endpoint の組み合わせで固定 IP 経由のアクセスが可能か検証しました。
2026.05.27

はじめに

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 等)を許可できる場合や、ドメイン許可のプロキシが利用できる場合は、そちらの利用をおすすめします。

参考リンク

https://docs.aws.amazon.com/AmazonS3/latest/userguide/privatelink-interface-endpoints.html

https://docs.aws.amazon.com/vpc/latest/privatelink/privatelink-access-aws-services.html

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事