Infrastructure as code (IaC) is defined as the process of managing and provisioning components in a data center through machine-readable definition files. Usually, JSON or YAML files.

In our cloud environments, we want to automatically deploy code to new regions and centrally manage it in a version control system. AWS offers its own declaration format for IaC called CloudFormation template. AWS CloudFormation allows you to create, provision, and manage AWS and other resources by treating infrastructure as code.

In this guide, I want to cover the more advanced intrinsic functions of CloudFormation and how you can use them in your templates. You can use intrinsic functions in the resource properties, outputs, metadata attributes, and update policy attribute only.

Enjoy this article? Subscribe to receive the latest news about cloud security here 📫

Fn::GetAtt

{ "Fn::GetAtt" : [ "logicalNameOfResource", "attributeName" ] }

{
   "Fn::GetAtt":[
      "OpenSearchDataStream",
      "Arn"
   ]
}

This function will get the ARN or other details from a created resource. Make sure the resource exists before you reference it. You can use the DependsOn attribute to ensure this.

Fn::Sub

{ "Fn::Sub" : [ String, { Var1Name: Var1Value, Var2Name: Var2Value } ] }

{
   "Fn::Sub":"arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*"
}

This function replaces parts of your input string with variables. The following predefined parameters are supported:

  • AWS::AccountId
  • AWS::NotificationARNs
  • AWS::NoValue
  • AWS::Partition
  • AWS::Region
  • AWS::StackId
  • AWS::StackName
  • AWS::URLSuffix

I usually use it to construct an ARN.

Fn::Split

{ "Fn::Split" : [ "delimiter", "source string" ] }

{
   "Fn::Split":[
      ":",
      {
         "Fn::GetAtt":[
            "OpenSearchDeliveryStreamLogGroup",
            "Arn"
         ]
      }
   ]
}

This function will split an input string by a defined delimiter. It is usually used together with Fn::Select and I have never used it standalone.

Fn::Select

{ "Fn::Select" : [ index, listOfObjects ] }

{
   "Fn::Select":[
      "6",
      {
         "Fn::Split":[
            ":",
            {
               "Fn::GetAtt":[
                  "OpenSearchDeliveryStreamLogGroup",
                  "Arn"
               ]
            }
         ]
      }
   ]
}

This function is returning a part of a string from a list of objects. It always starts with the index 0.
I often use it together with the Parameters attribute if I need to split an ARN and only return a certain part. In this example, it only returns the name of the log group of a provided CloudWatch LogGroup ARN.

Fn::Join

{ "Fn::Join" : [ "delimiter", [ comma-delimited list of values ] ] }

{
   "Fn::Join":[
      "",
      [
         {
            "Fn::GetAtt":[
               "LogBackupS3Bucket",
               "Arn"
            ]
         },
         "/*"
      ]
   ]
}

This function appends several values to a single one. This is handy if you need to create resources for policies for example. We can use it together with other functions too.

Fn::Join with lambda soruce code

{
   "Type":"AWS::Lambda::Function",
   "Properties":{
      "FunctionName":"CloudWatchTransformFunction",
      "Handler":"index.lambda_handler",
      "Code":{
         "ZipFile":{
            "Fn::Join":[
               "",
               [
                  "import boto3\n",
                  "import json\n",
                  "import re\n",
                  "s3Client = boto3.client('s3')\n",
                  "s3Resource = boto3.resource('s3')\n",
                  "def lambda_handler(event, context):\n",
                  "    for event in event['Records']:\n",
                  "        bucketName = event['s3']['bucket']['name']\n",
                  "        objectName = event['s3']['object']['key']\n",
                  "        fileName = event['s3']['object']['key'].split('/')[-1]\n",
               ]
            ]
         }
      },
      "Runtime":"python3.8",
      ...
}

One special use case for this function is to add source code to a lambda function. It is better readable than a single line of code.

DependsOn

"DependsOn":"CloudWatchTransformFunctionRole"

This attribute is used to define that the creation of a specific resource follows another. I often use it if a certain resource requires an IAM role or CloudWatch LogGroup created beforehand and I need to ensure the order is followed. Otherwise, the CloudFormation template may fail with an error message that the ARN can not be referenced.

Share this post