CloudFormation GuardをGitHub Actionsに組み込んでみた

2020.10.31

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

CloudFormation Guard を使うことで、CloudFormation をデプロイ前にコンプライアンスチェックできます。
前回の記事は、CloudFormation Guard をローカルで実行しました。

CloudFormation Guard を CI/CD パイプラインに組み込むことによって、デプロイ前にチェックし、コンプライアンスに準拠していないリソースがデプロイされることを防げます。

本記事では、CI/CD パイプラインに GitHub Actions を使ったパターンを試してみます。 なお、CodeCommit / CodePipeline / CodeBuild を使用するパターンについては、以下の公式ブログで紹介されています。

やってみた

CloudFormation Guard で「SSHポートが全公開されているセキュリティグループを許可しない」ルールを作り、該当するセキュリティグループがあった場合にデプロイを中止させます。

実装は、以下の手順で進めます。

  1. CloudFormation テンプレートを作成
  2. GitHub Actions のワークフローを作成
  3. GitHub Secrets を設定
  4. 実行

1. CloudFormation テンプレートを作成

セキュリティグループのテンプレートを抜粋します。

sg.yml

  ALBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub ${ProjectPrefix}-alb-sg
      GroupDescription: !Sub ${ProjectPrefix}-alb-sg
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-alb-sg
      VpcId:  
        Fn::ImportValue: !Sub ${NetworkStackNamePrefix}-VpcId
      SecurityGroupIngress:
      - IpProtocol: "tcp"
        FromPort: 443
        ToPort: 443
        CidrIp: "0.0.0.0/0"

  WebSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub ${ProjectPrefix}-web-sg
      GroupDescription: !Sub ${ProjectPrefix}-web-sg
      Tags:
        - Key: Name
          Value: !Sub ${ProjectPrefix}-web-sg
      VpcId:  
        Fn::ImportValue: !Sub ${NetworkStackNamePrefix}-VpcId
      SecurityGroupIngress:
      - IpProtocol: "tcp"
        FromPort: 22
        ToPort: 22
        CidrIp: "0.0.0.0/0"

よくあるALBとEC2のセキュリティグループを想定しており、ALB は443番ポートで全許可し、EC2 は22番ポートを全許可しています。

2. GitHub Actions のワークフローを作成

ワークフローでは以下を行います。

  • GitHub Secrets に登録した認証情報をセット
  • CloudFormation Guard をインストール
  • CloudFormation Guard を実行
  • CloudFormation デプロイ

check_cfn_guard.yml

name: Compliance check with CloudFormation Guard
on:
  push
defaults:
  run:
      shell: bash
jobs:
  check-cfn-templates:
    name: Check AWS CloudFormation templates
    env:
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    runs-on: ubuntu-18.04
    steps:
      - uses: actions/checkout@v1

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1

      - name: install CloudFormation Guard
        env: 
          CFN_GUARD_VERSION: 1.0.0
        run: |
          wget https://github.com/aws-cloudformation/cloudformation-guard/releases/download/${CFN_GUARD_VERSION}/cfn-guard-linux-${CFN_GUARD_VERSION}.tar.gz
          tar -xvf cfn-guard-linux-${CFN_GUARD_VERSION}.tar.gz

      - name: check network template with CloudFormation Guard
        env: 
          TEMPLATE: network
        run: |
          ./cfn-guard-linux/cfn-guard check --template ./templates/${TEMPLATE}.yml --rule_set ./cfn-guard-rules/${TEMPLATE}_ruleset

      - name: check security group template with CloudFormation Guard
        env: 
          TEMPLATE: sg
        run: |
          ./cfn-guard-linux/cfn-guard check --template ./templates/${TEMPLATE}.yml --rule_set ./cfn-guard-rules/${TEMPLATE}_ruleset

      - name: Deploy network stack
        env:
          STACK_NAME: network
        run: |
          aws cloudformation deploy \
            --stack-name ${STACK_NAME} \
            --template-file ./templates/${STACK_NAME}.yml \
            --no-fail-on-empty-changeset

      - name: Deploy security group stack
        env:
          STACK_NAME: sg
        run: |
          aws cloudformation deploy \
            --stack-name ${STACK_NAME} \
            --template-file ./templates/${STACK_NAME}.yml \
            --no-fail-on-empty-changeset

41 行目で CloudFormation Guard でセキュリティグループのチェックを行っています。ルールセットの中身は以下になります。

sg_ruleset

let disallowed_ingress_rule = [{"CidrIp":"0.0.0.0/0","FromPort":22,"IpProtocol":"tcp","ToPort":22}]

AWS::EC2::SecurityGroup SecurityGroupIngress.* NOT_IN %disallowed_ingress_rule

SecurityGroup のプロパティ SecurityGroupIngress に、変数 disallowed_ingress_rule のルールが含まれないことを定義しています。
ルールセットの書き方の詳細については、こちらを参照ください。

3. GitHub Secrets を設定

GitHub リポジトリから Settings > Secrets を開き、以下のシークレットを登録します。

  • AWS_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY

4. 実行

準備が完了しました。最終的なディレクトリ構成は以下となります。

.
├── README
├── .github
│   └── workflows
│       └── check_cfn_guard.yml
├── cfn-guard-rules
│   ├── network_ruleset
│   └── sg_ruleset
└── templates
    ├── network.yml
    └── sg.yml

GitHub にプッシュします。

$ git add -A
$ git commit -m 'initial commit'
$ git push

GitHub Actions のワークフローを確認すると、cfn-guard でルールセットに準拠していないため、エラーが発生しています。

エラーの原因となっている、45 行目を以下の通り変更し、再度 GitHub にプッシュします。

sg.yml

      SecurityGroupIngress:
      - IpProtocol: "tcp"
        FromPort: 22
        ToPort: 22
        CidrIp: "123.45.67.89/32"

ワークフローを確認すると、エラーが消えデプロイで来ていることを確認できました。

さいごに

CloudFormation Guard を GitHub Actions に組み込み、デプロイ前に自動でテンプレートがコンプライアンスに準拠していることをチェックさせてみました。
今回は行いませんでしたが、cfn-lint もワークフローに入れることで、より安全なデプロイができると思います。

本ブログで作成したリソースはこちらになります。

参考