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,
|
- **S3 + CloudFront**: `encoded-bucket` holds finished transcoded output,
|
||||||
served publicly via CloudFront using Origin Access Control (OAC) — the
|
served publicly via CloudFront using Origin Access Control (OAC) — the
|
||||||
bucket itself blocks all public access; only CloudFront's OAC principal
|
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
|
- **S3 raw uploads**: `raw-uploads-bucket` is a separate, private bucket for
|
||||||
pre-transcode uploads — deliberately kept apart from `encoded-bucket` so
|
pre-transcode uploads — deliberately kept apart from `encoded-bucket` so
|
||||||
raw source video is never reachable through the public CDN. CORS is
|
raw source video is never reachable through the public CDN. CORS is
|
||||||
|
|||||||
+65
-4
@@ -426,6 +426,42 @@ func main() {
|
|||||||
return err
|
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{
|
distribution, err = cloudfront.NewDistribution(ctx, "encoded-bucket-cdn", &cloudfront.DistributionArgs{
|
||||||
Enabled: pulumi.Bool(true),
|
Enabled: pulumi.Bool(true),
|
||||||
Comment: pulumi.String("Edge caching for encoded-bucket assets"),
|
Comment: pulumi.String("Edge caching for encoded-bucket assets"),
|
||||||
@@ -442,10 +478,14 @@ func main() {
|
|||||||
DefaultCacheBehavior: &cloudfront.DistributionDefaultCacheBehaviorArgs{
|
DefaultCacheBehavior: &cloudfront.DistributionDefaultCacheBehaviorArgs{
|
||||||
TargetOriginId: pulumi.String(originId),
|
TargetOriginId: pulumi.String(originId),
|
||||||
ViewerProtocolPolicy: pulumi.String("redirect-to-https"),
|
ViewerProtocolPolicy: pulumi.String("redirect-to-https"),
|
||||||
AllowedMethods: pulumi.ToStringArray([]string{"GET", "HEAD"}),
|
// OPTIONS is listed so CloudFront answers CORS preflights
|
||||||
CachedMethods: pulumi.ToStringArray([]string{"GET", "HEAD"}),
|
// itself from the response headers policy below; without it
|
||||||
Compress: pulumi.Bool(true),
|
// a preflight is rejected with 403 before the policy runs.
|
||||||
CachePolicyId: pulumi.String(cachingOptimizedPolicyId),
|
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{
|
Restrictions: &cloudfront.DistributionRestrictionsArgs{
|
||||||
GeoRestriction: &cloudfront.DistributionRestrictionsGeoRestrictionArgs{
|
GeoRestriction: &cloudfront.DistributionRestrictionsGeoRestrictionArgs{
|
||||||
@@ -1274,6 +1314,27 @@ func main() {
|
|||||||
corsOrigins = pulumi.StringArray{pulumi.Sprintf("http://%s", cmsAlb.DnsName)}
|
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{
|
_, err = s3.NewBucketCorsConfigurationV2(ctx, "raw-uploads-bucket-cors", &s3.BucketCorsConfigurationV2Args{
|
||||||
Bucket: rawUploadsBucket.ID(),
|
Bucket: rawUploadsBucket.ID(),
|
||||||
CorsRules: s3.BucketCorsConfigurationV2CorsRuleArray{
|
CorsRules: s3.BucketCorsConfigurationV2CorsRuleArray{
|
||||||
|
|||||||
Reference in New Issue
Block a user