はじめに
SAMを利用してPrivate APIを作成する良いサンプルがなかったので作りました。
※誤りあればご指摘いただけると幸いです。
前提
・aws-cli、sam-cliがインストール済みであること。
・credentialが設定済みであること。(aws configureが便利)
・SAM用のS3バケットが用意されていること。
サンブルコード
template.yamlのみ確認した場合はこちら↓
サンプルコードの使い方
| 1 2 3 4 5 6 7 8 9 10 11 | # サンプルを取得 git clone https://github.com/sun-bs/apigateway-lambda-private-api-sample.git # template.yaml内のVpcIdとSubnetIdを書き換える vim template.yaml # Lambdaをビルドする sam build # スタックをデプロイする(SAM用のS3バケット名を指定してください) sam deploy --stack-name private-api --s3-bucket [S3バケット名] --region ap-northeast-1 --capabilities CAPABILITY_NAMED_IAM --template .aws-sam/build/template.yaml | 
curlサンプル(動作確認)
apigatewayのIDを置換して、同じVPC内でcurlコマンドを発行してください。
| 1 2 | curl https://[your apigateway id].execute-api.ap-northeast-1.amazonaws.com/api/private-api-endpoint/dummy-get curl -d dummy-post https://[your apigateway id].execute-api.ap-northeast-1.amazonaws.com/api/private-api-endpoint | 
Q & A
Q. Private APIにカスタムドメインを指定できないか。
A. 指定不可です。API Gatewayにカスタムドメインを設定する機能はありますが、privateでは使用できません。
route53への設定を考える方もいらっしゃるかもしれませんが、
・割り当て先であるPrivate Host Zoneに対するAliasレコードは指定不可です。
・内部のネットワークのみで名前解決される前提でCNAMEで指定したとしてもAPI GatewayのTSL証明書記載のドメインとアクセス先ドメインが異なるため、HTTPSの接続時にクライアント側でエラーとなります。
Q. RegionalでPrivateっぽく使えないか。
A. 不可です。VPCのリソースではないため、インターネット経由のアクセスとなってしまいPrivateなネットワークを経由しません。
また、VPC EndpointのポリシーでIP制限は可能ですが、アクセス元VPCやVPCエンドポイントのアクセス制限は不可です。
一応、VPC EndpointのポリシーでNAT GatewayのIPアドレスを指定することで擬似的にPrivateにすることはできます。
Q. open API部分を別ファイルに切り出せないか。
A. DifinitionUriで別ファイルにすることは可能ですが、cloudformationの関数が使用不可となるため不便です。
また、Fn::TransformしたものをDifinitionBodyへ指定することも可能ですが、S3へのファイルアップロードが必要となるため億劫です。
template.yaml
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Mappings:   Constant:     ResourceId:       VpcId: vpc-xxxxxxxxxxxxxxxxx # your VPC ID       SubnetId: subnet-xxxxxxxxxxxxxxxxx # your subnet ID Resources:   ######################################################################   #  Lambda                                                            #   ######################################################################   # Lambda   PrivateApiFunction:     Type: AWS::Serverless::Function     Properties:       CodeUri: lambda_src/       Handler: app.lambda_handler       Runtime: python3.9       FunctionName: private-api       Role: !GetAtt PrivateApiRole.Arn       Events:         GetEndpoint:           Type: Api           Properties:             Path: /private-api-endpoint/{arg}             Method: get             RestApiId: !Ref PrivateApiApiGateway         PostEndpoint:           Type: Api           Properties:             Path: /private-api-endpoint             Method: post             RestApiId: !Ref PrivateApiApiGateway   # Lambda実行ロール   PrivateApiRole:     Type: 'AWS::IAM::Role'     Properties:       AssumeRolePolicyDocument:         Version: "2012-10-17"         Statement:           - Effect: Allow             Principal:               Service:                 - lambda.amazonaws.com             Action: 'sts:AssumeRole'       RoleName: private-api-lambda-role       ManagedPolicyArns:         - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole   ######################################################################   #  API Gateway                                                       #   ######################################################################   # Api GatewayへのLambdaアクセス許可   PrivateApiFunctionPermission:     Type: AWS::Lambda::Permission     Properties:       FunctionName: !GetAtt PrivateApiFunction.Arn       Action: lambda:InvokeFunction       Principal: apigateway.amazonaws.com       SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${PrivateApiApiGateway}/*'   # API Gateway   PrivateApiApiGateway:     Type: AWS::Serverless::Api     Properties:       Name: private-api       StageName: api       MethodSettings:         # ログを有効化         - DataTraceEnabled: true           LoggingLevel: 'INFO'           ResourcePath: '/*'           HttpMethod: '*'       EndpointConfiguration:         Type: PRIVATE         VPCEndpointIds:           - !Ref PrivateApiVpcEndpoint       DefinitionBody:         openapi: 3.0.3         info:           title: private-api           description: private-api         schemes:           - https         consumes:           - application/json         produces:           - application/json         paths:           /private-api-endpoint/{arg}:             get:               # API GatewayがLambdaを呼び出す設定               x-amazon-apigateway-integration:                 contentHandling: "CONVERT_TO_TEXT"                 uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${PrivateApiFunction.Arn}/invocations"                 passthroughBehavior: when_no_templates                 payloadFormatVersion: "1.0"                 connectionType: "INTERNET"                 httpMethod: POST                 type: aws_proxy           /private-api-endpoint:             post:               # API GatewayがLambdaを呼び出す設定               x-amazon-apigateway-integration:                 contentHandling: "CONVERT_TO_TEXT"                 uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${PrivateApiFunction.Arn}/invocations"                 passthroughBehavior: when_no_templates                 payloadFormatVersion: "1.0"                 connectionType: "INTERNET"                 httpMethod: POST                 type: aws_proxy         # VPCエンドポイントからのリクエストのみに制限する。         x-amazon-apigateway-policy:           Version: '2012-10-17'           Statement:             - Effect: Allow               Principal: '*'               Action: 'execute-api:Invoke'               Resource:                 - !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:*/*'               Condition:                 StringEquals:                   aws:SourceVpce: !Ref PrivateApiVpcEndpoint   ######################################################################   #  API Gateway共通設定                                                #   ######################################################################   # API Gatewayのログ出力用ロール   ApiGatewayCloudWatchRole:     Type: 'AWS::IAM::Role'     Properties:       AssumeRolePolicyDocument:         Version: 2012-10-17         Statement:           - Effect: Allow             Principal:               Service:                 - apigateway.amazonaws.com             Action: 'sts:AssumeRole'       Path: /       ManagedPolicyArns:         - arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs   # API Gatewayにログ出力用ロールを設定   ApiGatewayAccount:     Type: 'AWS::ApiGateway::Account'     Properties:       CloudWatchRoleArn: !GetAtt ApiGatewayCloudWatchRole.Arn   ######################################################################   #  VPCエンドポイント                                                   #   ######################################################################   # VPCエンドポイント   PrivateApiVpcEndpoint:     Type: AWS::EC2::VPCEndpoint     Properties:       ServiceName: !Sub com.amazonaws.${AWS::Region}.execute-api       SubnetIds:         - !FindInMap [Constant, ResourceId, SubnetId]       VpcId: !FindInMap [Constant, ResourceId, VpcId]       VpcEndpointType: Interface       SecurityGroupIds:         - !GetAtt PrivateApiSg.GroupId       PrivateDnsEnabled: true   # VPCエンドポイント用セキュリティグループ   PrivateApiSg:     Type: AWS::EC2::SecurityGroup     Properties:       GroupDescription: PrivateApiSg       GroupName: PrivateApiSg       VpcId: !FindInMap [Constant, ResourceId, VpcId]       SecurityGroupIngress:         - IpProtocol: tcp           FromPort: 443           ToPort: 443           CidrIp: 0.0.0.0/0 |