Cryptic CloudFormation failure when creating CloudFront Distribution - amazon-web-services

I have a CloudFormation template set up to track a CloudFront distribution among other things. Getting this set up, I created an AWS::CertificateManager::Certificate and an AWS::CloudFront::Distribution resource, where the CDN just serves from a non-website S3 origin.
When I run the change set, I get this incredibly vague failure.
"Access denied for operation 'AWS::CloudFront::Distribution'." kind of loses me here. For one thing, it's not clear to me what operation this is supposed to be. On top of that, the stack rollback after this is incomplete. The CloudFormation events don't even show an attempt to remove the CDN or the cert, and when I try to hit the CloudFront URL from my browser, it works flawlessly, so I am not even sure what my template was trying to do here that failed. In fact, the only reason this is an issue for me is because the incomplete rollback tries to revert my lambdas in the stack to nodejs8.10, which causes larger failures. If that weren't an issue, I don't know that I would feel the effects of this vague error.
Template, based on the static site sample from a couple of years ago:
AWSTemplateFormatVersion: 2010-09-09
Transform:
- AWS::Serverless-2016-10-31
- AWS::CodeStar
Parameters:
ProjectId:
Type: String
Description: AWS CodeStar projectID used to associate new resources to team members
CodeDeployRole:
Type: String
Description: IAM role to allow AWS CodeDeploy to manage deployment of AWS Lambda functions
Stage:
Type: String
Description: The name for a project pipeline stage, such as Staging or Prod, for which resources are provisioned and deployed.
Default: ''
Globals:
Api:
BinaryMediaTypes:
- image~1png
Function:
Runtime: nodejs14.x
AutoPublishAlias: live
DeploymentPreference:
Enabled: true
Type: Canary10Percent5Minutes
Role: !Ref CodeDeployRole
Resources:
MahCert:
Type: AWS::CertificateManager::Certificate
Properties:
DomainName: domain.com
DomainValidationOptions:
- DomainName: domain.com
HostedZoneId: Z2GZX5ZQI1HO5L
SubjectAlternativeNames:
- '*.domain.com'
CertificateTransparencyLoggingPreference: ENABLED
ValidationMethod: DNS
CloudFrontCDN:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Comment: Source for all static resources
PriceClass: PriceClass_100
Aliases:
- domain.com
ViewerCertificate:
AcmCertificateArn: !Ref MahCert
MinimumProtocolVersion: TLSv1.2_2021
SslSupportMethod: sni-only
DefaultRootObject: index.html
DefaultCacheBehavior:
ViewerProtocolPolicy: redirect-to-https
CachePolicyId: b2884449-e4de-46a7-ac36-70bc7f1ddd6d
TargetOriginId: SiteBucket
Enabled: True
Origins:
- DomainName: <my_bucket>.s3.amazonaws.com
Id: SiteBucket
S3OriginConfig:
OriginAccessIdentity: ''
ServerlessRestApi:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
DefinitionBody:
swagger: 2.0
info:
title: Static resource proxy
paths:
/static/{proxy+}:
get:
x-amazon-apigateway-integration:
httpMethod: ANY
type: http_proxy
uri: <my_bucket>.s3.amazonaws.com/static/{proxy}
responses: {}
GetHelloWorld:
Type: AWS::Serverless::Function
Properties:
Handler: index.get
Role:
Fn::GetAtt:
- LambdaExecutionRole
- Arn
Events:
GetEvent:
Type: Api
Properties:
Path: /
Method: get
ProxyEvent:
Type: Api
Properties:
Path: /{proxy+}
Method: any
GetStaticContent:
Type: AWS::Serverless::Function
Properties:
Handler: index.getResource
Role:
Fn::GetAtt:
- LambdaExecutionRole
- Arn
Events:
GetResourceEvent:
Type: Api
Properties:
Path: /static/{folder}/{file}
Method: get
GetQuote:
Type: AWS::Serverless::Function
Properties:
Handler: index.getQuote
Role:
Fn::GetAtt:
- LambdaDynamoDBReadRole
- Arn
Events:
GetRandomQuoteEvent:
Type: Api
Properties:
Path: /getquote
Method: get
GetQuoteEvent:
Type: Api
Properties:
Path: /getquote/{id}
Method: get
LambdaExecutionRole:
Description: Creating service role in IAM for AWS Lambda
Type: AWS::IAM::Role
Properties:
RoleName: !Sub 'CodeStar-${ProjectId}-Execution${Stage}'
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: [lambda.amazonaws.com]
Action: sts:AssumeRole
Path: /
ManagedPolicyArns:
- !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
PermissionsBoundary: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/CodeStar_${ProjectId}_PermissionsBoundary'
LambdaDynamoDBReadRole:
Description: Creating service role in IAM for AWS Lambda
Type: AWS::IAM::Role
Properties:
RoleName: !Sub '${ProjectId}-DynamoDB-Read'
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: [lambda.amazonaws.com]
Action: sts:AssumeRole
Path: /
Policies:
-
PolicyName: "dynamodb-read-quotes"
PolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Action:
- "dynamodb:GetItem"
- "dynamodb:DescribeTable"
Resource: "<dynamo_arn>"
ManagedPolicyArns:
- !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
Note - domain.com is not the actual domain I'm using here.
Updates:
I deleted the stack completely and recreated it from this template, thinking something was wrong with the history of the stack. However, I got the same error.
The IAM role that the stack uses has these permissions
Indeed, the problem persisted even after granting this role full write access to CloudFront resources.

