There is this wicked post about configuring an API Gateway method for CORS through CloudFormation, and I'm giving it a go. I want to create the following endpoint with two methods, "options" and "post":
/image/submit
Here is my CF template snippet:
ApiDefault:
Type: "AWS::ApiGateway::RestApi"
Properties:
Name: "Stash-Default"
FailOnWarnings: true
ApiDefaultDeployment:
Type: AWS::ApiGateway::Deployment
DependsOn:
- "ApiMethodImageSubmitPost"
- "ApiMethodImageSubmitOption"
Properties:
RestApiId: !Ref "ApiDefault"
StageName: "v1"
ApiResourceImage:
Type: "AWS::ApiGateway::Resource"
Properties:
ParentId: !GetAtt ["ApiDefault", "RootResourceId"]
PathPart: "image"
RestApiId: !Ref "ApiDefault"
ApiResourceImageSubmit:
Type: "AWS::ApiGateway::Resource"
Properties:
ParentId: !Ref "ApiResourceImage"
PathPart: "submit"
RestApiId: !Ref "ApiDefault"
ApiMethodImageSubmitPost:
Type: "AWS::ApiGateway::Method"
Properties:
HttpMethod: "POST"
AuthorizationType: "NONE"
MethodResponses:
- StatusCode: "200"
Integration:
IntegrationHttpMethod: "POST"
Type: "AWS_PROXY"
IntegrationResponses:
- StatusCode: "200"
Credentials: !GetAtt [ "ExecuteApiMethodImageSubmit", "Arn" ]
Uri: !Sub
- "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations"
- lambdaArn: !GetAtt [ "ImageReceive", "Arn" ]
RestApiId: !Ref "ApiDefault"
ResourceId: !Ref "ApiResourceImageSubmit"
ApiMethodImageSubmitOption:
Type: "AWS::ApiGateway::Method"
Properties:
HttpMethod: "OPTIONS"
AuthorizationType: "NONE"
Integration:
Type: "MOCK"
IntegrationResponses:
- StatusCode: "200"
ResponseParameters:
method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
method.response.header.Access-Control-Allow-Methods: "'POST,OPTIONS'"
method.response.header.Access-Control-Allow-Origin: "'*'"
MethodResponses:
- StatusCode: "200"
ResponseModels:
application/json: "Empty"
ResponseParameters:
method.response.header.Access-Control-Allow-Headers: false
method.response.header.Access-Control-Allow-Methods: false
method.response.header.Access-Control-Allow-Origin: false
RestApiId: !Ref "ApiDefault"
ResourceId: !Ref "ApiResourceImageSubmit"
It bombs saying ApiMethodImageSubmitPost:
Method already exists for this resource (Service: AmazonApiGateway;
Status Code: 409; Error Code: ConflictException; Request ID:
454cf46a-b434-4626-bd4b-b6d4fe21142c)
Can you create two http-methods for a single API resource in this fashion? I'm not having a ton of luck with AWS' docs on this one.
Related
for testing purposes, I need to create Postgres Database which will have Public access and will be available from anywhere. My current CloudFormation looks like this:
---
AWSTemplateFormatVersion: "2010-09-09"
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
InternetGateway:
Type: AWS::EC2::InternetGateway
VPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
MapPublicIpOnLaunch: true
AvailabilityZone: eu-central-1a
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.2.0/24
MapPublicIpOnLaunch: true
AvailabilityZone: eu-central-1b
DBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: Subnets for RDS database
SubnetIds:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow all inbound traffic
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
CidrIp: 0.0.0.0/0
DBInstance:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceIdentifier: ourpostgres
DBName: "database"
AllocatedStorage: "5"
DBInstanceClass: db.t3.micro
Engine: postgres
VPCSecurityGroups:
- !Ref SecurityGroup
DBSubnetGroupName: !Ref DBSubnetGroup
PubliclyAccessible: true
MasterUsername: myusername
MasterUserPassword: mypassword
Outputs:
DBInstanceEndpoint:
Description: Endpoint to access the Postgres database
Value: !GetAtt [DBInstance, Endpoint.Address]
After running this CloudFormation database instance starts successfully but I am still unable to login into the database from a local machine (using Sequel Pro as the viewer).
I tried already set up VPC, Security Groups, Gateway and Subnets but it still seems like I am missing something.
Can you help me to identify the issues in CloudFormation above?
The reason for your issue is that your subnets are private.
While the CloudFormation template does create an Internet Gateway and attaches it to the VPC, the two subnets are using the default Route Table, which does not contain a route to the Internet Gateway. Therefore, the subnets are effectively private subnets.
You would need to update the template to:
Create a Route Table
Route traffic for 0.0.0.0/0 to the Internet Gateway
Associate the Route Table to both of the subnets
As an example, here's some code that I grabbed from Building a VPC with CloudFormation - Part 1:
# Some route tables for our subnets:
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: Public
PublicRoute1: # Public route table has direct routing to IGW:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
You would need something similar to that.
With help of the community, I was able to make this running.
Here is the the complete solution
---
AWSTemplateFormatVersion: "2010-09-09"
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
InternetGateway:
Type: AWS::EC2::InternetGateway
VPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: Public
PublicRoute1:
Type: AWS::EC2::Route
DependsOn: InternetGateway
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
MapPublicIpOnLaunch: true
AvailabilityZone: eu-central-1a
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.2.0/24
MapPublicIpOnLaunch: true
AvailabilityZone: eu-central-1b
Subnet1Association:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: PublicRouteTable
SubnetId:
Ref: PublicSubnet1
Subnet2Association:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: PublicRouteTable
SubnetId:
Ref: PublicSubnet2
DBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: Subnets for RDS database
SubnetIds:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow all inbound traffic
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
CidrIp: 0.0.0.0/0
DBInstance:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceIdentifier: ourpostgres
DBName: "dividends"
AllocatedStorage: "5"
DBInstanceClass: db.t3.micro
Engine: postgres
VPCSecurityGroups:
- !Ref SecurityGroup
DBSubnetGroupName: !Ref DBSubnetGroup
PubliclyAccessible: true
MasterUsername: myusername
MasterUserPassword: mypassword
Outputs:
DBInstanceEndpoint:
Description: Endpoint to access the Postgres database
Value: !GetAtt [DBInstance, Endpoint.Address]
Also do not use SequelPro for connecting to Postgres Database
see https://stackoverflow.com/a/60947061/14027842
I have a role defined like this:
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
AWSAccountId:
Type: String
OidcProvider:
Type: String
AppNamespace:
Type: String
AppServiceAccountName:
Type: String
Resources:
CloudWatchRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Principal:
Federated:
- !Join ["", [ "arn:aws:iam::", !Ref AWSAccountId, ":oidc-provider/", !Ref OidcProvider ] ]
Action:
- "sts:AssumeRoleWithWebIdentity"
Condition:
StringEquals:
!Sub ${OidcProvider}:sub: "system:serviceaccount:${AppNamespace}:${AppServiceAccountName}"
My challenge is how to substitute parameters in the StringEquals section. Everything works in the Federated block. But in the StringEquals block I couldn't get join or sub to work.
With the code as is, I get error message:
An error occurred (ValidationError) when calling the CreateStack operation:
Template format error[/Resources/CloudWatchRole/Properties/AssumeRolePolicyDocument/
Statement/0/Condition/StringEquals] map keys must be strings; received a map instead
So, I guess my issue is how to substitute variables in the keys of a map. UserData didn't help either.
You problem is on Federated not on StringEquals.
Federated value needs to be string but you define it as Map. Please remove - before !Join.
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
AWSAccountId:
Type: String
OidcProvider:
Type: String
AppNamespace:
Type: String
AppServiceAccountName:
Type: String
Resources:
CloudWatchRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument: !Sub
- |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "${IamOidcProviderArn}"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"${OidcProvider}:sub": "system:serviceaccount:${AppNamespace}:${AppServiceAccountName}"
}
}
}
]
}
- IamOidcProviderArn: !Join
- ''
- - 'arn:aws:iam::'
- !Ref AWSAccountId
- ':oidc-provider/'
- !Ref OidcProvider
OidcProvider: !Ref OidcProvider
AppNamespace: !Ref AppNamespace
AppServiceAccountName: !Ref AppServiceAccountName
I have set a HTTP proxy with AWS::Serverless::Api using SAM. Although I was able to set CacheClusterEnabled and CacheClusterSize. I have not found a property to set the time to live of cache data. Where can I set this config?
Here is my templates.yml file:
Resources:
ProxyApi:
Type: AWS::Serverless::Api
Properties:
CacheClusterEnabled: !FindInMap [EnvMap, !Ref Env, cacheClusterEnabled]
CacheClusterSize: !FindInMap [EnvMap, !Ref Env, cacheClusterSize]
Name: !Join [ '-', [!Ref Env, 'lead-generation-proxy'] ]
StageName: !Ref Env
DefinitionBody:
Fn::Transform:
Name: AWS::Include
Parameters:
Location: openapi/proxy.yml
And here is the API created:
openapi: 3.0.1
info:
version: 1.0.0
paths:
"/{proxy+}":
x-amazon-apigateway-any-method:
parameters:
- name: proxy
in: path
required: true
schema:
type: string
responses: {}
x-amazon-apigateway-integration:
responses:
default:
statusCode: '200'
requestParameters:
integration.request.path.proxy: method.request.path.proxy
uri:
Fn::FindInMap : [EnvMap, Ref: Env, proxyUrl]
passthroughBehavior: when_no_match
httpMethod: ANY
type: http_proxy
Anyone interested in the answer. I have found that we could configure these attributes with https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html#cfn-apigateway-stage-methodsetting-datatraceenabled. Here is my template with this configuration:
ProxyApi:
Type: AWS::Serverless::Api
Properties:
Name: !Join ["-", [!Ref Env, "lead-generation-proxy"]]
StageName: !Ref Env
TracingEnabled: !FindInMap [EnvMap, !Ref Env, tracingEnabled]
CacheClusterEnabled: !FindInMap [EnvMap, !Ref Env, cacheClusterEnabled]
CacheClusterSize: !FindInMap [EnvMap, !Ref Env, cacheClusterSize]
MethodSettings:
- HttpMethod: '*'
ResourcePath: '/*'
LoggingLevel: INFO
CacheTtlInSeconds: 400
CachingEnabled: !FindInMap [EnvMap, !Ref Env, cacheClusterEnabled]
DataTraceEnabled: !FindInMap [EnvMap, !Ref Env, cacheClusterEnabled]
MetricsEnabled: !FindInMap [EnvMap, !Ref Env, cacheClusterEnabled]
DefinitionBody:
Fn::Transform:
Name: AWS::Include
Parameters:
Location: openapi/proxy.yml
I am having a problem getting the requestTemplates and responseTemplates I define to work.
The relevant piece of my (processed) template is here:
/cse:
options:
x-amazon-apigateway-integration:
type: mock
requestTemplates:
application/json: |
{
"statusCode" : 200
}
responses:
default:
statusCode: '200'
responseTemplates:
application/json: |
{}
responseParameters:
method.response.header.Access-Control-Allow-Origin: '''*'''
method.response.header.Access-Control-Allow-Methods: '''GET,OPTIONS'''
summary: CORS support
responses:
'200':
headers:
Access-Control-Allow-Origin:
schema:
type: string
Access-Control-Allow-Methods:
schema:
type: string
description: Default response for CORS method
get:
x-amazon-apigateway-integration:
httpMethod: POST
requestTemplates:
application/json: |
{
"body": $input.json('$'),
"headers": {
#foreach($header in $input.params().header.keySet())
"$header": "$util.escapeJavaScript($input.params().header.get($header))" #if($foreach.hasNext),#end
#end
},
"method": "$context.httpMethod",
"params": {
#foreach($param in $input.params().path.keySet())
"$param": "$util.escapeJavaScript($input.params().path.get($param))" #if($foreach.hasNext),#end
#end
},
"query": {
#foreach($queryParam in $input.params().querystring.keySet())
"$queryParam": "$util.escapeJavaScript($input.params().querystring.get($queryParam))" #if($foreach.hasNext),#end
#end
}
}
type: aws
responses:
default:
statusCode: 200
responseTemplates:
text/html: $input.path('$')
responseParameters:
method.response.header.Access-Control-Allow-Origin: '''*'''
method.response.header.Access-Control-Allow-Methods: '''GET,OPTIONS'''
uri: !Sub >-
arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${CSELambda.Arn}/invocations
responses:
'200':
headers:
Access-Control-Allow-Origin:
type: string
Access-Control-Allow-Methods:
type: string
description: OK
The options bit is auto-generated by AWS by adding a Cors entry in the template and works fine.
See how it defines a responseTemplates and requestTemplates and it works.
The get bit is the one I am trying to get to work. As far as I can tell, I am following the same format as the auto-generated options bit, but the API Gateway is not setting these 2 templates, and I am at a loss.
Been Googling, trying different things to no avail, so far. Any help appreciated.
I am trying to set up my API Gateway so it has this simple method response:
And I am using CloudFormation and I keep running into errors. I believe this is pretty simple but I am stuck after spending hours reading docs. Here is my method resource (in YAML):
MyMethod:
Type: "AWS::ApiGateway::Method"
Properties:
AuthorizationType: "NONE"
HttpMethod: "GET"
Integration:
Type: AWS
Credentials: !GetAtt MyRole.Arn
IntegrationHttpMethod: "POST"
Uri:
Fn::Join: [ "", [ "arn:aws:apigateway:", Ref: "AWS::Region", ":states:action/SendTaskSuccess" ] ]
PassthroughBehavior: WHEN_NO_TEMPLATES
RequestTemplates:
application/json: |
{
"output": "\"Approve link was clicked.\"",
"taskToken": "$input.params('taskToken')"
}
IntegrationResponses:
- StatusCode: 200
ResponseTemplates: {"application/json": "$input.json('$.body')"}
RequestParameters:
method.request.querystring.taskToken: false
OperationName: succeed
ResourceId: !Ref MyResource
RestApiId: !Ref MyApi
Do I need a MethodResponse property?
Ok it looks like I just had to add this:
MethodResponses:
- StatusCode: 200
ResponseModels: { "application/json": "Empty" }
ApiPATCH:
Type: AWS::ApiGateway::Method
Properties:
RestApiId: APIGateway
ResourceId: ProxyResourceROOT
HttpMethod: PATCH
AuthorizationType: NONE
Integration:
Type: AWS
IntegrationHttpMethod: POST
Uri: !Join
- ''
- - 'arn:aws:apigateway:'
- !Ref 'AWS::Region'
- ':lambda:path/2015-03-31/functions/'
- !GetAtt
- LambdaFunction
- Arn
- /invocations
IntegrationResponses:
- StatusCode: 200
MethodResponses:
- StatusCode: 200
ResponseModels:
application/json: 'Empty'
Yes that's right. You need to add the following:
MethodResponses:
StatusCode: 200
ResponseModels:
application/json: 'Empty'