CloudFormation Guardを利用してAWS Configのカスタムルールを作成する

記事タイトルとURLをコピーする

こんにちは。島村です。

今更ながらではありますが、Cloud Formation Guardを利用して、
AWS Configのカスタムルールを作成できるようになりました。

早速試してみたいと思います。

aws.amazon.com

CloudFormation Guardとは

CloudFormation Guardとはオープンソースのポリシー評価ツールです。
ツールをインストールして、CLIで操作します。
JSONやYAML形式の構造化されたデータをポリシー評価できます。

ドメイン固有言語にはなりますが、ルールを記載したファイル(.guard)を用意し、
チェックコマンドを流すと、ルールファイルの通り、CloudFormationテンプレートが記述されているかチェックしてくれます。

CloudFormationという名前が付いてますが、CloudFormation テンプレート以外にも
CloudFormation 変更セット、JSON ベースの Terraform 構成ファイル、または Kubernetes 構成ファイルの検証できるようです。

今後、Terraformの構成ファイルチェックもやってみたいと思います。 CloudFromationテンプレートのやってみたブログが弊社エンジニアが執筆してますのでよろしければご覧ください。

blog.serverworks.co.jp

AWS Config カスタムルールをCloudFormation Guardで作成するメリット

これまでは、AWS Config カスタムルールを作成するにはAWS Lambdaを作成する必要がありました。
AWS Lambdaなので、プログラミング言語でルールを記述する必要がありますし、
言語ごとにランタイムの更新や、更新に伴うコードの修正なども発生します。

CloudFormation Guardを使うことで、Lambdaの開発が必要なくなり
Lambdaの運用保守もなくなる点がメリットになると考えます。

実際に作ってみる

CloudFormation Guardを利用したAWS Configのカスタムルールはコンソール/CLIどちらでも作成が可能です。
今回はAWSコンソールを使用して手を動かしていきたいと思います。

AWS Configコンソールへ移動後、[ルール]→[ルールを追加]を選択します。

今回はEC2にPublic IPアドレスが付与されているかどうかをチェックする簡単なルールを作成してみたいと思います。
以下は、CloudFormation Guardのドメイン固有言語で記載したルールです。

let aws_ec2_instance_resources = Resources.*[ Type == 'AWS::EC2::Instance' ]

rule ec2_public_ip_disabled{
  configuration.publicIpAddress empty
}

Public IPアドレスの設定が有効になっていないことを確認しています。

CloudFormation Guardのルールの書き方は別途解説の記事を作成したいと思います。


CloudFormation GuardでConfigのカスタムルールを作成する場合、Configの設定情報を参考にします。
設定情報はConfigコンソールから確認が可能です。

以下はEC2の設定情報です。