Based on the chat discussion.
The cause of the issue was found to be missing IAM permissions for the IAM role that is used to deploy the stack. Specifically, the permission that was missing was:
cloudfront:GetDistribution - Grants permission to get the information about a web distribution
Adding that permission to the role, solved the problem.
To find the missing permission, CloudTrial's Event History was used.

Related

How to catch logs of external endpoint getting triggerred in cloudwatch aws

Im new to aws and was trying to use Event brigde and api destination for pinging and endpoint. I am successful in creating api destination and api connection
using cloud formation template and also successfully created IAM role and rule for the same, but i want to have the logs in cloudwatch triggering that endpoint after a scheduled expression. (There is no event triggering that endpoint, i want to trigger it after every 5 min)
Below is my cloud formation template
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- events.amazonaws.com
Action:
- sts:AssumeRole
Path: '/jobs-role/'
Policies:
- PolicyName: eventbridge-api-destinations
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- events:InvokeApiDestination
Resource: !GetAtt Destination.Arn
ApiConnection:
Type: AWS::Events::Connection
Properties:
AuthorizationType: API_KEY
Description: Connection to API
AuthParameters:
ApiKeyAuthParameters:
ApiKeyName: authorization
ApiKeyValue: MyAPIkey
Destination:
Type: AWS::Events::ApiDestination
Properties:
ConnectionArn: !GetAtt ApiConnection.Arn
Description: API Destination to send events
HttpMethod: POST
InvocationEndpoint: !Ref ApiDestinationInvocationEndpoint
InvocationRateLimitPerSecond: 10
RecordsRule:
Type: AWS::Events::Rule
Properties:
Description: !Sub 'Trigger ${RecordsRole} according to the specified schedule'
State: ENABLED
ScheduleExpression: "rate(5 minutes)"
Targets:
- Id: !Sub '${RecordsRole}'
Arn: !GetAtt Destination.Arn
RoleArn: !GetAtt RecordsRole.Arn
HttpParameters:
HeaderParameters:
Content-type: application/json;charset=utf-8
Can anyone tell me what i am lacking?
Below is link to my screenshot of invocations graph in metrics
[1]: https://i.stack.imgur.com/Zms44.png

stream logs to elastic using cloudformation template

