特定のCIDR範囲のVPCをチェックする

# はじめに

VPCが特定のCIDR範囲かどうかをチェックする方法を作ってみましたので記事にしました。

# 概要
以下の２つを作りました。
- 作成済みのVPCのCIDR範囲をチェックするコマンド
    - 全リージョンを一度にチェック
- "（作成済み・今後作成される）VPCのCIDR範囲をチェックするAWS Config"を作成するCFnテンプレート
    - リージョンごと
        - 複数のリージョンに設定するには、StackSetsなどを用いると良いと思います

# きっかけ
以下の~~ツイート~~ポストを見かけたのがきっかけでした。

https://twitter.com/hatake_book/status/1682992600733851648

公式では以下になります。

https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/vpc-cidr-blocks.html#vpc-sizing-ipv4


そのため今回は"172.17.0.0/16"っぽい範囲をチェックする仕組みを作ってみました。

:::note warn
CIDRを評価する式は適当なので適宜変えてください。
:::

# 参考

https://dev.classmethod.jp/articles/aws-cli-all-region/

https://opt-p.co.jp/blog/aws/post-2080/

https://dev.classmethod.jp/articles/config-vpc-cidr/

https://blog.serverworks.co.jp/create/custom-config-rules



# やったこと

## 事前準備

まずはNGなVPCを、以下のCFnテンプレートを用いて作成します。
```yaml:createNgVpc.yaml
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
  VpcCIDRBlock:
    Type: String
    Default: 172.17.0.0/16
  VpcName:
    Type: String
    Default: NgVpc
Resources:
  NgVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDRBlock
      Tags:
        - Key: Name
          Value: !Ref VpcName
```

## チェックコマンド

チェックするコマンドとして、以下を作りました。CloudShellで実行できます。
```
regions=(`aws ec2 describe-regions --query Regions[*].RegionName --output text`)

for region in ${regions[@]}
do echo "[${region}]"
aws ec2 describe-vpcs --query 'Vpcs[].[{Name:Tags[?Key==`Name`].Value|[0],CIDR:CidrBlock}]' --output text --region ${region} | grep '^172.17.'
echo "---------------------"
done

```

以下のように出力されます。

<details><summary>クリックで表示</summary><div>

```
[ap-south-1]
---------------------
[eu-north-1]
---------------------
[eu-west-3]
---------------------
[eu-west-2]
---------------------
[eu-west-1]
---------------------
[ap-northeast-3]
172.17.0.0/16   NgVpc
---------------------
[ap-northeast-2]
---------------------
[ap-northeast-1]
---------------------
[ca-central-1]
---------------------
[sa-east-1]
---------------------
[ap-southeast-1]
---------------------
[ap-southeast-2]
---------------------
[eu-central-1]
---------------------
[us-east-1]
172.17.0.0/16   NgVpc
---------------------
[us-east-2]
---------------------
[us-west-1]
---------------------
[us-west-2]
---------------------
```
</div></details>

## AWS Configを作るCFnテンプレート

まずは以前の記事を参考に、以下リソースを作ります。

https://qiita.com/a_b_/items/98af3d4165a5cadaa604#cloudformation%E3%81%A7%E4%BD%9C%E6%88%90

- S3
- IAMロール
- レコーダー
    - VPCのみレコード対象
- 配信チャンネル


<details><summary>クリックで表示</summary><div>

S3のDeletionPolicyはわざと無効化しています。

```yaml:createConfigRule.yaml
AWSTemplateFormatVersion: 2010-09-09

Resources:
  BucketForDeliveryChannel:
    Type: AWS::S3::Bucket
    # DeletionPolicy: Retain

  RoleForConfigRecorder:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - config.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWS_ConfigRole
      Policies:
        # 配信先への書き込み権限
        - PolicyName: ConfigPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - s3:PutObject*
                Resource:
                  - !Sub arn:aws:s3:::${BucketForDeliveryChannel}/*
                Condition:
                  StringLike:
                    s3:x-amz-acl: bucket-owner-full-control

  ConfigRecorder:
    Type: AWS::Config::ConfigurationRecorder
    Properties:
      RoleARN: !GetAtt RoleForConfigRecorder.Arn
      RecordingGroup:
        # https://docs.aws.amazon.com/ja_jp/config/latest/developerguide/resource-config-reference.html
        ResourceTypes:
          - AWS::EC2::VPC
    DependsOn: 
      - BucketForDeliveryChannel
      - RoleForConfigRecorder

  ConfigDeliveryChannel:
    Type: AWS::Config::DeliveryChannel
    Properties:
      S3BucketName: !Ref BucketForDeliveryChannel
    DependsOn: 
      - BucketForDeliveryChannel
      - RoleForConfigRecorder
```

