Giving OriginAccessIdentity reference in CloudFormation or serverless.yml - amazon-s3

I want to have a CloudFront distribution with access to a private S3 bucket. For that, I have to create an origin access identity. Manually, I can do that using the AWS console, but I wanted to create it via a CloudFormation script or with Serverless (using serverless.yml). While doing this, I am able to add a physical Id of the origin access identity to my CloudFront distribution (using one script).
Relevant documentation: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/quickref-cloudfront.html
I tried this:
myDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Origins:
- DomainName:bucket.s3.amazonaws.com
Id: myS3Origin
S3OriginConfig: {
OriginAccessIdentity:origin-access-identity/cloudfront/ !Ref cloudfrontoriginaccessidentity
}
Enabled: 'true'
Comment: Some comment
DefaultCacheBehavior:
ForwardedValues:
QueryString: 'false'
Cookies:
Forward: none
AllowedMethods:
- GET
- HEAD
- OPTIONS
TargetOriginId: myS3Origin
ViewerProtocolPolicy: redirect-to-https
PriceClass: PriceClass_200
ViewerCertificate:
CloudFrontDefaultCertificate: 'true'
cloudfrontoriginaccessidentity:
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: "some comment"
I have to create an origin access identity and a CloudFront distribution having this identity. Can we do both of these things in one CloudFormation script or with Serverless (using serverless.yml)?

You definitely can create an origin access identity and the CloudFront distribution in the same serverless.yml.
I've modified your scenario and changed the OriginAccessIdentity to use Fn::Join.
myDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Origins:
- DomainName:bucket.s3.amazonaws.com
Id: myS3Origin
S3OriginConfig:
OriginAccessIdentity:
Fn::Join:
- ''
-
- 'origin-access-identity/cloudfront/'
- Ref: cloudfrontoriginaccessidentity
Enabled: 'true'
Comment: Some comment
DefaultCacheBehavior:
ForwardedValues:
QueryString: 'false'
Cookies:
Forward: none
AllowedMethods:
- GET
- HEAD
- OPTIONS
TargetOriginId: myS3Origin
ViewerProtocolPolicy: redirect-to-https
PriceClass: PriceClass_200
ViewerCertificate:
CloudFrontDefaultCertificate: 'true'
cloudfrontoriginaccessidentity:
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: "some comment"
The serverless examples repo has a great example of this too: https://github.com/serverless/examples/blob/master/aws-node-single-page-app-via-cloudfront/serverless.yml

Yes, you can create both in the same CloudFormation template. The cloudfrontoriginaccessidentity is a separate resource so needs to be moved out from underneath myDistribution.
myDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Origins:
- DomainName:bucket.s3.amazonaws.com
Id: myS3Origin
S3OriginConfig: {
OriginAccessIdentity:origin-access-identity/cloudfront/ !Ref cloudfrontoriginaccessidentity
}
Enabled: 'true'
Comment: Some comment
DefaultCacheBehavior:
ForwardedValues:
QueryString: 'false'
Cookies:
Forward: none
AllowedMethods:
- GET
- HEAD
- OPTIONS
TargetOriginId: myS3Origin
ViewerProtocolPolicy: redirect-to-https
PriceClass: PriceClass_200
ViewerCertificate:
CloudFrontDefaultCertificate: 'true'
cloudfrontoriginaccessidentity:
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: "toyoguard-acces-identity"

Dont forget to add the s3 policy and bucket to your dependsOn list

Related

GCP API Gateway: Path parameters are being passed as query params

