The intent here is to provide:
- a reusable CloudFormation template that...
- builds out a Lambda function capable of maintaining...
- any number of S3 bucket policies to allow...
- read access to objects within said bucket(s) only from Cloudflare source IP ranges
Ultimately, this would allow hosting of static resources in S3 by way of Cloudflare proxied traffic while preventing direct access to that same S3 bucket outside of Cloudflare.
Create a new stack in AWS CloudFormation using the main.yaml
template in the root of this repository, and follow the prompts.
Be sure that you first understand what resources it will create and what options you have in how they are configured.
Once your stack is created, don't forget to run the Lambda function at least once to create the bucket policies.
The CF template sets up a Lambda function on the Node.js 20.x runtime, using a single CommonJS file with no package dependencies. The file itself is minified and written inline into the CF template, and you can see the unminified source code here.
Q. Why the need for a Lambda to manage the policies, you might ask?
A. Cloudflare may (infrequently) update their published IP ranges, and the function will retrieve the latest list of ranges, compare them against the existing bucket resource policy, and update the policy only as needed.
The same result could be accomplished with a manually created policy (see generated policy example), which would require manual updates any time Cloudflare changes their published IP ranges.
The Lambda function will create the resource policy on the bucket in question if it does not yet exist.
If the bucket policy exists and already contains unrelated policy statements, only the specific Cloudflare policy statement will be added or updated, leaving any other statements as they are.
On "Block Public Access" bucket settings
When blocking of all public access to a bucket is enabled, the Lambda will be unable to put any changes to that bucket's policy, in the form of an
AccessDenied
error.In order to update the allowed Cloudflare IP ranges from the Lambda, you will need to disable blocking through public bucket policies, found under the bucket's Permissions tab:
S3 will still warn you that the bucket may be publicly accessible, but the managed bucket policy statement will deny access to any IP outside of Cloudflare's ranges.
On buckets across different regions
Initially, buckets in different regions than the Lambda proved problematic. Adding an extra API call to determine each bucket's location has seemed to fix the issue but adds a second or two to the function running time.
Static website hosting does not appear to matter either way, though directory buckets are a whole other matter and may not be supported.
Deploying the CloudFormation template should create 3-4 initial resources, depending on the template parameters you provide:
- an execution role for the Lambda function
- an (inline) policy for this role that enables
- basic lambda permissions (creating log stream, putting log events)
- basic S3 policy permissions (getting, putting and deleting bucket policies, getting policy status)
- an (inline) policy for this role that enables
- a Lambda function (named
writeS3PolicyForCloudflare
by default) that uses this execution role - an (optional) EventBridge rule to invoke this function regularly (eg, daily, weekly, monthly or quarterly)
- a Cloudwatch logging group (named after the function by default)
The template itself includes the following configurable parameters, used in the noted ways:
FunctionNameParameter
- name of the Lambda function; defaults towriteS3PolicyForCloudflare
LogVerboseLambdaParameter
- indicates whether the Lambda should produce verbose debugging output during each run; defaults tofalse
(minimal output)LogRetentionParameter
- days to retain log streams produced by every Lambda execution; defaults to3
(automatically deleted after three days)BucketNamesParameter
- comma delimited list of buckets whose resource policies are to be managed; while cross-account management is theoretically possible, currently any buckets must be owned by the same account as the lambda execution roleInvokeFunctionOnScheduleParameter
- indicates whether to invoke the Lambda on a recurring schedule; defaults tofalse
(no schedule created)InvocationScheduleParameter
- schedule expression indicating how often to invoke the Lambda; defaults tocron(0 0 1 * ? *)
(once at midnight UTC on the first of each month)ProjectTagParameter
- value of theProject
tag that will be added to each resource the template directly creates; defaults tos3-cloudflare-policy-writer
- note that EventBridge rules can't currently be tagged via the creating CF template
That would be me, Evan S Kaufman. I am a tech generalist, and a web developer by trade.