This article guides the reader on how to validate their CloudFormation Template using cfn-lint and cfn-nag tools. It also explains how to create a pipeline that validates a CloudFormation template and deploys it to CloudFormation.

It is good DevOps practice to always include a step for checking our code/templates for security and syntax errors. Also, In our case, it saves us from having a Pipeline fail because of syntax errors.

Requirements/Prerequisites

  • An AWS Account.
  • Created a User on the account with Permissions to provision resources on the account.
  • A CodeCommit repo containing your buildspec.yml and the CloudFormation Template to be validated.

buildspec.yml Contents

The buildspec.yml file uploaded on our CodeCommit repo should contain the following code.

First, it installs the cfn-lint and cfn-nag tools. Then, it checks the CloudFormation template using the two tools.

version: 0.2
phases:
  install:
    runtime-versions:
        ruby: 2.6    
    commands:
    - pip3 install awscli --upgrade --quiet
    - pip3 install cfn-lint --quiet              
    - apt-get install jq git -y -q
    - gem install cfn-nag
  build:
    commands:
    - cd ./
    - cfn-lint ECR.yaml
    - cfn_nag_scan -i ECR.yaml

Where ECR.yaml should be replaced with the name of the template you want to validate. Your cloudformation template has to be uploaded to the CodeCommit repository.

CloudFormation cfn-lint

Using cfn-lint enables syntax error checks on your CloudFormation Template. To check your template, you run the below command. Assuming that our template is written in YAML format.

cfn-lint templatename.yaml

CloudFormation cfn-nag

The cfn-nag tool is for security checks. It examines the CloudFormation Template for any insecure infrastructure e.g. security groups that allows access for everyone. To check your template, you run the below command. Assuming that our template is written in YAML format.

cfn_nag_scan -i template.yaml

Create a CodeBuild Project

We will create a CodeBuild project to check and validate our CloudFormation template before we deploy it to CloudFormation. We will use the below CloudFormation Template to create our Build Project.

AWSTemplateFormatVersion: "2010-09-09"
Description: "Template to create codebuild Project"

Parameters:
    ProjectName:
        Type: String
        Description: The CodeBuild Project Name
        Default: Validate-CF-Template

    CodeBuildRole:
        Type: String
        Description: The name codebuild role
        Default: CodeBuild-ValidateCF

Resources:
  IAMRole:
    Type: 'AWS::IAM::Role'
    Properties:
      Description: The CodeBuild Validate CF Template Role
      RoleName: !Ref CodeBuildRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
              - codebuild.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AWSCodeBuildAdminAccess
        - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
        - arn:aws:iam::aws:policy/AWSCodeCommitFullAccess
        - arn:aws:iam::aws:policy/AmazonS3FullAccess
      Tags: 
        - 
          Key: "Project"
          Value: "test-blog"
        - 
          Key: "Environment"
          Value: "test"
        - 
          Key: "createdBy"
          Value: "Maureen Barasa"
        - 
          Key: "Name"
          Value: !Ref CodeBuildRole

  BuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: !Ref ProjectName
      Description: build project to validate our CF Template
      ServiceRole: !Ref IAMRole
      Artifacts:
          Type: NO_ARTIFACTS
      Environment:
          Type: LINUX_CONTAINER
          ComputeType: BUILD_GENERAL1_SMALL
          Image: aws/codebuild/standard:4.0
          PrivilegedMode: true  
      Source:
          Location: https://git-codecommit.eu-central-1.amazonaws.com/v1/repos/test-lint
          Type: CODECOMMIT
          BuildSpec: buildspec.yml
      TimeoutInMinutes: 15
      Tags: 
        - 
          Key: "Project"
          Value: "test-blog"
        - 
          Key: "Environment"
          Value: "test"
        - 
          Key: "createdBy"
          Value: "Maureen Barasa"
        - 
          Key: "Name"
          Value: !Ref ProjectName

Outputs:
    Project:
        Description: The codebuild build project
        Value: !Ref ProjectName
        Export:
          Name: "BuildProject"
          Value: !Ref ProjectName

First, the template starts by provisioning a CodeBuild role. Then it creates the Build Project.

N/B: The user should customize the template. The names of Resources, Properties, and Tags should match the user’s specific requirements.

When you run the build project, you get the below output:

Build Project Results
Build Project Results

