> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blindcast.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# CDN Configuration

> Set up CloudFront or Cloudflare to serve encrypted HLS segments with correct CORS headers and cache policies.

Encrypted HLS segments should be served from a CDN. The CDN cannot decrypt the content (zero-knowledge) — it caches and delivers opaque bytes, just like any other static asset.

## Architecture

```mermaid theme={null}
sequenceDiagram
  participant Player as Player (browser)
  participant CDN as CDN (CloudFront / Cloudflare)
  participant S3 as S3 (origin)
  participant KS as Key Server

  Player->>CDN: GET /content/vid-001/manifest.m3u8
  CDN->>S3: Cache miss → fetch from origin
  CDN-->>Player: manifest (with EXT-X-KEY pointing to key server)

  Player->>KS: GET /keys/vid-001 (with auth)
  KS-->>Player: content key (16 bytes)

  Player->>CDN: GET /content/vid-001/seg-0.ts
  CDN-->>Player: encrypted segment (cache hit)
  Player->>Player: decrypt with content key
```

## CORS headers

The player fetches segments via `fetch()` from a different origin than your app. The CDN must return CORS headers.

### CloudFront

Create a response headers policy:

```json theme={null}
{
  "CorsConfig": {
    "AccessControlAllowOrigins": {
      "Items": ["https://app.example.com"]
    },
    "AccessControlAllowMethods": {
      "Items": ["GET", "HEAD"]
    },
    "AccessControlAllowHeaders": {
      "Items": ["*"]
    },
    "AccessControlExposeHeaders": {
      "Items": ["Content-Length", "Content-Range"]
    },
    "OriginOverride": true
  }
}
```

Attach this policy to your CloudFront distribution's behavior for the segment path pattern (`/content/*`).

### Cloudflare

Add a Transform Rule or Worker to set CORS headers:

```
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, HEAD
Access-Control-Expose-Headers: Content-Length, Content-Range
```

Or use Cloudflare's CORS settings in the dashboard under your zone's security settings.

## Cache policy

| Asset             | Cache behavior                                       | Why                                                         |
| ----------------- | ---------------------------------------------------- | ----------------------------------------------------------- |
| `.ts` segments    | Cache forever (`max-age=31536000, immutable`)        | Segments are content-addressed and never change             |
| `.m3u8` manifests | Short TTL or no-cache (`max-age=0, must-revalidate`) | Manifests may be updated (e.g., live streams, key rotation) |
| `.key` files      | Do not cache                                         | Key requests go directly to the key server, not the CDN     |

### CloudFront cache policy

Create a cache policy for segments:

```json theme={null}
{
  "MinTTL": 86400,
  "MaxTTL": 31536000,
  "DefaultTTL": 31536000
}
```

For manifests, use a separate behavior with `MinTTL: 0` and forward the `Cache-Control` header from the origin.

### S3 metadata

Set cache headers when uploading segments:

```bash theme={null}
# The CLI sets these automatically during upload
# If uploading manually:
aws s3 cp seg-0.ts s3://bucket/content/vid-001/seg-0.ts \
  --cache-control "max-age=31536000, immutable" \
  --content-type "video/mp2t"
```

## S3 as origin

### CloudFront + S3

1. Create an S3 bucket (private, no public access)
2. Create a CloudFront distribution with the S3 bucket as origin
3. Use Origin Access Control (OAC) to let CloudFront read from S3 without making the bucket public
4. Set the origin path to match your upload prefix (e.g., `/content`)

### Cloudflare + R2

R2 buckets can be connected as custom domains in Cloudflare:

1. Create an R2 bucket
2. Connect a custom domain (e.g., `cdn.example.com`)
3. R2 serves objects directly with Cloudflare's CDN

## Security

The CDN sees only encrypted bytes. Even if CDN cache is compromised, the attacker gets ciphertext without the decryption key. The key server is a separate service that requires authentication.

**Do not cache key server responses.** Key requests contain authentication tokens and return cryptographic material. They should always go directly to the key server.

## Testing

Verify CORS headers are set correctly:

```bash theme={null}
curl -I -H "Origin: https://app.example.com" \
  https://cdn.example.com/content/vid-001/seg-0.ts

# Should include:
# Access-Control-Allow-Origin: https://app.example.com
```

Verify cache behavior:

```bash theme={null}
# First request: cache miss
curl -I https://cdn.example.com/content/vid-001/seg-0.ts
# X-Cache: Miss from cloudfront (or CF-Cache-Status: MISS)

# Second request: cache hit
curl -I https://cdn.example.com/content/vid-001/seg-0.ts
# X-Cache: Hit from cloudfront (or CF-Cache-Status: HIT)
```
