Table of Contents
CloudFront
CloudFront is AWS’s Content Delivery Network (CDN). A CDN is a special system designed to deliver static content more efficiently. If all content is served from a specific server, the physical distance between the client and that server would increase the latency involved in delivering the content. However, a CDN like CloudFront can deliver content much faster through edge locations around the world that act as caches.
CDN Content Access Control
Consider a service like Canva. In Canva, you can decorate the editor using various elements on the left panel. But be careful! Since Canva is a web service, if developer mode is enabled, one can find the URL of these elements and share them for unauthorized use.
Since Canva ultimately provides elements contracted from external vendors, such misuse can be problematic. How should access control be managed in situations where content needs to be provided to the general public? For such usability, CloudFront offers a signed URL feature.
Signed URLs
A signed URL is a feature that allows detailed control over content access using additional information like expiration time. Let’s understand this through an example where the URL is signed with an expiration time.
https://content.test.com/some/static/file/to/serve.jpg?Expires=1664974447&Signature=askEm~nFo4x736ky7plV6a1HMV6Ngugstmr8cKtM0cB3how8RiYt0y9PnYCW7~Mm4H9AamEBQz4gB34ZmQm1M8Gs6WOw1UNj3ArakE6hj8iYIXTLHAf4S3FZnb-CT0bUVnyFD4zgIdgkesm1d0IwrZcG9DeNaJyiBdyuHphtPIH5AF9l5HEN24VrB2Ge1TZHuyqMxrOCZitWb0qi9AqNN6ksugycd1whn50DjO7W0QWqIw5VLFRna0ISPep0osKdOzZ7-j5mhlScvNet1QaivYUiWjqx~DsPYD1saxl077dM1LmT3OiOZxOrs914uUo8tNIRhp895-8PKdkxgwx~Eg__&Key-Pair-Id=K31DYHTOA6ENGO
This looks incredibly long, but in fact, only the first part before the query string is the actual URL. The lengthy query string is for access control. Here’s what each query parameter means:
Parameter | Format | Description |
---|---|---|
Expires | Integer | Unix timestamp in seconds. Specifies the URL’s expiration time. Access is not possible after this time |
Signature | String | The content of the URL and query string signed with a private key. It helps verify if the URL has been tampered with |
Key-Pair-Id | String | The registered public key that can verify the Signature signed with the private key |
With the signed URL feature, URLs remain intact while easily controlling access! Note that any manipulation of the URL after signing is considered tampering, so include any necessary query parameters before signing.
Custom Policies
What if you want to control access with more complex conditions than just expiration time? CloudFront offers Canned policies and Custom policies for this purpose. Canned policies allow access control only through expiration time. The above example used a Canned policy. Custom policies offer more options, such as specifying a start time for access and controlling access through IP addresses.
Description | Canned policy | Custom policy |
---|---|---|
You can reuse the policy statement for multiple files. To reuse the policy statement, you must use wildcard characters in the Resource object. For more information, see Values that you specify in the policy statement for a signed URL that uses a custom policy.) | No | Yes |
You can specify the date and time that users can begin to access your content. | No | Yes (optional) |
You can specify the date and time that users can no longer access your content. | Yes | Yes |
You can specify the IP address or range of IP addresses of the users who can access your content. | No | Yes (optional) |
The signed URL includes a base64-encoded version of the policy, which results in a longer URL. | No | Yes |
Signed Cookies
This article primarily discusses signed URLs, but there’s also an option for signed cookies, which is worth mentioning. While signed URLs control access to specific resources, signed cookies control access based on the user. When the server provides a signed cookie, the client can access resources while the cookie is valid. Unlike signed URLs, which are signed for each resource, signed cookies are signed per user, reducing the number of signatures.
CloudFront uses asymmetric key encryption for signing, which is computationally intensive, so reducing the number of signings can improve performance. As such in the case of Canva, it would make more sense to use signed cookies instead of signed urls. For more details, refer to the documentation on signed cookies.
Setting Up Access Control
Let’s set up and use signed URLs. The process is quite intuitive, so I’ll give a brief overview.
1. Generating Asymmetric Key Pair
The key pair should be SSH-2 RSA, base64 encoded in PEM format, and 2048 bits in size. -> aws Guide
openssl genrsa -out private_key.pem
openssl rsa -in private_key.pem -out public_key.pem -pubout
2. Register the public key in CloudFront > Key management > Public keys
3. Add a behavior in CloudFront distribution settings
- Path pattern: The path pattern to apply the behavior.
- Origin and origin groups: The content origin server.
- Create and set a key group using the public key registered in step 2.
4. Signing the URL using AWS SDK in Java
- Add AWS SDK dependency.
- Only SDK version 1.x supports PEM files as of the date of writing.
implementation ("com.amazonaws:aws-java-sdk-cloudfront:1.12.239")
- Sign your URL
File privateKeyFile = new File("/.../private_key.pem");
String keyPairId = "Key group id generated from step 3";
Date dateLessThan = DateUtils.parseISO8601Date("2022-11-14T22:20:00.000Z");
try {
PrivateKey privateKey = SignerUtils.loadPrivateKey(privateKeyFile);
return CloudFrontUrlSigner.getSignedURLWithCannedPolicy(
resourceUrl,
keyPairId,
privateKey,
dateLessThan
);
} catch (InvalidKeySpecException e) {
log.error("", e);
throw new RuntimeException(e);
} catch (IOException e) {
log.error("", e);
throw new RuntimeException(e);
}
How Does Signing Work?
Lastly, let’s briefly understand how the signed URL is made. As mentioned earlier, signed URLs use the following for signing and verification:
- SSH-2 RSA key pair
- Base64 encoded PEM format
- 2048 key size
1. RSA
RSA is a well-known asymmetric key encryption algorithm, contrasting with symmetric key encryption where the same key is used for both encryption and decryption. In asymmetric encryption, a public key and a private key form a pair. Data encrypted with the public key can only be decrypted with the private key, and vice versa. For example, if a server encrypts plaintext with a private key, a client can decrypt it with the public key.
This one-way nature of encryption and decryption ensures that only an authenticated user (server) can encrypt, making it widely used in signatures. Signed URLs also use this RSA method.
2. RSA Key Generation
RSA key pairs are generated as follows:
- Choose two large prime numbers, p and q
- Calculate n = pq (the bit count of n is the key size)
- When φ(n) = (p-1)(q-1), select e smaller than φ(n) and coprime to φ(n)
- Choose d such that (e * d) mod φ(n) = 1
With the numbers n, e, and d, use (n, e) for the public key and (n, d) for the private key. Lets try that again with some actual numbers.
- Choose p=17, q=11.
- n = pq = 17*11 = 187
- φ(n) = (p-1)(q-1) = 16*10 = 160, select e=7, which is smaller than and coprime to 160.
- Find d=23 such that (7 * d) mod 160 = 1.
Result: n = 187, e = 7, d = 23
3. RSA Key Encryption and Decryption
With the keys generated, encryption is straightforward. Raise plaintext (M) to the power of e or d, then apply modular n.
M = plaintext, C = ciphertext
// Encrypt with public key
M^e mod n = C
// Encrypt with private key
M^d mod n = C
Decryption is the opposite: take the cipher text and apply the inverse key operation modulo n.
M = plaintext, C = ciphertext
// Decrypt with private key
C^d mod n = M
// Decrypt with public key
C^e mod n = M
Let’s try encrypting plaintext 150 with the private key and decrypting with the public key from the example above:
// Encrypt plaintext 150 with the private key
150^23 mod 187 = 57
// Decrypt ciphertext 57 with the public key
57^7 mod 187 = 150