[アップデート]AWS CloudFormationがIaCジェネレーターで特定のリソースタイプをスキャンできるようになったので、AWS CLIで試してみた

[アップデート]AWS CloudFormationがIaCジェネレーターで特定のリソースタイプをスキャンできるようになったので、AWS CLIで試してみた

AWS CloudFormationのIaCジェネレーターで特定のリソースタイプをスキャンできるようになったので、AWS CLIで試してみました。今までは全リソースを問答無用でスキャンしていたのが、今回のアップデートで部分的にスキャン・選択できるようになっています。
Clock Icon2025.03.28

はじめに

おのやんです。

この度のアップデートにより、AWS CloudFormation(以下、CFn)のIaCジェネレーターで特定のリソースタイプをスキャン対象として指定できるようになりました。

https://aws.amazon.com/jp/about-aws/whats-new/2025/03/aws-cloudformation-targeted-resource-scans-iac-generator/

Developers.IOではすでにアップデートブログが公開されていますが、AWS CLIを使うとどういった操作になるのか気になりました。

https://dev.classmethod.jp/articles/cloudformation-targeted-resource-scans-iac-generator/

ということで、今回はこちらのアップデートをAWS CLI経由で実際に試します。

IaCジェネレータとは?

IaCジェネレータは、CFnの機能のひとつです。AWSアカウントに存在している既存リソースをIaCジェネレータがスキャンして、CFnテンプレートとして出力してくれます。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/generate-IaC.html

本アップデートが入るまでは、IaCジェネレータでは既存リソースをすべてスキャンする仕様でした。それが、今回のアップデートで特定のリソースタイプに絞ってCFnテンプレートをスキャンできるようになっています。

やってみた

それでは、実際にAWSアカウントで 特定リソースタイプへのIaCジェネレータスキャンを試してみます。

AWSマネジメントコンソール経由

AWS CLIで検証する前に、試しにCFnのコンソール画面経由で、部分スキャンを実行してみます。

CFnのコンソール画面に移動して、ナビゲーションパネルからIaCジェネレータを選択します。

スクリーンショット 2025-03-28 10.12.37

IaCジェネレータ画面で、「新しいスキャンを開始」から「特定のリソースをスキャン」を指定します。ここが今回のアップデートで選択できるようになった部分です。

スクリーンショット 2025-03-28 10.13.53

こちらを選択すると、「部分スキャンを実施」のポップアップが出てきます。ここで、リソースタイプを選択できます。今回は現在別関係で検証中のリソースであるAWS::Lambda::FunctionAWS::Kinesis::StreamAWS::S3::Bucketの3つを選択してみます。

スクリーンショット 2025-03-28 10.14.49

スキャンが終わったら、このように緑色のバーが出てきます。「テンプレートを作成」を選択します。

スクリーンショット 2025-03-28 10.16.16

今回は、CFnテンプレートの名前としてaws-test-cfn-generator-templateを入力します。削除ポリシー・置換ポリシーはデフォルトで行きます。

スクリーンショット 2025-03-28 10.16.54

次の画面では、実際にCFnテンプレートに出力したいリソースを確認できます。アップデート前だと、全リソースから CFnに含みたいリソースを指定する形だったのですが、今回のアップデートにより、今回選択してスキャンしたAWS::Lambda::FunctionAWS::Kinesis::StreamAWS::S3::Bucketのリソースから選べるようになっています。

スクリーンショット 2025-03-28 11.18.16

CFnテンプレートに含めたいリソースによっては、関連リソースもサジェストされます。AWS Lambda(以下、Lambda)関数だとリソースベースポリシーなどがサジェストされていますね。今回はこれらもCFnで管理したいので、全て含めた状態で進めます。

スクリーンショット 2025-03-28 11.20.41

テンプレートが正常に作成されると、こちらのようにIaCジェネレータコンソール上で確認できるCFnテンプレートが作成されます。

スクリーンショット 2025-03-28 11.24.10

AWS CLI経由

