⠛⠥⠝⠑⠽⠎⠥⠎ /home/asguneysu

Developing a Self Destructing Cloudformation Stack

by Ahmed Şeref Güneysu

In this tutorial, we will develop a self destructing cloudformation stack. The stack can ben named harakiri since it will delete not only the resources created but also itself after a specified duration. The stack is best suited for timely.

For example, a potential customer wants to try your SaaS before paid subscription and you do not want to be charged for the unused resources.

It would be better have a basic knowledge about CloudFormation.

AWS Cloud Formation is a infrastructure as a code that creates, updates and deletes resources with JSON or YAML configuration files.

Defining Stack

AWS Cloud Formation template has five sections:

  • Mapping
  • Parameters
  • Conditions
  • Resources
  • Outputs

In the resources section we define the AWS Resources that will be created. I will keep it simple and focus on only the resources section.

Trivial Section

AWSTemplateFormatVersion: "2010-09-09"

Mappings:
  RegionMap:
    us-east-1:
      AMI: "ami-0ff8a91507f77f867"

Parameters: {}

Conditions: {}

There is nothing fancy here. Although we are not going to create and EC2 instance, at least one region (the region you will be creating the stack) is required. Also AMI (Amazon Machine Image) can be wrong. Just skip it.

IAM Role for The Lambda

We use lambda for triggering the stack delete process. This IAM role must be priviled to delete all resources created with stack. You would like give full privilege this role but be safe by giving fine grained privilege by giving privilege to this role for only the resources created with stack.

We gave privilege to this role:

  • Deleting all cloud formation stacks
  • Deleting all IAM roles. (This role will able to delete itself)
  • Deleting all lambda functions
  • Deleting all cloudwatch events and rules
<details class="ba bw2 mv2 pa2 b--light-gray">
    <summary class="fira mv1 dim fw7">Partial Template: Defining the IAM Role</summary>
    <span><a download="cf-defining-the-iam-role.yml" class="link black bg-animate hover-bg-light-yellow pv3 ph3" href="./src/cf-defining-the-iam-role.yml"><i
        class="icon icon-download"> </i> download</a> </span>        
    <div class="highlight"><pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="c"># warning this yaml file is the **ONLY** one section of CF template.</span><span class="w">

# do not try to use it standalone. Resources: LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: "/" Policies: - PolicyName: root PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - logs: Resource: arn:aws:logs::: - Effect: Allow Action: - cloudformation:DeleteStack Resource: "" - Effect: Allow Action: - iam:DeleteRolePolicy - iam:DeleteRole Resource: "" - Effect: Allow Action: - lambda:DeleteFunction Resource: "" - Effect: Allow Action: - events:RemoveTargets Resource: "" - Effect: Allow Action: - events:DeleteRule Resource: "" - Effect: Allow Action: - lambda:RemovePermission Resource: ""

Lambda Function

This lambda function will start stack delete process.

  • How does this lambda function trigger?
  • Cloud Watch Rules.

We will define a cloud watch timer later. The cloud watch timer will trigger every X times but since the lambda function will delete all resources created with stack, probably there would be no second execution.

Boto is a Python package that provides interfaces to Amazon Web Services.

import boto3
client = boto3.client('cloudformation')

def handler(event, context):
  return client.delete_stack(StackName=event.StackName)

Source code of lambda function is simple. It requires a simple JSON object with a StackName property which is the name of the Cloud Formation stack.

<details class="ba bw2 mv2 pa2 b--light-gray">
    <summary class="fira mv1 dim fw7">Partial Template: Defining the Lambda Function &#34;Harakiri&#34;</summary>
    <span><a download="cf-defining-the-lambda-fn.yml" class="link black bg-animate hover-bg-light-yellow pv3 ph3" href="./src/cf-defining-the-lambda-fn.yml"><i
        class="icon icon-download"> </i> download</a> </span>        
    <div class="highlight"><pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="c"># warning this yaml file is the **ONLY** one section of CF template.</span><span class="w">

# do not try to use it standalone. HarakiriLambda: Type: AWS::Lambda::Function Properties: Handler: index.handler Role: !GetAtt LambdaExecutionRole.Arn Code: ZipFile: | import boto3 client = boto3.client('cloudformation') def handler(event, context): return client.delete_stack( StackName=event.StackName ) Runtime: python3.6 PermissionForEventsToInvokeLambda: Type: AWS::Lambda::Permission Properties: FunctionName: Ref: "HarakiriLambda" Action: "lambda:InvokeFunction" Principal: "events.amazonaws.com" SourceArn: Fn::GetAtt: - "HarakiriRule" - "Arn"