Cloudtrail default logs can be streamed to elasticsearch domain as shown in this image. How do I achieve this using cloudformation template?
Update:
If you are using aws-cli, take a look at my answer here.
Well, after a few hours of exploring and reading a lot of documentation I finally succeeded to create this template.
Designer Overview :
In order to enable the stream logs to elasticsearch we need to create the following resources:
The lambda function will forward the logs from cloudwatch log group to Elasticsearch.
Relevant IAM Role to get logs from cloudwatch and insert to Elasticsearch.
Lambda permission - The AWS::Lambda::Permission resource grants an AWS service or another account permission to use a function to allow the cloudwatch log group to trigger the lambda.
Subscription Filter - The AWS::Logs::SubscriptionFilter resource specifies a subscription filter and associates it with the specified log group. Subscription filters allow you to subscribe to a real-time stream of log events and have them delivered to a specific destination.
Template usage:
Download LogsToElasticsearch.zip from my Github page.
Update var endpoint = '${Elasticsearch_Endpoint}'; in index.js with your Elasticseatch url e.g - 'search-xxx-yyyy.eu-west-1.es.amazonaws.com';.
Copy the zip file to s3 bucket which will be used in the template (LambdaArtifactBucketName).
Fill relevant Parameters - you can find descriptions to each resource.
Template YAML:
AWSTemplateFormatVersion: 2010-09-09
Description: Enable logs to elasticsearch
Parameters:
ElasticsearchDomainName:
Description: Name of the Elasticsearch domain that you want to insert logs to
Type: String
Default: amitb-elastic-domain
CloudwatchLogGroup:
Description: Name of the log group you want to subscribe
Type: String
Default: /aws/eks/amitb-project/cluster
LambdaName:
Description: Name of the lambda function
Type: String
Default: amitb-cloudwatch-logs
LambdaRole:
Description: Name of the role used by the lambda function
Type: String
Default: amit-cloudwatch-logs-role
LambdaArtifactBucketName:
Description: The bucket where the lambda function located
Type: String
Default: amit-bucket
LambdaArtifactName:
Description: The name of the lambda zipped file
Type: String
Default: LogsToElasticsearch.zip
VPC:
Description: Choose which VPC the Lambda-functions should be deployed to
Type: 'AWS::EC2::VPC::Id'
Default: vpc-1111111
Subnets:
Description: Choose which subnets the Lambda-functions should be deployed to
Type: 'List<AWS::EC2::Subnet::Id>'
Default: 'subnet-123456789,subnet-123456456,subnet-123456741'
SecurityGroup:
Description: Select the Security Group to use for the Lambda-functions
Type: 'List<AWS::EC2::SecurityGroup::Id>'
Default: 'sg-2222222,sg-12345678'
Resources:
ExampleInvokePermission:
Type: 'AWS::Lambda::Permission'
DependsOn: ExampleLambdaFunction
Properties:
FunctionName:
'Fn::GetAtt':
- ExampleLambdaFunction
- Arn
Action: 'lambda:InvokeFunction'
Principal: !Sub 'logs.${AWS::Region}.amazonaws.com'
SourceArn: !Sub >-
arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${CloudwatchLogGroup}:*
SourceAccount: !Ref 'AWS::AccountId'
LambdaExecutionRole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: !Ref LambdaRole
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
Policies:
- PolicyName: lambda-to-es-via-vpc-policy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- 'es:*'
Resource:
- !Sub >-
arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${ElasticsearchDomainName}
- PolicyName: logs-and-ec2-permissions
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- 'ec2:CreateNetworkInterface'
- 'ec2:DescribeNetworkInterfaces'
- 'ec2:DeleteNetworkInterface'
- 'logs:CreateLogGroup'
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
Resource: '*'
ExampleLambdaFunction:
Type: 'AWS::Lambda::Function'
DependsOn: LambdaExecutionRole
Properties:
Code:
S3Bucket: !Ref LambdaArtifactBucketName
S3Key: !Ref LambdaArtifactName
FunctionName: !Ref LambdaName
Handler: !Sub '${LambdaName}.handler'
Role:
'Fn::GetAtt':
- LambdaExecutionRole
- Arn
Runtime: nodejs8.10
Timeout: '300'
VpcConfig:
SecurityGroupIds: !Ref SecurityGroup
SubnetIds: !Ref Subnets
MemorySize: 512
SubscriptionFilter:
Type: 'AWS::Logs::SubscriptionFilter'
DependsOn: ExampleInvokePermission
Properties:
LogGroupName: !Ref CloudwatchLogGroup
FilterPattern: '[host, ident, authuser, date, request, status, bytes]'
DestinationArn:
'Fn::GetAtt':
- ExampleLambdaFunction
- Arn
Results:
Cloudwatch log:
Hope you find it helpfull.
Update 02/09/2020:
node.js 8.10 is now deprecated, you should use node.js 10 or 12.
https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html

