DEV Community

harsh patel
harsh patel

Posted on

AWS CloudFront Security Implementation Guide

Combining Origin Access Control (OAC) and Signed URLs with Trusted Key Groups


Executive Summary

This guide provides a comprehensive implementation framework for securing Amazon S3 static assets through AWS CloudFront using Origin Access Control (OAC) and Signed URLs with Trusted Key Groups. This architecture establishes multiple security layers to protect sensitive content while maintaining optimal delivery performance.


Architecture Overview

This architecture establishes a two-layer security model where both end-user access and origin access are independently verified and protected.

User → CloudFront (Signed URLs)

  • Users must present a cryptographically signed URL containing an expiration timestamp.
  • CloudFront validates the signature using public keys from trusted Key Groups.
  • Any invalid or expired requests are rejected at the edge, preventing unauthorized access.

CloudFront → S3 (Origin Access Control)

  • CloudFront authenticates to S3 using AWS Signature Version 4.
  • S3 bucket policies ensure that requests only come from authorized CloudFront distributions.
  • Direct access to S3 is blocked, enforcing all data delivery through CloudFront.

Security Layers

  • Viewer Authentication: Signed URL validation using RSA key pairs
  • Edge Security: CloudFront distribution with restricted access policies
  • Origin Authentication: OAC with SigV4 signing for S3 access
  • Bucket Security: S3 bucket policies restricting access to specific CloudFront distributions

Implementation Specifications

Phase 1: S3 Bucket Configuration

1.1 Create Private S3 Bucket (Console)

  1. Navigate to Amazon S3 Console
  2. Click Create bucket
  3. Enter Bucket name: demo-oac-secure-assets
  4. Select AWS Region
  5. Under Block Public Access, select Block all public access
  6. (Optional) Enable Versioning for audit trail
  7. Click Create bucket

1.2 Upload Sample Content (Console)

  1. Select your newly created bucket
  2. Click Upload → Add files
  3. Select your static assets (HTML, CSS, JS, images)
  4. Click Upload
  5. Verify objects show No public access in the permissions column

Phase 2: Origin Access Control Setup

2.1 Create OAC Configuration (Console)

  1. Navigate to CloudFront Console
  2. In left navigation, select Origin access
  3. Click Create control setting
  4. Configure:
    • Name: secure-oac-configuration
    • Description: Origin Access Control for secure S3 access
    • Signing protocol: SigV4
    • Signing behavior: Always
  5. Click Create

Phase 3: Cryptographic Key Management

3.1 Generate Key Pair Locally

# Generate 2048-bit RSA private key
openssl genrsa -out private_key.pem 2048

# Extract public key
openssl rsa -in private_key.pem -pubout -out public_key.pem

# Set secure permissions
chmod 600 private_key.pem
Enter fullscreen mode Exit fullscreen mode

3.2 Create CloudFront Public Key (Console)

  1. In CloudFront Console, open Key management → Public keys
  2. Click Create public key
  3. Configure:
    • Name: secure-content-key
    • Public key: paste contents of public_key.pem
  4. Click Create public key
  5. Note the generated Key pair ID (format: K123456789ABCDEF)

3.3 Establish Trusted Key Group (Console)

  1. In CloudFront Console, open Key management → Key groups
  2. Click Create key group
  3. Configure:
    • Name: trusted-key-group
    • Description: Key group for signed URL validation
    • Public keys: select your created public key
  4. Click Create key group

Phase 4: CloudFront Distribution Configuration

4.1 Create Distribution (Console)

  1. Navigate to CloudFront ConsoleCreate distribution
  2. Origin settings:
    • Origin domain: select your S3 bucket (demo-oac-secure-assets.s3.amazonaws.com)
    • Origin path: (Leave blank)
    • Name: SecureS3Origin
    • Origin access: Origin access control settings (recommended)
    • Select control setting: your created OAC
    • Bucket policy: Yes, update the bucket policy

