AWS SAM and CloudFront

  1. Part One: AWS SAM
  2. Part Two: AWS SSO
  3. Part Three: AWS CloudFront

Part One: AWS SAM

  • Note: This is a brief introduction about how to setup a Serverless Application Model using VS Code and Nodejs. I will probably be adding more details later.

Visual Studio Code

  • VS Code is an perfect IDE for writing/testing/deploying SAM. Some key points need attention are listed.
  1. AWS credentials in Users/UserName/.aws folder.
  2. Create yaml file with resources: lambda function, policies, runtime language, environment variable etc…
  3. Writing lambda function in Nodejs, CROS policy…
  4. CRUD operation: marshall/unmarshall object etc…
  5. AWS CLI: package and deploy SAM to AWS
  • AWS CLI command to package and deploy SAM to AWS.
1
sam deploy --template-file output-yamlFileName.yaml --stack-name Your_Stack_Name --capabilities CAPABILITY_IAM
1
sam package --template-file yamlFileName.yaml --s3-bucket S3_Bucket_Name --output-template-file output-yamlFileName.yaml

Part Two: AWS SSO

Create Custom SAML Application

We can use AWS SSO as an IDP for our web application.

Create new Custom SAML 2.0 application.

This is IDP metadata, we will download it after finishing configuring return attributes.

This is SP metadata, which we will prepare and upload.

This is where we config the return attribute in SAML response. Supported attribute list can be found [here](https://docs.aws.amazon.com/singlesignon/latest/userguide/attributemappingsconcept.html?icmpid=docs_sso_console)

Create Users

  • Users can be created by simply fill this form. You can also setup MFA device after creating the user.

Part Three: AWS CloudFront

Restricting Access to Files in Amazon S3 Buckets

  • You can optionally secure the content in your Amazon S3 bucket so that users can access it through CloudFront but cannot access it directly by using Amazon S3 URLs. This prevents someone from bypassing CloudFront and using the Amazon S3 URL to get content that you want to restrict access to. (Using Origin Access Identity)

Block all public access to S3 bucket

  • When creating new distribution in CloudFront, do the following steps
  1. Select Web distribution
  2. Select the S3 bucket you want to connect to
  3. Select Redirect HTTP to HTTPS if you don’t want people to access your content by HTTP requests
  4. Select Yes for ‘Restrict Viewer Access’
  5. Select Self as trusted signer

Create Trusted Signer

Note When 'Restrict Viewer Access' is selected, you can specify which account is the 'Trusted Signer'. Which means they have the permission to create signed URL or signed cookie for people to access your private content.

Self is the default 'Truested Signer', which is the account your are currently using. You can also add another accounts by entering their account ID.

Create another behavior

  • Login page should be accessiable by public, so as the js, css and image files. So we should create another behavior to let CloudFront know which file access is retricted and which are not.

Path Pattern decides which files can be set to public(also select No for 'Retrict Viewer Access' this time)

Rules about Path Pattern can be found [here](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/distribution-web-values-specify.html#DownloadDistValuesPathPattern)

Create CloudFront Key Pairs

We need CloudFront key pairs to create signed URLs and signed Cookies, they are a pair of public and private keys AWS uses to encrypt requests people send to CloudFront, so it knows whether they are authenticated users.

This step can only be done using AWS root account, IAM account cannot create CloudFront key pairs.

  • Save the private key pem file and Access key ID to a save place. We will use it later.

Create Signed URL

This is the most important part.

I used two ways to create signed URL, nodejs and C#. I use nodejs to create a lambda function as an API, which will return the signed URL generated. I also add this feature to the SAML project, so when IDP returned SAML response, I can add signed details to the URL and redirect user to the home page.

Note that yaml file supports multiple line string (a vertical line followed by a hyphen), so we can add private key string in the yaml file as a environment variable, so as the public key.

Also note expiry time input are in [Unix Epoch Time format](https://www.epochconverter.com/) in miliseconds(13 digits). However, the expires argument in the signed URL are in seconds format (10 digits).

Also note that C# doesn't recognize private key string in PEM format. We have to convert it to XML format before create signed URL.

  • The signed URL created will append three arguments at the end of the original URL: Expires, Signature and Key-Pair-Id

  • Note the signature contains information about the original URL so you cannot reuse it with different URLs. We have to generate new signed URLs when direct user to other pages.(this will also refresh the session time, which is we want.)

Create Signed Cookies

NodeJs: Signed Cookies can be created using below code. I used this post as a reference.

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
"use strict"
const AWS = require("aws-sdk")

exports.handler = async (event, context, callback) => {
var cfsign = require('aws-cloudfront-sign');

// 5 seconds
var expireTime = Math.floor((+new Date() + 1000 * 300) / 1000);

var signingParams = {
keypairId: process.env.PUBLIC_KEY,
privateKeyString: process.env.PRIVATE_KEY,
// Optional - this can be used as an alternative to privateKeyString
//privateKeyPath: '/path/to/private/key',
expireTime: expireTime
}

const body = JSON.parse(event.body);

// Method 2: Generating singed Cookies
let policy = {
'Statement': [{
'Resource': body.url + '*',
'Condition': {
'DateLessThan': {'AWS:EpochTime': expireTime}
}
}]
};
let policyString = JSON.stringify(policy);
const signer = new AWS.CloudFront.Signer(signingParams.keypairId, signingParams.privateKeyString);

const options = {
url: body.url,
policy: policyString
};

const signedCookie = signer.getSignedCookie(options);

var response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin" : "*"
},
body: JSON.stringify(signedCookie)
}

callback(null, response);
};
  • By default, CloudFront caches a response from Amazon S3 for 24 hours, so if you just updated contents in S3 bucket, CloudFront may still serve you the outdated content. Use the below code in cmd to force CloudFront to update its files from S3.
1
aws cloudfront create-invalidation --distribution-id 'YOUR DISTRIBUTION ID' --paths "/*"

References

  1. Task List for Serving Private Content using S3 and CloudFront
  2. Restricting Access to Amazon S3 Content by Using an Origin Access Identity
  3. Specifying the AWS Accounts That Can Create Signed URLs and Signed Cookies (Trusted Signers)
  4. Reformatting the CloudFront Private Key (.NET and Java Only)
  5. Using Signed URLs
  6. Creating a Signed URL Using a Canned Policy
  7. Code Example: Creating Amazon CloudFront Signed URLs in Node.js
  8. Code Example: Create a URL Signature Using C# and the .NET Framework
  9. Code Example: Create Signed Cookies using NodeJs
  10. Test Signed Cookies using Postman
  11. Epoch & Unix Timestamp Conversion Tools
  12. Why is CloudFront serving outdated content from Amazon S3?
  13. RSA public/private keys in YAML
  14. Convert PEM to XML in C#
  15. CloudFront Path Pattern Rules