Permissions for Lambda invocation conflicts with API Gateway permissions

I've got an app deployed to Lambda and I'm using API Gateway to forward HTTP requests over to the app.
My problem is that it looks like API Gateway isn't forwarding requests that aren't to the base URL of the app.
In other words, the code inside my Lambda handler gets executed when there's an HTTP request to https://blabblahblah.execute-api.us-west-2.amazonaws.com/Prod/. However, an HTTP request to https://blabblahblah.execute-api.us-west-2.amazonaws.com/Prod/pong throws a 500 and no code gets executed. I've got some logging statements inside my handler and they don't log anything for non-base-URL requests.
I've narrowed the problem down to a permissions issue.
I have 2 sets of permission that aren't working well together.
I need to allow the API Gateway to invoke my lambda function
The lambda function needs to be able to call itself.
I thought I had both these permissions working correctly but any HTTP requests that aren't aimed at the base URL throw a 500 in the API Gateway (i.e. I see no Cloudwatch log entries for the request but the response is 500).
I think that means that there must be some error in my SAM template.
Resources:
IAMRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: ['sts:AssumeRole']
Effect: Allow
Principal:
Service: [lambda.amazonaws.com]
Version: 2012-10-17
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
MyFunction:
Type: AWS::Serverless::Function
Properties:
Handler: lambda.handler
Role: !GetAtt IAMRole.Arn
Runtime: ruby2.5
CodeUri: "./src/"
MemorySize: 512
Timeout: 30
Events:
MyAppApi:
Type: Api
Properties:
Path: /
Method: ANY
RestApiId: !Ref MyAppAPI
MyAppAPI:
Type: AWS::Serverless::Api
Properties:
Name: MyAppAPI
StageName: Prod
DefinitionBody:
swagger: '2.0'
basePath: '/'
info:
title: !Ref AWS::StackName
paths:
/{proxy+}:
x-amazon-apigateway-any-method:
responses: {}
x-amazon-apigateway-integration:
uri:
!Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations'
passthroughBehavior: "when_no_match"
httpMethod: POST
type: "aws_proxy"
/:
post:
responses: {}
x-amazon-apigateway-integration:
uri:
!Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations'
passthroughBehavior: "when_no_match"
httpMethod: POST
type: "aws_proxy"
ConfigLambdaPermission:
Type: "AWS::Lambda::Permission"
DependsOn:
- MyFunction
Properties:
Action: lambda:InvokeFunction
FunctionName: !Ref MyFunction
Principal: apigateway.amazonaws.com
ConfigLambdaPermission:
Type: "AWS::Lambda::Permission"
DependsOn:
- MyFunction
Properties:
Action: lambda:InvokeFunction
FunctionName: !Ref MyFunction
Principal: !GetAtt IAMRole.Arn
Outputs:
MyFunction:
Description: Lambda Function for interacting with Slack
Value:
Fn::GetAtt:
- MyFunction
- Arn
MyAppAppUrl:
Description: App endpoint URL
Value: !Sub "https://${MyAppAPI}.execute-api.${AWS::Region}.amazonaws.com/"
Any idea how I can get things working right?
Yes, you need to add the other resource as well as a event trigger. And you lambda permissions have no use in the above template. Here's how it worked for me.
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Resources:
IAMRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: ['sts:AssumeRole']
Effect: Allow
Principal:
Service: [lambda.amazonaws.com]
Version: 2012-10-17
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
MyFunction:
Type: AWS::Serverless::Function
Properties:
Handler: roleTest.roleTest
Role: !GetAtt IAMRole.Arn
Runtime: nodejs8.10
CodeUri: s3://apicfn/roleTest.zip
MemorySize: 512
Timeout: 30
Events:
MyAppApi1:
Type: Api
Properties:
Path: /
Method: ANY
MyAppApi2:
Type: Api
Properties:
Path: /{proxy+}
Method: ANY
Outputs:
MyFunction:
Description: Lambda Function for interacting with Slack
Value:
Fn::GetAtt:
- MyFunction
- Arn
PS: do not even need the Serverless::Api section for this template.