CloudWatch Timer (Rule)

We will define a cloud watch cron rule that will be executed every thirty minutes but we expect it does not executed twice about five or ten minutes after the first execution, all resources including this rule will be deleted.

We prepare a simple JSON object at line 107, for the lambda by substituting the stack name.

<details class="ba bw2 mv2 pa2 b--light-gray">
    <summary class="fira mv1 dim fw7">Partial Template: Defining the CloudWatch Timer</summary>
    <span><a download="cf-defining-the-cloud-watch-timer.yml" class="link black bg-animate hover-bg-light-yellow pv3 ph3" href="./src/cf-defining-the-cloud-watch-timer.yml"><i
        class="icon icon-download"> </i> download</a> </span>        
    <div class="highlight"><pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="c"># warning this yaml file is the **ONLY** one section of CF template.</span><span class="w">

# do not try to use it standalone. HarakiriRule: Type: AWS::Events::Rule Properties: Description: "ScheduledRule" ScheduleExpression: "cron(0/30 * * * ? *)" State: "ENABLED" Targets: - Arn: Fn::GetAtt: - "HarakiriLambda" - "Arn" Id: "HarakiriLambdaV1" Input: !Sub - "{&#34;StackName&#34;: <span class="s2">"${Stack}&#34;}" - { Stack: !Ref "AWS::StackName" }

Final

  • Go to AWS Cloud Formation Console: console.aws.amazon.com/cloudformation
  • Click Create Stack button and upload the template.
  • Hit the Next button and give a name to your stack.
  • Watch the events tabs at the bottom.

You will see the stack create process, just after 2-3 minutes, stack delete in progress event will appear.

<details class="ba bw2 mv2 pa2 b--light-gray">
    <summary class="fira mv1 dim fw7">See the complete Harakiri Cloud Formation Template</summary>
    <span><a download="template.yml" class="link black bg-animate hover-bg-light-yellow pv3 ph3" href="./src/template.yml"><i
        class="icon icon-download"> </i> download</a> </span>        
    <div class="highlight"><pre class="chroma"><code class="language-yaml" data-lang="yaml"><span class="k">AWSTemplateFormatVersion</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;2010-09-09&#34;</span><span class="w">

Mappings: RegionMap: us-east-1: AMI: "ami-0ff8a91507f77f867" Parameters: {} Conditions: {} Resources: LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: "/" Policies: - PolicyName: root PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - logs: # - logs:CreateLogGroup # - logs:CreateLogStream # - logs:PutLogEvents Resource: arn:aws:logs:::
- Effect: Allow Action: - cloudformation:DeleteStack Resource: "
"
- Effect: Allow Action: - iam:DeleteRolePolicy - iam:DeleteRole Resource: "
"
- Effect: Allow Action: - lambda:DeleteFunction Resource: ""
- Effect: Allow Action: - events:RemoveTargets Resource: "
" - Effect: Allow Action: - events:DeleteRule Resource: "" - Effect: Allow Action: - lambda:RemovePermission Resource: "" HarakiriLambda: Type: AWS::Lambda::Function Properties: Handler: index.handler Role: !GetAtt LambdaExecutionRole.Arn Code: ZipFile: | import boto3 client = boto3.client('cloudformation') def handler(event, context): return client.delete_stack( StackName=event.StackName ) Runtime: python3.6 PermissionForEventsToInvokeLambda: Type: AWS::Lambda::Permission Properties: FunctionName: Ref: "HarakiriLambda" Action: "lambda:InvokeFunction" Principal: "events.amazonaws.com" SourceArn: Fn::GetAtt: - "HarakiriRule" - "Arn"
HarakiriRule: Type: AWS::Events::Rule Properties: Description: "ScheduledRule" ScheduleExpression: "cron(0/30 * * * ? *)" State: "ENABLED" Targets: - Arn: Fn::GetAtt: - "HarakiriLambda" - "Arn" Id: "HarakiriLambdaV1" Input: !Sub - "{&#34;StackName&#34;: <span class="s2">"${Stack}&#34;}" - { Stack: !Ref "AWS::StackName" }
Outputs: StackRegion: Value: Ref: "AWS::Region" AccountId: Value: Ref: "AWS::AccountId" StackId: Value: Ref: "AWS::StackId" StackName: Value: Ref: "AWS::StackName"

From the CLI you can create the stack by typing:

aws cloudformation deploy \
    --stack-name myteststack \
	  --capabilities CAPABILITY_IAM \
--template-file template.yml

Conclusion

I hope you enjoyed and learnt a lot.

Happy coding.

comments powered by Disqus
DMCA.com Protection Status