</div></details>

次はルールを作成します。AWSマネージドルールには、今回に使えそうなルールがなかったので、Lambdaを作りそれを使います。

公式の例を参考に、LambdaにアタッチするIAMロールや、Lambdaのログ出力先のロググループ作成も加えています。

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-configrule.html#aws-resource-config-configrule--examples

またNode.jsは、(Lambdaで使える最新の)18ではエラーになってしまったので、16を使っています。

<details><summary>クリックで表示</summary><div>

```yaml:createConfigRule.yaml
AWSTemplateFormatVersion: 2010-09-09

Resources:
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSConfigRulesExecutionRole
      Policies:
        # CloudWatch
        - PolicyName: write-cloudwatchlogs
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: 
                  - 'logs:CreateLogStream'
                  - 'logs:PutLogEvents'
                Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*"

  ConfigPermissionToCallLambda: 
    Type: AWS::Lambda::Permission
    Properties: 
      FunctionName: 
        Fn::GetAtt: 
          - VolumeAutoEnableIOComplianceCheck
          - Arn
      Action: "lambda:InvokeFunction"
      Principal: "config.amazonaws.com"
      SourceAccount: !Ref 'AWS::AccountId'
  VolumeAutoEnableIOComplianceCheck: 
    Type: AWS::Lambda::Function
    Properties: 
      Code: 
        ZipFile: 
          !Sub |
            var aws  = require('aws-sdk');
            var config = new aws.ConfigService();
            var ec2 = new aws.EC2();
            exports.handler = function(event, context) {
                compliance = evaluateCompliance(event, function(compliance, event) {
                      var configurationItem = JSON.parse(event.invokingEvent).configurationItem;
                      var putEvaluationsRequest = {
                          Evaluations: [{
                              ComplianceResourceType: configurationItem.resourceType,
                              ComplianceResourceId: configurationItem.resourceId,
                              ComplianceType: compliance,
                              OrderingTimestamp: configurationItem.configurationItemCaptureTime
                          }],
                          ResultToken: event.resultToken
                      };
                      config.putEvaluations(putEvaluationsRequest, function(err, data) {
                          if (err) context.fail(err);
                          else context.succeed(data);
                      });
                  });
              };
              function evaluateCompliance(event, doReturn) {
                  var configurationItem = JSON.parse(event.invokingEvent).configurationItem;
                  if (configurationItem.resourceType !== 'AWS::EC2::VPC')
                      doReturn('NOT_APPLICABLE', event);
                  else if ( (configurationItem.configuration.cidrBlock).indexOf("172.17.") === 0 )
                      doReturn('NON_COMPLIANT', event);
                  else
                      doReturn('COMPLIANT', event);
              }
      Handler: "index.handler"
      Runtime: nodejs16.x
      Timeout: 30
      Role: 
        Fn::GetAtt: 
          - LambdaExecutionRole
          - Arn
  ConfigRuleForVolumeAutoEnableIO: 
    Type: AWS::Config::ConfigRule
    Properties: 
      Source: 
        Owner: "CUSTOM_LAMBDA"
        SourceDetails: 
          - 
            EventSource: "aws.config"
            MessageType: "ConfigurationItemChangeNotification"
        SourceIdentifier: 
          Fn::GetAtt: 
            - VolumeAutoEnableIOComplianceCheck
            - Arn
    DependsOn: ConfigPermissionToCallLambda

  FunctionLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "/aws/lambda/${VolumeAutoEnableIOComplianceCheck}"
      RetentionInDays: 3653
```

</div></details>

作成後、評価が終わると、非準拠の旨が表示されます。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/3df53959-0f97-d44d-5975-e1e7f7b1efb3.png)


## 片付け

CFnテンプレートで作成しているので、スタックの削除でリソースはなくなります。S3バケットは中にファイルがありますので、バケットを空にしてから削除する必要があります。

# おわりに
AWS Configは以前少し触ってみたことがありましたが、今回はルールをLambdaで作成するところまでやってみました。以前は設計図と呼ばれるものがあったようですが、現在ではその方法はなくなっていたので結構手間取りました。
この記事がどなたかのお役に立てれば幸いです。