How set Proxy on AWS API Gateway using Cloudformation

I have a lambda function that will handle PUT and GET requests using Amazon API Gateway {proxy+}.
It is working correctly when all the settings are set manually by the Amazon Console. but I want to automate it using AWS Cloudformation.
To inform you, I will write steps to set {proxy+}:
1) create a simple Lambda function and paste this lines of code inside it:
import boto3
def lambda_handler(event, context):
return {
"statusCode": 200,
"headers": {
"Content-Type": 'text/html',
"Access-Control-Allow-Origin": "*"
},
"body": "Hello Reza Amya, Your Lambda is working..!"
}
2) goto Amazon API Gateway and click on Create API.
3) choose New API, fill API name, select Edge optimized from the list for Endpoint Type then click on Create API
4) then your API is created and you should be on it's Resources page, if you are not, go to the Resources page for the created API.
5) from Actions select Create Resource
6) Select Configure as proxy resource (then it should change other fields automatically, if it doesn't, type proxy for Resource Name and {proxy+} for Resource Path) then click on Create Resource
7) Select Lambda Function Proxy for Integration type and select your lambda function from Lambda Function and click on Save
8) on the Add Permission to Lambda Function popup, click on Ok
9) from Actions click on Deploy API
10) Select New Stage from the list for Deployment stage then type a name for Stage name (for me, I have typed 'api') and click on Deploy
11) on the stage on the root page for your deployed API, you can see Invoke URL. click on it, and it will open new tab linked to somewhere like this: https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/api/
12) add a simple segment to end of your URL like this:
https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/api/test
now you should see bellow message in your browser page:
Hello Reza Amya, Your Lambda is working..!
Now the problem is I have written all these steps inside a Yaml file:
AWSTemplateFormatVersion: 2010-09-09
Description: My Lambda Function
Parameters:
S3Bucket:
Description: S3 Bucket where the Lambda code is
Type: String
S3Key:
Description: S3 Key where the Lambda code is
Type: String
S3ObjectVersion:
Description: Version of the S3 Key to use
Type: String
Resources:
apiGateway:
Type: "AWS::ApiGateway::RestApi"
Properties:
Name: "my-api"
Description: "My API"
EndpointConfiguration:
Types:
- EDGE
Resource:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId:
Ref: "apiGateway"
ParentId:
Fn::GetAtt:
- "apiGateway"
- "RootResourceId"
PathPart: "{proxy+}"
ProxyMethod:
Type: 'AWS::ApiGateway::Method'
Properties:
HttpMethod: ANY
ResourceId: !Ref Resource
RestApiId: !Ref apiGateway
AuthorizationType: NONE
RequestParameters:
method.request.path.proxy: true
Integration:
CacheKeyParameters:
- 'method.request.path.proxy'
RequestParameters:
integration.request.path.proxy: 'method.request.path.proxy'
Type: AWS_PROXY
IntegrationHttpMethod: ANY
Uri: !Sub
- arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Arn}/invocations
- Arn:
Fn::GetAtt:
- LambdaFunction
- Arn
PassthroughBehavior: WHEN_NO_MATCH
IntegrationResponses:
- StatusCode: 200
apiGatewayDeployment:
Type: "AWS::ApiGateway::Deployment"
DependsOn:
- "ProxyMethod"
Properties:
RestApiId: !Ref "apiGateway"
StageName: "dev"
IAMRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: 'sts:AssumeRole'
Policies:
- PolicyName: Logging
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- 'logs:CreateLogGroup'
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
Resource: 'arn:aws:logs:*:*:*'
- PolicyName: AccessToDynamoDB
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- 'dynamodb:CreateTable'
- 'dynamodb:DeleteItem'
- 'dynamodb:DeleteTable'
- 'dynamodb:GetItem'
- 'dynamodb:GetRecords'
- 'dynamodb:UpdateItem'
- 'dynamodb:UpdateTable'
- 'dynamodb:PutItem'
- 'dynamodb:UpdateTable'
Resource: 'arn:aws:dynamodb:*:*:*'
LambdaFunction:
Type: AWS::Lambda::Function
Properties:
Code:
S3Bucket: {Ref: S3Bucket}
S3Key: {Ref: S3Key}
S3ObjectVersion: {Ref: S3ObjectVersion}
Handler: main.lambda_handler
MemorySize: 128
Role: {'Fn::GetAtt': [IAMRole, Arn]}
Runtime: python3.6
Timeout: 300
LambdaInvokePermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !GetAtt
- LambdaFunction
- Arn
Action: 'lambda:InvokeFunction'
Principal: apigateway.amazonaws.com
SourceArn: !Sub arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${apiGateway}/*/*
Outputs:
apiGatewayInvokeURL:
Value: !Sub "https://${apiGateway}.execute-api.${AWS::Region}.amazonaws.com/${apiGateway}"
lambdaArn:
Value: !GetAtt "LambdaFunction.Arn"
The above Yaml file will create the Lambda function and will deploy the API, but it will show bellow error when I am trying to test the API:
{"message": "Internal server error"}
Can you please guide me what is wrong and how I can solve the problem?
The issue is related to you IntegrationHttpMethod setting. Although your APIGateway method is ANY, the IntegrationHttpMethod must always be POST for AWS Lambda.
This would lead to the following method declaration.
ProxyMethod:
Type: 'AWS::ApiGateway::Method'
Properties:
HttpMethod: ANY
ResourceId: !Ref Resource
RestApiId: !Ref apiGateway
AuthorizationType: NONE
RequestParameters:
method.request.path.proxy: true
Integration:
CacheKeyParameters:
- 'method.request.path.proxy'
RequestParameters:
integration.request.path.proxy: 'method.request.path.proxy'
Type: AWS_PROXY
IntegrationHttpMethod: POST
Uri: !Sub
- arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Arn}/invocations
- Arn:
Fn::GetAtt:
- LambdaFunction
- Arn
PassthroughBehavior: WHEN_NO_MATCH
IntegrationResponses:
- StatusCode: 200

Attach bucket policy to bucket generated by serverless

I'm attempting to create an S3 bucket with serverless, which works, however in order to manipulate files in it I need a bucket policy. I'm having a hard time understanding where and how to add a policy that uses the generated S3bucket name created when serverless deploys for the first time
##serverless.yml##
service: vcc-nametags-api
# Use the serverless-webpack plugin to transpile ES6
plugins:
- serverless-webpack
- serverless-offline
- serverless-ding
# serverless-webpack configuration
# Enable auto-packing of external modules
custom:
# Our stage is based on what is passed in when running serverless
# commands. Or fallsback to what we have set in the provider section.
stage: ${opt:stage, self:provider.stage}
# Set our DynamoDB throughput for prod and all other non-prod stages.
# Load our webpack config
webpack:
webpackConfig: ./webpack.config.js
includeModules: true
environment: ${file(env.yml):${self:custom.stage}, file(env.yml):default}
provider:
name: aws
runtime: nodejs8.10
stage: dev
region: us-east-1
# These environment variables are made available to our functions
# under process.env.
environment:
S3DBBucketName:
Ref: NametagsDatabaseBucket
functions:
# Defines an HTTP API endpoint that calls the main function in create.js
# - path: url path is /tags
# - method: POST request
# - cors: enabled CORS (Cross-Origin Resource Sharing) for browser cross
# domain api call
# - authorizer: authenticate using the AWS IAM role
create:
handler: create.main
events:
- http:
path: tags
method: post
cors: true
get:
# Defines an HTTP API endpoint that calls the main function in get.js
# - path: url path is /tags/{id}
# - method: GET request
handler: get.main
events:
- http:
path: tags/{id}
method: get
cors: true
list:
# Defines an HTTP API endpoint that calls the main function in list.js
# - path: url path is /tags
# - method: GET request
handler: list.main
events:
- http:
path: tags
method: get
cors: true
update:
# Defines an HTTP API endpoint that calls the main function in update.js
# - path: url path is /tags/{id}
# - method: PUT request
handler: update.main
events:
- http:
path: tags/{id}
method: put
cors: true
delete:
# Defines an HTTP API endpoint that calls the main function in delete.js
# - path: url path is /tags/{id}
# - method: DELETE request
handler: delete.main
events:
- http:
path: tags/{id}
method: delete
cors: true
# Create our resources with separate CloudFormation templates
resources:
# S3DB
- ${file(resources/s3-database.yml)}
##s3-database.yml##
Resources:
NametagsDatabaseBucket:
Type: AWS::S3::Bucket
Properties:
# Set the CORS policy
CorsConfiguration:
CorsRules:
-
AllowedOrigins:
- '*'
AllowedHeaders:
- '*'
AllowedMethods:
- GET
- PUT
- POST
- DELETE
- HEAD
MaxAge: 3000
NametagsDatabaseBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket:
Ref: NametagsDatabaseBucket
PolicyDocument:
Statement:
- Sid: PublicReadGetObject
Effect: Allow
Principal: "*"
Action:
- "s3:DeleteObject"
- "s3:GetObject"
- "s3:ListBucket"
- "s3:PutObject"
Resource:
Fn::Join: [
"", [
"arn:aws:s3:::",
{
"Ref": "NametagsDatabaseBucket"
},
"/*"
]
]
# Print out the name of the bucket that is created
Outputs:
NametagsDatabaseBucketName:
Value:
Ref: NametagsDatabaseBucket
I've tried various combinations I've found on the internet as well as adding it to an iamroles property in the serverless.yml file but I can't seem to get anything to work
The Resource Reference Name seems to matter, I have always had to use the name of the bucket in the resource name. For example, a bucket with www.example.com needs a reference name of S3BucketWwwexamplecom.
However I also notice that the BucketName element is missing from your example.
This is from working example for a static website with a Bucket Policy:
resources:
Resources:
S3BucketWwwexamplecom:
Type: AWS::S3::Bucket
DeletionPolicy: Delete
Properties:
BucketName: ${self:custom.s3WwwBucket}
CorsConfiguration:
CorsRules:
- AllowedMethods:
- PUT
- GET
- POST
- HEAD
AllowedOrigins:
- "https://${self:custom.myDomain}"
AllowedHeaders:
- "*"
AccessControl: PublicRead
WebsiteConfiguration:
IndexDocument: index.html
BucketPolicyWwwexamplecom:
Type: 'AWS::S3::BucketPolicy'
Properties:
PolicyDocument:
Statement:
- Sid: PublicReadForGetBucketObjects
Effect: Allow
Principal: '*'
Action:
- 's3:GetObject'
Resource: arn:aws:s3:::${self:custom.s3WwwBucket}/*
Bucket:
Ref: S3BucketWwwexamplecom
Since you are using a lambda to upload you should create an IAM Role for your Lambda and an IAM Policy with only the permissions required for operation. You might accomplish this by using the following excerpt in your cloud formation:
AWSTemplateFormatVersion: '2010-09-09'
Description: My Template
Resources:
LambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
RoleName: !Sub ${AWS::StackName}-LambdaRole
S3Policy:
Type: AWS::IAM::Policy
Properties:
PolicyName: S3_Writer
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:*
Resource: !Sub
- arn:aws:s3:::${BucketName}/*
- BucketName: !Ref NametagsDatabaseBucket
Roles:
- !Ref TaskRole
Outputs:
LambdaRole:
Value: !Sub "${LambdaRole.Arn}"
Export:
Name: !Sub ${AWS::StackName}-LambdaRole
Then in your serverless.yml just refer to the task role created using something like this to reference the execution role:
service: vcc-nametags-api
provider:
role: ${cf:${env:YOUR_STACK_ENV, 'YOUR_STACK_NAME'}.LambdaRole}
We have a setup like this working in several projects, I hope it works for you.