はじめに

VPC内のリソース(EC2など)からS3バケットへプライベートアクセスしたい場合は、S3ゲートウェイVPCエンドポイントが採用されることが多いと思われるが、S3ゲートウェイVPCエンドポイントは同じリージョンにあるS3バケットしか対応していない。
そのため、別リージョンにあるS3バケットへAWS内部ネットワークのみを経由してプライベートにアクセスしたい場合は、今回の構成のように別リージョンのVPC、VPCピアリング、別リージョンのS3インターフェースVPCエンドポイントが必要になる。
(VPCピアリングはTransit Gatewayにて代替可能だがここでは割愛する)

この記事ではその構成を構築するに当たっての詳細な設定手順を解説する。
なお、インターネットアクセスの無いEC2インスタンスへコンソール接続する方法は、VPCエンドポイントを備えたSSMセッションマネージャーやEC2 Instance Connect Endpointを備えたEC2 Instance Connectなどがあるが、今回はシンプルかつ費用を抑えられるEC2 Instance Connectを使用した。
また、前提として同じAWSアカウントを使用している。

構成図

art97267_diagram

手順

1. 接続元AWSリージョン(構成図左)のリソース構築

1-1. VPCとルートテーブル(ルート)の作成

まずはVPCを作成する。
この構成ではVPCピアリングを使用するため、接続元VPCと接続先VPCのCIDR範囲が重複しないよう考慮する。
VPCの設定例は次の通り。CIDR範囲は「10.0.0.0/16」とし、DNSホスト名とDNS解決は共に有効にする。
メインルートテーブルはターゲット「local」の送信先「10.0.0.0/16」としておくが、サブネットにアタッチなどはせず基本的に使用しない。
art97267_step1-1

1-2. プライベートサブネットとルートテーブル(ルート)の作成

プライベートサブネットを作成する。設定例は次の通りで、CIDR範囲は「10.0.0.0/20」としアベイラビリティゾーンは「ap-northeast-1a」とする。
またこのプライベートサブネットにアタッチするルートテーブルとそのルートは、この時点ではメインルートテーブルと同じくターゲット「local」の送信先「10.0.0.0/16」とする。
art97267_step1-2

1-3. EC2 Instance Connect Endpointとそのセキュリティグループの作成

EC2 Instance Connect Endpointを作成する。VPCコンソールでは、VCP > エンドポイント > エンドポイントを作成 から作成できる。
こちらも設定例は次の通り。VPCとサブネットは「1-1」「1-2」で作成したものを選択し、エンドポイントタイプ(作成画面の表記ではサービスカテゴリ)は「EC2 Instance Connect Endpoint」(作成画面の表記では「EC2 インスタンス接続エンドポイント」)となる。
art97267_step1-3
セキュリティグループは個人的な慣習でセルフルールをインバウンドに追加しているがいるが無くても良い。またアウトバウンドも慣習的に全てのプロトコルで「0.0.0.0/0」を許可しているが、ポート22のSSHのみをEC2インスタンスのセキュリティグループに許可してやるだけでも良い。
art97267_step1-3-2
art97267_step1-3-3

1-4. EC2インスタンスとセキュリティグループの作成

EC2インスタンスを作成する。
AMIはAWS CLIが組み込まれている方が都合が良いため、Amazon Linux 2023のものを使用した。
デプロイするサブネットは「1-2」で作成したものを選択する。
アタッチするIAMロールには「AmazonS3FullAccess」AWSマネージドポリシーを使用した。
art97267_step1-4
セキュリティグループについては、インバウンドにポート22のSSHでソースを「1-3」で作成したEC2 Instance Connect Endpointのセキュリティグループとする。
アウトバウンドは「1-3」と同じく全てのプロトコルで「0.0.0.0/0」としている。
art97267_step1-4-2
art97267_step1-4-3

2. 接続先AWSリージョン(構成図右)のリソース構築

2-1. VPCとルートテーブル(ルート)の作成

「1-1」と同様にVPCを作成するが、既に述べたように接続元VPCと接続先VPCのCIDR範囲が重複しないよう考慮する。
CIDR範囲は「10.1.0.0/16」とし、それ以外は同様で良い。(ルートは変更したCIDR範囲に合わせて設定すること)

2-2. プライベートサブネットとルートテーブル(ルート)の作成

こちらも「1-2」と同様に作成し、CIDR範囲は「10.1.0.0/20」とする。
ルートテーブルとそのルートも変更したCIDR範囲に合わせて設定する。

2-3. S3インターフェースVPCエンドポイントとセキュリティグループの作成

S3インターフェースVPCエンドポイントを作成する。
サービスは、サービス名「com.amazonaws.us-east-1.s3」の タイプ「Interface」 を選択する。
VPCとサブネットは「2-1」「2-2」で作成したものを選択する。ポリシーはなしで良い。
「プライベート DNS 名が有効になっています」(作成画面の表記では「DNS 名を有効化」)は「いいえ」(作成画面の表記ではチェックなし)とする。
art97267_step2-3
セキュリティグループについては、インバウンドにポート443のHTTPSでソースを接続元VPCのCIDR範囲「10.0.0.0/16」とする。(AWS CLIはHTTPSを使用するため許可している)
アウトバウンドは例の如く全てのプロトコルで「0.0.0.0/0」としている。
art97267_step2-3-2
art97267_step2-3-3

2-4. S3バケットの作成