{
  "version": "1.3",
  "accountId": "89307521xxxx",
  "configurationItemCaptureTime": "2023-04-18T07:57:19.967Z",
  "configurationItemStatus": "ResourceDiscovered",
  "configurationStateId": "1681804639967",
  "configurationItemMD5Hash": "",
  "arn": "arn:aws:ec2:us-east-1:893075211920:instance/i-0a8860e30dbe87487",
  "resourceType": "AWS::EC2::Instance",
  "resourceId": "i-0a8860e30dbe87487",
  "awsRegion": "us-east-1",
  "availabilityZone": "us-east-1c",
  "resourceCreationTime": "2023-04-18T07:56:49.000Z",
  "tags": {
    "Name": "test"
  },
  "relatedEvents": [],
  "relationships": [
    {
      "resourceType": "AWS::EC2::VPC",
      "resourceId": "vpc-a115a3db",
      "relationshipName": "Is contained in Vpc"
    },
    {
      "resourceType": "AWS::EC2::Subnet",
      "resourceId": "subnet-1c395932",
      "relationshipName": "Is contained in Subnet"
    },
    {
      "resourceType": "AWS::EC2::Volume",
      "resourceId": "vol-060f4f01afd681efd",
      "relationshipName": "Is attached to Volume"
    },
    {
      "resourceType": "AWS::EC2::SecurityGroup",
      "resourceId": "sg-014bfab522d76fb3e",
      "relationshipName": "Is associated with SecurityGroup"
    },
    {
      "resourceType": "AWS::EC2::NetworkInterface",
      "resourceId": "eni-05fb8eece2bf695ed",
      "relationshipName": "Contains NetworkInterface"
    }
  ],
  "configuration": {
    "amiLaunchIndex": 0,
    "imageId": "ami-06e46074ae430fba6",
    "instanceId": "i-0a8860e30dbe87487",
    "instanceType": "t2.micro",
    "launchTime": "2023-04-18T07:56:49.000Z",
    "monitoring": {
      "state": "disabled"
    },
    "placement": {
      "availabilityZone": "us-east-1c",
      "groupName": "",
      "tenancy": "default"
    },
    "privateDnsName": "ip-172-31-95-199.ec2.internal",
    "privateIpAddress": "172.31.95.199",
    "productCodes": [],
    "publicDnsName": "ec2-52-23-209-58.compute-1.amazonaws.com",
    "publicIpAddress": "52.23.209.58",
    "state": {
      "code": 16,
      "name": "running"
    },
    "stateTransitionReason": "",
    "subnetId": "subnet-1c395932",
    "vpcId": "vpc-a115a3db",
    "architecture": "x86_64",
    "blockDeviceMappings": [
      {
        "deviceName": "/dev/xvda",
        "ebs": {
          "attachTime": "2023-04-18T07:56:50.000Z",
          "deleteOnTermination": true,
          "status": "attached",
          "volumeId": "vol-060f4f01afd681efd"
        }
      }
    ],
    "clientToken": "aa2b259c-0068-4bf5-9d54-1708ed9d7e56",
    "ebsOptimized": false,
    "enaSupport": true,
    "hypervisor": "xen",
    "elasticGpuAssociations": [],
    "elasticInferenceAcceleratorAssociations": [],
    "networkInterfaces": [
      {
        "association": {
          "ipOwnerId": "amazon",
          "publicDnsName": "ec2-52-23-209-58.compute-1.amazonaws.com",
          "publicIp": "52.23.209.58"
        },
        "attachment": {
          "attachTime": "2023-04-18T07:56:49.000Z",
          "attachmentId": "eni-attach-08a2aac23126170f9",
          "deleteOnTermination": true,
          "deviceIndex": 0,
          "status": "attached",
          "networkCardIndex": 0
        },
        "description": "",
        "groups": [
          {
            "groupName": "launch-wizard-8",
            "groupId": "sg-014bfab522d76fb3e"
          }
        ],
        "ipv6Addresses": [],
        "macAddress": "12:a6:a6:13:f9:77",
        "networkInterfaceId": "eni-05fb8eece2bf695ed",
        "ownerId": "893075211920",
        "privateDnsName": "ip-172-31-95-199.ec2.internal",
        "privateIpAddress": "172.31.95.199",
        "privateIpAddresses": [
          {
            "association": {
              "ipOwnerId": "amazon",
              "publicDnsName": "ec2-52-23-209-58.compute-1.amazonaws.com",
              "publicIp": "52.23.209.58"
            },
            "primary": true,
            "privateDnsName": "ip-172-31-95-199.ec2.internal",
            "privateIpAddress": "172.31.95.199"
          }
        ],
        "sourceDestCheck": true,
        "status": "in-use",
        "subnetId": "subnet-1c395932",
        "vpcId": "vpc-a115a3db",
        "interfaceType": "interface"
      }
    ],
    "rootDeviceName": "/dev/xvda",
    "rootDeviceType": "ebs",
    "securityGroups": [
      {
        "groupName": "launch-wizard-8",
        "groupId": "sg-014bfab522d76fb3e"
      }
    ],
    "sourceDestCheck": true,
    "tags": [
      {
        "key": "Name",
        "value": "test"
      }
    ],
    "virtualizationType": "hvm",
    "cpuOptions": {
      "coreCount": 1,
      "threadsPerCore": 1
    },
    "capacityReservationSpecification": {
      "capacityReservationPreference": "open"
    },
    "hibernationOptions": {
      "configured": false
    },
    "licenses": [],
    "metadataOptions": {
      "state": "applied",
      "httpTokens": "required",
      "httpPutResponseHopLimit": 2,
      "httpEndpoint": "enabled"
    },
    "enclaveOptions": {
      "enabled": false
    },
    "bootMode": "uefi-preferred"
  },
  "supplementaryConfiguration": {},
  "resourceTransitionStatus": "None"
}

Public IPアドレス部分を見ていただくとわかりますが、設定が入っているので、
Config カスタムルールでは非準拠として検出されるのが期待値となります。
続けて設定を行なっていきます。

評価モードは、デプロイリソースのみを対象にするよう設定を行います。
デプロイする前に評価するプロアクティブモードという機能もあります。
デプロイする前に評価する機能は、CloudFormationによるデプロイを行う前に評価を行うことで
作成されるリソースがコンプライアンス要件を満たした状態なのかどうかを確認してくれます。
今回は作成した後のリソースの設定を確認するので、オフのままとします。

[次へ]を選択し、設定情報を確認して問題なければ作成します。

Configのカスタムルールを作成したら、グローバルIPアドレスを付与した状態でEC2インスタンスを作成します。

作成後、作成したカスタムルールを確認してみると非準拠になっていることが確認できました。

最後に

これまでConfigのカスタムルールはLambdaを作成する必要がありました。
CloudFormation Guardを利用したルールの書き方を覚える必要はありますが、 CloudFormation以外の構成管理ツールのチェックもできたりするため覚えてもいいかもしれません。

今回はCloudFormation GuardでConfig Rulesのカスタムルールの作成を紹介しましたが、 CloudFormation GuardをCI/CDパイプラインに組み込めることもできるので、その辺りもキャッチアップできればと思います。

島村 輝 (Shimamura Hikaru) 記事一覧はコチラ

最近ECS周りをキャッチアップ中。趣味は車・バイク全般。
一応、AWS12冠です。