👋

AWSのリソース情報を設定情報含めてCLIで一括取得する

2022/02/24に公開

結論から。以下のコマンドを実行する。

export AWS_REGION=ap-northeast-1
curl https://awscli.amazonaws.com/v2/documentation/api/latest/reference/configservice/list-discovered-resources.html |
 grep docutils | grep -o "AWS::[^<]*" |
 xargs -i -r -t sh -c "
  aws configservice list-discovered-resources --resource-type {} |
  jq .resourceIdentifiers[].resourceId |
  xargs -n1 -r -IXXX echo resourceType={},resourceId=\\\"XXX\\\" |
  xargs -L100 -r -d"\\\\\n" -P 1 aws configservice batch-get-resource-config --resource-keys >> {}.json"

AWSのリソース情報を設定情報含めて一括取得したい

AWSには直接的にはリソース情報を一括取得する方法が用意されていない。

リソース情報の一覧を取得する方法としては、resourcegroupstaggingapiのget-resourcesにて取得する手法が以下のページ等で紹介されている。
https://dev.classmethod.jp/articles/cli-resource-search/
https://qiita.com/shu85t/items/241ffce4de64370aadbd

resourcegroupstaggingapiで取得する方法には2つの点でタイトルの目的に対して不十分である。

1点目は、リソースの設定情報は取得できないこと。設定情報を取得しようとすると、取得してきたarn等を元に各サービスの設定情報取得のAPIを個々に実行して取得しなければならない。これは辛い。

2点目は、ドキュメントに記載の通り、基本的にタグの付いていないリソースは取得できない。空のタグのリソースも出力されるので、付いていなくても取得できているようにも見えるが、個々に見ていくとタグが付いていない場合取得できていないリソースの方が多く見受けられた。

Returns all the tagged or previously tagged resources that are located in the specified Amazon Web Services Region for the account.

https://awscli.amazonaws.com/v2/documentation/api/latest/reference/resourcegroupstaggingapi/get-resources.html

別の方法として、Configのbatch-get-resource-configを利用する方法が以下のページで紹介されている。残念ながら、ページ内では具体的に一括取得するコマンドが用意されていなかった。それを実装したのが冒頭で示したコマンドとなる。
https://qiita.com/nykym/items/ebaa2b82e7f5aea57a1a

解説

AWS Configはリソースの情報を取得し管理している。従って、Configが管理している情報を取得するというのは、適切な手法と考えられる。
ConfigのAPIでリソースの構成情報取得に利用可能な候補は、以下の2つが挙げられる。

get-resource-config-historyでは、履歴やタグも含めて情報が取得できるが、リソース1つずつしか取得できない。ある程度の利用状況であれば、リソース数は大量となるため、全量取得しようとすると相当な時間を要してしまう。多重度を上げるとAPIのRateLimitにかかってしまうため、一括取得という点では扱いが難しい。
batch-get-resource-configは1回で100個のリソースまで取得可能なため、一括取得には適している。結果にはリソースの構成情報も含まれるが、タグについては、含まれない。

逐行解説

export AWS_REGION=ap-northeast-1

まずは、取得対象のリージョンを指定している。
AWS Configはリージョンサービスである。リージョン毎に取得する必要がある。

curl https://awscli.amazonaws.com/v2/documentation/api/latest/reference/configservice/list-discovered-resources.html |
 grep docutils | grep -o "AWS::[^<]*"

ドキュメントページから取得可能なリソースのタイプを取得している。執筆時点では115のリソースタイプに対応している。AWSのリソース全体から見れば大きく不足しているが、主要なサービスはカバーできているのではないかと思う。

xargs -i -r -t sh -c

リソースタイプごとの処理をしていく。リソースタイプ毎にファイルを分けて出力したかったため、sh -cで後続を実行するとともに、進捗情報がわかるように-iを付けている。

  aws configservice list-discovered-resources --resource-type {} |
  jq .resourceIdentifiers[].resourceId

list-discovered-resourcesでリソースタイプごとに、存在するリソースのID一覧を取得している。

  xargs -n1 -r -IXXX echo resourceType={},resourceId=\\\"XXX\\\" |
  xargs -L100 -r -d"\\\\\n" -P 1 aws configservice batch-get-resource-config --resource-keys >> {}.jso

リソースの一覧をbatch-get-resource-configのパラメータの形式に整形するとともに、同時に取得可能なリソースが100個までのため、xargsで100個ずつの処理とし、リソースタイプ毎のファイルに出力している。

