AWSのリソース情報を設定情報含めてCLIで一括取得する
結論から。以下のコマンドを実行する。
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にて取得する手法が以下のページ等で紹介されている。
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.
別の方法として、Configのbatch-get-resource-configを利用する方法が以下のページで紹介されている。残念ながら、ページ内では具体的に一括取得するコマンドが用意されていなかった。それを実装したのが冒頭で示したコマンドとなる。
解説
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というものがある。
こちらの方がカバーしているリソースタイプも多い。
それぞれのリソース毎のDescribe系のAPIを実行しているようで、リソースに応じた出力内容が得られる。正直このツールで十分なようにも思うが、よく作りこまれている分、微妙にエラーが出たりで悩まされる部分があった。
ソースが公開されているとはいえ、全設定情報を取得するため、勝手に送信されてしまったら、予期せぬ改変が加えられてしまったら、など漠然とした不安を覚えないこともない。
おわりに
単純なコマンドでConfigから取得する方法には、安心感と手軽さがある。
リソースの網羅性に不足はあるものの、Configさえ有効にしていれば他に何も必要としないという点、リソース横断的に同一の形式で構成情報を取得できるという点で、本記事の手法もそれなりに使いどころはありそうだ。
Discussion