Once our template is validated we get results showing any failures or warnings. My Template has no Syntax or Security errors but I have 1 warning.

In case we have a failure the build project will fail. For example, if we have a syntax error on our template. See results below.

Build Project Fail Results
Build Project Fail Results

Create a CodePipeline To Deploy the Template to CloudFormation

We can add the above build project to a pipeline to deploy our CloudFormation template. Use the below template to create a Pipeline that validate your CloudFormation template and deploys it to CloudFormation for provisioning of resources.

AWSTemplateFormatVersion: "2010-09-09"
Description: "Template to create codepipeline"

Parameters:
  PipelineName:
        Type: String
        Description: The CodePipeline Name
        Default: Deploy-to-CloudFormation

  Stack:
        Type: String
        Description: The Cloudformation Stack Name
        Default: eu-central-1-create-ECR-Stack

  PipelineRole:
        Type: String
        Description: The Cloudformation Deploy Role Name
        Default: codepipeline-ECR

  CFRole:
        Type: String
        Description: The CodePipeline Role Name
        Default: codepipeline-cloudformation

  CWEventsRole:
        Type: String
        Description: The Cloudwatch Events Role Name
        Default: codepipeline-cwevents


  BucketName:
        Type: String
        Description: The vpc where our bastion hosts will be located
        Default: maureen-test-cp
  
