How to create a lambda which invalidates cloudfront using cloudformation?

When people first see CloudFormation YAML, it looks like programming code.

But it's actually more like giving AWS a construction blueprint:

"Please create these AWS resources for me."

In this case, the blueprint says:


1. Parameters

Parameters:
  DistributionId:
    Type: String

This means:

"Before deploying, ask for a CloudFront Distribution ID."

So instead of hardcoding:

E123ABC

you can reuse the same template for:

just by supplying different parameter values.


2. IAM Role

LambdaExecutionRole:
  Type: AWS::IAM::Role

Lambda is not automatically allowed to do AWS actions.

AWS security works like:

"Everything is denied unless permission is explicitly granted."

So Lambda needs an IAM Role.

Think of the role like an employee badge.

Without the badge:

ACCESS DENIED

3. Trust Policy

AssumeRolePolicyDocument:

This part answers:

"Who may use this role?"
Principal:
  Service:
    - lambda.amazonaws.com

Meaning:

"The Lambda service is allowed to use this role."

Without this, Lambda cannot even start running.


4. Permission Policy

Action:
  - cloudfront:CreateInvalidation

This part answers:

"What may Lambda do?"

Meaning:

"Lambda is allowed to clear CloudFront cache."

5. Lambda Function

Type: AWS::Lambda::Function

Now CloudFormation creates the actual Lambda.


6. Runtime

Runtime: python3.12

Meaning:

"Run this Lambda using Python."

7. Handler

Handler: index.lambda_handler

This means:

index           → filename
lambda_handler  → function name

So Lambda expects:

def lambda_handler(event, context):

inside:

index.py

8. Environment Variables

Environment:
  Variables:
    DISTRIBUTION_ID: !Ref DistributionId

This is where CloudFormation connects the parameter to Lambda.

!Ref DistributionId means:

"Take the parameter value and inject it into Lambda."

So if deployment provides:

E123ABC456

then inside Python:

os.environ["DISTRIBUTION_ID"]

returns:

E123ABC456

9. Inline Lambda Code

Code:
  ZipFile: |

Normally Lambda code is uploaded as a ZIP file.

But for tiny utility Lambdas, CloudFormation lets you directly write the Python code inside YAML.


10. boto3

import boto3

boto3 is AWS SDK for Python.

Think of it as:

"Python library for talking to AWS services."

11. CloudFront Client

cloudfront = boto3.client("cloudfront")

Creates a helper object for CloudFront API calls.


12. Reading Distribution ID

distribution_id = os.environ["DISTRIBUTION_ID"]

Reads the value CloudFormation injected earlier.


13. Invalidation Request

cloudfront.create_invalidation(...)

This is the actual:

"Clear CloudFront cache"

operation.


14. Why "/*"?

"Items": ["/*"]

Means:

"Invalidate all cached files."

Equivalent to:

/index.html
/app.js
/styles.css
everything

15. CallerReference

str(context.aws_request_id)

CloudFront requires every invalidation request to be unique.

Lambda already gets a unique request ID for every execution, so it uses that.


What CloudFormation Tracks

When CloudFormation creates resources, it remembers them.

Think of CloudFormation keeping a notebook:

"I created this IAM Role"
"I created this Lambda"
"I attached this policy"

This tracking is why CloudFormation can later:


What Happens When You UPDATE the Stack?

Suppose original policy was:

Action:
  - cloudfront:CreateInvalidation

Later you change it to:

Action:
  - cloudfront:CreateInvalidation
  - s3:ListBucket

CloudFormation compares:

Old YAML vs New YAML

and realizes:

"The IAM policy changed."

So it automatically updates the role policy in AWS.

You do not manually edit IAM Console.

CloudFormation becomes the source of truth.


What Happens If You REMOVE Permissions?

Suppose old template had:

- s3:ListBucket

but new template removes it.

During stack update:

CloudFormation updates the policy

and that permission disappears from IAM.

AWS resources are continuously adjusted to match the YAML.


Does CloudFormation Recreate the Role?

Usually:

No

Simple permission updates are typically done in-place.

The role itself usually stays the same.


What Happens If You DELETE the Stack?

CloudFormation deletes the resources it created.

That means:


Important Detail About Policies

This template uses:

Policies:

inside the role.

That creates an inline policy.

Inline policy means:

"The policy lives inside the role."

So when the role is deleted:

the policy disappears too

because it was part of the role itself.


What If Someone Manually Changes the Role in AWS Console?

Suppose CloudFormation created:

cloudfront:CreateInvalidation

Then somebody manually adds:

s3:DeleteBucket

through IAM Console.

Now AWS no longer matches the YAML.

This is called:

configuration drift

Later, if CloudFormation updates the stack again, it may overwrite those manual changes to make AWS match the template again.

That's why teams usually treat CloudFormation as:

"The single source of truth."

Final Mental Model

Your YAML is basically:

Desired AWS infrastructure

CloudFormation reads it and says:

Create this Lambda
Create this IAM Role
Attach these permissions
Pass this Distribution ID
Keep AWS matching this template

Final YAML File

AWSTemplateFormatVersion: '2010-09-09'

Parameters:
  DistributionId:
    Type: String

Resources:

  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:

      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole

      Policies:
        - PolicyName: CloudFrontInvalidationPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:

              - Effect: Allow
                Action:
                  - cloudfront:CreateInvalidation
                Resource: "*"

              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: "*"

  InvalidateCloudFrontLambda:
    Type: AWS::Lambda::Function
    Properties:

      FunctionName: invalidate-cloudfront

      Runtime: python3.12

      Handler: index.lambda_handler

      Role: !GetAtt LambdaExecutionRole.Arn

      Timeout: 30

      Environment:
        Variables:
          DISTRIBUTION_ID: !Ref DistributionId

      Code:
        ZipFile: |
          import os
          import boto3

          cloudfront = boto3.client("cloudfront")

          def lambda_handler(event, context):

              distribution_id = os.environ["DISTRIBUTION_ID"]

              response = cloudfront.create_invalidation(
                  DistributionId=distribution_id,
                  InvalidationBatch={
                      "Paths": {
                          "Quantity": 1,
                          "Items": ["/*"]
                      },
                      "CallerReference": str(context.aws_request_id)
                  }
              )

              return {
                  "statusCode": 200,
                  "body": "CloudFront invalidation created"
              }