はじめに
AWS PrivateLink を利用してサービスを他 VPC や他アカウントへ公開する場合、以下のような構成を採用するケースがよくあります。
Consumer VPC
↓
Interface Endpoint
↓
Endpoint Service
↓
NLB
↓
ALB
↓
ECS
こういうときのTLSどうすんの?をまとめた記事です。
結論
- TLS終端はNLBかALB
- Private DNSで名前解決
Consumer VPC
┌─────────────────────────┐
│ https://api.example.com │
│ │ HTTPS │
│ ▼ │
│ Interface Endpoint │
└─────┬───────────────────┘
│ AWS PrivateLink
▼
Provider VPC
┌─────────────────────────┐
│ Endpoint Service │
│ (Private DNS Name) │
│ │ │
│ ▼ │
│ NLB (TLS Termination) │
│ │ HTTP │
│ ▼ │
│ ALB │
│ │ HTTP │
│ ▼ │
│ ECS │
└─────────────────────────┘
1. TLS終端はNLBかALB
ECS終端にしない理由は、
- 運用コストが小さいから
- ALB~ECSは、AWS管理のVPC内で完結していてリスクが低く、わざわざ暗号化する必要がないから
ゼロトラスト要件のようにセキュリティ要件が厳しい場合は例外
cdk(typescript)での例
(NLB終端)
import * as acm from "aws-cdk-lib/aws-certificatemanager";
const certificate = new acm.Certificate(this, "Certificate", {
domainName: "api.example.com",
validation: acm.CertificateValidation.fromDns(),
});
import * as elbv2 from "aws-cdk-lib/aws-elasticloadbalancingv2";
const alb = new elbv2.ApplicationLoadBalancer(this, "Alb", {
vpc,
internetFacing: false,
});
// ALBのHTTP Listener
const albListener = alb.addListener("HttpListener", {
port: 80,
});
import * as targets from "aws-cdk-lib/aws-elasticloadbalancingv2-targets";
const nlb = new elbv2.NetworkLoadBalancer(this, "Nlb", {
vpc,
internetFacing: false,
});
// ALBをNLBのターゲットとして登録
const targetGroup = new elbv2.NetworkTargetGroup(this, "AlbTargetGroup", {
vpc,
port: 80,
protocol: elbv2.Protocol.TCP,
targets: [
new targets.AlbTarget(alb, 80),
],
});
// NLBのTLS Listener
nlb.addListener("TlsListener", {
port: 443,
protocol: elbv2.Protocol.TLS,
certificates: [certificate],
defaultTargetGroups: [targetGroup],
});
NLB→ALB のターゲットグループはTCPなのに、HTTP Listenerで受けるというのがややこしいところ
NLBとALBのどちらをTLS終端にするかは好みの問題っぽいです。WAFの手前で平文なのが気に入らないならALB終端という程度。
ただし、ALB終端の場合 NLB→ALB はTCPなのが注意点。
2. Private DNSで名前解決
ConsumerがVPCインターフェースエンドポイントを作成すると、AWSはvpce-xxxxxxxx.vpce.amazonaws.comのようなエンドポイントのDNS名を払い出します。
Consumer側の実際の接続先は、このDNS名です。
しかし、TLSの原則として
利用者がアクセスするFQDN
= TLS証明書のFQDN
である必要があります。なぜなら、接続先のDNS名がSNIに送信され、NLBやALBはこのSNIを見て証明書を返すからです。
そのためConsumerは、
api.example.com (TLS証明書のFQDNとする)
↓
vpce-xxxxxxxx.vpce.amazonaws.com
となるよう DNS を設定する必要があります。複数のConsumerがいるならそれぞれで設定しないといけないので面倒。
そこで登場するのがPrivate DNS Nameです。
- ProviderでVPCエンドポイントサービスに、
api.example.comでPrivate DNS Nameを設定 - ConsumerはVPCエンドポイントの
Private DNS Enabledを有効にするだけ -
api.example.com→Interface Endpointが自動的に解決される
つまり、
利用者がアクセスするFQDN
= Private DNS Name
= TLS証明書のFQDN
が実現できる。
cdk(typescript)での例
Provider側
import * as ec2 from "aws-cdk-lib/aws-ec2";
const endpointService = new ec2.CfnVPCEndpointService(this, "EndpointServiceDns", {
networkLoadBalancerArns: [nlb.loadBalancerArn],
privateDnsName: "api.example.com",
acceptanceRequired: true,
});
Consumer側
const endpoint = new ec2.InterfaceVpcEndpoint(this, "Endpoint", {
vpc,
service: new ec2.InterfaceVpcEndpointService(
"{EndpointServiceのvpcEndpointService名}",
443
),
privateDnsEnabled: true, // 承認前はtrueだとデプロイに失敗する
});
エンドポイントサービス側でまだ承認されていないエンドポイントは、Private DNS Enabledを有効にしてデプロイできない。したがって、最初は無効にしてデプロイして承認後に有効化する。
まとめ
AWS PrivateLink + NLB + ALB + ECS 構成では
- TLS終端はNLBかALB
- Private DNSで名前解決
が、シンプル・運用容易・コスト効率のバランスが最も良い構成です。