name: asset-inventory description: Maintain IT asset inventory and configuration management database. Track hardware, software, and cloud resources. Use when managing IT assets. license: MIT metadata: author: devops-skills version: "1.0"
Asset Inventory
Maintain comprehensive IT asset inventory using automated discovery, AWS Config rules, cloud asset discovery scripts, CMDB integration, and tagging enforcement for compliance and operational visibility.
When to Use
- Building or maintaining an IT asset inventory for compliance frameworks (ISO 27001, SOC 2, FedRAMP)
- Implementing automated cloud resource discovery across accounts and regions
- Enforcing tagging standards for cost allocation, ownership, and data classification
- Integrating asset data with a CMDB for operational workflows
- Preparing for audits that require a complete system component inventory
Asset Categories and Schema
asset_categories:
compute:
cloud:
- EC2 instances / Azure VMs / GCE instances
- Lambda functions / Azure Functions / Cloud Functions
- ECS/EKS clusters and tasks
- Container images in registries
on_premise:
- Physical servers
- Virtual machines (VMware, Hyper-V)
storage:
- S3 buckets / Azure Storage / GCS buckets
- EBS volumes / Managed Disks / Persistent Disks
- RDS instances / Azure SQL / Cloud SQL
- DynamoDB tables / Cosmos DB / Firestore
- EFS / Azure Files / Filestore
network:
- VPCs / VNets / VPC Networks
- Load balancers (ALB, NLB, Azure LB, GCP LB)
- DNS zones and records
- VPN gateways and connections
- CDN distributions
security:
- IAM users, roles, and policies
- KMS keys / Key Vault keys
- Certificates (ACM, Key Vault, Certificate Manager)
- Security groups / NSGs / Firewall rules
- WAF configurations
applications:
- SaaS subscriptions
- Licensed software
- Custom applications
- APIs and integrations
endpoints:
- Laptops and desktops
- Mobile devices
- Printers and peripherals
asset_record_schema:
required_fields:
asset_id: "Unique identifier (auto-generated)"
name: "Human-readable name"
type: "Category from above taxonomy"
provider: "AWS / Azure / GCP / On-Premise / SaaS"
account_or_subscription: "Cloud account ID"
region: "Deployment region/location"
owner: "Team or individual responsible"
data_classification: "Public / Internal / Confidential / Restricted"
environment: "Production / Staging / Development / Sandbox"
status: "Active / Decommissioning / Retired"
created_date: "When the asset was provisioned"
last_seen: "Last automated discovery timestamp"
optional_fields:
cost_center: "For cost allocation"
compliance_scope: "SOC2 / HIPAA / PCI / None"
backup_policy: "Backup schedule reference"
dr_tier: "Critical / Essential / Standard / Non-essential"
expiration_date: "For time-limited resources"
tags: "Key-value pairs from cloud provider"
dependencies: "Upstream and downstream services"
AWS Resource Discovery Script
#!/usr/bin/env bash
# aws-asset-discovery.sh - Discover and inventory all AWS resources
OUTPUT_DIR="./asset-inventory/aws/$(date +%Y-%m-%d)"
mkdir -p "$OUTPUT_DIR"
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
echo "=== AWS Asset Discovery for Account $ACCOUNT_ID ==="
# EC2 Instances
echo "--- EC2 Instances ---"
aws ec2 describe-instances \
--query 'Reservations[*].Instances[*].{
InstanceId:InstanceId,
Type:InstanceType,
State:State.Name,
AZ:Placement.AvailabilityZone,
VpcId:VpcId,
PrivateIP:PrivateIpAddress,
PublicIP:PublicIpAddress,
LaunchTime:LaunchTime,
Name:Tags[?Key==`Name`].Value|[0],
Owner:Tags[?Key==`Owner`].Value|[0],
Environment:Tags[?Key==`Environment`].Value|[0]
}' --output json | jq 'flatten' > "$OUTPUT_DIR/ec2-instances.json"
# RDS Databases
echo "--- RDS Instances ---"
aws rds describe-db-instances \
--query 'DBInstances[*].{
DBInstanceId:DBInstanceIdentifier,
Engine:Engine,
EngineVersion:EngineVersion,
Class:DBInstanceClass,
Status:DBInstanceStatus,
MultiAZ:MultiAZ,
Encrypted:StorageEncrypted,
Endpoint:Endpoint.Address,
BackupRetention:BackupRetentionPeriod
}' --output json > "$OUTPUT_DIR/rds-instances.json"
# S3 Buckets
echo "--- S3 Buckets ---"
aws s3api list-buckets --query 'Buckets[*].{Name:Name,Created:CreationDate}' --output json | \
jq -c '.[]' | while read -r bucket; do
name=$(echo "$bucket" | jq -r '.Name')
region=$(aws s3api get-bucket-location --bucket "$name" --query 'LocationConstraint' --output text 2>/dev/null)
encryption=$(aws s3api get-bucket-encryption --bucket "$name" 2>/dev/null | jq -r '.ServerSideEncryptionConfiguration.Rules[0].ApplyServerSideEncryptionByDefault.SSEAlgorithm' 2>/dev/null)
versioning=$(aws s3api get-bucket-versioning --bucket "$name" --query 'Status' --output text 2>/dev/null)
echo "{\"Name\":\"$name\",\"Region\":\"${region:-us-east-1}\",\"Encryption\":\"${encryption:-none}\",\"Versioning\":\"${versioning:-Disabled}\"}"
done | jq -s '.' > "$OUTPUT_DIR/s3-buckets.json"
# Lambda Functions
echo "--- Lambda Functions ---"
aws lambda list-functions \
--query 'Functions[*].{
Name:FunctionName,
Runtime:Runtime,
MemorySize:MemorySize,
Timeout:Timeout,
LastModified:LastModified,
CodeSize:CodeSize
}' --output json > "$OUTPUT_DIR/lambda-functions.json"
# VPCs and Security Groups
echo "--- VPCs ---"
aws ec2 describe-vpcs \
--query 'Vpcs[*].{
VpcId:VpcId,
CidrBlock:CidrBlock,
State:State,
IsDefault:IsDefault,
Name:Tags[?Key==`Name`].Value|[0]
}' --output json > "$OUTPUT_DIR/vpcs.json"
echo "--- Security Groups ---"
aws ec2 describe-security-groups \
--query 'SecurityGroups[*].{
GroupId:GroupId,
GroupName:GroupName,
VpcId:VpcId,
Description:Description,
IngressRuleCount:length(IpPermissions),
EgressRuleCount:length(IpPermissionsEgress)
}' --output json > "$OUTPUT_DIR/security-groups.json"
# IAM Users and Roles
echo "--- IAM Users ---"
aws iam list-users \
--query 'Users[*].{UserName:UserName,Created:CreateDate,PasswordLastUsed:PasswordLastUsed}' \
--output json > "$OUTPUT_DIR/iam-users.json"
echo "--- IAM Roles ---"
aws iam list-roles \
--query 'Roles[*].{RoleName:RoleName,Created:CreateDate,LastUsed:RoleLastUsed.LastUsedDate}' \
--output json > "$OUTPUT_DIR/iam-roles.json"
# EKS Clusters
echo "--- EKS Clusters ---"
aws eks list-clusters --query 'clusters' --output json | jq -r '.[]' | while read -r cluster; do
aws eks describe-cluster --name "$cluster" \
--query 'cluster.{Name:name,Version:version,Status:status,Endpoint:endpoint,Created:createdAt}'
done | jq -s '.' > "$OUTPUT_DIR/eks-clusters.json" 2>/dev/null
# KMS Keys
echo "--- KMS Keys ---"
aws kms list-keys --query 'Keys[*].KeyId' --output text | tr '\t' '\n' | while read -r key_id; do
aws kms describe-key --key-id "$key_id" \
--query 'KeyMetadata.{KeyId:KeyId,Description:Description,State:KeyState,Created:CreationDate,Manager:KeyManager}' 2>/dev/null
done | jq -s '.' > "$OUTPUT_DIR/kms-keys.json"
# Generate summary
echo "=== Inventory Summary ==="
echo "EC2 Instances: $(jq 'length' "$OUTPUT_DIR/ec2-instances.json")"
echo "RDS Instances: $(jq 'length' "$OUTPUT_DIR/rds-instances.json")"
echo "S3 Buckets: $(jq 'length' "$OUTPUT_DIR/s3-buckets.json")"
echo "Lambda Functions: $(jq 'length' "$OUTPUT_DIR/lambda-functions.json")"
echo "VPCs: $(jq 'length' "$OUTPUT_DIR/vpcs.json")"
echo "Security Groups: $(jq 'length' "$OUTPUT_DIR/security-groups.json")"
echo "IAM Users: $(jq 'length' "$OUTPUT_DIR/iam-users.json")"
echo "IAM Roles: $(jq 'length' "$OUTPUT_DIR/iam-roles.json")"
echo "Inventory saved to $OUTPUT_DIR"
AWS Config Rules for Inventory Compliance
# Enable AWS Config recorder
aws configservice put-configuration-recorder \
--configuration-recorder name=default,roleARN=arn:aws:iam::123456789012:role/aws-config-role \
--recording-group allSupported=true,includeGlobalResourceTypes=true
# Start recording
aws configservice start-configuration-recorder --configuration-recorder-name default
# Enable required-tags Config rule
aws configservice put-config-rule --config-rule '{
"ConfigRuleName": "required-tags",
"Source": {
"Owner": "AWS",
"SourceIdentifier": "REQUIRED_TAGS"
},
"InputParameters": "{\"tag1Key\":\"Owner\",\"tag2Key\":\"Environment\",\"tag3Key\":\"CostCenter\",\"tag4Key\":\"DataClassification\"}",
"Scope": {
"ComplianceResourceTypes": [
"AWS::EC2::Instance",
"AWS::RDS::DBInstance",
"AWS::S3::Bucket",
"AWS::Lambda::Function"
]
}
}'
# Config rule for encryption compliance
aws configservice put-config-rule --config-rule '{
"ConfigRuleName": "encrypted-volumes",
"Source": {
"Owner": "AWS",
"SourceIdentifier": "ENCRYPTED_VOLUMES"
}
}'
aws configservice put-config-rule --config-rule '{
"ConfigRuleName": "rds-storage-encrypted",
"Source": {
"Owner": "AWS",
"SourceIdentifier": "RDS_STORAGE_ENCRYPTED"
}
}'
aws configservice put-config-rule --config-rule '{
"ConfigRuleName": "s3-bucket-server-side-encryption-enabled",
"Source": {
"Owner": "AWS",
"SourceIdentifier": "S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED"
}
}'
# Query AWS Config for all resources of a type
aws configservice list-discovered-resources --resource-type AWS::EC2::Instance
aws configservice list-discovered-resources --resource-type AWS::RDS::DBInstance
# Advanced query with AWS Config SQL
aws configservice select-resource-config \
--expression "SELECT resourceId, resourceType, tags, configuration.instanceType
WHERE resourceType = 'AWS::EC2::Instance'
AND tags.tag('Environment') = 'production'"
# Get compliance summary
aws configservice get-compliance-summary-by-config-rule
aws configservice get-compliance-summary-by-resource-type
Tagging Enforcement
# AWS Tag Policy (applied via AWS Organizations)
tag_policy:
tags:
Owner:
tag_key:
"@@assign": "Owner"
enforced_for:
"@@assign":
- "ec2:instance"
- "rds:db"
- "s3:bucket"
- "lambda:function"
Environment:
tag_key:
"@@assign": "Environment"
tag_value:
"@@assign":
- "production"
- "staging"
- "development"
- "sandbox"
DataClassification:
tag_key:
"@@assign": "DataClassification"
tag_value:
"@@assign":
- "public"
- "internal"
- "confidential"
- "restricted"
CostCenter:
tag_key:
"@@assign": "CostCenter"
# Terraform - Enforce tags on all resources with default_tags
provider "aws" {
region = "us-east-1"
default_tags {
tags = {
ManagedBy = "terraform"
Environment = var.environment
Owner = var.team_name
CostCenter = var.cost_center
DataClassification = var.data_classification
}
}
}
Multi-Cloud Discovery
#!/usr/bin/env bash
# multi-cloud-discovery.sh - Discover assets across AWS, Azure, and GCP
OUTPUT_DIR="./asset-inventory/multi-cloud/$(date +%Y-%m-%d)"
mkdir -p "$OUTPUT_DIR"
echo "=== Multi-Cloud Asset Discovery ==="
# AWS - using Resource Groups Tagging API
echo "--- AWS Resources ---"
aws resourcegroupstaggingapi get-resources \
--query 'ResourceTagMappingList[*].{ARN:ResourceARN,Tags:Tags}' \
--output json > "$OUTPUT_DIR/aws-all-resources.json"
echo "AWS resources: $(jq 'length' "$OUTPUT_DIR/aws-all-resources.json")"
# Azure - using Resource Graph
echo "--- Azure Resources ---"
az graph query -q "Resources | project name, type, location, resourceGroup, subscriptionId, tags" \
--output json > "$OUTPUT_DIR/azure-all-resources.json" 2>/dev/null
# GCP - using Cloud Asset Inventory
echo "--- GCP Resources ---"
gcloud asset search-all-resources \
--scope="organizations/ORG_ID" \
--format=json > "$OUTPUT_DIR/gcp-all-resources.json" 2>/dev/null
# Find untagged resources
echo "=== Untagged Resources ==="
jq '[.[] | select(.Tags == null or .Tags == [])] | length' "$OUTPUT_DIR/aws-all-resources.json"
echo "Discovery complete. Results in $OUTPUT_DIR"
CMDB Integration
"""
CMDB sync script - Normalize cloud assets and push to CMDB API.
"""
import json
import requests
from datetime import datetime, timezone
class CMDBSync:
def __init__(self, cmdb_url, api_token):
self.cmdb_url = cmdb_url
self.headers = {
"Authorization": f"Bearer {api_token}",
"Content-Type": "application/json",
}
def normalize_aws_instance(self, instance):
"""Convert AWS EC2 instance to common asset schema."""
tags = {t["Key"]: t["Value"] for t in (instance.get("Tags") or [])}
return {
"asset_id": f"aws:{instance['InstanceId']}",
"name": tags.get("Name", instance["InstanceId"]),
"type": "compute",
"provider": "aws",
"region": instance.get("AZ", "unknown")[:-1],
"configuration": {
"instance_type": instance.get("Type"),
"state": instance.get("State"),
"vpc_id": instance.get("VpcId"),
},
"owner": tags.get("Owner", "unassigned"),
"environment": tags.get("Environment", "unknown"),
"data_classification": tags.get("DataClassification", "unknown"),
"status": "active" if instance.get("State") == "running" else "stopped",
"last_seen": datetime.now(timezone.utc).isoformat(),
}
def sync_assets(self, assets):
"""Push normalized assets to CMDB."""
results = {"created": 0, "updated": 0, "errors": 0}
for asset in assets:
try:
resp = requests.get(
f"{self.cmdb_url}/assets/{asset['asset_id']}",
headers=self.headers,
)
if resp.status_code == 200:
requests.put(
f"{self.cmdb_url}/assets/{asset['asset_id']}",
headers=self.headers,
json=asset,
)
results["updated"] += 1
else:
requests.post(
f"{self.cmdb_url}/assets",
headers=self.headers,
json=asset,
)
results["created"] += 1
except Exception:
results["errors"] += 1
return results
Asset Inventory Checklist
asset_inventory_checklist:
discovery:
- [ ] Automated discovery scripts running for all cloud accounts
- [ ] Discovery covers all resource types (compute, storage, network, IAM)
- [ ] Multi-region discovery enabled
- [ ] On-premise assets cataloged
- [ ] SaaS subscriptions inventoried
- [ ] Discovery runs daily (minimum weekly)
classification:
- [ ] Required tags defined (Owner, Environment, DataClassification, CostCenter)
- [ ] Tag enforcement via AWS Organizations tag policies
- [ ] Tag enforcement via Terraform default_tags
- [ ] Tag enforcement via CI/CD policy checks (Checkov, OPA)
- [ ] Untagged resource reports generated and tracked
configuration_management:
- [ ] AWS Config enabled in all regions
- [ ] Config rules enforce encryption, tagging, and security baselines
- [ ] Configuration compliance summary reviewed weekly
- [ ] Drift detection enabled for IaC-managed resources
cmdb:
- [ ] CMDB sync automated from cloud discovery
- [ ] Common schema defined across all providers
- [ ] Reconciliation process identifies orphaned records
- [ ] New resources auto-assigned default owner
- [ ] Asset lifecycle tracked (created, active, decommissioning, retired)
governance:
- [ ] Asset owners assigned and current
- [ ] Quarterly inventory reconciliation conducted
- [ ] Compliance scope tagging accurate (SOC2, HIPAA, PCI)
- [ ] Asset inventory available for auditor review
- [ ] Decommissioned assets tracked for data retention compliance
Best Practices
- Automate discovery rather than relying on manual inventory: cloud environments change too fast for spreadsheets
- Use AWS Config, Azure Resource Graph, and GCP Cloud Asset Inventory as authoritative data sources
- Enforce tagging at provisioning time through IaC defaults and policy-as-code guardrails
- Assign every asset an owner: unowned resources become security and cost liabilities
- Reconcile inventory regularly and investigate orphaned assets (CMDB record with no real resource and vice versa)
- Track data classification as a mandatory tag to support compliance scoping decisions
- Maintain asset lifecycle states to distinguish active resources from those being decommissioned
- Integrate asset inventory with incident response to quickly identify affected systems during investigations
- Export inventory data for compliance audits in accessible formats (CSV, JSON)
- Review untagged and unclassified resource reports weekly to maintain inventory quality