結果

以下のような形でリソースタイプ毎に出力ファイルを得ることができる。

出力結果ファイル一覧
AWS::ACM::Certificate.json
AWS::ApiGateway::RestApi.json
AWS::ApiGateway::Stage.json
AWS::ApiGatewayV2::Api.json
AWS::ApiGatewayV2::Stage.json
AWS::AutoScaling::AutoScalingGroup.json
AWS::AutoScaling::LaunchConfiguration.json
AWS::AutoScaling::ScalingPolicy.json
AWS::AutoScaling::ScheduledAction.json
AWS::Backup::BackupPlan.json
AWS::Backup::BackupSelection.json
AWS::Backup::BackupVault.json
AWS::Backup::RecoveryPoint.json
AWS::CloudFormation::Stack.json
AWS::CloudFront::Distribution.json
AWS::CloudFront::StreamingDistribution.json
AWS::CloudTrail::Trail.json
AWS::CloudWatch::Alarm.json
AWS::CodeBuild::Project.json
AWS::CodeDeploy::Application.json
AWS::CodeDeploy::DeploymentConfig.json
AWS::CodeDeploy::DeploymentGroup.json
AWS::CodePipeline::Pipeline.json
AWS::Config::ConformancePackCompliance.json
AWS::Config::ResourceCompliance.json
AWS::DynamoDB::Table.json
AWS::EC2::CustomerGateway.json
AWS::EC2::EIP.json
AWS::EC2::EgressOnlyInternetGateway.json
AWS::EC2::FlowLog.json
AWS::EC2::Host.json
AWS::EC2::Instance.json
AWS::EC2::InternetGateway.json
AWS::EC2::NatGateway.json
AWS::EC2::NetworkAcl.json
AWS::EC2::NetworkInterface.json
AWS::EC2::RegisteredHAInstance.json
AWS::EC2::RouteTable.json
AWS::EC2::SecurityGroup.json
AWS::EC2::Subnet.json
AWS::EC2::TransitGateway.json
AWS::EC2::VPC.json
AWS::EC2::VPCEndpoint.json
AWS::EC2::VPCEndpointService.json
AWS::EC2::VPCPeeringConnection.json
AWS::EC2::VPNConnection.json
AWS::EC2::VPNGateway.json
AWS::EC2::Volume.json
AWS::ECR::Repository.json
AWS::ECS::Cluster.json
AWS::ECS::Service.json
AWS::ECS::TaskDefinition.json
AWS::EFS::AccessPoint.json
AWS::EFS::FileSystem.json
AWS::EKS::Cluster.json
AWS::ElasticBeanstalk::Application.json
AWS::ElasticBeanstalk::ApplicationVersion.json
AWS::ElasticBeanstalk::Environment.json
AWS::ElasticLoadBalancing::LoadBalancer.json
AWS::ElasticLoadBalancingV2::LoadBalancer.json
AWS::Elasticsearch::Domain.json
AWS::IAM::Group.json
AWS::IAM::Policy.json
AWS::IAM::Role.json
AWS::IAM::User.json
AWS::KMS::Key.json
AWS::Kinesis::Stream.json
AWS::Kinesis::StreamConsumer.json
AWS::Lambda::Function.json
AWS::NetworkFirewall::Firewall.json
AWS::NetworkFirewall::FirewallPolicy.json
AWS::NetworkFirewall::RuleGroup.json
AWS::OpenSearch::Domain.json
AWS::QLDB::Ledger.json
AWS::RDS::DBCluster.json
AWS::RDS::DBClusterSnapshot.json
AWS::RDS::DBInstance.json
AWS::RDS::DBSecurityGroup.json
AWS::RDS::DBSnapshot.json
AWS::RDS::DBSubnetGroup.json
AWS::RDS::EventSubscription.json
AWS::Redshift::Cluster.json
AWS::Redshift::ClusterParameterGroup.json
AWS::Redshift::ClusterSecurityGroup.json
AWS::Redshift::ClusterSnapshot.json
AWS::Redshift::ClusterSubnetGroup.json
AWS::Redshift::EventSubscription.json
AWS::S3::AccountPublicAccessBlock.json
AWS::S3::Bucket.json
AWS::SNS::Topic.json
AWS::SQS::Queue.json
AWS::SSM::AssociationCompliance.json
AWS::SSM::FileData.json
AWS::SSM::ManagedInstanceInventory.json
AWS::SSM::PatchCompliance.json
AWS::SecretsManager::Secret.json
AWS::ServiceCatalog::CloudFormationProduct.json
AWS::ServiceCatalog::CloudFormationProvisionedProduct.json
AWS::ServiceCatalog::Portfolio.json
AWS::Shield::Protection.json
AWS::ShieldRegional::Protection.json
AWS::WAF::RateBasedRule.json
AWS::WAF::Rule.json
AWS::WAF::RuleGroup.json
AWS::WAF::WebACL.json
AWS::WAFRegional::RateBasedRule.json
AWS::WAFRegional::Rule.json
AWS::WAFRegional::RuleGroup.json
AWS::WAFRegional::WebACL.json
AWS::WAFv2::IPSet.json
AWS::WAFv2::ManagedRuleSet.json
AWS::WAFv2::RegexPatternSet.json
AWS::WAFv2::RuleGroup.json
AWS::WAFv2::WebACL.json
AWS::XRay::EncryptionConfig.json