4.2 Configure Default Cache Behavior

  • Cache policy: CachingOptimized
  • Viewer protocol policy: Redirect HTTP to HTTPS
  • Restrict viewer access: Yes
  • Trusted key groups: select your created key group
  • Additional:
    • AWS WAF: Enable if required
    • HTTP/2: Enabled
    • IPv6: Enabled

4.3 Distribution Settings

  • Price class: choose based on geographic requirements
  • Alternate domain name (CNAME): configure if using custom domain
  • SSL certificate: default or custom ACM certificate
  • Click Create distribution
  • Wait for status to change from In ProgressDeployed

Phase 5: S3 Bucket Policy Implementation

5.1 Verify Automatic Policy Creation (Console)

  1. Go to S3 Console → select bucket demo-oac-secure-assetsPermissions tab
  2. Verify an auto-generated bucket policy that allows CloudFront access via OAC
  3. If not present, apply the manual policy below

5.2 Manual Bucket Policy (if required)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFrontOACAccess",
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::demo-oac-secure-assets/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/E2ABCD1234EXAMPLE"
        }
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Replace placeholders:

  • 123456789012 → your AWS Account ID
  • E2ABCD1234EXAMPLE → your CloudFront Distribution ID

Signed URL Generation Implementation

Python Implementation

import base64
import json
import time
from urllib.parse import urlencode
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding

class CloudFrontSigner:
    def __init__(self, key_pair_id: str, private_key_path: str):
        self.key_pair_id = key_pair_id
        self.private_key_path = private_key_path

    def _b64url_encode(self, data: bytes) -> str:
        """Base64 URL-safe encoding without padding"""
        return base64.b64encode(data).decode().replace('+', '-').replace('=', '_').replace('/', '~')

    def _sign_policy(self, policy: bytes) -> str:
        """Sign policy document using RSA private key"""
        with open(self.private_key_path, 'rb') as key_file:
            private_key = serialization.load_pem_private_key(
                key_file.read(),
                password=None
            )
        signature = private_key.sign(policy, padding.PKCS1v15(), hashes.SHA1())
        return self._b64url_encode(signature)

    def generate_signed_url(self, resource_url: str, expires_in: int = 3600) -> str:
        """Generate CloudFront signed URL with custom policy"""
        expiration = int(time.time()) + expires_in
        policy = {
            "Statement": [
                {
                    "Resource": resource_url,
                    "Condition": {
                        "DateLessThan": { "AWS:EpochTime": expiration }
                    }
                }
            ]
        }
        policy_json = json.dumps(policy, separators=(',', ':')).encode()
        policy_encoded = self._b64url_encode(policy_json)
        signature = self._sign_policy(policy_json)
        return f"{resource_url}?Policy={policy_encoded}&Signature={signature}&Key-Pair-Id={self.key_pair_id}"

# Implementation Example
signer = CloudFrontSigner(
    key_pair_id="K123456789ABCDEF",
    private_key_path="/secure/keys/private_key.pem"
)

signed_url = signer.generate_signed_url(
    resource_url="https://d123456789.cloudfront.net/secure-document.pdf",
    expires_in=7200  # 2 hours validity
)
print("Signed URL:", signed_url)
Enter fullscreen mode Exit fullscreen mode

Phase 6: Testing

Test Scenario Expected Result Validation Steps
Direct S3 Access HTTP 403 Access Denied 1) Copy S3 object URL → 2) Open in browser → 3) Verify Access Denied
Unsigned CloudFront Access HTTP 403 Access Denied 1) Copy CloudFront URL → 2) Access w/o params → 3) Verify Access Denied
Valid Signed URL HTTP 200 OK 1) Generate signed URL → 2) Open in browser → 3) Verify content loads
Expired Signed URL HTTP 403 Access Denied 1) Generate URL with past expiration → 2) Access → 3) Verify rejection
Modified Signature HTTP 403 Access Denied 1) Alter signature parameter → 2) Access → 3) Verify rejection

Results Comparison

🔒 Access Without Signed URL (Blocked)

Access Denied Screenshot)

✅ Access With Signed URL (Successful)

Access Allowed Screenshot

Top comments (0)