I'm trying to use GCP API Gateway to create a single endpoint for a couple of my backend services (A,B,C,D), each with their own path structure. I have the Gateway configured for one of the services as follows:
swagger: '2.0'
info:
title: <TITLE>
description: <DESC>
version: 1.0.0
schemes:
- https
produces:
- application/json
paths:
/service_a/match/{id_}:
get:
summary: <SUMMARY>
description: <DESC>
operationId: match_id_
parameters:
- required: true
type: string
name: id_
in: path
- required: true
type: boolean
default: false
name: bool_first
in: query
- required: false
type: boolean
default: false
name: bool_Second
in: query
x-google-backend:
address: <cloud_run_url>/match/{id_}
deadline: 60.0
responses:
'200':
description: Successful Response
'422':
description: Validation
This deploys just fine. But when I hit the endpoint gateway_url/service_a/match/123, it gets routed to cloud_run_url/match/%7Bid_%7D?id_=123 instead of cloud_run_url/match/123.
How can I fix this?
Editing my answer as I misunderstood the issue.
It seems like the { are getting leaked from your configuration as ASCII code, so when you call
x-google-backend:
address: <cloud_run_url>/match/{id_}
deadline: 60.0
it doesn't show the correct ID.
So this should be a leak issue from your yaml file and you can approach this the same way as in this thread about using path params

Authenticate AppSync queries console with Cognito User Pools

I am trying to authenticate Queries playground in AWS AppSync console. I have created User Pool and linked it to the AppSync API, I have also created an App Client in Cognito User Pool (deployed using CloudFormation). It appears under Select the authorization provider to use for running queries on this page: in the console.
When I run test query I get:
{
"errors": [
{
"errorType": "UnauthorizedException",
"message": "Unable to parse JWT token."
}
]
}
This is what I would expect. There is an option to Login with User Pools. The issue is I can't select any Client ID and when I choose to insert Client ID manually, anything I enter I get Invalid UserPoolId format. I am trying to copy Pool ID from User Pool General settings (format eu-west-2_xxxxxxxxx) but no joy. Btw, I am not using Amplify and I have not configured any Identity Pools.
EDIT:
Here is the CloudFormation GraphQLApi definition:
MyApi:
Type: AWS::AppSync::GraphQLApi
Properties:
Name: !Sub "${AWS::StackName}-api"
AuthenticationType: AMAZON_COGNITO_USER_POOLS
UserPoolConfig:
UserPoolId: !Ref UserPoolClient
AwsRegion: !Sub ${AWS::Region}
DefaultAction: ALLOW
To set up the stack using CloudFormation I have followed these 2 examples:
https://adrianhall.github.io/cloud/2018/04/17/deploy-an-aws-appsync-graphql-api-with-cloudformation/
https://gist.github.com/adrianhall/f330a10451f05a529680f32978dddb64
Turns out they both (same author) have an issue in them in the section where ApiGraphQL is defined. This:
MyApi:
Type: AWS::AppSync::GraphQLApi
Properties:
Name: !Sub "${AWS::StackName}-api"
AuthenticationType: AMAZON_COGNITO_USER_POOLS
UserPoolConfig:
UserPoolId: !Ref UserPoolClient
AwsRegion: !Sub ${AWS::Region}
DefaultAction: ALLOW
Should be:
MyApi:
Type: AWS::AppSync::GraphQLApi
Properties:
Name: !Sub "${AWS::StackName}-api"
AuthenticationType: AMAZON_COGNITO_USER_POOLS
UserPoolConfig:
UserPoolId: !Ref UserPool
AwsRegion: !Sub ${AWS::Region}
DefaultAction: ALLOW
Thank you #Myz for pointing me back to review the whole CF yaml file

Serverless function with authorizer arn provided returns 401

I am using serverless.
When I setup one of my functions as the following, which includes authorizer, on the client, I receive 401.
However when I remove it, there are no problems.
provider:
name: aws
runtime: nodejs8.10
region: eu-west-1
environment:
USER_POOL_ARN: "arn:aws:cognito-idp:eu-west-1:974280.....:userpool/eu-west-1........"
functions:
create:
handler: handlers/create.main
events:
- http:
path: create
method: post
cors: true
authorizer:
type: COGNITO_USER_POOLS
name: serviceBAuthFunc
arn: ${self:provider.environment.USER_POOL_ARN}
On the client, I expect a logged in user of the same user pool could get expected response. However it returns 401.
Any help is appreciated. Thanks.
After desperate hours spent, I have come up with the solution.
For anyone who comes across the same issue, here is a solution that worked for me.
Add integration: lambda after cors: true (though the order doesn't matter).
Below is just demonstrating that.
functions:
create:
handler: handlers/create.main
events:
- http:
path: create
method: post
cors: true
integration: lambda // this solves the problem
authorizer:
type: COGNITO_USER_POOLS
name: serviceBAuthFunc
arn: ${self:provider.environment.USER_POOL_ARN}
Send Authorization header with the value of Auth.currentSession().idToken.jwtToken while making the request.
Below is an example for sending headers using API of #aws-amplify/api and Auth of #aws-amplify/auth.
const currentSession = await Auth.currentSession()
await API.post(
'your-endpoint-name',
"/your-endpoint-path/..",
{
headers: {
'Authorization': currentSession.idToken.jwtToken
}
}
)

How to get SNS topic ARN inside lambda handler and set permissions to wite to it?

I have two lambda functions defined in serverless.yml: graphql and convertTextToSpeech. The former (in one of the GraphQL endpoints) should write to SNS topic to execute the latter one. Here is my serverless.yml file:
service: hello-world
provider:
name: aws
runtime: nodejs6.10
plugins:
- serverless-offline
functions:
graphql:
handler: dist/app.handler
events:
- http:
path: graphql
method: post
cors: true
convertTextToSpeach:
handler: dist/tasks/convertTextToSpeach.handler
events:
- sns:
topicName: convertTextToSpeach
displayName: Convert text to speach
And GraphQL endpoint writing to SNS:
// ...
const sns = new AWS.SNS()
const params = {
Message: 'Test',
Subject: 'Test SNS from lambda',
TopicArn: 'arn:aws:sns:us-east-1:101972216059:convertTextToSpeach'
}
await sns.publish(params).promise()
// ...
There are two issues here:
Topic ARN (which is required to write to a topic) is hardcoded it. How I can get this in my code "dynamically"? Is it provided somehow by serverless framework?
Even when topic arn is hardcoded lambda functions does not have permissions to wrote to that topic. How I can define such permissions in serverless.yml file?
1) You can resolve the topic dynamically.
This can be done through CloudFormation Intrinsic Functions, which are available within the serverless template (see the added environment section).
functions:
graphql:
handler: handler.hello
environment:
topicARN:
Ref: SNSTopicConvertTextToSpeach
events:
- http:
path: graphql
method: post
cors: true
convertTextToSpeach:
handler: handler.hello
events:
- sns:
topicName: convertTextToSpeach
displayName: Convert text to speach
In this case, the actual topic reference name (generated by the serverless framework) is SNSTopicConvertTextToSpeach. The generation of those names is explained in the serverless docs.
2) Now that the ARN of the topic is mapped into an environment variable, you can access it within the GraphQL lambda through the process variable (process.env.topicARN).
// ...
const sns = new AWS.SNS()
const params = {
Message: 'Test',
Subject: 'Test SNS from lambda',
TopicArn: process.env.topicARN
}
await sns.publish(params).promise()
// ...