2025/03/28現在、IaCジェネレータの特定リソースタイプスキャンは英語版AWSドキュメントにのみ反映されています。日本語版ドキュメントには反映されていませんので、英語版ドキュメントを参考にします。

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/iac-generator-start-resource-scan.html

AWS CLIでは、まず以下のようにconfig.jsonを作成します。

config
[
    {
        "Types": [
            "AWS::Lambda::Function",
            "AWS::Kinesis::Stream",
            "AWS::S3::Bucket"
        ]
    }
]

AWS CLIが最新バージョンになっていない場合は、こちらのページからアップデートを実施します。今回はMacから検証したので、PKGファイルを再度ダウンロードしています。

https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/getting-started-install.html

% aws --version
aws-cli/2.13.15 Python/3.11.4 Darwin/23.5.0 exe/x86_64 prompt/off

# アップデートを実行

% aws --version
aws-cli/2.25.5 Python/3.12.9 Darwin/23.5.0 exe/x86_64

この状態で、こちらのコマンドを実行します。

% aws cloudformation start-resource-scan --scan-filters file://config.json --region ap-northeast-1
{
    "ResourceScanId": "arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:resourceScan/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"
}

describe-resource-scanコマンドを実行して、このようにCOMPLETEが確認できれば、正常にスキャンが終了しています。

% aws cloudformation describe-resource-scan --region ap-northeast-1 --resource-scan-id arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:resourceScan/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx
{
    "ResourceScanId": "arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:resourceScan/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx",
    "Status": "COMPLETE",
    "StartTime": "2025-03-28T03:38:11.673000+00:00",
    "EndTime": "2025-03-28T03:39:15.602000+00:00",
    "PercentageCompleted": 100.0,
    "ResourceTypes": [
        "AWS::Lambda::Function",
        "AWS::Lambda::Permission",
        "AWS::S3::Bucket"
    ],
    "ResourcesRead": 15,
    "ScanFilters": [
        {
            "Types": [
                "AWS::Lambda::Function",
                "AWS::Kinesis::Stream",
                "AWS::S3::Bucket"
            ]
        }
    ]
}

次に、list-resource-scan-resourcesコマンドで、スキャンされたリソースを一覧表示します。コンソール上で確認した時と同じく、AWS::Lambda::FunctionAWS::Kinesis::StreamAWS::S3::Bucketのリソースのみがスキャンされています。

% aws cloudformation list-resource-scan-resources --region ap-northeast-1\ 
  --resource-scan-id arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:resourceScan/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx
{
    "Resources": [
        {
            "ResourceType": "AWS::Lambda::Function",
            "ResourceIdentifier": {
                "FunctionName": "xxxx"
            },
            "ManagedByStack": true
        },
        {
            "ResourceType": "AWS::Lambda::Function",
            "ResourceIdentifier": {
                "FunctionName": "xxxx-xxxx"
            },
            "ManagedByStack": false
        },
        ....
        {
            "ResourceType": "AWS::Lambda::Permission",
            "ResourceIdentifier": {
                "FunctionName": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:xxxx",
                "Id": "lambda-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"
            },
            "ManagedByStack": false
        },
        ...
        {
            "ResourceType": "AWS::S3::Bucket",
            "ResourceIdentifier": {
                "BucketName": "xxxx"
            },
            "ManagedByStack": false
        }
    ]
}

このうち、生成したいリソースをJSONで記述します。今回は関連リソースも含めて全てJSONに明示的に記述します。

