Search code examples
amazon-s3amazon-cloudfrontpre-signed-url

S3-backed CloudFront and signed URLs


Originally I set up an S3 bucket "bucket.mydomain.com" and used a CNAME in my DNS so I could pull files from there as if it was a subdomain. This worked for http with:

bucket.mydomain.com/image.jpg

or with https like:

s3.amazonaws.com/bucket.mydomain.com/image.jpg

Some files in this bucket were public access but some were "authenticated read" so that I would have to generate a signed URL with expiration in order for them to be read/downloaded.

I wanted to be able to use https without the amazon name in the URL, so I setup a CloudFront distribution with the S3 bucket as the origin. Now I can use https like:

bucket.mydomain.com/image.jpg

The problem I have now is that it seems either all my files in the bucket have to be public read, or they all have to be authenticated read.

How can I force signed URLs to be used for some files, but have other files be public read?


Solution

  • it seems either all my files in the bucket have to be public read, or they all have to be authenticated read

    That is -- sort of -- correct, at least in a simple configuration.

    CloudFront has a feature called an Origin Access Identity (OAI) that allows it to authenticate requests that it sends to your bucket.

    CloudFront also supports controlling viewer access to your resources using CloudFront signed URLs (and signed cookies).

    But these two features are independent of each other.

    If an OAI is configured, it always sends authentication information to the bucket, regardless of whether the object is private or public.

    Similarly, if you enable Restrict Viewer Access for a cache behavior, CloudFront will always require viewer requests to be signed, regardless of whether the object is private or public (in the bucket), because CloudFront doesn't know.

    There are a couple of options.

    If your content is separated logically by path, the solution is simple: create multiple Cache Behaviors, with Path Patterns to match, like /public/* or /private/* and configure them with individual, appropriate Restrict Viewer Access settings. Whether the object is public in the bucket doesn't matter, since CloudFront will pass-through requests for (e.g.) /public/* without requiring a signed URL if that Cache Behavior does not "Restrict Viewer Access." You can create 25 unique Cache Behavior Path Patterns by default.

    If that is not a solution, you could create two CloudFront distributions. One would be without an OAI and without Restrict Viewer Acccess enabled. This distribution can only fetch public objects. The second distribution would have an OAI and would require signed URLs. You would use this for private objects (it would work for public objects, too -- but they would still need signed URLs). There would be no price difference here, but you might have cross-origin issues to contend with.

    Or, you could modify your application to sign all URLs for otherwise public content when HTML is being rendered (or API responses, or whatever the context is for your links).

    Or, depending on the architecture of your platform, there are probably other more complex approaches that might make sense, depending on the mix of public and private and your willingness to add some intelligence at the edge with Lambda@Edge triggers, which can do things like inspect/modify requests in flight, consult external logic and data sources (e.g. look up a session cookie in DynamoDB), intercept errors, and generate redirects.