プライベートなAPI Gatewayに対して、NLB経由でアクセスするための構成を検証しました。NLBの特徴としてプライベートIPを指定できることがあるので、プライベートIPを固定して、かつ、ドメインでアクセスできる構成を試してみたいと思い、実際にCloudFormationで作ってみました。ふつうNLBを再作成するとプライベートIPやエンドポイントが変わってしまいますが、プライベートIPを指定してNLBも作成してあげることで、再作成でもアクセスするIPが変わらなくなります。また、セキュリティ上VPCエンドポイントに直接アクセスできないリソースでも、NLBを間にはさむことでアクセスできるようになるかもしれないということで、構成の幅も広がると思います。
CloudFormationのテンプレートはGitHubにアップロードしてあります。
前提
- プライベートなAPI GatewayとACMは作成済み
NLB用のターゲットグループを作成
ターゲットに指定するIPはAPI Gateway用のVPCエンドポイントのネットワークインターフェースから確認します。
テンプレートは下記になります。
TargetGroupForVpcendpointForNlb:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Sub ${SysName}-${EnvName}-tg-for-nlb
Port: 443
Protocol: TLS
TargetType: ip
VpcId: !Ref VpcId
HealthCheckIntervalSeconds: 30
HealthCheckPort: traffic-port
HealthCheckProtocol: TCP
HealthCheckTimeoutSeconds: 10
HealthyThresholdCount: 2
UnhealthyThresholdCount: 2
Targets:
- Id: !Select [ "0", !Ref VpcEndpointIps ]
Port: 443
- Id: !Select [ "1", !Ref VpcEndpointIps ]
Port: 443
Tags:
- Key: Name
Value: !Sub ${SysName}-${EnvName}-nlb-tg
NLBを作成
ターゲットグループを作成したら、そのターゲットグループを指定したNLBとリスナーを作成します。NLBを作成する際にプライベートIPを指定して作成するようにします。
テンプレートは下記になります。
NetworkLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Sub ${SysName}-${EnvName}-nlb
Scheme: internal
Type: network
SecurityGroups: !Ref SecurityGroupsForNlb
SubnetMappings:
- PrivateIPv4Address: !Select [ "0", !Ref NlbStaticIps ]
SubnetId: !Select [ "0", !Ref NlbSubnetIds ]
- PrivateIPv4Address: !Select [ "1", !Ref NlbStaticIps ]
SubnetId: !Select [ "1", !Ref NlbSubnetIds ]
Tags:
- Key: Name
Value: !Sub ${SysName}-${EnvName}-nlb
NlbTlsListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref NetworkLoadBalancer
Port: 443
Protocol: TLS
DefaultActions:
- Type: forward
TargetGroupArn: !Ref TargetGroupForVpcendpointForNlb
Certificates:
- CertificateArn: !Ref CertificateArn
ここまで設定出来れば、NLBに割り当てたIPでアクセスできるようになります。ただ、証明書の検証でエラーがでます。
$ curl https://172.31.25.205/api/pets/1
curl: (60) SSL: no alternative certificate subject name matches target host name '172.31.25.205'
More details here: https://curl.se/docs/sslcerts.html
curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.
エラーが出ないようにするには証明書の検証を無視してアクセスすることになります。
$ curl --insecure -H "x-apigw-api-id:<api-id>" https://172.31.25.205/api/pets/1
{
"id": 1,
"type": "dog",
"price": 249.99
}
カスタムドメインの設定
API Gatewayとドメインを対応させます。プライベートのAPI Gatewayの場合はリソースポリシーの設定も必要になります。
Resources:
CustomDomainName:
Type: AWS::ApiGateway::DomainNameV2
Properties:
CertificateArn: !Ref CertificateArn
DomainName: !Ref DomainName
EndpointConfiguration:
Types:
- PRIVATE
Policy:
Fn::ToJsonString:
Version: 2012-10-17
Statement:
- Effect: Deny
Principal: "*"
Action: execute-api:Invoke
Resource:
- "execute-api:/*"
Condition:
StringNotEquals:
aws:SourceVpce: !Ref VpcEndpointId
- Effect: Allow
Principal: "*"
Action: execute-api:Invoke
Resource:
- "execute-api:/*"
DomainNameAssociation:
Type: AWS::ApiGateway::DomainNameAccessAssociation
Properties:
AccessAssociationSource: !Ref VpcEndpointId
AccessAssociationSourceType: VPCE
DomainNameArn: !Ref CustomDomainName
ApiPathMapping:
Type: AWS::ApiGateway::BasePathMappingV2
Properties:
DomainNameArn: !Ref CustomDomainName
RestApiId: !Ref RestApiId
Stage: api
プライベートホステッドゾーンにレコード登録
プライベートホステッドゾーンにAレコードを登録します。Aレコードの値にはNLBに割り当てたプライベートIPを指定します。
PrivateHostedZone:
Type: AWS::Route53::HostedZone
Properties:
Name: !Ref DomainName
VPCs:
- VPCId: !Ref VpcId
VPCRegion: !Sub ${AWS::Region}
DomainRecordSet:
Type: AWS::Route53::RecordSet
Properties:
HostedZoneId: !Ref PrivateHostedZone
Name: !Ref DomainName
ResourceRecords: !Ref IpAddresses
Type: A
TTL: "300"
これでリソースの作成は終わりです。あとは動作確認になります。
動作確認
名前解決できているか確認します。
$ dig api.hogehoge.com
; <<>> DiG 9.18.28 <<>> api.hogehoge.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 57538
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;api.androidpug.com. IN A
;; ANSWER SECTION:
api.hogehoge.com. 253 IN A 172.31.42.17
api.hogehoge.com. 253 IN A 172.31.25.205
;; Query time: 0 msec
;; SERVER: 172.31.0.2#53(172.31.0.2) (UDP)
;; WHEN: Wed Jan 01 12:52:15 UTC 2025
;; MSG SIZE rcvd: 79
NLBに割り当てたプライベートIPが表示されていますね。次に、ドメインでアクセスできることを確認します。
$ curl https://api.hogehoge.com/pets/1
{
"id": 1,
"type": "dog",
"price": 249.99
}
アクセス出来ているので無事に設定完了しました。
参考資料
リソース作成で参考にした情報は↓になります。