S3バケットを作成する。
基本的に設定はほとんどデフォルトのままで良い。(ブロックパブリックアクセスのオンや暗号化のSSE-S3など)
ただし、今回はS3バケットへのアップロードをS3インターフェースVPCエンドポイント経由のみに制限したいため、次のようなバケットポリシーを設定する。
「BUCKET_NAME」は作成したバケット名で置換し、「VPC_ENDPOINT_ID」は「2-3」で作成したS3インターフェースVPCエンドポイントID(vpce-*)で置換する。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Deny",
            "Principal": {
                "AWS": "*"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::BUCKET_NAME/*",
            "Condition": {
                "StringNotEquals": {
                    "aws:sourceVpce": "VPC_ENDPOINT_ID"
                }
            }
        },
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::BUCKET_NAME/*",
            "Condition": {
                "StringEquals": {
                    "aws:sourceVpce": "VPC_ENDPOINT_ID"
                }
            }
        }
    ]
}

3. VPCピアリングによる接続

3-1. 接続元AWSリージョンでのVPCピアリングの作成

VPCピアリング接続を作成する。
作成画面では、「VPC ID (リクエスタ)」には「1-1」で作成したVPC IDを指定する。
「ピアリング接続するもう一方の VPC を選択」では、同一アカウントであるため「自分のアカウント」を選択し「リージョン」は「別のリージョン」を選択、プルダウンから「2」のリージョンを選択する。
「VPC ID (アクセプタ)」には「2-1」で作成したVPC IDを指定する。(画像内、DNS設定の部分は設定不要)
art97267_step3-1

3-2. 接続元AWSリージョンでのルートテーブルへのルート追加

「1-2」で作成したルートテーブルに、ターゲットを「3-1」で作成したVPCピアリング接続IDとし送信先を「10.1.0.0/16」としたルートを追加する。
art97267_step3-2

3-3. 接続先AWSリージョンでのVPCピアリングの承諾

接続先リージョンに「3-1」で作成したVPCピアリング接続が表示されているため、そのアクションから「リクエストを承諾」を選択する。
処理が正常に完了するとVPCピアリング接続のステータスが「アクティブ」になる。
art97267_step3-3

3-4. 接続先AWSリージョンでのルートテーブルへのルート追加

「2-2」で作成したルートテーブルに、ターゲットを「3-1」で作成したVPCピアリング接続IDとし送信先を「10.0.0.0/16」としたルートを追加する。
art97267_step3-4

4. 動作確認

4-1. ローカルからEC2への接続

インターネットアクセスの無いEC2インスタンスへEC2 Instance Connect Endpoint経由でコンソール接続する場合は、次のAWS CLIコマンドを実行する。INSTANCE_IDはEC2インスタンスIDで置換する。
aws ec2-instance-connect ssh --instance-id INSTANCE_ID --os-user ec2-user
コマンドが成功すると、次のようにコンソールへ接続できる。
art97267_step4-1
AWS CLIコマンドによる接続には適切な適切なIAM権限が必要であるため、管理者などの強力な権限を使用するか、以下のAWS公式ドキュメントに従って適切な許可を付与すること。
参考:ユーザーに EC2 Instance Connect Endpoint を使用したインスタンスへの接続を許可

4-2. 接続元リージョンのEC2から接続先リージョンのS3へのアップロード

Amazon Linux 2023にはデフォルトで AWS CLIが組み込まれているため、特にセットアップせずAWS CLIをそのまま実行できる。
EC2内で次のLinuxコマンドを実行し、まずはアップロード検証用のダミーファイルを作成する。
touch dummy.txt
作成できたら次のAWS CLIコマンドを実行し作成したダミーファイルをS3バケットへアップロードする。
BUCKET_NAMEは「2-4」作成したS3バケット名で置換し、VPC_ENDPOINT_DNSは「2-3」で作成したS3インターフェースVPCエンドポイントのDNS名で置換する。
aws s3 cp dummy.txt s3://BUCKET_NAME/ --endpoint-url https://bucket.VPC_ENDPOINT_DNS
S3インターフェースVPCエンドポイントのDNS名はコンソールの以下から確認できる。
DNS名が二つあるが上はリージョンのエンドポイントを指しており、下はアベイラビリティゾーンのものを指している。
通信コスト削減などで意図的にAZを指定したい場合を除き、基本的には上のリージョンのエンドポイントで良い。
art97267_step4-2
コマンドが成功すると次のような出力になる。
art97267_step4-2-2
S3コンソール上でもアップロードされたことが確認できる。
バケットポリシーによってS3インターフェースVPCエンドポイントを経由してプライベートネットワーク内でアップロードできたことが証明されている。
art97267_step4-2-3

クリーンアップ

ここまでのAWSリソースを作成している場合は、削除を忘れずに行うこと。
特に2つのリージョンを使用したためリージョン跨ぎで削除する必要があることに注意する。

終わりに

ユースケースとしては、システムのリージョン移行などで移行途中にストレージのS3バケットとコンピューティングのEC2を別リージョンで動かさなければならないタイミングがある場合に使用できる。
ただ、ここまで見てきたように別リージョンのS3バケットへプライベートアクセスする構成はそれなりに労力を必要とする。
そのため、そもそもS3のクロスリージョンレプリケーションなどでS3バケットの方を移動する方法が要件上可能であればまずその方法を検討・採用した方が良い。
以上