Implementing governance in depth for serverless applications2. Proactive Controls with AWS CloudFormation Guard4 min

AWS CloudFormation Guard is an open-source, general-purpose, policy-as-code evaluation tool. This can be used for proactive governance and compliance by validating Infrastructure as Code (IaC) templates and service compositions against policy rules. These rules can be customized based on your team or organizational requirements. For serverless resources like Lambda functions, the CloudFormation Guard rules can be used to control resource creation and configuration updates by defining the required property settings needed while creating or updating a Lambda function.

Compliance administrators define the list of controls and governance policies that are required for deploying and updating Lambda functions. Platform administrators implement the controls in CI/CD pipelines, as pre-commit validation webhooks with code repositories, and provide developers with command line tools for validating templates and code on local workstations. Developers author code, validate templates with command line tools, and then commit code to repositories, which are then automatically validated via the CI/CD pipelines prior to deployment into an AWS environment.

CloudFormation Guard allows you to write your rules and implement your controls with a domain-specific language as follows.

2-proactive-guard-rule-dsl

For example, suppose you wanted to ensure that developers choose only the latest runtimes. You could specify two different policies, one to identify runtimes that are already deprecated and another to identify runtimes that are to be deprecated soon. To do this, you might write the following etc/rules.guard file:

let lambda_functions = Resources.*[
    Type == "AWS::Lambda::Function"
]

rule lambda_already_deprecated_runtime when %lambda_functions !empty {
    %lambda_functions {
        Properties {
            when Runtime exists {
                Runtime !in ["dotnetcore3.1", "nodejs12.x", "python3.6", "python2.7", "dotnet5.0", "dotnetcore2.1", "ruby2.5", "nodejs10.x", "nodejs8.10", "nodejs4.3", "nodejs6.10", "dotnetcore1.0", "dotnetcore2.0", "nodejs4.3-edge", "nodejs"] <<Lambda function is using a deprecated runtime.>>
            }
        }
    }
}

rule lambda_soon_to_be_deprecated_runtime when %lambda_functions !empty {
    %lambda_functions {
        Properties {
            when Runtime exists {
                Runtime !in ["nodejs16.x", "nodejs14.x", "python3.7", "java8", "dotnet7", "go1.x", "ruby2.7", "provided"] <<Lambda function is using a runtime that is targeted for deprecation.>>
            }
        }
    }
}

Now suppose you write the following iac/lambda.yaml CloudFormation template that defines a Lambda function:

  Fn:
    Type: AWS::Lambda::Function
    Properties:
      Runtime: python3.7
      CodeUri: src
      Handler: fn.handler
      Role: !GetAtt FnRole.Arn
      Layers:
        - arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:35

After installing the CloudFormation Guard utility, you could then validate your template as follows:

cfn-guard validate --rules etc/rules.guard --data iac/lambda.yaml

This would give you an output as follows:

lambda.yaml Status = FAIL
FAILED rules
rules.guard/lambda_soon_to_be_deprecated_runtime
---
Evaluating data lambda.yaml against rules rules.guard
Number of non-compliant resources 1
Resource = Fn {
  Type      = AWS::Lambda::Function
  Rule = lambda_soon_to_be_deprecated_runtime {
    ALL {
      Check =  Runtime not IN  ["nodejs16.x","nodejs14.x","python3.7","java8","dotnet7","go1.x","ruby2.7","provided"] {
        ComparisonError {
          Message          = Lambda function is using a runtime that is targeted for deprecation.
          Error            = Check was not compliant as property [/Resources/Fn/Properties/Runtime[L:88,C:15]] was not present in [(resolved, Path=[L:0,C:0] Value=["nodejs16.x","nodejs14.x","python3.7","java8","dotnet7","go1.x","ruby2.7","provided"])]
        }
          PropertyPath    = /Resources/Fn/Properties/Runtime[L:88,C:15]
          Operator        = NOT IN
          Value           = "python3.7"
          ComparedWith    = [["nodejs16.x","nodejs14.x","python3.7","java8","dotnet7","go1.x","ruby2.7","provided"]]
          Code:
               86.  Fn:
               87.    Type: AWS::Lambda::Function
               88.    Properties:
               89.      Runtime: python3.7
               90.      CodeUri: src
               91.      Handler: fn.handler

      }
    }
  }
}

CloudFormation Guard allows your developers to see from their local developer workstations that they need to update the template to use a runtime that is allowed by the organization. This happens prior to committing to a code repository and subsequently failing checks within a CI/CD pipeline. As a result, your developers get this feedback on how to develop compliant templates and shift his or her time to writing code that delivers business value. This control can be applied on the local developer workstation, in a pre-commit validation webhook, and/or in the CI/CD pipeline prior to deployment.

Caveats

Serverless developers who are using AWS SAM templates to define Lambda functions, be aware that you need to update the CloudFormation Guard rule to search for the AWS::Serverless::Function resource type as follows.

let lambda_functions = Resources.*[
    Type == "AWS::Serverless::Function"
]

CloudFormation Guard also expects the properties to be included within the resource definition. Meanwhile, AWS SAM templates allow for properties to be specified in a separate Globals section. Properties that are defined in the Globals section are not validated with your CloudFormation Guard rules.

As outlined in the CloudFormation Guard troubleshooting documentation, be aware that CloudFormation Guard doesn’t support short-form intrinsics like !GetAtt or !Sub and instead require using the expanded forms: Fn::GetAtt and Fn::Sub. The example above doesn’t evaluate the Role property, so the short-form intrinsic was used for simplicity.


Created by:

Heeki Park
Heeki ParkHeeki Park is a Principal Solutions Architect at AWS and has been helping customers build serverless and cloud-native applications since joining AWS in 2016.
Pallavi Srivastava
Pallavi SrivastavaSenior Cloud Support Engineer, Serverless