こんにちは。SCSKのふくちーぬです。
皆さんは、プライベート(閉域網)環境下での AWS Lambda や Amazon API Gateway を利用したことありますでしょうか。VPC Lambdaに関する詳細な解説や設計ポイントを知りたい方は、以下の記事から先にご覧いただければ幸いです。
今回は、Amazon API Gateway の VPC エンドポイントを利用する際の落とし穴について解説します。
AWSサービスエンドポイントの種類
AWSでは、各種サービスにおいてサービスへのリクエストを受け付けるエンドポイントを用意しています。以下のように、大きく2種類に分かれています。
・全ての機能を有するサービスエンドポイント
例:Lambda, CloudWatch, CloudFormation …etc
・機能を分割したサービスエンドポイント
例:API Gateway, App Sync, Bedrock …etc
API Gatewayのサービスエンドポイントの提供体系
API Gatewayではサービスエンドポイントとしてコントロールプレーン/データプレーンの2種を用意しています。
API Gatewayにおいては、APIの呼び出し機能を有するデータプレーンのみVPCエンドポイントとして提供されます。
・com.amazonaws.<リージョン>.execute-api
つまり、API の作成・管理機能を有するコントロールプレーンはインターネット経由の通信ではないと利用できません。
・apigateway.<リージョン>.amazonaws.com
結論
- API GatewayのVPCエンドポイントでは、APIの呼び出し機能のみ利用可能である
- プライベート(閉域網)環境にてAWSサービスを利用する際は、各種AWSサービスエンドポイントが、VPCエンドポイントとして提供されているか確認すること
検証
今回は、VPC LambdaにてNAT Gateway経由でAPI Gatewayのエンドポイント一覧を取得します。
アーキテクチャ
- VPC Lambdaは、プライベートサブネットに配置します。
- VPC Lambdaは、NAT Gateway経由でAPI Gatewayのコントールプレーンにアクセスします。
CloudFormationテンプレート
下記のリソースを作成するCloudFormationテンプレートです。
- VPC
- インターネットゲートウェイ
- NATゲートウェイ
- パブリックサブネット、プライベートサブネット
- ルートテーブル
- セキュリティグループ
- Lambda
- IAMロール
以下のテンプレートをデプロイしてください。
AWSTemplateFormatVersion: 2010-09-09 Description: cfn vpc lambda Parameters: ResourceName: Type: String Resources: # ------------------------------------------------------------# # VPC # ------------------------------------------------------------# VPC: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/16 EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Name Value: !Sub "${ResourceName}-VPC" # ------------------------------------------------------------# # Subnet # ------------------------------------------------------------# PublicSubnet1a: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC CidrBlock: 10.0.0.0/24 AvailabilityZone: ap-northeast-1a MapPublicIpOnLaunch: false Tags: - Key: Name Value: !Sub "${ResourceName}-PublicSubnet-1a" PublicSubnet1c: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC CidrBlock: 10.0.1.0/24 AvailabilityZone: ap-northeast-1a MapPublicIpOnLaunch: false Tags: - Key: Name Value: !Sub "${ResourceName}-PublicSubnet-1c" PrivateSubnet1a: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC CidrBlock: 10.0.2.0/24 AvailabilityZone: ap-northeast-1a MapPublicIpOnLaunch: false Tags: - Key: Name Value: !Sub "${ResourceName}-PrivateSubnet-1a" PrivateSubnet1c: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC CidrBlock: 10.0.3.0/24 AvailabilityZone: ap-northeast-1c MapPublicIpOnLaunch: false Tags: - Key: Name Value: !Sub "${ResourceName}-PrivateSubnet-1c" # ------------------------------------------------------------# # InternetGateway # ------------------------------------------------------------# InternetGateway: Type: "AWS::EC2::InternetGateway" Properties: Tags: - Key: Name Value: !Sub "${ResourceName}-igw" InternetGatewayAttachment: Type: "AWS::EC2::VPCGatewayAttachment" Properties: InternetGatewayId: !Ref InternetGateway VpcId: !Ref VPC # ------------------------------------------------------------# # NATGateway # ------------------------------------------------------------# EIP: Type: "AWS::EC2::EIP" Properties: Domain: vpc NATGateway: Type: "AWS::EC2::NatGateway" Properties: AllocationId: !GetAtt EIP.AllocationId SubnetId: !Ref PublicSubnet1a Tags: - Key: Name Value: !Sub "${ResourceName}-natgw" # ------------------------------------------------------------# # RouteTable # ------------------------------------------------------------# PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${ResourceName}-PublicRouteTable" PublicRoute: Type: "AWS::EC2::Route" Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: "0.0.0.0/0" GatewayId: !Ref InternetGateway PublicSubnet1aAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet1a RouteTableId: !Ref PublicRouteTable PublicSubnet1cAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet1c RouteTableId: !Ref PublicRouteTable PrivateRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${ResourceName}-PrivateRouteTable" PrivateRoute: Type: "AWS::EC2::Route" Properties: RouteTableId: !Ref PrivateRouteTable DestinationCidrBlock: "0.0.0.0/0" NatGatewayId: !Ref NATGateway PrivateSubnet1aAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnet1a RouteTableId: !Ref PrivateRouteTable PrivateSubnet1cAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnet1c RouteTableId: !Ref PrivateRouteTable # ------------------------------------------------------------# # Security Group # ------------------------------------------------------------# LambdaSecurityGrouptoFull: Type: AWS::EC2::SecurityGroup Properties: GroupName: !Sub "${ResourceName}-outbound-full-sg" GroupDescription: Security Group for Lambda to full VpcId: !Ref VPC SecurityGroupEgress: - IpProtocol: -1 CidrIp: 0.0.0.0/0 # ------------------------------------------------------------# # Lambda # ------------------------------------------------------------# LambdaFunction: Type: 'AWS::Lambda::Function' Properties: Handler: index.lambda_handler Role: !GetAtt LambdaExecutionRole.Arn FunctionName: !Sub "${ResourceName}-get-api" Runtime: python3.12 Timeout: 3 MemorySize: 128 Code: ZipFile: | import boto3 import json from datetime import date, datetime client_api = boto3.client('apigateway') def json_serial(obj): # 日付型の場合には、文字列に変換します if isinstance(obj, (datetime, date)): return obj.isoformat() # 上記以外はサポート対象外 raise TypeError("Type %s not serializable" % type(obj)) def lambda_handler(event, context): response = client_api.get_rest_apis() response_serial = json.dumps(response, default=json_serial) print(response_serial) return response_serial VpcConfig: SecurityGroupIds: - !Ref LambdaSecurityGrouptoFull SubnetIds: - !Ref PrivateSubnet1a - !Ref PrivateSubnet1c FunctionLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub "/aws/lambda/${LambdaFunction}" # ------------------------------------------------------------# # IAM Role # ------------------------------------------------------------# LambdaExecutionRole: Type: AWS::IAM::Role Properties: RoleName: !Sub "${ResourceName}-IRL-LAMBDA" AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: 'Allow' Principal: Service: 'lambda.amazonaws.com' Action: 'sts:AssumeRole' ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole - !GetAtt LambdaPolicy.PolicyArn LambdaPolicy: Type: AWS::IAM::ManagedPolicy Properties: ManagedPolicyName: !Sub ${ResourceName}-lambda-policy Description: IAM Managed Policy with APIGateway GET Access PolicyDocument: Version: '2012-10-17' Statement: - Effect: 'Allow' Action: - 'apigateway:GET' Resource: - '*'
API情報取得の確認
Lambdaにて、テストイベントを発行します。
以下のように、ロググループ内にてAPI情報が取得できていれば成功です。
最後に
いかがだったでしょうか。
各種AWSサービスがどのような形でサービスエンドポイントを提供しているか解説しました。API GatewayのVPCエンドポイントの利用用途としては、プライベート型API Gatewayの呼び出しに使用できることをご留意ください。
閉域網環境でAWSを利用する際は、各種AWSサービスがVPCエンドポイントをサポートしているか確認していただければ幸いです。
本記事が皆様のお役にたてば幸いです。
ではサウナラ~🔥