CloudFormation Guardを利用してVPCに関するConfigのカスタムルールを色々試してみた

習熟のため、AWS CloudFormation Guard を使って VPC に関する AWS Config のカスタムルルールが作成してみました。事前学習に利用したドキュメントも紹介します。
2022.08.08

AWS CloudFormation Guard を利用して、AWS Config のカスタムルールを作成できるようになりました。今後のカスタムルール作成は CloudFormation Guard を利用してみたいと思ったので、習熟のために VPC に関するいくつかのカスタムルールを作ってみました。CloudFormation Guard でカスタムルールを作成するために学習したドキュメントも合わせて紹介します。


試してみた

今回は、VPC(AWS::EC2::VPC)に関するカスタムルールを Guard で作成してみました。

事前学習

始めに AWS のブログを確認して、とりあえず動くカスタムルールを作成してイメージを掴みました。

Announcing AWS Config Custom Rules using Guard Custom policy | AWS Cloud Operations & Migrations Blog

その後、GitHub 上のドキュメントで CloudFormation Guard の文法を学習しました。

cloudformation-guard/docs at main · aws-cloudformation/cloudformation-guard

後で気づいたのですが、AWS のユーザーガイドにも Guard の文法を記載したページがありましたので、こちらを参照でも良いかもしれません。

Getting started with AWS CloudFormation Guard - AWS CloudFormation Guard

GitHub 上にはサンプルもあります。サンプルは CloudFormation のチェック向けと思われますが、文法は同じなので Config のカスタムルールの参考になります。

cloudformation-guard/guard-examples at main · aws-cloudformation/cloudformation-guard

Config が保持するデータの構造も知っておく必要があるため、確認しました。

マネジメントコンソールから確認する場合は、Config サービスの「リソース」から VPC のリソース ID で検索することで確認できます。

「設定項目(JSON)の表示」から確認します。

AWS CLI で取得する場合は次のブログが参考になります。

VPC の設定項目例です。サブネットを作成していない VPC となります。また、後述において設定項目を記載している箇所がありますが、この例とは異なる項目例を記載している場合がありますので、ご注意願います。

VPCの設定項目例(折りたたんでいます)
{
  "version": "1.3",
  "accountId": "111122223333",
  "configurationItemCaptureTime": "2022-08-03T15:25:01.025Z",
  "configurationItemStatus": "OK",
  "configurationStateId": "1659540301025",
  "configurationItemMD5Hash": "",
  "arn": "arn:aws:ec2:ap-northeast-1:111122223333:vpc/vpc-0636fc95b3cf20d33",
  "resourceType": "AWS::EC2::VPC",
  "resourceId": "vpc-0636fc95b3cf20d33",
  "awsRegion": "ap-northeast-1",
  "availabilityZone": "Multiple Availability Zones",
  "tags": {
    "Name": "test3-vpc"
  },
  "relatedEvents": [],
  "relationships": [
    {
      "resourceType": "AWS::EC2::SecurityGroup",
      "resourceId": "sg-06b039c10a3838d4e",
      "relationshipName": "Contains SecurityGroup"
    },
    {
      "resourceType": "AWS::EC2::RouteTable",
      "resourceId": "rtb-017783b6ec2783d70",
      "relationshipName": "Contains RouteTable"
    },
    {
      "resourceType": "AWS::EC2::NetworkAcl",
      "resourceId": "acl-095afdcd82caff293",
      "relationshipName": "Contains NetworkAcl"
    }
  ],
  "configuration": {
    "cidrBlock": "172.16.0.0/24",
    "dhcpOptionsId": "dopt-0024732f85497bcd2",
    "state": "available",
    "vpcId": "vpc-0636fc95b3cf20d33",
    "ownerId": "111122223333",
    "instanceTenancy": "default",
    "ipv6CidrBlockAssociationSet": [
      {
        "associationId": "vpc-cidr-assoc-092cd3f6cc6250add",
        "ipv6CidrBlock": "2001:db8:1:1::/56",
        "ipv6CidrBlockState": {
          "state": "associated"
        },
        "networkBorderGroup": "ap-northeast-1",
        "ipv6Pool": "Amazon"
      }
    ],
    "cidrBlockAssociationSet": [
      {
        "associationId": "vpc-cidr-assoc-0ee4a773782a3f3fd",
        "cidrBlock": "172.16.0.0/24",
        "cidrBlockState": {
          "state": "associated"
        }
      },
      {
        "associationId": "vpc-cidr-assoc-0dd35303baf668482",
        "cidrBlock": "172.16.1.0/24",
        "cidrBlockState": {
          "state": "associated"
        }
      }
    ],
    "isDefault": false,
    "tags": [
      {
        "key": "Name",
        "value": "test3-vpc"
      }
    ]
  },
  "supplementaryConfiguration": {},
  "resourceTransitionStatus": "None"
}