取得内容

例えば、AWS::EC2:Instanceでは以下のような形で取得できている。

AWS::EC2:Instance.json
{
    "baseConfigurationItems": [
        {
            "version": "1.3",
            "accountId": "123456789012",
            "configurationItemCaptureTime": "2022-02-24T03:20:30.716000+09:00",
            "configurationItemStatus": "ResourceDiscovered",
            "configurationStateId": "1645640430716",
            "arn": "arn:aws:ec2:us-west-2:123456789012:instance/i-0e2750c5caf8750a1",
            "resourceType": "AWS::EC2::Instance",
            "resourceId": "i-0e2750c5caf8750a1",
            "awsRegion": "us-west-2",
            "availabilityZone": "us-west-2a",
            "resourceCreationTime": "2022-02-23T16:58:28+09:00",
            "configuration": "{\"amiLaunchIndex\":0,\"imageId\":\"ami-0182935b57e855f97\",\"instanceId\":\"i-0e2750c5caf8750a1\",\"instanceType\":\"t2.large\",\"kernelId\":null,\"keyName\":\"HOME\",\"launchTime\":\"2022-02-23T07:58:28.000Z\",\"monitoring\":{\"state\":\"disabled\"},\"placement\":{\"availabilityZone\":\"us-west-2a\",\"affinity\":null,\"groupName\":\"\",\"partitionNumber\":null,\"hostId\":null,\"tenancy\":\"default\",\"spreadDomain\":null,\"hostResourceGroupArn\":null},\"platform\":\"windows\",\"privateDnsName\":\"ip-172-31-43-128.us-west-2.compute.internal\",\"privateIpAddress\":\"172.31.43.128\",\"productCodes\":[],\"publicDnsName\":\"ec2-54-187-60-217.us-west-2.compute.amazonaws.com\",\"publicIpAddress\":\"54.187.60.217\",\"ramdiskId\":null,\"state\":{\"code\":16,\"name\":\"running\"},\"stateTransitionReason\":\"\",\"subnetId\":\"subnet-01b7b849\",\"vpcId\":\"vpc-9cfb50e5\",\"architecture\":\"x86_64\",\"blockDeviceMappings\":[{\"deviceName\":\"/dev/sda1\",\"ebs\":{\"attachTime\":\"2022-02-23T07:58:29.000Z\",\"deleteOnTermination\":true,\"status\":\"attached\",\"volumeId\":\"vol-0fc1edaf349f338ab\"}}],\"clientToken\":\"\",\"ebsOptimized\":false,\"enaSupport\":true,\"hypervisor\":\"xen\",\"iamInstanceProfile\":{\"arn\":\"arn:aws:iam::123456789012:instance-profile/AdminRole\",\"id\":\"AIPAYLQFLJ4D27WSLASNX\"},\"instanceLifecycle\":\"spot\",\"elasticGpuAssociations\":[],\"elasticInferenceAcceleratorAssociations\":[],\"networkInterfaces\":[{\"association\":{\"carrierIp\":null,\"ipOwnerId\":\"amazon\",\"publicDnsName\":\"ec2-54-187-60-217.us-west-2.compute.amazonaws.com\",\"publicIp\":\"54.187.60.217\"},\"attachment\":{\"attachTime\":\"2022-02-23T07:58:28.000Z\",\"attachmentId\":\"eni-attach-0a4661b0bfcdf41c4\",\"deleteOnTermination\":true,\"deviceIndex\":0,\"status\":\"attached\",\"networkCardIndex\":0},\"description\":\"\",\"groups\":[{\"groupName\":\"defaultrdp\",\"groupId\":\"sg-0529cf3c44fccc674\"},{\"groupName\":\"default\",\"groupId\":\"sg-aa7980d5\"}],\"ipv6Addresses\":[],\"macAddress\":\"06:3a:a2:8d:dd:17\",\"networkInterfaceId\":\"eni-00747b6adfa43e2dc\",\"ownerId\":\"123456789012\",\"privateDnsName\":\"ip-172-31-43-128.us-west-2.compute.internal\",\"privateIpAddress\":\"172.31.43.128\",\"privateIpAddresses\":[{\"association\":{\"carrierIp\":null,\"ipOwnerId\":\"amazon\",\"publicDnsName\":\"ec2-54-187-60-217.us-west-2.compute.amazonaws.com\",\"publicIp\":\"54.187.60.217\"},\"primary\":true,\"privateDnsName\":\"ip-172-31-43-128.us-west-2.compute.internal\",\"privateIpAddress\":\"172.31.43.128\"}],\"sourceDestCheck\":true,\"status\":\"in-use\",\"subnetId\":\"subnet-01b7b849\",\"vpcId\":\"vpc-9cfb50e5\",\"interfaceType\":\"interface\"}],\"outpostArn\":null,\"rootDeviceName\":\"/dev/sda1\",\"rootDeviceType\":\"ebs\",\"securityGroups\":[{\"groupName\":\"defaultrdp\",\"groupId\":\"sg-0529cf3c44fccc674\"},{\"groupName\":\"default\",\"groupId\":\"sg-aa7980d5\"}],\"sourceDestCheck\":true,\"spotInstanceRequestId\":\"sir-fwb68e4q\",\"sriovNetSupport\":null,\"stateReason\":null,\"tags\":[],\"virtualizationType\":\"hvm\",\"cpuOptions\":{\"coreCount\":2,\"threadsPerCore\":1},\"capacityReservationId\":null,\"capacityReservationSpecification\":{\"capacityReservationPreference\":\"open\",\"capacityReservationTarget\":null},\"hibernationOptions\":{\"configured\":false},\"licenses\":[],\"metadataOptions\":{\"state\":\"applied\",\"httpTokens\":\"optional\",\"httpPutResponseHopLimit\":1,\"httpEndpoint\":\"enabled\"},\"enclaveOptions\":{\"enabled\":false},\"bootMode\":null}",
            "supplementaryConfiguration": {}
        }
    ],
    "unprocessedResourceKeys": []
}