Resources:
  S3:
    Type: AWS::S3::Bucket
    Properties:
      AccessControl: Private
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
               SSEAlgorithm: AES256
      BucketName: !Ref BucketName
      PublicAccessBlockConfiguration:
        BlockPublicAcls: TRUE
        BlockPublicPolicy: TRUE
        IgnorePublicAcls: TRUE
        RestrictPublicBuckets: TRUE
      VersioningConfiguration:
        Status: Enabled
      Tags: 
        - 
          Key: "Project"
          Value: "test-blog"
        - 
          Key: "Environment"
          Value: "test"
        - 
          Key: "createdBy"
          Value: "Maureen Barasa"
        - 
          Key: "Name"
          Value: !Ref BucketName

  CodePipelineRole:
    Type: 'AWS::IAM::Role'
    Properties:
      Description: The CodeBuild Validate CF Template Role
      RoleName: !Ref PipelineRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
              - "codepipeline.amazonaws.com"
            Action:
              - 'sts:AssumeRole'            
      Policies:
        - PolicyName: root
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: 
                  - 'iam:PassRole'
                Resource: '*'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AWSCodePipeline_FullAccess
        - arn:aws:iam::aws:policy/AWSCodeCommitFullAccess
        - arn:aws:iam::aws:policy/AmazonS3FullAccess
        - arn:aws:iam::aws:policy/AWSCodeBuildAdminAccess
        - arn:aws:iam::aws:policy/AWSCodeDeployFullAccess
        - arn:aws:iam::aws:policy/AWSCloudFormationFullAccess
      Tags: 
        - 
          Key: "Project"
          Value: "test-blog"
        - 
          Key: "Environment"
          Value: "test"
        - 
          Key: "createdBy"
          Value: "Maureen Barasa"
        - 
          Key: "Name"
          Value: !Ref PipelineRole

  CloudFormationRole:
    Type: 'AWS::IAM::Role'
    Properties:
      Description: The CodeBuild Validate CF Template Role
      RoleName: !Ref CFRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
              - "cloudformation.amazonaws.com"
            Action:
              - 'sts:AssumeRole'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonS3FullAccess
        - arn:aws:iam::aws:policy/PowerUserAccess
      Tags: 
        - 
          Key: "Project"
          Value: "test-blog"
        - 
          Key: "Environment"
          Value: "test"
        - 
          Key: "createdBy"
          Value: "Maureen Barasa"
        - 
          Key: "Name"
          Value: !Ref CFRole

  AmazonCloudWatchEventRole:
    Type: 'AWS::IAM::Role'
    Properties:
      Description: The CodeBuild Validate CF Template Role
      RoleName: !Ref CWEventsRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
              - "events.amazonaws.com"
            Action:
              - 'sts:AssumeRole'
      Policies:
        - PolicyName: root
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: 
                  - 'codepipeline:StartPipelineExecution'
                Resource: !Join [ '', [ 'arn:aws:codepipeline:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':', !Ref DeployPipeline ] ] 
      Tags: 
        - 
          Key: "Project"
          Value: "test-blog"
        - 
          Key: "Environment"
          Value: "test"
        - 
          Key: "createdBy"
          Value: "Maureen Barasa"
        - 
          Key: "Name"
          Value: !Ref CWEventsRole

  AmazonCloudWatchEventRule:
    Type: AWS::Events::Rule
    Properties:
      EventPattern:
        source:
          - aws.codecommit
        detail-type:
          - 'CodeCommit Repository State Change'
        resources:
          - arn:aws:codecommit:eu-central-1:429758582529:test-lint
        detail:
          event:
            - referenceCreated
            - referenceUpdated
          referenceType:
            - branch
          referenceName:
            - master
      Targets:
        -
          Arn: 
            !Join [ '', [ 'arn:aws:codepipeline:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':', !Ref DeployPipeline ] ]
          RoleArn: !GetAtt AmazonCloudWatchEventRole.Arn
          Id: codepipeline-DeployPipeline

  DeployPipeline: 
    Type: AWS::CodePipeline::Pipeline 
    Properties: 
      RoleArn: !GetAtt CodePipelineRole.Arn
      Name: !Ref PipelineName
      ArtifactStore:
          Location: !Ref S3
          Type: S3
      Stages: 
        - 
          Name: Source 
          Actions: 
            - 
              Name: SourceAction
              ActionTypeId: 
                Category: Source 
                Owner: AWS 
                Version: 1 
                Provider: CodeCommit 
              OutputArtifacts: 
                - 
                  Name: SourceArtifact 
              Configuration: 
                RepositoryName: test-lint
                BranchName: master
                PollForSourceChanges: false 
              RunOrder: 1 
        - 
          Name: Build 
          Actions: 
            -
              Name: BuildAction
              InputArtifacts:
                - Name: SourceArtifact   
              ActionTypeId: 
                Category: Build 
                Owner: AWS 
                Version: 1 
                Provider: CodeBuild
              Configuration:
                ProjectName: !ImportValue BuildProject
                BatchEnabled: 'false'
              OutputArtifacts: []
              RunOrder: 1

        - 
          Name: Deploy 
          Actions: 
            - 
              Name: DeployAction
              InputArtifacts:
                - Name: SourceArtifact
              ActionTypeId: 
                Category: Deploy 
                Owner: AWS 
                Version: 1
                Provider: CloudFormation 
              Configuration: 
                ActionMode: CREATE_UPDATE
                StackName: !Ref Stack
                Capabilities: CAPABILITY_IAM,CAPABILITY_NAMED_IAM
                RoleArn: !GetAtt CloudFormationRole.Arn
                TemplatePath: "SourceArtifact::ECR.yaml" 
              RunOrder: 1  
      Tags: 
        - 
          Key: "Project"
          Value: "test-blog"
        - 
          Key: "Environment"
          Value: "test"
        - 
          Key: "createdBy"
          Value: "Maureen Barasa"
        - 
          Key: "Name"
          Value: !Ref PipelineName

The template starts by provisioning an S3 bucket to upload our CodePipeline artifacts. Then it creates three roles. The CodePipeline role to be assumed by CodePipeline, the CloudFormation role to be used on the deploy stage and the CloudWatch events role. The CloudWatch events role is used by the CloudWatch events rule to monitor for changes in the CodeCommit repository and trigger the pipeline to start.

Finally, the template provisions the pipeline. The pipeline has three stages. Source, Build and Deploy. The source stage uploads code from CodeCommit. The build stage uses the build project to validate our CloudFormation Template. Finally the deploy stage, deploys the template to CloudFormation.

N/B: The user should customize the template. The names of Resources, Properties, and Tags should match the user’s specific requirements.

A Successful Pipeline
The CodePipeline

Other AWS Guides:

Happy Building!!!

Your support is our everlasting motivation,
that cup of coffee is what keeps us going!


As we continue to grow, we would wish to reach and impact more people who visit and take advantage of the guides we have on our blog. This is a big task for us and we are so far extremely grateful for the kind people who have shown amazing support for our work over the time we have been online.

Thank You for your support as we work to give you the best of guides and articles. Click below to buy us a coffee.

LEAVE A REPLY

Please enter your comment!
Please enter your name here