次の節以降では、需要があるかは別にして、練習として VPC のカスタムルールを作成した例を紹介していきます。


指定した DHCP オプションセットが設定されているか

let dhcp_option_set_id = "dopt-0024732f85497bcd2"

rule vpc_dhcp_option_check {
    configuration.dhcpOptionsId == %dhcp_option_set_id
}
  • dhcp_option_set_idで指定した DHCP オプションセットが設定されていない場合は非準拠
  • トリガー設定
    • 変更範囲 : リソース
      • リソースカテゴリ : AWS リソース
      • リソースタイプ : AWS EC2 VPC

configurationパラメータ内のdhcpOptionsIdと比較しています。

(略)
  "configuration": {
    "cidrBlock": "172.16.0.0/24",
    "dhcpOptionsId": "dopt-0024732f85497bcd2",
    "state": "available",
    "vpcId": "vpc-0636fc95b3cf20d33",
    "ownerId": "111122223333",
    "instanceTenancy": "default",
(略)

DHCP オプションセットのリソース ID をパラメータとして指定することもできます。DHCPOptionSetIdをパラメータのキーとした場合の例です。

rule vpc_dhcp_option_check {
    configuration.dhcpOptionsId == CONFIG_RULE_PARAMETERS.DHCPOptionSetId
}

パラメータ設定は次の画像のようになります。


CIDR がプライベート IPv4 アドレスの範囲であるか

let ipv4_cdir_block = configuration.cidrBlock
let ipv4_cdir_block_association_set = configuration.cidrBlockAssociationSet.*

rule vpc_ipv4_cidr_block {
    %ipv4_cdir_block == /(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)/
}

rule vpc_ipv4_cidr_block_association_set {
    %ipv4_cdir_block_association_set.cidrBlock == /(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)/
}
  • CIDR がプライベート IPv4 アドレスの範囲でない場合は非準拠
  • トリガー設定
    • 変更範囲 : リソース
      • リソースカテゴリ : AWS リソース
      • リソースタイプ : AWS EC2 VPC

プライベート IPv4 アドレスかどうかは CIDR の前方一致で確認しています(他にもっと良い方法があるかも)。

確認するパラメータはcidrBlockcidrBlockAssociationSet内のcidrBlockの両方にしています。VPC に追加の CIDR を設定している場合はcidrBlockAssociationSetに追加されるため、両方を確認する内容にしました。しかしながら、VPC 作成時に指定する CIDR もcidrBlockAssociationSetに含まれているため、cidrBlockAssociationSet内のcidrBlockのチェックだけでも問題ないかもしれません。

(略)
  "configuration": {
    "cidrBlock": "172.16.0.0/24",
    "dhcpOptionsId": "dopt-0024732f85497bcd2",
    "state": "available",
    "vpcId": "vpc-0636fc95b3cf20d33",
    "ownerId": "111122223333",
    "instanceTenancy": "default",
    "ipv6CidrBlockAssociationSet": [
      {
        "associationId": "vpc-cidr-assoc-092cd3f6cc6250add",
        "ipv6CidrBlock": "2001:db8:1:1::/56",
        "ipv6CidrBlockState": {
          "state": "associated"
        },
        "networkBorderGroup": "ap-northeast-1",
        "ipv6Pool": "Amazon"
      }
    ],
    "cidrBlockAssociationSet": [
      {
        "associationId": "vpc-cidr-assoc-0ee4a773782a3f3fd",
        "cidrBlock": "172.16.0.0/24",
        "cidrBlockState": {
          "state": "associated"
        }
      },
      {
        "associationId": "vpc-cidr-assoc-0dd35303baf668482",
        "cidrBlock": "172.16.1.0/24",
        "cidrBlockState": {
          "state": "associated"
        }
      }
    ],
    "isDefault": false,
    "tags": [
      {
        "key": "Name",
        "value": "test3-vpc"
      }
    ]
  },
(略)


IPv6 アドレスを設定していないか

rule vpc_ipv6_not_enabled {
    configuration.ipv6CidrBlockAssociationSet empty
}
  • IPv6 を設定している場合は非準拠
  • トリガー設定
    • 変更範囲 : リソース
      • リソースカテゴリ : AWS リソース
      • リソースタイプ : AWS EC2 VPC

IPv6 アドレスを設定している場合は、ipv6CidrBlockAssociationSetに値が入るため、ipv6CidrBlockAssociationSetが空の場合に準拠、空ではない場合に非準拠としています。次の例では、IPv6 アドレスが存在するため非準拠となります。

(略)
  "configuration": {
    "cidrBlock": "172.16.0.0/24",
    "dhcpOptionsId": "dopt-0024732f85497bcd2",
    "state": "available",
    "vpcId": "vpc-0636fc95b3cf20d33",
    "ownerId": "111122223333",
    "instanceTenancy": "default",
    "ipv6CidrBlockAssociationSet": [
      {
        "associationId": "vpc-cidr-assoc-092cd3f6cc6250add",
        "ipv6CidrBlock": "2001:db8:1:1::/56",
        "ipv6CidrBlockState": {
          "state": "associated"
        },
        "networkBorderGroup": "ap-northeast-1",
        "ipv6Pool": "Amazon"
      }
    ],
(略)


IPv6 アドレスを設定しているか

rule vpc_ipv6_enabled {
    configuration.ipv6CidrBlockAssociationSet !empty
}
  • IPv6 を設定していない場合は非準拠
  • トリガー設定
    • 変更範囲 : リソース
      • リソースカテゴリ : AWS リソース
      • リソースタイプ : AWS EC2 VPC

IPv6 アドレスを設定している場合は、ipv6CidrBlockAssociationSetに値が入るため、ipv6CidrBlockAssociationSetが空ではない場合に準拠、空の場合に非準拠としています。次の例では、IPv6 アドレスが存在していないため非準拠となります。

(略)
 "configuration": {
    "cidrBlock": "10.0.0.0/16",
    "dhcpOptionsId": "dopt-0ae97f3769798f9ff",
    "state": "available",
    "vpcId": "vpc-057355bebd0cc89b2",
    "ownerId": "111122223333",
    "instanceTenancy": "default",
    "ipv6CidrBlockAssociationSet": [],
(略)

一つ前の節の「IPv6 アドレスを設定していないか」の逆パターンなのですが、作成時にはどちらか準拠判定となるのか混乱しました。慣れが必要そうです。


Internet Gateway が関連付けされていないか

let vpc_relationships = relationships.*

rule vpc_without_igw {
    %vpc_relationships.resourceType != "AWS::EC2::InternetGateway"
}
  • Internet Gateway が関連付けされている場合は非準拠
  • トリガー設定
    • 変更範囲 : リソース
      • リソースカテゴリ : AWS リソース
      • リソースタイプ : AWS EC2 VPC

Internet Gateway が関連付けされている場合は、relationshipsに Internet Gateway のリソースが存在するため、relationships内にresourceTypeAWS::EC2::InternetGatewayであるリソースが存在していないことを確認しています。

(略)
  "relationships": [
    {
      "resourceType": "AWS::EC2::InternetGateway",
      "resourceId": "igw-0f8ac80ef1c0f31a2",
      "relationshipName": "Is attached to InternetGateway"
    },
    {
      "resourceType": "AWS::EC2::Subnet",
      "resourceId": "subnet-0e8a21edbfaed39ae",
      "relationshipName": "Contains Subnet"
    },
(略)


デフォルト VPC が存在しないか

rule no_default_vpc {
    resourceId != /^vpc-[0-9a-z]{8}$/
}
  • デフォルト VPC が存在している場合は非準拠
  • トリガー設定
    • 変更範囲 : リソース
      • リソースカテゴリ : AWS リソース
      • リソースタイプ : AWS EC2 VPC

デフォルト VPC のvcpIdはユーザーが作成した VPC と比べて、文字長が短い特徴があるので、それを利用して判定してみました。

(略)
  "configuration": {
    "cidrBlock": "172.31.0.0/16",
    "dhcpOptionsId": "dopt-19400970",
    "state": "available",
    "vpcId": "vpc-0a440263",
    "ownerId": "111122223333",
    "instanceTenancy": "default",
(略)

もしかしたら、私が見逃しているだけでもっと簡単にデフォルト VPC であることを判定する方法があるかもしれません。上記のカスタムルールでは、デフォルト VPC のリソース ID に関する仕様が変わったとき、もしくは、私が認識できていないリソース ID のパターンがあるときに動作しない懸念があります。その点にご注意ください。


さいごに

CloudFormation Guard を習熟したく、VPC に関する Config のカスタムルールを作成してみました。ルールを書いている途中で、どちらが準拠でどちらが非準拠になるか混乱することが多かったです。同じ内容のカスタムルールでも複数の書き方があるので、どのような書き方が良いのかは今後も模索していきたいと思いました。

このブログがどなたかのご参考になれば幸いです。