参考にしたのはこちら
ただ、この例はInternalなALBを用意し、そこから、Private Subnet → VPC Endpoint+ privatelink → S3なので、別にPublic Subnetに置いたALBでもいけたのが、今回の記事の内容です。
結論
VPC Endpoint(Interface)を使うとうまくいきました。
ALB → Vpc Endpoint(Interface) → S3
何が嬉しいのか
- ALB → S3 間で認証を完結できる
SSO(シングルサインオン)等で、社内のみで見れるようにしたい社内サイトとか、認証をALBに集約したい時はとても嬉しいと思います。
OIDCを使用して、ALBに認証後、S3にルーティングすると、アプリ側で認証を実装する手間が減ります。
ALB → S3で認証が完結するということは、特にwordpressなどで構築されたサイトだと、プラグインや自作で認証を実装すると思うのですが、それが不要になります。
概要図
下記の図でいう 4 → 2 です
前提
- VPC(
Public Subnet) - ALB
- S3(ホスト名と同一)
今回は、Public SubnetのVPC Endpointをアタッチする例です。
Private Subnetの場合は、別途 Privatelink 使用するようです。
手順
前提のALBとVPCがあれば、VPC EndpointのIPアドレスをターゲットグループに設定しましょう。
- VPC Endpointを作成
- VPC Endpoint作成後、IPv4 アドレスをコピー
- ALBのターゲットグループ作成(ターゲットの種類
IP、 アドレスタイプIPv4) - ターゲットグループの
IPアドレスに2でコピーしたIPアドレスを設定 - ターゲットグループのヘルスチェックを
307,405にする - 終了
これで、ALB → S3が設定できました。
ユースケース
- ユーザー数少ないけど、認証が必要なアプリ。
- サーバーサイドで認証実装できない(or 面倒)
パフォーマンス向上
- 規模が大きくなってALBから出ていく通信料が気になるようになったら、ALBの前にCloudFront
- ALB - S3間に FileCache、Storage Gatewayをキャッシュサーバーとして検討(未検証。条件:ALBのターゲットグループ設定、CDK対応)
注意
-
S3のバケット名
手順どおりやっても、S3のバケット名が、ホスト名と同一にならないと見つからないです。
例えば、https://sample-test.co.jpであれば、バケット名はsample-test.co.jpです。 -
通信料
もし動画や大きなファイルを扱うのであれば、通信料も大きくなっていくので、ALBの前段にCloudFrontを置くか、ALB - S3間の間にファイルキャッシュできるようなサービスをおくと通信料もレスポンスタイムも早くなるのでしょう。
CDKのコード
実はVpc Endpointから、IPアドレスとるの難しいんだなと。。。
Vpc Endpointから簡単にとれるように貢献したいところ。。。
// vpc endpoint 作成
const vpcEndpoint = vpc.addInterfaceEndpoint("InterfaceVpcEndpointForS3", {
service: InterfaceVpcEndpointAwsService.S3,
subnets: { subnetType: SubnetType.PUBLIC },
privateDnsEnabled: false,
});
// vpc endpoint から IP Addressを取得
const getEndpointPrivateIpAddress = (index: number) => {
const privateIpAddressField = `NetworkInterfaces.${index}.PrivateIpAddress`;
const resource = new AwsCustomResource(this, `GetEndpointIp${index}`, {
onUpdate: {
service: "EC2",
action: "describeNetworkInterfaces",
outputPaths: [privateIpAddressField],
parameters: { NetworkInterfaceIds: vpcEndpoint.vpcEndpointNetworkInterfaceIds },
physicalResourceId: PhysicalResourceId.of(privateIpAddressField),
},
policy: AwsCustomResourcePolicy.fromSdkCalls({ resources: AwsCustomResourcePolicy.ANY_RESOURCE }),
});
return resource.getResponseField(privateIpAddressField);
};
// publicSubnets を取得
const { publicSubnets } = vpcEndpoint.node.scope as Vpc;
// publicSubnets の個数 を取得
const ipAddresses = publicSubnets.map((_, index) => Token.asString(getEndpointPrivateIpAddress(index)));
// ALB の ターゲットグループを作成
const vpcEndpointTargetGroup = new ApplicationTargetGroup(this, "VpcEndpointTargetGroup", {
vpc,
targetType: TargetType.IP,
port: 443,
protocol: ApplicationProtocol.HTTPS,
healthCheck: {
healthyHttpCodes: "307,405",
},
});
// vpc endpoint の IPアドレス ALB の ターゲットグループに設定
for (const ipAddress of ipAddresses) {
vpcEndpointTargetGroup.addTarget(new IpTarget(ipAddress));
}
CDKのほうが大変でした。。。
もっと簡単に、作ったVPC endpointからIPアドレス取得できる方法あったら教えてくださいmm