resources.json
{
    "Resources": [
        {
            "ResourceType": "AWS::Lambda::Function",
            "ResourceIdentifier": {
                "FunctionName": "xxxx"
            }
        },
        {
            "ResourceType": "AWS::Lambda::Permission",
            "ResourceIdentifier": {
                "FunctionName": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:notify-aws-billing",
                "Id": "xxxxxxxxxxxx_event_permissions_from_cm-members-cloudtrail-xxxxxxxxxxxx_for_xxxx"
            }
        },
        {
            "ResourceType": "AWS::Lambda::Permission",
            "ResourceIdentifier": {
                "FunctionName": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:notify-aws-billing",
                "Id": "lambda-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"
            }
        },
        {
            "ResourceType": "AWS::Lambda::Permission",
            "ResourceIdentifier": {
                "FunctionName": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:xxxx",
                "Id": "xxxxxxxxxxxx_event_permissions_from_xxxx-xxxxxxxxxxxx_for_xxxx"
            }
        },
        {
            "ResourceType": "AWS::S3::Bucket",
            "ResourceIdentifier": {
                "BucketName": "aws-test-bucket-1"
            }
        }
    ]
}

これらが終わったら、create-generated-templateコマンドで、実際にテンプレートを作成していきます。

% aws cloudformation create-generated-template --region ap-northeast-1 \
--generated-template-name AWSTestTemplate \
--resources file://resources.json
{
    "GeneratedTemplateId": "arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:generatedTemplate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"
}

生成されたCFnテンプレート

上記2つ、AWSマネジメントコンソールかAWS CLIを経由して、実際に生成したCfnテンプレートがこちらになります。文章がかなり多めですので、トグル下に格納しています。

生成されたCFnテンプレート(特定のリソースを表す情報はマスク済み)
---
Metadata:
  AWSToolsMetrics:
    IaC_Generator: "arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:generatedTemplate/xxxx-xxxx-xxxx"
Parameters:
  LambdaFunctionAWSTestCodeZipFileS05PE:
    NoEcho: "true"
    Type: "String"
    Description:
      "(Node.js and Python) The source code of your Lambda function. If\
      \ you include your function source inline with this parameter, CFN places it\
      \ in a file named ``index`` and zips it to create a [deployment package](https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-package.html).\
      \ This zip file cannot exceed 4MB. For the ``Handler`` property, the first part\
      \ of the handler identifier must be ``index``. For example, ``index.handler``.\n\
      \  When you specify source code inline for a Node.js function, the ``index``\
      \ file that CFN creates uses the extension ``.js``. This means that LAM treats\
      \ the file as a CommonJS module. ES modules aren't supported for inline functions.\n\
      \   For JSON, you must escape quotes and special characters such as newline\
      \ (``\\n``) with a backslash.\n If you specify a function that interacts with\
      \ an AWS CloudFormation custom resource, you don't have to write your own functions\
      \ to send responses to the custom resource that invoked the function. AWS CloudFormation\
      \ provides a response module ([cfn-response](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-lambda-function-code-cfnresponsemodule.html))\
      \ that simplifies sending responses. See [Using Lambda with CloudFormation](https://docs.aws.amazon.com/lambda/latest/dg/services-cloudformation.html)\
      \ for details."
  LambdaFunctionAWSTestCodeS3Bucketxxxxx:
    NoEcho: "true"
    Type: "String"
    Description:
      "An Amazon S3 bucket in the same AWS-Region as your function. The\
      \ bucket can be in a different AWS-account."
  LambdaFunctionAWSTestCodeS3Keyxxxxx:
    NoEcho: "true"
    Type: "String"
    Description: "The Amazon S3 key of the deployment package."
  LambdaFunctionAWSTestCodeS3ObjectVersionxxxxx:
    NoEcho: "true"
    Type: "String"
    Description:
      "For versioned objects, the version of the deployment package object\
      \ to use."
  LambdaFunctionAWSTestCodeImageUrixxxxx:
    NoEcho: "true"
    Type: "String"
    Description:
      "URI of a [container image](https://docs.aws.amazon.com/lambda/latest/dg/lambda-images.html)\
      \ in the Amazon ECR registry."
  LambdaFunctionAWSTestCodeSourceKMSKeyArnxxxxx:
    NoEcho: "true"
    Type: "String"
    Description:
      "The ARN of the KMSlong (KMS) customer managed key that's used to\
      \ encrypt your function's .zip deployment package. If you don't provide a customer\
      \ managed key, Lambda uses an [owned key](https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#aws-owned-cmk)."