Error creating BucketPolicy in CloudFormation yaml

I'm trying to use the following yaml to create an S3 Bucket Policy in CloudFormation:
cloudTrailBucketPolicy:
Type: "AWS::S3::BucketPolicy"
Properties:
Bucket:
Ref: cloudtrailBucket
PolicyDocument:
-
Action:
- "s3:GetBucketAcl"
Effect: Allow
Resource:
Fn::Join:
- ""
-
- "arn:aws:s3:::"
-
Ref: cloudtrailBucket
- "/*"
Principal: "*"
-
Action:
- "s3:PutObject"
Effect: Allow
Resource:
Fn::Join:
- ""
-
- "arn:aws:s3:::"
-
Ref: cloudtrailBucket
- "/*"
Principal:
Service: cloudtrail.amazonaws.com
When I try to do this, I get a message that "Value of property PolicyDocument must be an object"
Anyone have any ideas?
Looks like you solved the issue, but for readability you can compress your formatting by using !Sub and knowing that action allows single values as well as list. One of the main reasons I like yaml is that you use less vertical.
PolicyDocument:
-
Action: "s3:GetBucketAcl"
Effect: Allow
Resource: !Sub arn:aws:s3:::${cloudtrailBucket}
Principal: "*"
-
Action: "s3:PutObject"
Effect: Allow
Resource: !Sub arn:aws:s3:::${cloudtrailBucket}/*
Principal:
Service: cloudtrail.amazonaws.com
The PolicyDocument property of the AWS::S3::BucketPolicy Resource has a required type of JSON Object. The YAML template in your question incorrectly provides a JSON Array containing two JSON Objects as the value of the PolicyDocument property, hence the error message you received.
To fix this error, the objects should be properly nested within a Statement element which is missing from the current template.
Refer to the IAM Policy Elements Reference for more detail on IAM Policy Document syntax.
Ahhh. s3:GetBucketAcl is an action on a bucket. I removed the /* in the first statement and it worked. Gee. Super helpful error message.