This commit is contained in:
@@ -372,7 +372,14 @@ on boot if any required var is empty.
|
||||
- **S3 + CloudFront**: `encoded-bucket` holds finished transcoded output,
|
||||
served publicly via CloudFront using Origin Access Control (OAC) — the
|
||||
bucket itself blocks all public access; only CloudFront's OAC principal
|
||||
can read it.
|
||||
can read it. The distribution carries a **response headers policy** adding
|
||||
permissive CORS headers (`*`, `GET`/`HEAD`/`OPTIONS`), and `OPTIONS` is in
|
||||
the behaviour's allowed/cached methods so CloudFront answers preflights
|
||||
itself: HLS is fetched by JavaScript, so a playlist or segment served
|
||||
without `Access-Control-Allow-Origin` is discarded by the browser. The
|
||||
bucket also carries its own equivalent CORS rule, which is what the
|
||||
LocalStack stack relies on — there is no CloudFront there and
|
||||
`PLAYBACK_BASE_URL` points the player straight at S3.
|
||||
- **S3 raw uploads**: `raw-uploads-bucket` is a separate, private bucket for
|
||||
pre-transcode uploads — deliberately kept apart from `encoded-bucket` so
|
||||
raw source video is never reachable through the public CDN. CORS is
|
||||
|
||||
+65
-4
@@ -426,6 +426,42 @@ func main() {
|
||||
return err
|
||||
}
|
||||
|
||||
// HLS is played by JavaScript (hls.js fetches the .m3u8 playlist
|
||||
// and every .ts/.m4s segment with XHR), so the CDN has to answer
|
||||
// with CORS headers or the browser drops the response — the
|
||||
// "CORS Missing Allow Origin" failure. The S3 origin sends none of
|
||||
// its own, so CloudFront adds them here, at the edge, for cached
|
||||
// and uncached responses alike.
|
||||
corsHeaders, err := cloudfront.NewResponseHeadersPolicy(ctx, "encoded-bucket-cors-headers", &cloudfront.ResponseHeadersPolicyArgs{
|
||||
Comment: pulumi.String("CORS headers so browsers can fetch HLS playlists and segments"),
|
||||
CorsConfig: &cloudfront.ResponseHeadersPolicyCorsConfigArgs{
|
||||
AccessControlAllowCredentials: pulumi.Bool(false),
|
||||
AccessControlAllowHeaders: &cloudfront.ResponseHeadersPolicyCorsConfigAccessControlAllowHeadersArgs{
|
||||
Items: pulumi.ToStringArray([]string{"*"}),
|
||||
},
|
||||
AccessControlAllowMethods: &cloudfront.ResponseHeadersPolicyCorsConfigAccessControlAllowMethodsArgs{
|
||||
Items: pulumi.ToStringArray([]string{"GET", "HEAD", "OPTIONS"}),
|
||||
},
|
||||
// Playback is public and unauthenticated, so any origin may
|
||||
// read it. A wildcard also keeps Origin out of the cache
|
||||
// key: with a fixed list CloudFront would have to vary the
|
||||
// cached response on the request's Origin header.
|
||||
AccessControlAllowOrigins: &cloudfront.ResponseHeadersPolicyCorsConfigAccessControlAllowOriginsArgs{
|
||||
Items: pulumi.ToStringArray([]string{"*"}),
|
||||
},
|
||||
// Content-Range/Content-Length are what a player reads back
|
||||
// when it seeks with a Range request.
|
||||
AccessControlExposeHeaders: &cloudfront.ResponseHeadersPolicyCorsConfigAccessControlExposeHeadersArgs{
|
||||
Items: pulumi.ToStringArray([]string{"Content-Length", "Content-Range", "Date", "ETag"}),
|
||||
},
|
||||
AccessControlMaxAgeSec: pulumi.Int(3000),
|
||||
OriginOverride: pulumi.Bool(true),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
distribution, err = cloudfront.NewDistribution(ctx, "encoded-bucket-cdn", &cloudfront.DistributionArgs{
|
||||
Enabled: pulumi.Bool(true),
|
||||
Comment: pulumi.String("Edge caching for encoded-bucket assets"),
|
||||
@@ -442,10 +478,14 @@ func main() {
|
||||
DefaultCacheBehavior: &cloudfront.DistributionDefaultCacheBehaviorArgs{
|
||||
TargetOriginId: pulumi.String(originId),
|
||||
ViewerProtocolPolicy: pulumi.String("redirect-to-https"),
|
||||
AllowedMethods: pulumi.ToStringArray([]string{"GET", "HEAD"}),
|
||||
CachedMethods: pulumi.ToStringArray([]string{"GET", "HEAD"}),
|
||||
Compress: pulumi.Bool(true),
|
||||
CachePolicyId: pulumi.String(cachingOptimizedPolicyId),
|
||||
// OPTIONS is listed so CloudFront answers CORS preflights
|
||||
// itself from the response headers policy below; without it
|
||||
// a preflight is rejected with 403 before the policy runs.
|
||||
AllowedMethods: pulumi.ToStringArray([]string{"GET", "HEAD", "OPTIONS"}),
|
||||
CachedMethods: pulumi.ToStringArray([]string{"GET", "HEAD", "OPTIONS"}),
|
||||
Compress: pulumi.Bool(true),
|
||||
CachePolicyId: pulumi.String(cachingOptimizedPolicyId),
|
||||
ResponseHeadersPolicyId: corsHeaders.ID(),
|
||||
},
|
||||
Restrictions: &cloudfront.DistributionRestrictionsArgs{
|
||||
GeoRestriction: &cloudfront.DistributionRestrictionsGeoRestrictionArgs{
|
||||
@@ -1274,6 +1314,27 @@ func main() {
|
||||
corsOrigins = pulumi.StringArray{pulumi.Sprintf("http://%s", cmsAlb.DnsName)}
|
||||
}
|
||||
|
||||
// The encoded bucket needs its own CORS rule for the LocalStack stack,
|
||||
// where there is no CloudFront in front of it and PLAYBACK_BASE_URL
|
||||
// points the player straight at S3. Behind CloudFront the response
|
||||
// headers policy already covers playback, but the rule is harmless
|
||||
// there and keeps a direct-to-bucket player working either way.
|
||||
_, err = s3.NewBucketCorsConfigurationV2(ctx, "encoded-bucket-cors", &s3.BucketCorsConfigurationV2Args{
|
||||
Bucket: bucket.ID(),
|
||||
CorsRules: s3.BucketCorsConfigurationV2CorsRuleArray{
|
||||
&s3.BucketCorsConfigurationV2CorsRuleArgs{
|
||||
AllowedMethods: pulumi.ToStringArray([]string{"GET", "HEAD"}),
|
||||
AllowedOrigins: pulumi.ToStringArray([]string{"*"}),
|
||||
AllowedHeaders: pulumi.ToStringArray([]string{"*"}),
|
||||
ExposeHeaders: pulumi.ToStringArray([]string{"Content-Length", "Content-Range", "Date", "ETag"}),
|
||||
MaxAgeSeconds: pulumi.Int(3000),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = s3.NewBucketCorsConfigurationV2(ctx, "raw-uploads-bucket-cors", &s3.BucketCorsConfigurationV2Args{
|
||||
Bucket: rawUploadsBucket.ID(),
|
||||
CorsRules: s3.BucketCorsConfigurationV2CorsRuleArray{
|
||||
|
||||
Reference in New Issue
Block a user