Resources:
  LambdaPermissionFunctionAWSTestxx:
    UpdateReplacePolicy: "Retain"
    Type: "AWS::Lambda::Permission"
    DeletionPolicy: "Retain"
    Properties:
      FunctionName:
        Fn::GetAtt:
          - "LambdaFunctionAWSTest"
          - "Arn"
      Action: "lambda:InvokeFunction"
      SourceArn:
        Fn::GetAtt:
          - "S3BucketAWSTestxxxxxxxxxxxx"
          - "Arn"
      Principal: "s3.amazonaws.com"
      SourceAccount: "xxxxxxxxxxxx"
  LambdaFunctionAWSTest:
    UpdateReplacePolicy: "Retain"
    Type: "AWS::Lambda::Function"
    DeletionPolicy: "Retain"
    Properties:
      MemorySize: 128
      Description: ""
      TracingConfig:
        Mode: "Active"
      Timeout: 20
      RuntimeManagementConfig:
        UpdateRuntimeOn: "Auto"
      Handler: "lambda_function.lambda_handler"
      Code:
        SourceKMSKeyArn:
          Ref: "LambdaFunctionAWSTestCodeSourceKMSKeyArnxxxxx"
        S3ObjectVersion:
          Ref: "LambdaFunctionAWSTestCodeS3ObjectVersionxxxxx"
        S3Bucket:
          Ref: "LambdaFunctionAWSTestCodeS3Bucketxxxxx"
        ZipFile:
          Ref: "LambdaFunctionAWSTestCodeZipFilexxxxx"
        ImageUri:
          Ref: "LambdaFunctionAWSTestCodeImageUrixxxxx"
        S3Key:
          Ref: "LambdaFunctionAWSTestCodeS3Keyxxxx"
      Role: "arn:aws:iam::xxxxxxxxxxxx:role/aws-test-lambda-role"
      FileSystemConfigs: []
      FunctionName: "aws-test-function"
      Runtime: "python3.11"
      PackageType: "Zip"
      LoggingConfig:
        LogFormat: "Text"
        LogGroup: "/aws/lambda/aws-test-function"
      RecursiveLoop: "Terminate"
      Environment:
        Variables:
          AWS_LAMBDA_EXEC_WRAPPER: "/opt/otel-instrument"
          SLACK_WEBHOOK_URL: "https://hooks.slack.com/services/xxxxxxxx/xxxxxxxxxxxxxxxx"
      EphemeralStorage:
        Size: 512
      Layers:
        - "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:layer:aws-test-layer-1:1"
        - "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:layer:aws-test-layer-2:2"
      Architectures:
        - "x86_64"
  S3BucketAWSTestxxxxxxxxxxxx:
    UpdateReplacePolicy: "Retain"
    Type: "AWS::S3::Bucket"
    DeletionPolicy: "Retain"
    Properties:
      PublicAccessBlockConfiguration:
        RestrictPublicBuckets: true
        IgnorePublicAcls: true
        BlockPublicPolicy: true
        BlockPublicAcls: true
      BucketName: "aws-test-bucket-1-xxxxxxxxxxxx"
      OwnershipControls:
        Rules:
          - ObjectOwnership: "BucketOwnerEnforced"
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - BucketKeyEnabled: true
            ServerSideEncryptionByDefault:
              SSEAlgorithm: "aws:kms"
              KMSMasterKeyID: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
      ObjectLockConfiguration:
        ObjectLockEnabled: "Enabled"
      ObjectLockEnabled: true
      LifecycleConfiguration:
        TransitionDefaultMinimumObjectSize: "all_storage_classes_128K"
        Rules:
          - Status: "Enabled"
            ExpirationInDays: 1095
            Id: "Delete-After-3years"
          - Status: "Enabled"
            AbortIncompleteMultipartUpload:
              DaysAfterInitiation: 7
            Id: "Delete-Incomplete-Multipart-Upload-7days"
      VersioningConfiguration:
        Status: "Enabled"
      Tags:
        - Value: "SecureAccount"
          Key: "aws:test"
  LambdaPermissionFunctionAWSTest:
    UpdateReplacePolicy: "Retain"
    Type: "AWS::Lambda::Permission"
    DeletionPolicy: "Retain"
    Properties:
      FunctionName:
        Fn::GetAtt:
          - "LambdaFunctionAWSTest"
          - "Arn"
      Action: "lambda:InvokeFunction"
      SourceArn:
        Fn::GetAtt:
          - "S3BucketCmmemberscloudtrailxxxxxxxxxxxx"
          - "Arn"
      Principal: "s3.amazonaws.com"
      SourceAccount: "xxxxxxxxxxxx"
  S3BucketCmmemberscloudtrailxxxxxxxxxxxx:
    UpdateReplacePolicy: "Retain"
    Type: "AWS::S3::Bucket"
    DeletionPolicy: "Retain"
    Properties:
      PublicAccessBlockConfiguration:
        RestrictPublicBuckets: true
        IgnorePublicAcls: true
        BlockPublicPolicy: true
        BlockPublicAcls: true
      BucketName: "aws-test-bucket-2-xxxxxxxxxxxx"
      OwnershipControls:
        Rules:
          - ObjectOwnership: "BucketOwnerEnforced"
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - BucketKeyEnabled: true
            ServerSideEncryptionByDefault:
              SSEAlgorithm: "aws:kms"
              KMSMasterKeyID: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
      ObjectLockConfiguration:
        ObjectLockEnabled: "Enabled"
        Rule:
          DefaultRetention:
            Years: 1
            Mode: "COMPLIANCE"
      ObjectLockEnabled: true
      LoggingConfiguration:
        TargetObjectKeyFormat:
          SimplePrefix: {}
        DestinationBucketName: "aws-test-bucket-2-xxxxxxxxxxxx"
        LogFilePrefix: ""
      NotificationConfiguration:
        QueueConfigurations: []
        TopicConfigurations: []
        LambdaConfigurations:
          - Function:
              Fn::GetAtt:
                - "LambdaFunctionAWSTest"
                - "Arn"
            Filter:
              S3Key:
                Rules:
                  - Value: ""
                    Name: "Prefix"
                  - Value: ""
                    Name: "Suffix"
            Event: "s3:LifecycleExpiration:*"
      LifecycleConfiguration:
        TransitionDefaultMinimumObjectSize: "all_storage_classes_128K"
        Rules:
          - Status: "Enabled"
            Id: "Delete-After-3years"
            NoncurrentVersionExpiration:
              NoncurrentDays: 1
            ExpirationInDays: 1095
          - Status: "Enabled"
            AbortIncompleteMultipartUpload:
              DaysAfterInitiation: 7
            Id: "Delete-Incomplete-Multipart-Upload-7days"
      VersioningConfiguration:
        Status: "Enabled"
      Tags:
        - Value: "SecureAccount"
          Key: "aws:test"
  LambdaPermissionFunctionAWSTestDt:
    UpdateReplacePolicy: "Retain"
    Type: "AWS::Lambda::Permission"
    DeletionPolicy: "Retain"
    Properties:
      FunctionName:
        Fn::GetAtt:
          - "LambdaFunctionAWSTest"
          - "Arn"
      Action: "lambda:InvokeFunction"
      SourceArn: "arn:aws:events:ap-northeast-1:xxxxxxxxxxxx:rule/aws-test-lambda-role"
      Principal: "events.amazonaws.com"

IaCジェネレータの作成手順がより簡単に

今までは全リソースを問答無用でスキャンして、その中からCFnテンプレートに含めたいリソースを選択していました。今回のアップデートでリソースタイプをあらかじめ指定した状態でスキャンできるようになったため、IaCジェネレータの作成手順がより簡潔になった印象です。

今回はAWS CLIで検証しましたが、日本語のドキュメントが追いついていなかったり、英語版のドキュメントであってもミスが見られたりしたので、試行錯誤が必要でした。

本記事が参考になれば幸いです。では!

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.