How CloudFront signed URLs work

CloudFront provides a mechanism for controlling access to paths. Learn why and why not you should use it.

Background

CloudFront signed URLs provide a mechanism to control access to the content served through a distribution. Unlike the Origin Access Identity, it restricts access to which users can see the content.

When you create a distribution, by default, it is open to everybody who knows the URL. But sometimes you want to limit that. For example, if you have a video course and you only want subscribed users to have access.

You can use signed URLs for this.

Let’s see how they work, and what are the drawbacks!

Why use them

A common scenario is when you have a public part of your site that is non-protected, allowing anonymous access, and there is a private part, accessible only to a subset of the users. You can configure the latter to require a signed URL, so you can have control over who has access.

Who signs the URL?

You need to do it on your backend, after validating that you want the user to have access to that path. With some custom logic, you can allow access only to paid users or people who subscribed to your offer.

Signed URLs are also origin-agnostic so that you protect the path and not the origin. In effect, it supports API requests, S3 objects, and all types of custom origins.

How signing works

In order to sign a request, you need to associate a key pair with your AWS account. For this, you need the root account, as IAM users cannot manage them. After logging in with the root account, access the form through the “My Security Credentials” menu on the console, find the CloudFront key pairs, and add a new one.

On the CloudFront distribution, tick the “Restrict Viewer Access” checkbox. Since it is per-behavior, you can have protected and unprotected paths side-by-side. Now when you try to access the protected path, you’ll get a MissingKey error.

Then you need to prepare your backend to do the signing. For NodeJS, you can use the aws-cloudfront-sign library. You’ll also need the private key and the key pair ID.

With the above library, this code can be used to do the signing:

const cfUtil = require('aws-cloudfront-sign');

const cfPk =`<certificate>`;

const cfKeyPairId = "<key pair ID>";
const cfURL = "<URL to sign>";

const signedUrl = cfUtil.getSignedUrl(cfURL, {
	keypairId: cfKeyPairId,
	expireTime: Date.now() + 60000,
	privateKeyString: cfPk
});
console.log(signedUrl);

With the correct certificate and the key pair ID, you’ll get back a signed URL, similar to this one:

http://d1vk2xptkdkbf8.cloudfront.net/test.txt
?Expires=1538298438
&Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cDovL2QxdmsyeHB0a2RrYmY4LmNsb3VkZnJvbnQubmV0L3Rlc3QudHh0IiwiQ29uZGl0aW9uIjp7IkRhdGVMZXNzVGhhbiI6eyJBV1M6RXBvY2hUaW1lIjoxNTM4Mjk4NDM4fX19XX0_
&Signature=RUmA~ekaS6zbO2cGPP0CdAaNmj-TvsOcL-F5edONfQKxXHamK2Vgru2Ha45UyTxdTYo8ThhcFlhJloqdcUIm3DB9Mi-JVZQP7kI9qN0skKGWvNbg2DMVEvxFTYGgwINFJfOm-6Q3fpCpU9p-Nk-HCblpr28puSq8TRer-6gzAxV1PM8VzZC5NHXSkrN58vNNfnt-btKkruVs3ThBVHh6yK1f9sYbzqC3J-6BkKyznYpJJSXZOpchahZ0SnvoY5~OiusCuYK52r874HbAFSM1-KDSo0hRbAV8M8wYNph~9zs88~s8JrDVp2Kn-5pLtzpLK5pLT0ydKCTCif9D2awD6Q__
&Key-Pair-Id=APKAIUPZLWMMO3R5EGXA

If you don’t want to modify the URLs, you can use signed cookies. To generate them, call the getSignedCookies function, which returns three cookies:

  • CloudFront-Policy
  • CloudFront-Signature
  • CloudFront-Key-Pair-Id

Send them to the user and then he will be able to access the protected content on the original URLs.

What can you set for signed urls

There are a few parameters that you can set when you generate the signed URLs.

You can use custom policies, where you can set all the possible parameters, or the so-called “canned” ones which are a stripped-down version. There is no big difference between them. In the former case, the policy is part of the URL, making it a bit longer, but it should hardly ever be a problem.

You can set the following parameters:

  • The URL which you want to give access to. You can use wildcards here, which is especially useful for signed cookies.
  • DateLessThan, which is the expiration time.
  • DateGreaterThan, which defines when the signed URL become effective. Personally, I don’t see any usecase for this, as you can also restrict your backend not to sign the URL until this time, but it is supported.
  • IP, which is an IPV4 CIDR block. I have a mixed feeling about this, as the IP address of the user can change which might mean they lose access to the protected content. But it can be a powerful feature to prevent stolen signed URLs.

How to manage key pairs

You can have only 2 key pairs for an account at any given moment, and they can be managed only by the root user.

But you can also define Trusted Signers to the CloudFormation behavior, which is a list of accounts whose keys are accepted for signing.

The main problem is that you need root account access to add a new key, and there is no API to automate it. I believe setting up an Organization and creating a separate account just to store the keys might solve the first issue, but you’d still need to do everything by hand.

How to rotate the keys

Since you can have two active keys at the same time, this means you can rotate the signing keys.

The process is as follows:

  • Add a new key pair
  • Update the backend to use the new keys
  • Wait until expiration. For example, if you use 15 minutes when you sign, you need to wait 15 minutes.
  • Delete the old key

How to revoke a signed URL

If you find yourself in a situation when you need to revoke an URL you’d signed, you need to distrust the key that was used for the signing. Either remove it from the account (or make it inactive) or remove the account itself from the Trusted Signers list. Keep in mind that doing so will revoke all signed URLs that was signed with that key/account.

Conclusion

Signed URLs provide a powerful way to secure content served via CloudFront. But especially compared to S3 signed URLs, they are extremely inflexible. You need root account access and you need to set up and rotate the keys by hand.

This makes this approach to protect resources very hard to implement and maintain.

06 November 2018

Interesting article?

Get hand-crafted emails on new content!