構成情報もconfigurationの項目で取得できている。S3のバケット情報などであれば、supplementaryConfigurationの項目にACL情報などが載ってくる。

JSONが一繋ぎの文字列で含まれるため上記のままだと見づらいが、例えば以下のようなコマンドで、見やすく整形可能だ。

AWS::EC2:Instance.jsonのconfiguration抽出と整形結果
$ cat AWS\:\:EC2\:\:Instance.json |
 jq ".baseConfigurationItems[].configuration" -r |
 jq .
{
  "amiLaunchIndex": 0,
  "imageId": "ami-0182935b57e855f97",
  "instanceId": "i-0e2750c5caf8750a1",
  "instanceType": "t2.large",
  "kernelId": null,
  "keyName": "HOME",
  "launchTime": "2022-02-23T07:58:28.000Z",
  "monitoring": {
    "state": "disabled"
  },
  "placement": {
    "availabilityZone": "us-west-2a",
    "affinity": null,
    "groupName": "",
    "partitionNumber": null,
    "hostId": null,
    "tenancy": "default",
    "spreadDomain": null,
    "hostResourceGroupArn": null
  },
  "platform": "windows",
  "privateDnsName": "ip-172-31-43-128.us-west-2.compute.internal",
  "privateIpAddress": "172.31.43.128",
  "productCodes": [],
  "publicDnsName": "ec2-54-187-60-217.us-west-2.compute.amazonaws.com",
  "publicIpAddress": "54.187.60.217",
  "ramdiskId": null,
  "state": {
    "code": 16,
    "name": "running"
  },
  "stateTransitionReason": "",
  "subnetId": "subnet-01b7b849",
  "vpcId": "vpc-9cfb50e5",
  "architecture": "x86_64",
  "blockDeviceMappings": [
    {
      "deviceName": "/dev/sda1",
      "ebs": {
        "attachTime": "2022-02-23T07:58:29.000Z",
        "deleteOnTermination": true,
        "status": "attached",
        "volumeId": "vol-0fc1edaf349f338ab"
      }
    }
  ],
  "clientToken": "",
  "ebsOptimized": false,
  "enaSupport": true,
  "hypervisor": "xen",
  "iamInstanceProfile": {
    "arn": "arn:aws:iam::123456789012:instance-profile/AdminRole",
    "id": "AIPAYLQFLJ4D27WSLASNX"
  },
  "instanceLifecycle": "spot",
  "elasticGpuAssociations": [],
  "elasticInferenceAcceleratorAssociations": [],
  "networkInterfaces": [
    {
      "association": {
        "carrierIp": null,
        "ipOwnerId": "amazon",
        "publicDnsName": "ec2-54-187-60-217.us-west-2.compute.amazonaws.com",
        "publicIp": "54.187.60.217"
      },
      "attachment": {
        "attachTime": "2022-02-23T07:58:28.000Z",
        "attachmentId": "eni-attach-0a4661b0bfcdf41c4",
        "deleteOnTermination": true,
        "deviceIndex": 0,
        "status": "attached",
        "networkCardIndex": 0
      },
      "description": "",
      "groups": [
        {
          "groupName": "defaultrdp",
          "groupId": "sg-0529cf3c44fccc674"
        },
        {
          "groupName": "default",
          "groupId": "sg-aa7980d5"
        }
      ],
      "ipv6Addresses": [],
      "macAddress": "06:3a:a2:8d:dd:17",
      "networkInterfaceId": "eni-00747b6adfa43e2dc",
      "ownerId": "123456789012",
      "privateDnsName": "ip-172-31-43-128.us-west-2.compute.internal",
      "privateIpAddress": "172.31.43.128",
      "privateIpAddresses": [
        {
          "association": {
            "carrierIp": null,
            "ipOwnerId": "amazon",
            "publicDnsName": "ec2-54-187-60-217.us-west-2.compute.amazonaws.com",
            "publicIp": "54.187.60.217"
          },
          "primary": true,
          "privateDnsName": "ip-172-31-43-128.us-west-2.compute.internal",
          "privateIpAddress": "172.31.43.128"
        }
      ],
      "sourceDestCheck": true,
      "status": "in-use",
      "subnetId": "subnet-01b7b849",
      "vpcId": "vpc-9cfb50e5",
      "interfaceType": "interface"
    }
  ],
  "outpostArn": null,
  "rootDeviceName": "/dev/sda1",
  "rootDeviceType": "ebs",
  "securityGroups": [
    {
      "groupName": "defaultrdp",
      "groupId": "sg-0529cf3c44fccc674"
    },
    {
      "groupName": "default",
      "groupId": "sg-aa7980d5"
    }
  ],
  "sourceDestCheck": true,
  "spotInstanceRequestId": "sir-fwb68e4q",
  "sriovNetSupport": null,
  "stateReason": null,
  "tags": [],
  "virtualizationType": "hvm",
  "cpuOptions": {
    "coreCount": 2,
    "threadsPerCore": 1
  },
  "capacityReservationId": null,
  "capacityReservationSpecification": {
    "capacityReservationPreference": "open",
    "capacityReservationTarget": null
  },
  "hibernationOptions": {
    "configured": false
  },
  "licenses": [],
  "metadataOptions": {
    "state": "applied",
    "httpTokens": "optional",
    "httpPutResponseHopLimit": 1,
    "httpEndpoint": "enabled"
  },
  "enclaveOptions": {
    "enabled": false
  },
  "bootMode": null
}

その他の手法

海外企業がGitHubで公開しているツールでAWSetsというものがある。
https://github.com/trek10inc/awsets

こちらの方がカバーしているリソースタイプも多い。
それぞれのリソース毎のDescribe系のAPIを実行しているようで、リソースに応じた出力内容が得られる。正直このツールで十分なようにも思うが、よく作りこまれている分、微妙にエラーが出たりで悩まされる部分があった。
ソースが公開されているとはいえ、全設定情報を取得するため、勝手に送信されてしまったら、予期せぬ改変が加えられてしまったら、など漠然とした不安を覚えないこともない。

おわりに

単純なコマンドでConfigから取得する方法には、安心感と手軽さがある。
リソースの網羅性に不足はあるものの、Configさえ有効にしていれば他に何も必要としないという点、リソース横断的に同一の形式で構成情報を取得できるという点で、本記事の手法もそれなりに使いどころはありそうだ。

Discussion