From cbadcb77f03161b1104d0486ded5887810e24141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Farias?= Date: Thu, 16 Jan 2025 12:13:22 -0300 Subject: [PATCH] Refactor asset inventory (#2879) --- internal/inventory/ASSETS.md | 386 ++++++++------- internal/inventory/asset.go | 438 ++++++------------ .../awsfetcher/fetcher_ec2_instance.go | 78 ++-- .../awsfetcher/fetcher_ec2_instance_test.go | 102 ++-- internal/inventory/awsfetcher/fetcher_elb.go | 18 +- .../inventory/awsfetcher/fetcher_elb_test.go | 32 +- .../awsfetcher/fetcher_iam_policy.go | 114 +---- .../awsfetcher/fetcher_iam_policy_test.go | 44 +- .../inventory/awsfetcher/fetcher_iam_role.go | 23 +- .../awsfetcher/fetcher_iam_role_test.go | 48 +- .../inventory/awsfetcher/fetcher_iam_user.go | 23 +- .../awsfetcher/fetcher_iam_user_test.go | 45 +- .../inventory/awsfetcher/fetcher_lambda.go | 18 +- .../awsfetcher/fetcher_lambda_test.go | 16 +- .../awsfetcher/fetcher_networking.go | 106 +---- internal/inventory/awsfetcher/fetcher_rds.go | 19 +- .../inventory/awsfetcher/fetcher_rds_test.go | 34 +- .../inventory/awsfetcher/fetcher_s3_bucket.go | 28 +- .../awsfetcher/fetcher_s3_bucket_test.go | 62 +-- internal/inventory/awsfetcher/fetcher_sns.go | 18 +- .../inventory/awsfetcher/fetcher_sns_test.go | 16 +- .../inventory/azurefetcher/fetcher_account.go | 14 +- .../azurefetcher/fetcher_account_test.go | 28 +- .../azurefetcher/fetcher_activedirectory.go | 14 +- .../fetcher_activedirectory_test.go | 14 +- .../azurefetcher/fetcher_resource_graph.go | 14 +- .../fetcher_resource_graph_test.go | 28 +- .../inventory/azurefetcher/fetcher_storage.go | 14 +- .../azurefetcher/fetcher_storage_test.go | 30 +- internal/inventory/cloud_assets.xlsx | Bin 89134 -> 174786 bytes .../inventory/gcpfetcher/fetcher_assets.go | 38 +- .../gcpfetcher/fetcher_assets_test.go | 22 +- internal/inventory/inventory.go | 44 +- internal/inventory/inventory_test.go | 104 ++--- scripts/update_assets_md/main.go | 44 +- tests/commonlib/io_utils.py | 14 +- tests/commonlib/utils.py | 4 - .../tests/data/asset_inventory_test_case.py | 2 - .../data/aws_asset_inventory/test_cases.py | 134 ++---- .../data/azure_asset_inventory/test_cases.py | 102 ++-- .../data/gcp_asset_inventory/test_cases.py | 74 +-- .../product/tests/test_aws_asset_inventory.py | 28 +- .../tests/test_azure_asset_inventory.py | 28 +- .../product/tests/test_gcp_asset_inventory.py | 28 +- 44 files changed, 936 insertions(+), 1554 deletions(-) diff --git a/internal/inventory/ASSETS.md b/internal/inventory/ASSETS.md index 93c92c12a2..c2a286d796 100644 --- a/internal/inventory/ASSETS.md +++ b/internal/inventory/ASSETS.md @@ -1,200 +1,236 @@ ## AWS Resources -**Progress: 25% (22/85)** -Identity: 33% (4/12) -Infrastructure: 24% (18/73) +**Progress: 24% (21/85)** +Access Management: 50% (1/2) +Database: 100% (1/1) +FaaS: 100% (3/3) +Firewall: 100% (1/1) +Gateway: 100% (4/4) +Host: 100% (1/1) +Identity: 11% (1/9) +Infrastructure: 0% (0/55) +Load Balancer: 100% (2/2) +Messaging Service: 100% (1/1) +Networking: 100% (4/4) +Service Account: 100% (1/1) +Storage Bucket: 100% (1/1)
Full table -| Category | SubCategory | Type | SubType | Implemented? | -|---|---|---|---|---| -| Identity | Authentication | Certificate | API Gateway Client Certificate | No ❌ | -| Identity | Authentication | Credential | Access Key | No ❌ | -| Identity | Authentication | Credential | EC2 Key Pair | No ❌ | -| Identity | Authorization | ACL | S3 Access Control List | Yes ✅ | -| Identity | Authorization | Grant | KMS Key Grant | No ❌ | -| Identity | Authorization | Policy | S3 Bucket Policy Statement | No ❌ | -| Identity | Digital Identity | Group | IAM Group | No ❌ | -| Identity | Digital Identity | Policy | IAM Policy | Yes ✅ | -| Identity | Digital Identity | Policy | IAM Policy Statement | No ❌ | -| Identity | Digital Identity | Principal | IAM Principal | No ❌ | -| Identity | Digital Identity | Role | IAM Role | Yes ✅ | -| Identity | Digital Identity | User | IAM User | Yes ✅ | -| Infrastructure | Analytics | Cluster | EMR Cluster | No ❌ | -| Infrastructure | Compute | Configuration | Launch Configuration | No ❌ | -| Infrastructure | Compute | Configuration | Launch Template | No ❌ | -| Infrastructure | Compute | Configuration | Launch Template Version | No ❌ | -| Infrastructure | Compute | Image | EC2 AMI | No ❌ | -| Infrastructure | Compute | Reservation | EC2 Reserved Instance | No ❌ | -| Infrastructure | Compute | Scaling | Auto Scaling Group | No ❌ | -| Infrastructure | Compute | Serverless | Lambda Function | Yes ✅ | -| Infrastructure | Compute | Serverless | Lambda Function Alias | No ❌ | -| Infrastructure | Compute | Serverless | Lambda Layer | Yes ✅ | -| Infrastructure | Compute | Virtual Machine | EC2 Instance | Yes ✅ | -| Infrastructure | Container | Compute | ECS Container | No ❌ | -| Infrastructure | Container | Compute | ECS Container Instance | No ❌ | -| Infrastructure | Container | Compute | ECS Task | No ❌ | -| Infrastructure | Container | Configuration | ECS Container Definition | No ❌ | -| Infrastructure | Container | Configuration | ECS Task Definition | No ❌ | -| Infrastructure | Container | Image | ECR Image | No ❌ | -| Infrastructure | Container | Image | ECR Repository Image | No ❌ | -| Infrastructure | Container | Orchestration | ECS Cluster | No ❌ | -| Infrastructure | Container | Orchestration | ECS Service | No ❌ | -| Infrastructure | Container | Orchestration | EKS Cluster | No ❌ | -| Infrastructure | Container | Registry | ECR Repository | No ❌ | -| Infrastructure | Database | Data Warehouse | Redshift Cluster | No ❌ | -| Infrastructure | Database | NoSQL | DynamoDB Table | No ❌ | -| Infrastructure | Database | Relational | RDS Cluster | No ❌ | -| Infrastructure | Database | Relational | RDS Instance | Yes ✅ | -| Infrastructure | Database | Search | Elasticsearch Domain | No ❌ | -| Infrastructure | Integration | API | API Gateway Resource | No ❌ | -| Infrastructure | Integration | API | API Gateway REST API | No ❌ | -| Infrastructure | Integration | API | API Gateway Stage | No ❌ | -| Infrastructure | Integration | Event Source | Lambda Event Source Mapping | Yes ✅ | -| Infrastructure | Integration | Message Queue | SQS Queue | No ❌ | -| Infrastructure | Management | Cloud Account | Cloud Service Provider Account | No ❌ | -| Infrastructure | Management | Configuration | Config Configuration Recorder | No ❌ | -| Infrastructure | Management | Configuration | Config Delivery Channel | No ❌ | -| Infrastructure | Management | Inventory | Systems Manager Instance | No ❌ | -| Infrastructure | Management | Patch | Systems Manager Instance Patch | No ❌ | -| Infrastructure | Messaging | Notification Service | SNS Topic | Yes ✅ | -| Infrastructure | Network | DNS | DNS Record | No ❌ | -| Infrastructure | Network | DNS | Nameserver | No ❌ | -| Infrastructure | Network | DNS | Route53 DNS Record | No ❌ | -| Infrastructure | Network | DNS | Route53 DNS Zone | No ❌ | -| Infrastructure | Network | Endpoint | Network Endpoint | No ❌ | -| Infrastructure | Network | Firewall Rule | Inbound IP Permission | No ❌ | -| Infrastructure | Network | Firewall Rule | IP Rule | No ❌ | -| Infrastructure | Network | Firewall | EC2 Security Group | Yes ✅ | -| Infrastructure | Network | Gateway | Internet Gateway | Yes ✅ | -| Infrastructure | Network | Gateway | NAT Gateway | Yes ✅ | -| Infrastructure | Network | Interface | EC2 Network Interface | Yes ✅ | -| Infrastructure | Network | IP Address Range | VPC CIDR Block | No ❌ | -| Infrastructure | Network | IP Address Range | VPC IPv4 CIDR Block | No ❌ | -| Infrastructure | Network | IP Address Range | VPC IPv6 CIDR Block | No ❌ | -| Infrastructure | Network | IP Address | EC2 Private IP | No ❌ | -| Infrastructure | Network | IP Address | Elastic IP | No ❌ | -| Infrastructure | Network | IP Address | IP Address | No ❌ | -| Infrastructure | Network | Load Balancer | ELBv2 Listener | No ❌ | -| Infrastructure | Network | Load Balancer | Elastic Load Balancer | Yes ✅ | -| Infrastructure | Network | Load Balancer | Elastic Load Balancer v2 | Yes ✅ | -| Infrastructure | Network | Load Balancer | ELB Listener | No ❌ | -| Infrastructure | Network | Peering | VPC Peering Connection | Yes ✅ | -| Infrastructure | Network | Subnet | DB Subnet Group | No ❌ | -| Infrastructure | Network | Subnet | EC2 Subnet | Yes ✅ | -| Infrastructure | Network | Virtual Network | Transit Gateway | Yes ✅ | -| Infrastructure | Network | Virtual Network | Transit Gateway Attachment | Yes ✅ | -| Infrastructure | Network | Virtual Network | VPC | Yes ✅ | -| Infrastructure | Security | Encryption | KMS Key | No ❌ | -| Infrastructure | Security | Encryption | KMS Key Alias | No ❌ | -| Infrastructure | Security | Secrets Management | Secrets Manager Secret | No ❌ | -| Infrastructure | Security | Security Management | Security Hub | No ❌ | -| Infrastructure | Storage | Disk | EBS Volume | No ❌ | -| Infrastructure | Storage | Object Storage | S3 Bucket | Yes ✅ | -| Infrastructure | Storage | Snapshot | EBS Snapshot | No ❌ | -| Infrastructure | Storage | Snapshot | RDS Snapshot | No ❌ | +| Category | Old Type | Type | Implemented? | +|---|---|---|---| +| Access Management | IAM Policy | AWS IAM Policy | Yes ✅ | +| Access Management | S3 Access Control List | AWS S3 Access Control List | No ❌ | +| Database | RDS Instance | AWS RDS Instance | Yes ✅ | +| FaaS | Lambda Event Source Mapping | AWS Lambda Event Source Mapping | Yes ✅ | +| FaaS | Lambda Function | AWS Lambda Function | Yes ✅ | +| FaaS | Lambda Layer | AWS Lambda Layer | Yes ✅ | +| Firewall | EC2 Security Group | AWS EC2 Security Group | Yes ✅ | +| Gateway | Internet Gateway | AWS Internet Gateway | Yes ✅ | +| Gateway | NAT Gateway | AWS NAT Gateway | Yes ✅ | +| Gateway | Transit Gateway | AWS Transit Gateway | Yes ✅ | +| Gateway | Transit Gateway Attachment | AWS Transit Gateway Attachment | Yes ✅ | +| Host | EC2 Instance | AWS EC2 Instance | Yes ✅ | +| Identity | Access Key | | No ❌ | +| Identity | API Gateway Client Certificate | | No ❌ | +| Identity | IAM User | AWS IAM User | Yes ✅ | +| Identity | EC2 Key Pair | | No ❌ | +| Identity | IAM Group | | No ❌ | +| Identity | IAM Policy Statement | | No ❌ | +| Identity | IAM Principal | | No ❌ | +| Identity | KMS Key Grant | | No ❌ | +| Identity | S3 Bucket Policy Statement | | No ❌ | +| Infrastructure | API Gateway Resource | | No ❌ | +| Infrastructure | API Gateway REST API | | No ❌ | +| Infrastructure | API Gateway Stage | | No ❌ | +| Infrastructure | Auto Scaling Group | | No ❌ | +| Infrastructure | Cloud Service Provider Account | | No ❌ | +| Infrastructure | Config Configuration Recorder | | No ❌ | +| Infrastructure | Config Delivery Channel | | No ❌ | +| Infrastructure | DB Subnet Group | | No ❌ | +| Infrastructure | DNS Record | | No ❌ | +| Infrastructure | DynamoDB Table | | No ❌ | +| Infrastructure | EBS Snapshot | | No ❌ | +| Infrastructure | EBS Volume | | No ❌ | +| Infrastructure | EC2 AMI | | No ❌ | +| Infrastructure | EC2 Private IP | | No ❌ | +| Infrastructure | EC2 Reserved Instance | | No ❌ | +| Infrastructure | ECR Image | | No ❌ | +| Infrastructure | ECR Repository | | No ❌ | +| Infrastructure | ECR Repository Image | | No ❌ | +| Infrastructure | ECS Cluster | | No ❌ | +| Infrastructure | ECS Container | | No ❌ | +| Infrastructure | ECS Container Definition | | No ❌ | +| Infrastructure | ECS Container Instance | | No ❌ | +| Infrastructure | ECS Service | | No ❌ | +| Infrastructure | ECS Task | | No ❌ | +| Infrastructure | ECS Task Definition | | No ❌ | +| Infrastructure | EKS Cluster | | No ❌ | +| Infrastructure | ELBv2 Listener | | No ❌ | +| Infrastructure | Elastic IP | | No ❌ | +| Infrastructure | Elasticsearch Domain | | No ❌ | +| Infrastructure | ELB Listener | | No ❌ | +| Infrastructure | EMR Cluster | | No ❌ | +| Infrastructure | Inbound IP Permission | | No ❌ | +| Infrastructure | IP Address | | No ❌ | +| Infrastructure | IP Rule | | No ❌ | +| Infrastructure | KMS Key | | No ❌ | +| Infrastructure | KMS Key Alias | | No ❌ | +| Infrastructure | Lambda Function Alias | | No ❌ | +| Infrastructure | Launch Configuration | | No ❌ | +| Infrastructure | Launch Template | | No ❌ | +| Infrastructure | Launch Template Version | | No ❌ | +| Infrastructure | Nameserver | | No ❌ | +| Infrastructure | Network Endpoint | | No ❌ | +| Infrastructure | RDS Cluster | | No ❌ | +| Infrastructure | RDS Snapshot | | No ❌ | +| Infrastructure | Redshift Cluster | | No ❌ | +| Infrastructure | Route53 DNS Record | | No ❌ | +| Infrastructure | Route53 DNS Zone | | No ❌ | +| Infrastructure | Secrets Manager Secret | | No ❌ | +| Infrastructure | Security Hub | | No ❌ | +| Infrastructure | SQS Queue | | No ❌ | +| Infrastructure | Systems Manager Instance | | No ❌ | +| Infrastructure | Systems Manager Instance Patch | | No ❌ | +| Infrastructure | VPC CIDR Block | | No ❌ | +| Infrastructure | VPC IPv4 CIDR Block | | No ❌ | +| Infrastructure | VPC IPv6 CIDR Block | | No ❌ | +| Load Balancer | Elastic Load Balancer | AWS Elastic Load Balancer | Yes ✅ | +| Load Balancer | Elastic Load Balancer v2 | AWS Elastic Load Balancer v2 | Yes ✅ | +| Messaging Service | SNS Topic | AWS SNS Topic | Yes ✅ | +| Networking | EC2 Network Interface | AWS EC2 Network Interface | Yes ✅ | +| Networking | EC2 Subnet | AWS EC2 Subnet | Yes ✅ | +| Networking | VPC | AWS VPC | Yes ✅ | +| Networking | VPC Peering Connection | AWS VPC Peering Connection | Yes ✅ | +| Service Account | IAM Role | AWS IAM Role | Yes ✅ | +| Storage Bucket | S3 Bucket | AWS S3 Bucket | Yes ✅ |
## AZURE Resources -**Progress: 31% (16/51)** -Identity: 12% (1/8) -Infrastructure: 34% (15/43) +**Progress: 33% (18/54)** +Access Management: 100% (3/3) +Container Registry: 100% (1/1) +Database: 100% (3/3) +Host: 100% (1/1) +Identity: 9% (1/11) +Infrastructure: 7% (2/28) +Messaging Service: 100% (2/2) +Private Endpoint: 100% (1/1) +Snapshot: 100% (1/1) +Storage Bucket: 100% (1/1) +Volume: 100% (1/1) +Web Service: 100% (1/1)
Full table -| Category | SubCategory | Type | SubType | Implemented? | -|---|---|---|---|---| -| Identity | Access Management | Role Assignment | Azure Role Assignment | No ❌ | -| Identity | Access Management | Role | Azure Role | No ❌ | -| Identity | Application | Application | Azure AD Application | No ❌ | -| Identity | Digital Identity | Administrator | Azure Server AD Administrator | No ❌ | -| Identity | Digital Identity | Principal | Azure Principal | Yes ✅ | -| Identity | Directory | Group | Azure AD Group | No ❌ | -| Identity | Directory | User | Azure AD User | No ❌ | -| Identity | Service Identity | Service Principal | Azure AD Service Principal | No ❌ | -| Infrastructure | Application Integration | Message Queue | Azure Storage Queue | Yes ✅ | -| Infrastructure | Application Integration | Message Queue | Azure Storage Queue Service | Yes ✅ | -| Infrastructure | Application | Web Application | Azure App Service | Yes ✅ | -| Infrastructure | Compute | Virtual Machine | Azure Virtual Machine | Yes ✅ | -| Infrastructure | Container | Registry | Azure Container Registry | Yes ✅ | -| Infrastructure | Database | Backup and Recovery | Azure Recoverable Database | No ❌ | -| Infrastructure | Database | Backup and Recovery | Azure Restorable Dropped Database | No ❌ | -| Infrastructure | Database | Backup and Recovery | Azure Restore Point | No ❌ | -| Infrastructure | Database | High Availability | Azure Cosmos DB Account Failover Policy | No ❌ | -| Infrastructure | Database | High Availability | Azure Failover Group | No ❌ | -| Infrastructure | Database | NoSQL Database | Azure Cosmos DB Account | No ❌ | -| Infrastructure | Database | NoSQL Database | Azure Cosmos DB Cassandra Keyspace | No ❌ | -| Infrastructure | Database | NoSQL Database | Azure Cosmos DB Cassandra Table | No ❌ | -| Infrastructure | Database | NoSQL Database | Azure Cosmos DB Location | No ❌ | -| Infrastructure | Database | NoSQL Database | Azure Cosmos DB MongoDB Collection | No ❌ | -| Infrastructure | Database | NoSQL Database | Azure Cosmos DB MongoDB Database | No ❌ | -| Infrastructure | Database | NoSQL Database | Azure Cosmos DB SQL Container | No ❌ | -| Infrastructure | Database | NoSQL Database | Azure Cosmos DB SQL Database | No ❌ | -| Infrastructure | Database | NoSQL Database | Azure Cosmos DB Table Resource | No ❌ | -| Infrastructure | Database | NoSQL Database | Azure Storage Table | No ❌ | -| Infrastructure | Database | NoSQL Database | Azure Storage Table Service | No ❌ | -| Infrastructure | Database | Relational | Azure SQL Database | Yes ✅ | -| Infrastructure | Database | Relational | Azure SQL Server | Yes ✅ | -| Infrastructure | Database | Replication | Azure Replication Link | No ❌ | -| Infrastructure | Database | Scalability | Azure Elastic Pool | Yes ✅ | -| Infrastructure | Management | Cloud Account | Azure Subscription | Yes ✅ | -| Infrastructure | Management | Cloud Account | Azure Tenant | Yes ✅ | -| Infrastructure | Management | Resource Group | Azure Resource Group | Yes ✅ | -| Infrastructure | Network | DNS | Azure Server DNS Alias | No ❌ | -| Infrastructure | Network | Network Security | Azure Cosmos DB Virtual Network Rule | No ❌ | -| Infrastructure | Network | Private Connectivity | Azure Cosmos DB Private Endpoint Connection | No ❌ | -| Infrastructure | Security | Cross-Origin Resource Sharing | Azure Cosmos DB CORS Policy | No ❌ | -| Infrastructure | Security | Encryption | Azure Transparent Data Encryption | No ❌ | -| Infrastructure | Security | Threat Detection | Azure Database Threat Detection Policy | No ❌ | -| Infrastructure | Serverless | Function | Azure Function | No ❌ | -| Infrastructure | Storage | Disk | Azure Data Disk | No ❌ | -| Infrastructure | Storage | Disk | Azure Disk | Yes ✅ | -| Infrastructure | Storage | File Storage | Azure Storage File Service | No ❌ | -| Infrastructure | Storage | File Storage | Azure Storage File Share | No ❌ | -| Infrastructure | Storage | Object Storage | Azure Storage Blob Container | No ❌ | -| Infrastructure | Storage | Object Storage | Azure Storage Blob Service | Yes ✅ | -| Infrastructure | Storage | Snapshot | Azure Snapshot | Yes ✅ | -| Infrastructure | Storage | Storage | Azure Storage Account | Yes ✅ | +| Category | Old Type | Type | Implemented? | +|---|---|---|---| +| Access Management | Azure Resource Group | Azure Resource Group | Yes ✅ | +| Access Management | Azure Subscription | Azure Subscription | Yes ✅ | +| Access Management | Azure Tenant | Azure Tenant | Yes ✅ | +| Container Registry | Azure Container Registry | Azure Container Registry | Yes ✅ | +| Database | Azure Elastic Pool | Azure Elastic Pool | Yes ✅ | +| Database | Azure SQL Database | Azure SQL Database | Yes ✅ | +| Database | Azure SQL Server | Azure SQL Server | Yes ✅ | +| Host | Azure Virtual Machine | Azure Virtual Machine | Yes ✅ | +| Identity | Access Key | | No ❌ | +| Identity | API Gateway Client Certificate | | No ❌ | +| Identity | Azure AD Application | | No ❌ | +| Identity | Azure AD Group | | No ❌ | +| Identity | Azure AD Service Principal | | No ❌ | +| Identity | Azure AD User | | No ❌ | +| Identity | Azure Principal | Azure Principal | Yes ✅ | +| Identity | Azure Role | | No ❌ | +| Identity | Azure Role Assignment | | No ❌ | +| Identity | Azure Server AD Administrator | | No ❌ | +| Identity | EC2 Key Pair | | No ❌ | +| Infrastructure | Azure Cosmos DB Account | Azure Cosmos DB Account | Yes ✅ | +| Infrastructure | Azure Cosmos DB Account Failover Policy | | No ❌ | +| Infrastructure | Azure Cosmos DB Cassandra Keyspace | | No ❌ | +| Infrastructure | Azure Cosmos DB Cassandra Table | | No ❌ | +| Infrastructure | Azure Cosmos DB CORS Policy | | No ❌ | +| Infrastructure | Azure Cosmos DB Location | | No ❌ | +| Infrastructure | Azure Cosmos DB MongoDB Collection | | No ❌ | +| Infrastructure | Azure Cosmos DB MongoDB Database | | No ❌ | +| Infrastructure | Azure Cosmos DB Private Endpoint Connection | | No ❌ | +| Infrastructure | Azure Cosmos DB SQL Container | | No ❌ | +| Infrastructure | Azure Cosmos DB SQL Database | Azure Cosmos DB SQL Database | Yes ✅ | +| Infrastructure | Azure Cosmos DB Table Resource | | No ❌ | +| Infrastructure | Azure Cosmos DB Virtual Network Rule | | No ❌ | +| Infrastructure | Azure Data Disk | | No ❌ | +| Infrastructure | Azure Database Threat Detection Policy | | No ❌ | +| Infrastructure | Azure Failover Group | | No ❌ | +| Infrastructure | Azure Function | | No ❌ | +| Infrastructure | Azure Recoverable Database | | No ❌ | +| Infrastructure | Azure Replication Link | | No ❌ | +| Infrastructure | Azure Restorable Dropped Database | | No ❌ | +| Infrastructure | Azure Restore Point | | No ❌ | +| Infrastructure | Azure Server DNS Alias | | No ❌ | +| Infrastructure | Azure Storage Blob Container | | No ❌ | +| Infrastructure | Azure Storage File Service | | No ❌ | +| Infrastructure | Azure Storage File Share | | No ❌ | +| Infrastructure | Azure Storage Table | | No ❌ | +| Infrastructure | Azure Storage Table Service | | No ❌ | +| Infrastructure | Azure Transparent Data Encryption | | No ❌ | +| Messaging Service | Azure Storage Queue | Azure Storage Queue | Yes ✅ | +| Messaging Service | Azure Storage Queue Service | Azure Storage Queue Service | Yes ✅ | +| Private Endpoint | Azure Storage Account | Azure Storage Account | Yes ✅ | +| Snapshot | Azure Snapshot | Azure Snapshot | Yes ✅ | +| Storage Bucket | Azure Storage Blob Service | Azure Storage Blob Service | Yes ✅ | +| Volume | Azure Disk | Azure Disk | Yes ✅ | +| Web Service | Azure App Service | Azure App Service | Yes ✅ |
## GCP Resources **Progress: 56% (14/25)** -Identity: 75% (3/4) -Infrastructure: 55% (11/20) +Access Management: 100% (2/2) +Account: 100% (1/1) +Container Service: 100% (1/1) +FaaS: 100% (1/1) +Firewall: 100% (1/1) +Host: 100% (1/1) +Identity: 0% (0/1) +Infrastructure: 0% (0/9) +Load Balancer: 100% (1/1) Management: 0% (0/1) +Orchestrator: 100% (1/1) +Organization: 100% (2/2) +Service Usage Technology: 100% (1/1) +Storage Bucket: 100% (1/1) +Subnet: 100% (1/1)
Full table -| Category | SubCategory | Type | SubType | Implemented? | -|---|---|---|---|---| -| Identity | Access Management | IAM Policy | GCP IAM Policy | No ❌ | -| Identity | Access Management | IAM Role | GCP IAM Role | Yes ✅ | -| Identity | Service Identity | Service Account Key | GCP Service Account Key | Yes ✅ | -| Identity | Service Identity | Service Account | GCP Service Account | Yes ✅ | -| Infrastructure | Compute | Virtual Machine | GCP Instance | Yes ✅ | -| Infrastructure | Container | Orchestration | GKE Cluster | Yes ✅ | -| Infrastructure | Container | Serverless | GCP Cloud Run Service | Yes ✅ | -| Infrastructure | Management | Cloud Account | GCP Organization | Yes ✅ | -| Infrastructure | Management | Cloud Account | GCP Project | Yes ✅ | -| Infrastructure | Management | Resource Hierarchy | GCP Folder | Yes ✅ | -| Infrastructure | Network | DNS | GCP DNS Record Set | No ❌ | -| Infrastructure | Network | DNS | GCP DNS Zone | No ❌ | -| Infrastructure | Network | Firewall Rule | GCP IP Rule | No ❌ | -| Infrastructure | Network | Firewall | GCP Firewall | Yes ✅ | -| Infrastructure | Network | Firewall | GCP Network Tag | No ❌ | -| Infrastructure | Network | IP Address Range | IP Range | No ❌ | -| Infrastructure | Network | Load Balancing | GCP Compute Target Pool | No ❌ | -| Infrastructure | Network | Load Balancing | GCP Forwarding Rule | Yes ✅ | -| Infrastructure | Network | Network Interface | GCP Network Interface | No ❌ | -| Infrastructure | Network | Network Interface | GCP Network Interface Access Config | No ❌ | -| Infrastructure | Network | Subnet | GCP Subnet | Yes ✅ | -| Infrastructure | Network | Virtual Network | GCP VPC | No ❌ | -| Infrastructure | Serverless | Function | GCP Cloud Function | Yes ✅ | -| Infrastructure | Storage | Object Storage | GCP Bucket | Yes ✅ | -| Management | Resource Management | Label | GCP Bucket Label | No ❌ | +| Category | Old Type | Type | Implemented? | +|---|---|---|---| +| Access Management | GCP Service Account | GCP Service Account | Yes ✅ | +| Access Management | GCP Service Account Key | GCP Service Account Key | Yes ✅ | +| Account | GCP Project | GCP Project | Yes ✅ | +| Container Service | GCP Cloud Run Service | GCP Cloud Run Service | Yes ✅ | +| FaaS | GCP Cloud Function | GCP Cloud Function | Yes ✅ | +| Firewall | GCP Firewall | GCP Firewall | Yes ✅ | +| Host | GCP Instance | GCP Compute Instance | Yes ✅ | +| Identity | GCP IAM Policy | | No ❌ | +| Infrastructure | GCP Compute Target Pool | | No ❌ | +| Infrastructure | GCP DNS Record Set | | No ❌ | +| Infrastructure | GCP DNS Zone | | No ❌ | +| Infrastructure | GCP IP Rule | | No ❌ | +| Infrastructure | GCP Network Interface | | No ❌ | +| Infrastructure | GCP Network Interface Access Config | | No ❌ | +| Infrastructure | GCP Network Tag | | No ❌ | +| Infrastructure | GCP VPC | | No ❌ | +| Infrastructure | IP Range | | No ❌ | +| Load Balancer | GCP Forwarding Rule | GCP Load Balancing Forwarding Rule | Yes ✅ | +| Management | GCP Bucket Label | | No ❌ | +| Orchestrator | GKE Cluster | GCP Kubernetes Engine (GKE) Cluster | Yes ✅ | +| Organization | GCP Folder | GCP Folder | Yes ✅ | +| Organization | GCP Organization | GCP Organization | Yes ✅ | +| Service Usage Technology | GCP IAM Role | GCP IAM Role | Yes ✅ | +| Storage Bucket | GCP Bucket | GCP Bucket | Yes ✅ | +| Subnet | GCP Subnet | GCP Subnet | Yes ✅ |
diff --git a/internal/inventory/asset.go b/internal/inventory/asset.go index 99604dee43..05fd3a7aa5 100644 --- a/internal/inventory/asset.go +++ b/internal/inventory/asset.go @@ -17,137 +17,41 @@ package inventory -// AssetCategory is used to build the document index. Use only numbers, letters and dashes (-) -type AssetCategory string +import "github.com/samber/lo" -const ( - CategoryIdentity AssetCategory = "identity" - CategoryInfrastructure AssetCategory = "infrastructure" -) - -// AssetSubCategory is used to build the document index. Use only numbers, letters and dashes (-) -type AssetSubCategory string +// AssetCategory is used to build the document index. +type AssetCategory string const ( - SubCategoryAccessManagement AssetSubCategory = "access-management" - SubCategoryApplication AssetSubCategory = "application" - SubCategoryApplicationIntegration AssetSubCategory = "application-integration" - SubCategoryAuthorization AssetSubCategory = "authorization" - SubCategoryCompute AssetSubCategory = "compute" - SubCategoryContainer AssetSubCategory = "container" - SubCategoryDatabase AssetSubCategory = "database" - SubCategoryDigitalIdentity AssetSubCategory = "digital-identity" - SubCategoryIntegration AssetSubCategory = "integration" - SubCategoryManagement AssetSubCategory = "management" - SubCategoryMessaging AssetSubCategory = "messaging" - SubCategoryNetwork AssetSubCategory = "network" - SubCategoryServerless AssetSubCategory = "serverless" - SubCategoryServiceIdentity AssetSubCategory = "service-identity" - SubCategoryStorage AssetSubCategory = "storage" + CategoryAccessManagement AssetCategory = "Access Management" + CategoryAccount AssetCategory = "Account" + CategoryContainerRegistry AssetCategory = "Container Registry" + CategoryContainerService AssetCategory = "Container Service" + CategoryDatabase AssetCategory = "Database" + CategoryFaaS AssetCategory = "FaaS" + CategoryFirewall AssetCategory = "Firewall" + CategoryGateway AssetCategory = "Gateway" + CategoryHost AssetCategory = "Host" + CategoryIdentity AssetCategory = "Identity" + CategoryInfrastructure AssetCategory = "Infrastructure" + CategoryLoadBalancer AssetCategory = "Load Balancer" + CategoryMessagingService AssetCategory = "Messaging Service" + CategoryNetworking AssetCategory = "Networking" + CategoryOrchestrator AssetCategory = "Orchestrator" + CategoryOrganization AssetCategory = "Organization" + CategoryPrivateEndpoint AssetCategory = "Private Endpoint" + CategoryServiceAccount AssetCategory = "Service Account" + CategoryServiceUsageTechnology AssetCategory = "Service Usage Technology" + CategorySnapshot AssetCategory = "Snapshot" + CategoryStorageBucket AssetCategory = "Storage Bucket" + CategorySubnet AssetCategory = "Subnet" + CategoryVolume AssetCategory = "Volume" + CategoryWebService AssetCategory = "Web Service" ) -// AssetType is used to build the document index. Use only numbers, letters and dashes (-) +// AssetType is used to build the document index. type AssetType string -const ( - TypeAcl AssetType = "acl" - TypeCloudAccount AssetType = "cloud-account" - TypeDisk AssetType = "disk" - TypeEventSource AssetType = "event-source" - TypeFirewall AssetType = "firewall" - TypeGateway AssetType = "gateway" - TypeInterface AssetType = "interface" - TypeLoadBalancer AssetType = "load-balancer" - TypeMessageQueue AssetType = "message-queue" - TypeNoSQLDatabase AssetType = "nosql-database" - TypeNotificationService AssetType = "notification-service" - TypeObjectStorage AssetType = "object-storage" - TypePeering AssetType = "peering" - TypePolicy AssetType = "policy" - TypePrincipal AssetType = "principal" - TypeRegistry AssetType = "registry" - TypeRelationalDatabase AssetType = "relational" - TypeResourceGroup AssetType = "resource-group" - TypeRole AssetType = "role" - TypeScalability AssetType = "scalability" - TypeServerless AssetType = "serverless" - TypeServiceAccount AssetType = "service-account" - TypeServiceAccountKey AssetType = "service-account-key" - TypeSnapshot AssetType = "snapshot" - TypeStorage AssetType = "storage" - TypeSubnet AssetType = "subnet" - TypeUser AssetType = "user" - TypeVirtualMachine AssetType = "virtual-machine" - TypeVirtualNetwork AssetType = "virtual-network" - TypeWebApplication AssetType = "web-application" - TypeResourceHierarchy AssetType = "resource-hierarchy" - TypeOrchestration AssetType = "orchestration" - TypeFunction AssetType = "function" - TypeLoadBalancing AssetType = "load-balancing" - TypeIamRole AssetType = "iam-role" -) - -// AssetSubType is used to build the document index. Use only numbers, letters and dashes (-) -type AssetSubType string - -const ( - SubTypeAzureAppService AssetSubType = "azure-app-service" - SubTypeAzureContainerRegistry AssetSubType = "azure-container-registry" - SubTypeAzureCosmosDBAccount AssetSubType = "azure-cosmos-db-account" - SubTypeAzureCosmosDBSQLDatabase AssetSubType = "azure-cosmos-db-sql-database" - SubTypeAzureDisk AssetSubType = "azure-disk" - SubTypeAzureElasticPool AssetSubType = "azure-elastic-pool" - SubTypeAzurePrincipal AssetSubType = "azure-principal" - SubTypeAzureResourceGroup AssetSubType = "azure-resource-group" - SubTypeAzureSQLDatabase AssetSubType = "azure-sql-database" - SubTypeAzureSQLServer AssetSubType = "azure-sql-server" - SubTypeAzureSnapshot AssetSubType = "azure-snapshot" - SubTypeAzureStorageAccount AssetSubType = "azure-storage-account" - SubTypeAzureStorageBlobService AssetSubType = "azure-storage-blob-service" - SubTypeAzureStorageQueue AssetSubType = "azure-storage-queue" - SubTypeAzureStorageQueueService AssetSubType = "azure-storage-queue-service" - SubTypeAzureSubscription AssetSubType = "azure-subscription" - SubTypeAzureTenant AssetSubType = "azure-tenant" - SubTypeAzureVirtualMachine AssetSubType = "azure-virtual-machine" - SubTypeEC2 AssetSubType = "ec2-instance" - SubTypeEC2NetworkInterface AssetSubType = "ec2-network-interface" - SubTypeEC2Subnet AssetSubType = "ec2-subnet" - SubTypeELBv1 AssetSubType = "elastic-load-balancer" - SubTypeELBv2 AssetSubType = "elastic-load-balancer-v2" - SubTypeIAMPolicy AssetSubType = "iam-policy" - SubTypeIAMRole AssetSubType = "iam-role" - SubTypeIAMUser AssetSubType = "iam-user" - SubTypeInternetGateway AssetSubType = "internet-gateway" - SubTypeLambdaAlias AssetSubType = "lambda-function-alias" - SubTypeLambdaEventSourceMapping AssetSubType = "lambda-event-source-mapping" - SubTypeLambdaFunction AssetSubType = "lambda-function" - SubTypeLambdaLayer AssetSubType = "lambda-layer" - SubTypeNatGateway AssetSubType = "nat-gateway" - SubTypeRDS AssetSubType = "rds-instance" - SubTypeS3 AssetSubType = "s3-bucket" - SubTypeSNSTopic AssetSubType = "sns-topic" - SubTypeSecurityGroup AssetSubType = "ec2-security-group" - SubTypeTransitGateway AssetSubType = "transit-gateway" - SubTypeTransitGatewayAttachment AssetSubType = "transit-gateway-attachment" - SubTypeVpc AssetSubType = "vpc" - SubTypeVpcAcl AssetSubType = "s3-access-control-list" - SubTypeVpcPeeringConnection AssetSubType = "vpc-peering-connection" - SubTypeGcpProject AssetSubType = "gcp-project" - SubTypeGcpInstance AssetSubType = "gcp-instance" - SubTypeGcpSubnet AssetSubType = "gcp-subnet" - SubTypeGcpFirewall AssetSubType = "gcp-firewall" - SubTypeGcpBucket AssetSubType = "gcp-bucket" - SubTypeGcpOrganization AssetSubType = "gcp-organization" - SubTypeGcpFolder AssetSubType = "gcp-folder" - SubTypeGcpServiceAccount AssetSubType = "gcp-service-account" - SubTypeGcpServiceAccountKey AssetSubType = "gcp-service-account-key" - SubTypeGcpGkeCluster AssetSubType = "gke-cluster" - SubTypeGcpForwardingRule AssetSubType = "gcp-forwarding-rule" - SubTypeGcpCloudFunction AssetSubType = "gcp-cloud-function" - SubTypeGcpCloudRunService AssetSubType = "gcp-cloud-run-service" - SubTypeGcpIamRole AssetSubType = "gcp-iam-role" -) - const ( AwsCloudProvider = "aws" AzureCloudProvider = "azure" @@ -156,190 +60,145 @@ const ( // AssetClassification holds the taxonomy of an asset type AssetClassification struct { - Category AssetCategory `json:"category"` - SubCategory AssetSubCategory `json:"sub_category"` - Type AssetType `json:"type"` - SubType AssetSubType `json:"sub_type"` + Category AssetCategory `json:"category"` + Type AssetType `json:"type"` } // AssetClassifications below are used to generate // 'internal/inventory/ASSETS.md'. Please keep formatting consistent. var ( // AWS - AssetClassificationAwsEc2Instance = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryCompute, Type: TypeVirtualMachine, SubType: SubTypeEC2} - AssetClassificationAwsElbV1 = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryNetwork, Type: TypeLoadBalancer, SubType: SubTypeELBv1} - AssetClassificationAwsElbV2 = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryNetwork, Type: TypeLoadBalancer, SubType: SubTypeELBv2} - AssetClassificationAwsIamPolicy = AssetClassification{Category: CategoryIdentity, SubCategory: SubCategoryDigitalIdentity, Type: TypePolicy, SubType: SubTypeIAMPolicy} - AssetClassificationAwsIamRole = AssetClassification{Category: CategoryIdentity, SubCategory: SubCategoryDigitalIdentity, Type: TypeRole, SubType: SubTypeIAMRole} - AssetClassificationAwsIamUser = AssetClassification{Category: CategoryIdentity, SubCategory: SubCategoryDigitalIdentity, Type: TypeUser, SubType: SubTypeIAMUser} - AssetClassificationAwsLambdaEventSourceMapping = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryIntegration, Type: TypeEventSource, SubType: SubTypeLambdaEventSourceMapping} - AssetClassificationAwsLambdaFunction = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryCompute, Type: TypeServerless, SubType: SubTypeLambdaFunction} - AssetClassificationAwsLambdaLayer = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryCompute, Type: TypeServerless, SubType: SubTypeLambdaLayer} - AssetClassificationAwsInternetGateway = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryNetwork, Type: TypeGateway, SubType: SubTypeInternetGateway} - AssetClassificationAwsNatGateway = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryNetwork, Type: TypeGateway, SubType: SubTypeNatGateway} - AssetClassificationAwsNetworkAcl = AssetClassification{Category: CategoryIdentity, SubCategory: SubCategoryAuthorization, Type: TypeAcl, SubType: SubTypeVpcAcl} - AssetClassificationAwsNetworkInterface = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryNetwork, Type: TypeInterface, SubType: SubTypeEC2NetworkInterface} - AssetClassificationAwsSecurityGroup = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryNetwork, Type: TypeFirewall, SubType: SubTypeSecurityGroup} - AssetClassificationAwsSubnet = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryNetwork, Type: TypeSubnet, SubType: SubTypeEC2Subnet} - AssetClassificationAwsTransitGateway = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryNetwork, Type: TypeVirtualNetwork, SubType: SubTypeTransitGateway} - AssetClassificationAwsTransitGatewayAttachment = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryNetwork, Type: TypeVirtualNetwork, SubType: SubTypeTransitGatewayAttachment} - AssetClassificationAwsVpcPeeringConnection = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryNetwork, Type: TypePeering, SubType: SubTypeVpcPeeringConnection} - AssetClassificationAwsVpc = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryNetwork, Type: TypeVirtualNetwork, SubType: SubTypeVpc} - AssetClassificationAwsRds = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryDatabase, Type: TypeRelationalDatabase, SubType: SubTypeRDS} - AssetClassificationAwsS3Bucket = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryStorage, Type: TypeObjectStorage, SubType: SubTypeS3} - AssetClassificationAwsSnsTopic = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryMessaging, Type: TypeNotificationService, SubType: SubTypeSNSTopic} + AssetClassificationAwsEc2Instance = AssetClassification{CategoryHost, "AWS EC2 Instance"} + AssetClassificationAwsElbV1 = AssetClassification{CategoryLoadBalancer, "AWS Elastic Load Balancer"} + AssetClassificationAwsElbV2 = AssetClassification{CategoryLoadBalancer, "AWS Elastic Load Balancer v2"} + AssetClassificationAwsIamPolicy = AssetClassification{CategoryAccessManagement, "AWS IAM Policy"} + AssetClassificationAwsIamRole = AssetClassification{CategoryServiceAccount, "AWS IAM Role"} + AssetClassificationAwsIamUser = AssetClassification{CategoryIdentity, "AWS IAM User"} + AssetClassificationAwsLambdaEventSourceMapping = AssetClassification{CategoryFaaS, "AWS Lambda Event Source Mapping"} + AssetClassificationAwsLambdaFunction = AssetClassification{CategoryFaaS, "AWS Lambda Function"} + AssetClassificationAwsLambdaLayer = AssetClassification{CategoryFaaS, "AWS Lambda Layer"} + AssetClassificationAwsInternetGateway = AssetClassification{CategoryGateway, "AWS Internet Gateway"} + AssetClassificationAwsNatGateway = AssetClassification{CategoryGateway, "AWS NAT Gateway"} + AssetClassificationAwsNetworkAcl = AssetClassification{CategoryNetworking, "AWS EC2 Network ACL"} + AssetClassificationAwsNetworkInterface = AssetClassification{CategoryNetworking, "AWS EC2 Network Interface"} + AssetClassificationAwsSecurityGroup = AssetClassification{CategoryFirewall, "AWS EC2 Security Group"} + AssetClassificationAwsSubnet = AssetClassification{CategoryNetworking, "AWS EC2 Subnet"} + AssetClassificationAwsTransitGateway = AssetClassification{CategoryGateway, "AWS Transit Gateway"} + AssetClassificationAwsTransitGatewayAttachment = AssetClassification{CategoryGateway, "AWS Transit Gateway Attachment"} + AssetClassificationAwsVpcPeeringConnection = AssetClassification{CategoryNetworking, "AWS VPC Peering Connection"} + AssetClassificationAwsVpc = AssetClassification{CategoryNetworking, "AWS VPC"} + AssetClassificationAwsRds = AssetClassification{CategoryDatabase, "AWS RDS Instance"} + AssetClassificationAwsS3Bucket = AssetClassification{CategoryStorageBucket, "AWS S3 Bucket"} + AssetClassificationAwsSnsTopic = AssetClassification{CategoryMessagingService, "AWS SNS Topic"} + // Azure - AssetClassificationAzureAppService = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryApplication, Type: TypeWebApplication, SubType: SubTypeAzureAppService} - AssetClassificationAzureContainerRegistry = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryContainer, Type: TypeRegistry, SubType: SubTypeAzureContainerRegistry} - AssetClassificationAzureCosmosDBAccount = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryDatabase, Type: TypeNoSQLDatabase, SubType: SubTypeAzureCosmosDBAccount} - AssetClassificationAzureCosmosDBSQLDatabase = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryDatabase, Type: TypeNoSQLDatabase, SubType: SubTypeAzureCosmosDBSQLDatabase} - AssetClassificationAzureDisk = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryStorage, Type: TypeDisk, SubType: SubTypeAzureDisk} - AssetClassificationAzureElasticPool = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryDatabase, Type: TypeScalability, SubType: SubTypeAzureElasticPool} - AssetClassificationAzureResourceGroup = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryManagement, Type: TypeResourceGroup, SubType: SubTypeAzureResourceGroup} - AssetClassificationAzureSQLDatabase = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryDatabase, Type: TypeRelationalDatabase, SubType: SubTypeAzureSQLDatabase} - AssetClassificationAzureSQLServer = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryDatabase, Type: TypeRelationalDatabase, SubType: SubTypeAzureSQLServer} - AssetClassificationAzureServicePrincipal = AssetClassification{Category: CategoryIdentity, SubCategory: SubCategoryDigitalIdentity, Type: TypePrincipal, SubType: SubTypeAzurePrincipal} - AssetClassificationAzureSnapshot = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryStorage, Type: TypeSnapshot, SubType: SubTypeAzureSnapshot} - AssetClassificationAzureStorageAccount = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryStorage, Type: TypeStorage, SubType: SubTypeAzureStorageAccount} - AssetClassificationAzureStorageBlobService = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryStorage, Type: TypeObjectStorage, SubType: SubTypeAzureStorageBlobService} - AssetClassificationAzureStorageQueue = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryApplicationIntegration, Type: TypeMessageQueue, SubType: SubTypeAzureStorageQueue} - AssetClassificationAzureStorageQueueService = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryApplicationIntegration, Type: TypeMessageQueue, SubType: SubTypeAzureStorageQueueService} - AssetClassificationAzureSubscription = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryManagement, Type: TypeCloudAccount, SubType: SubTypeAzureSubscription} - AssetClassificationAzureTenant = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryManagement, Type: TypeCloudAccount, SubType: SubTypeAzureTenant} - AssetClassificationAzureVirtualMachine = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryCompute, Type: TypeVirtualMachine, SubType: SubTypeAzureVirtualMachine} + AssetClassificationAzureAppService = AssetClassification{CategoryWebService, "Azure App Service"} + AssetClassificationAzureContainerRegistry = AssetClassification{CategoryContainerRegistry, "Azure Container Registry"} + AssetClassificationAzureCosmosDBAccount = AssetClassification{CategoryInfrastructure, "Azure Cosmos DB Account"} + AssetClassificationAzureCosmosDBSQLDatabase = AssetClassification{CategoryInfrastructure, "Azure Cosmos DB SQL Database"} + AssetClassificationAzureDisk = AssetClassification{CategoryVolume, "Azure Disk"} + AssetClassificationAzureElasticPool = AssetClassification{CategoryDatabase, "Azure Elastic Pool"} + AssetClassificationAzureResourceGroup = AssetClassification{CategoryAccessManagement, "Azure Resource Group"} + AssetClassificationAzureSQLDatabase = AssetClassification{CategoryDatabase, "Azure SQL Database"} + AssetClassificationAzureSQLServer = AssetClassification{CategoryDatabase, "Azure SQL Server"} + AssetClassificationAzureServicePrincipal = AssetClassification{CategoryIdentity, "Azure Principal"} + AssetClassificationAzureSnapshot = AssetClassification{CategorySnapshot, "Azure Snapshot"} + AssetClassificationAzureStorageAccount = AssetClassification{CategoryPrivateEndpoint, "Azure Storage Account"} + AssetClassificationAzureStorageBlobService = AssetClassification{CategoryStorageBucket, "Azure Storage Blob Service"} + AssetClassificationAzureStorageQueue = AssetClassification{CategoryMessagingService, "Azure Storage Queue"} + AssetClassificationAzureStorageQueueService = AssetClassification{CategoryMessagingService, "Azure Storage Queue Service"} + AssetClassificationAzureSubscription = AssetClassification{CategoryAccessManagement, "Azure Subscription"} + AssetClassificationAzureTenant = AssetClassification{CategoryAccessManagement, "Azure Tenant"} + AssetClassificationAzureVirtualMachine = AssetClassification{CategoryHost, "Azure Virtual Machine"} // GCP - AssetClassificationGcpProject = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryManagement, Type: TypeCloudAccount, SubType: SubTypeGcpProject} - AssetClassificationGcpOrganization = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryManagement, Type: TypeCloudAccount, SubType: SubTypeGcpOrganization} - AssetClassificationGcpFolder = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryManagement, Type: TypeResourceHierarchy, SubType: SubTypeGcpFolder} - AssetClassificationGcpInstance = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryCompute, Type: TypeVirtualMachine, SubType: SubTypeGcpInstance} - AssetClassificationGcpBucket = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryStorage, Type: TypeObjectStorage, SubType: SubTypeGcpBucket} - AssetClassificationGcpFirewall = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryNetwork, Type: TypeFirewall, SubType: SubTypeGcpFirewall} - AssetClassificationGcpSubnet = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryNetwork, Type: TypeSubnet, SubType: SubTypeGcpSubnet} - AssetClassificationGcpServiceAccount = AssetClassification{Category: CategoryIdentity, SubCategory: SubCategoryServiceIdentity, Type: TypeServiceAccount, SubType: SubTypeGcpServiceAccount} - AssetClassificationGcpServiceAccountKey = AssetClassification{Category: CategoryIdentity, SubCategory: SubCategoryServiceIdentity, Type: TypeServiceAccountKey, SubType: SubTypeGcpServiceAccountKey} - - AssetClassificationGcpGkeCluster = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryContainer, Type: TypeOrchestration, SubType: SubTypeGcpGkeCluster} - AssetClassificationGcpForwardingRule = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryNetwork, Type: TypeLoadBalancing, SubType: SubTypeGcpForwardingRule} - AssetClassificationGcpIamRole = AssetClassification{Category: CategoryIdentity, SubCategory: SubCategoryAccessManagement, Type: TypeIamRole, SubType: SubTypeGcpIamRole} - AssetClassificationGcpCloudFunction = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryServerless, Type: TypeFunction, SubType: SubTypeGcpCloudFunction} - AssetClassificationGcpCloudRunService = AssetClassification{Category: CategoryInfrastructure, SubCategory: SubCategoryContainer, Type: TypeServerless, SubType: SubTypeGcpCloudRunService} + AssetClassificationGcpProject = AssetClassification{CategoryAccount, "GCP Project"} + AssetClassificationGcpOrganization = AssetClassification{CategoryOrganization, "GCP Organization"} + AssetClassificationGcpFolder = AssetClassification{CategoryOrganization, "GCP Folder"} + AssetClassificationGcpInstance = AssetClassification{CategoryHost, "GCP Compute Instance"} + AssetClassificationGcpBucket = AssetClassification{CategoryStorageBucket, "GCP Bucket"} + AssetClassificationGcpFirewall = AssetClassification{CategoryFirewall, "GCP Firewall"} + AssetClassificationGcpSubnet = AssetClassification{CategorySubnet, "GCP Subnet"} + AssetClassificationGcpServiceAccount = AssetClassification{CategoryAccessManagement, "GCP Service Account"} + AssetClassificationGcpServiceAccountKey = AssetClassification{CategoryAccessManagement, "GCP Service Account Key"} + AssetClassificationGcpGkeCluster = AssetClassification{CategoryOrchestrator, "GCP Kubernetes Engine (GKE) Cluster"} + AssetClassificationGcpForwardingRule = AssetClassification{CategoryLoadBalancer, "GCP Load Balancing Forwarding Rule"} + AssetClassificationGcpIamRole = AssetClassification{CategoryServiceUsageTechnology, "GCP IAM Role"} + AssetClassificationGcpCloudFunction = AssetClassification{CategoryFaaS, "GCP Cloud Function"} + AssetClassificationGcpCloudRunService = AssetClassification{CategoryContainerService, "GCP Cloud Run Service"} ) // AssetEvent holds the whole asset type AssetEvent struct { - Asset Asset - Network *AssetNetwork - Cloud *AssetCloud - Host *AssetHost - IAM *AssetIAM - ResourcePolicies []AssetResourcePolicy + Entity Entity + Event Event + Network *Network + Cloud *Cloud + Host *Host + User *User + Labels map[string]string + RawAttributes *any } -// Asset contains the identifiers of the asset -type Asset struct { - Id []string `json:"id"` - RelatedEntityId []string `json:"related_entity_id"` - Name string `json:"name"` +// Entity contains the identifiers of the asset +type Entity struct { + Id string `json:"id"` + Name string `json:"name"` AssetClassification - Tags map[string]string `json:"tags"` - Raw any `json:"raw"` -} - -// AssetNetwork contains network information -type AssetNetwork struct { - Ipv6Address *string `json:"ipv6_address,omitempty"` - NetworkId *string `json:"network_id,omitempty"` - NetworkInterfaceIds []string `json:"network_interface_ids,omitempty"` - PrivateDnsName *string `json:"private_dns_name,omitempty"` - PrivateIpAddress *string `json:"private_ip_address,omitempty"` - PublicDnsName *string `json:"public_dns_name,omitempty"` - PublicIpAddress *string `json:"public_ip_address,omitempty"` - RouteTableIds []string `json:"route_table_ids,omitempty"` - SecurityGroupIds []string `json:"security_group_ids,omitempty"` - SubnetIds []string `json:"subnet_ids,omitempty"` - TransitGatewayIds []string `json:"transit_gateway_ids,omitempty"` - VpcIds []string `json:"vpc_ids,omitempty"` -} -// AssetCloud contains information about the cloud provider -type AssetCloud struct { - AvailabilityZone *string `json:"availability_zone,omitempty"` - Provider string `json:"provider,omitempty"` - Region string `json:"region,omitempty"` - Account AssetCloudAccount `json:"account"` - Organization AssetCloudOrganization `json:"organization,omitempty"` - Instance *AssetCloudInstance `json:"instance,omitempty"` - Machine *AssetCloudMachine `json:"machine,omitempty"` - Project *AssetCloudProject `json:"project,omitempty"` - Service *AssetCloudService `json:"service,omitempty"` + // non exported fields + relatedEntityId []string } -type AssetCloudAccount struct { - Id string `json:"id,omitempty"` - Name string `json:"name,omitempty"` +type Event struct { + Kind string `json:"kind"` } -type AssetCloudOrganization struct { - Id string `json:"id,omitempty"` +type Network struct { Name string `json:"name,omitempty"` } -type AssetCloudInstance struct { - Id string `json:"id,omitempty"` - Name string `json:"name,omitempty"` +type Cloud struct { + Provider string `json:"provider,omitempty"` + Region string `json:"region,omitempty"` + AvailabilityZone string `json:"availability_zone,omitempty"` + AccountID string `json:"account.id,omitempty"` + AccountName string `json:"account.name,omitempty"` + InstanceID string `json:"instance.id,omitempty"` + InstanceName string `json:"instance.name,omitempty"` + MachineType string `json:"machine.type,omitempty"` + ServiceName string `json:"service.name,omitempty"` + ProjectID string `json:"project.id,omitempty"` + ProjectName string `json:"project.name,omitempty"` } -type AssetCloudMachine struct { - MachineType string `json:"machine_type,omitempty"` +type Host struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Architecture string `json:"architecture,omitempty"` + Type string `json:"type,omitempty"` + IP string `json:"ip,omitempty"` + MacAddress []string `json:"mac,omitempty"` } -type AssetCloudProject struct { - Id string `json:"id,omitempty"` +type User struct { + ID string `json:"id,omitempty"` Name string `json:"name,omitempty"` } -type AssetCloudService struct { - Name string `json:"name,omitempty"` -} - -// AssetHost contains information of the asset in case it is a host -type AssetHost struct { - Architecture string `json:"architecture"` - ImageId *string `json:"imageId"` - InstanceType string `json:"instance_type"` - Platform string `json:"platform"` - PlatformDetails *string `json:"platform_details"` -} - -type AssetIAM struct { - Id *string `json:"id"` - Arn *string `json:"arn"` -} - -// AssetResourcePolicy maps security policies applied directly on resources -type AssetResourcePolicy struct { - Version *string `json:"version,omitempty"` - Id *string `json:"id,omitempty"` - Effect string `json:"effect,omitempty"` - Principal map[string]any `json:"principal,omitempty"` - Action []string `json:"action,omitempty"` - NotAction []string `json:"notAction,omitempty"` - Resource []string `json:"resource,omitempty"` - NoResource []string `json:"noResource,omitempty"` - Condition map[string]any `json:"condition,omitempty"` -} - // AssetEnricher functional builder function type AssetEnricher func(asset *AssetEvent) -func NewAssetEvent(c AssetClassification, ids []string, name string, enrichers ...AssetEnricher) AssetEvent { +func NewAssetEvent(c AssetClassification, id string, name string, enrichers ...AssetEnricher) AssetEvent { a := AssetEvent{ - Asset: Asset{ - Id: removeEmpty(ids), + Entity: Entity{ + Id: id, Name: name, AssetClassification: c, }, + Event: Event{ + Kind: "asset", + }, } for _, enrich := range enrichers { @@ -351,57 +210,56 @@ func NewAssetEvent(c AssetClassification, ids []string, name string, enrichers . func WithRawAsset(raw any) AssetEnricher { return func(a *AssetEvent) { - a.Asset.Raw = &raw + a.RawAttributes = &raw } } func WithRelatedAssetIds(ids []string) AssetEnricher { return func(a *AssetEvent) { - a.Asset.RelatedEntityId = ids + ids = lo.Filter(ids, func(id string, _ int) bool { + return id != "" + }) + + if len(ids) == 0 { + a.Entity.relatedEntityId = nil + return + } + + a.Entity.relatedEntityId = lo.Uniq(ids) } } -func WithTags(tags map[string]string) AssetEnricher { +func WithLabels(labels map[string]string) AssetEnricher { return func(a *AssetEvent) { - if len(tags) == 0 { + if len(labels) == 0 { return } - a.Asset.Tags = tags + a.Labels = labels } } -func WithNetwork(network AssetNetwork) AssetEnricher { +func WithNetwork(network Network) AssetEnricher { return func(a *AssetEvent) { a.Network = &network } } -func WithCloud(cloud AssetCloud) AssetEnricher { +func WithCloud(cloud Cloud) AssetEnricher { return func(a *AssetEvent) { a.Cloud = &cloud } } -func WithHost(host AssetHost) AssetEnricher { +func WithHost(host Host) AssetEnricher { return func(a *AssetEvent) { a.Host = &host } } -func WithIAM(iam AssetIAM) AssetEnricher { +func WithUser(user User) AssetEnricher { return func(a *AssetEvent) { - a.IAM = &iam - } -} - -func WithResourcePolicies(policies ...AssetResourcePolicy) AssetEnricher { - return func(a *AssetEvent) { - if len(policies) == 0 { - return - } - - a.ResourcePolicies = policies + a.User = &user } } diff --git a/internal/inventory/awsfetcher/fetcher_ec2_instance.go b/internal/inventory/awsfetcher/fetcher_ec2_instance.go index 26c413c30f..a2b22adc11 100644 --- a/internal/inventory/awsfetcher/fetcher_ec2_instance.go +++ b/internal/inventory/awsfetcher/fetcher_ec2_instance.go @@ -58,66 +58,46 @@ func (e *ec2InstanceFetcher) Fetch(ctx context.Context, assetChannel chan<- inve return } - for _, instance := range instances { - if instance == nil { + for _, i := range instances { + if i == nil { continue } iamFetcher := inventory.EmptyEnricher() - if instance.IamInstanceProfile != nil { - iamFetcher = inventory.WithIAM(inventory.AssetIAM{ - Id: instance.IamInstanceProfile.Id, - Arn: instance.IamInstanceProfile.Arn, + if i.IamInstanceProfile != nil { + iamFetcher = inventory.WithUser(inventory.User{ + ID: pointers.Deref(i.IamInstanceProfile.Arn), }) } - subnetIds := []string{} - if id := pointers.Deref(instance.SubnetId); id != "" { - subnetIds = append(subnetIds, id) - } assetChannel <- inventory.NewAssetEvent( inventory.AssetClassificationAwsEc2Instance, - []string{instance.GetResourceArn(), pointers.Deref(instance.InstanceId)}, - instance.GetResourceName(), + i.GetResourceArn(), + pointers.Deref(i.PrivateDnsName), - inventory.WithRawAsset(instance), - inventory.WithTags(e.getTags(instance)), - inventory.WithCloud(inventory.AssetCloud{ + inventory.WithRelatedAssetIds([]string{pointers.Deref(i.InstanceId)}), + inventory.WithRawAsset(i), + inventory.WithLabels(e.getTags(i)), + inventory.WithCloud(inventory.Cloud{ Provider: inventory.AwsCloudProvider, - Region: instance.Region, - AvailabilityZone: e.getAvailabilityZone(instance), - Account: inventory.AssetCloudAccount{ - Id: e.AccountId, - Name: e.AccountName, - }, - Instance: &inventory.AssetCloudInstance{ - Id: pointers.Deref(instance.InstanceId), - Name: instance.GetResourceName(), - }, - Machine: &inventory.AssetCloudMachine{ - MachineType: string(instance.InstanceType), - }, - Service: &inventory.AssetCloudService{ - Name: "AWS EC2", - }, + Region: i.Region, + AvailabilityZone: e.getAvailabilityZone(i), + AccountID: e.AccountId, + AccountName: e.AccountName, + InstanceID: pointers.Deref(i.InstanceId), + InstanceName: i.GetResourceName(), + MachineType: string(i.InstanceType), + ServiceName: "AWS EC2", }), - inventory.WithHost(inventory.AssetHost{ - Architecture: string(instance.Architecture), - ImageId: instance.ImageId, - InstanceType: string(instance.InstanceType), - Platform: string(instance.Platform), - PlatformDetails: instance.PlatformDetails, + inventory.WithHost(inventory.Host{ + ID: pointers.Deref(i.InstanceId), + Name: pointers.Deref(i.PrivateDnsName), + Architecture: string(i.Architecture), + Type: string(i.InstanceType), + IP: pointers.Deref(i.PublicIpAddress), + MacAddress: i.GetResourceMacAddresses(), }), iamFetcher, - inventory.WithNetwork(inventory.AssetNetwork{ - NetworkId: instance.VpcId, - SubnetIds: subnetIds, - Ipv6Address: instance.Ipv6Address, - PublicIpAddress: instance.PublicIpAddress, - PrivateIpAddress: instance.PrivateIpAddress, - PublicDnsName: instance.PublicDnsName, - PrivateDnsName: instance.PrivateDnsName, - }), ) } } @@ -134,10 +114,10 @@ func (e *ec2InstanceFetcher) getTags(instance *ec2.Ec2Instance) map[string]strin return tags } -func (e *ec2InstanceFetcher) getAvailabilityZone(instance *ec2.Ec2Instance) *string { +func (e *ec2InstanceFetcher) getAvailabilityZone(instance *ec2.Ec2Instance) string { if instance.Placement == nil { - return nil + return "" } - return instance.Placement.AvailabilityZone + return pointers.Deref(instance.Placement.AvailabilityZone) } diff --git a/internal/inventory/awsfetcher/fetcher_ec2_instance_test.go b/internal/inventory/awsfetcher/fetcher_ec2_instance_test.go index 830203e94d..ca0e62c0e8 100644 --- a/internal/inventory/awsfetcher/fetcher_ec2_instance_test.go +++ b/internal/inventory/awsfetcher/fetcher_ec2_instance_test.go @@ -64,6 +64,14 @@ func TestEC2InstanceFetcher_Fetch(t *testing.T) { Placement: &types.Placement{ AvailabilityZone: pointers.Ref("1a"), }, + NetworkInterfaces: []types.InstanceNetworkInterface{ + { + MacAddress: pointers.Ref("mac1"), + }, + { + MacAddress: pointers.Ref("mac2"), + }, + }, }, Region: "us-east", } @@ -78,77 +86,55 @@ func TestEC2InstanceFetcher_Fetch(t *testing.T) { expected := []inventory.AssetEvent{ inventory.NewAssetEvent( inventory.AssetClassificationAwsEc2Instance, - []string{"arn:aws:ec2:us-east::ec2/234567890", "234567890"}, - "test-server", + "arn:aws:ec2:us-east::ec2/234567890", + "private-dns", + inventory.WithRelatedAssetIds([]string{"234567890"}), inventory.WithRawAsset(instance1), - inventory.WithTags(map[string]string{"Name": "test-server", "key": "value"}), - inventory.WithCloud(inventory.AssetCloud{ + inventory.WithLabels(map[string]string{"Name": "test-server", "key": "value"}), + inventory.WithCloud(inventory.Cloud{ Provider: inventory.AwsCloudProvider, Region: "us-east", - AvailabilityZone: pointers.Ref("1a"), - Account: inventory.AssetCloudAccount{ - Id: "123", - Name: "alias", - }, - Instance: &inventory.AssetCloudInstance{ - Id: "234567890", - Name: "test-server", - }, - Machine: &inventory.AssetCloudMachine{ - MachineType: "instance-type", - }, - Service: &inventory.AssetCloudService{ - Name: "AWS EC2", - }, + AvailabilityZone: "1a", + AccountID: "123", + AccountName: "alias", + InstanceID: "234567890", + InstanceName: "test-server", + MachineType: "instance-type", + ServiceName: "AWS EC2", }), - inventory.WithHost(inventory.AssetHost{ - Architecture: string(types.ArchitectureValuesX8664), - ImageId: pointers.Ref("image-id"), - InstanceType: "instance-type", - Platform: "linux", - PlatformDetails: pointers.Ref("ubuntu"), + inventory.WithHost(inventory.Host{ + ID: "234567890", + Name: "private-dns", + Architecture: string(types.ArchitectureValuesX8664), + Type: "instance-type", + IP: "public-ip-addr", + MacAddress: []string{"mac1", "mac2"}, }), - inventory.WithIAM(inventory.AssetIAM{ - Id: pointers.Ref("a123123"), - Arn: pointers.Ref("123123:123123:123123"), - }), - inventory.WithNetwork(inventory.AssetNetwork{ - NetworkId: pointers.Ref("vpc-id"), - SubnetIds: []string{"subnetId"}, - Ipv6Address: pointers.Ref("ipv6"), - PublicIpAddress: pointers.Ref("public-ip-addr"), - PrivateIpAddress: pointers.Ref("private-ip-addre"), - PublicDnsName: pointers.Ref("public-dns"), - PrivateDnsName: pointers.Ref("private-dns"), + inventory.WithUser(inventory.User{ + ID: "123123:123123:123123", }), ), inventory.NewAssetEvent( inventory.AssetClassificationAwsEc2Instance, - []string{}, + "", "", inventory.WithRawAsset(instance2), - inventory.WithTags(map[string]string{}), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AwsCloudProvider, - Region: "us-east", - Account: inventory.AssetCloudAccount{ - Id: "123", - Name: "alias", - }, - Instance: &inventory.AssetCloudInstance{ - Id: "", - Name: "", - }, - Machine: &inventory.AssetCloudMachine{ - MachineType: "", - }, - Service: &inventory.AssetCloudService{ - Name: "AWS EC2", - }, + inventory.WithLabels(map[string]string{}), + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + Region: "us-east", + AvailabilityZone: "", + AccountID: "123", + AccountName: "alias", + InstanceID: "", + InstanceName: "", + MachineType: "", + ServiceName: "AWS EC2", + }), + inventory.WithHost(inventory.Host{ + MacAddress: []string{}, }), - inventory.WithHost(inventory.AssetHost{}), - inventory.WithNetwork(inventory.AssetNetwork{SubnetIds: []string{}}), ), } diff --git a/internal/inventory/awsfetcher/fetcher_elb.go b/internal/inventory/awsfetcher/fetcher_elb.go index dc55532c58..10be3adf8f 100644 --- a/internal/inventory/awsfetcher/fetcher_elb.go +++ b/internal/inventory/awsfetcher/fetcher_elb.go @@ -81,19 +81,15 @@ func (f *elbFetcher) fetch(ctx context.Context, resourceName string, function el for _, item := range awsResources { assetChannel <- inventory.NewAssetEvent( classification, - []string{item.GetResourceArn()}, + item.GetResourceArn(), item.GetResourceName(), inventory.WithRawAsset(item), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AwsCloudProvider, - Region: item.GetRegion(), - Account: inventory.AssetCloudAccount{ - Id: f.AccountId, - Name: f.AccountName, - }, - Service: &inventory.AssetCloudService{ - Name: "AWS Networking", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + Region: item.GetRegion(), + AccountID: f.AccountId, + AccountName: f.AccountName, + ServiceName: "AWS Networking", }), ) } diff --git a/internal/inventory/awsfetcher/fetcher_elb_test.go b/internal/inventory/awsfetcher/fetcher_elb_test.go index a7c9c9702a..e97019e947 100644 --- a/internal/inventory/awsfetcher/fetcher_elb_test.go +++ b/internal/inventory/awsfetcher/fetcher_elb_test.go @@ -69,18 +69,14 @@ func TestELBv1Fetcher_Fetch(t *testing.T) { expected := []inventory.AssetEvent{ inventory.NewAssetEvent( inventory.AssetClassificationAwsElbV1, - []string{"arn:aws:elasticloadbalancing:::loadbalancer/my-elb-v1"}, + "arn:aws:elasticloadbalancing:::loadbalancer/my-elb-v1", "my-elb-v1", inventory.WithRawAsset(asset), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AwsCloudProvider, - Account: inventory.AssetCloudAccount{ - Id: "123", - Name: "alias", - }, - Service: &inventory.AssetCloudService{ - Name: "AWS Networking", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + AccountID: "123", + AccountName: "alias", + ServiceName: "AWS Networking", }), ), } @@ -119,18 +115,14 @@ func TestELBv2Fetcher_Fetch(t *testing.T) { expected := []inventory.AssetEvent{ inventory.NewAssetEvent( inventory.AssetClassificationAwsElbV2, - []string{"arn:aws:elasticloadbalancing:::loadbalancer/my-elb-v2"}, + "arn:aws:elasticloadbalancing:::loadbalancer/my-elb-v2", "my-elb-v2", inventory.WithRawAsset(asset), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AwsCloudProvider, - Account: inventory.AssetCloudAccount{ - Id: "123", - Name: "alias", - }, - Service: &inventory.AssetCloudService{ - Name: "AWS Networking", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + AccountID: "123", + AccountName: "alias", + ServiceName: "AWS Networking", }), ), } diff --git a/internal/inventory/awsfetcher/fetcher_iam_policy.go b/internal/inventory/awsfetcher/fetcher_iam_policy.go index da1a28a045..a54029cb29 100644 --- a/internal/inventory/awsfetcher/fetcher_iam_policy.go +++ b/internal/inventory/awsfetcher/fetcher_iam_policy.go @@ -74,22 +74,18 @@ func (i *iamPolicyFetcher) Fetch(ctx context.Context, assetChannel chan<- invent assetChannel <- inventory.NewAssetEvent( inventory.AssetClassificationAwsIamPolicy, - []string{policy.GetResourceArn(), pointers.Deref(policy.PolicyId)}, + policy.GetResourceArn(), resource.GetResourceName(), + inventory.WithRelatedAssetIds([]string{pointers.Deref(policy.PolicyId)}), inventory.WithRawAsset(policy), - inventory.WithResourcePolicies(convertPolicy(policy.Document)...), - inventory.WithTags(i.getTags(policy)), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AwsCloudProvider, - Region: awslib.GlobalRegion, - Account: inventory.AssetCloudAccount{ - Id: i.AccountId, - Name: i.AccountName, - }, - Service: &inventory.AssetCloudService{ - Name: "AWS IAM", - }, + inventory.WithLabels(i.getTags(policy)), + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + Region: awslib.GlobalRegion, + AccountID: i.AccountId, + AccountName: i.AccountName, + ServiceName: "AWS IAM", }), ) } @@ -104,95 +100,3 @@ func (i *iamPolicyFetcher) getTags(policy iam.Policy) map[string]string { return tags } - -func convertPolicy(policy map[string]any) []inventory.AssetResourcePolicy { - if len(policy) == 0 { - return nil - } - - version, hasVersion := policy["Version"].(string) - if !hasVersion { - version = "" - } - - switch statements := policy["Statement"].(type) { - case []map[string]any: - return convertStatements(statements, version) - case []any: - return convertAnyStatements(statements, version) - case map[string]any: - return []inventory.AssetResourcePolicy{convertStatement(statements, &version)} - } - return nil -} - -func convertAnyStatements(statements []any, version string) []inventory.AssetResourcePolicy { - policies := make([]inventory.AssetResourcePolicy, 0, len(statements)) - for _, statement := range statements { - policies = append(policies, convertStatement(statement.(map[string]any), &version)) - } - return policies -} - -func convertStatements(statements []map[string]any, version string) []inventory.AssetResourcePolicy { - policies := make([]inventory.AssetResourcePolicy, 0, len(statements)) - for _, statement := range statements { - policies = append(policies, convertStatement(statement, &version)) - } - return policies -} - -func convertStatement(statement map[string]any, version *string) inventory.AssetResourcePolicy { - p := inventory.AssetResourcePolicy{} - p.Version = version - - if sid, ok := statement["Sid"]; ok { - p.Id = pointers.Ref(sid.(string)) - } - - if effect, ok := statement["Effect"]; ok { - p.Effect = effect.(string) - } - - if anyPrincipal, ok := statement["Principal"]; ok { - switch principal := anyPrincipal.(type) { - case string: - p.Principal = map[string]any{principal: principal} - case map[string]any: - p.Principal = principal - } - } - - if action, ok := statement["Action"]; ok { - p.Action = anyToSliceString(action) - } - - if notAction, ok := statement["NotAction"]; ok { - p.NotAction = anyToSliceString(notAction) - } - - if resource, ok := statement["Resource"]; ok { - p.Resource = anyToSliceString(resource) - } - - if noResource, ok := statement["NoResource"]; ok { - p.NoResource = anyToSliceString(noResource) - } - - if condition, ok := statement["Condition"]; ok { - p.Condition = condition.(map[string]any) - } - - return p -} - -func anyToSliceString(anyString any) []string { - switch s := anyString.(type) { - case string: - return []string{s} - case []string: - return s - } - - return nil -} diff --git a/internal/inventory/awsfetcher/fetcher_iam_policy_test.go b/internal/inventory/awsfetcher/fetcher_iam_policy_test.go index b055c31a43..2157566f76 100644 --- a/internal/inventory/awsfetcher/fetcher_iam_policy_test.go +++ b/internal/inventory/awsfetcher/fetcher_iam_policy_test.go @@ -102,62 +102,42 @@ func TestIAMPolicyFetcher_Fetch(t *testing.T) { in := []awslib.AwsResource{policy1, nil, policy2, policy3} - cloudField := inventory.AssetCloud{ - Provider: inventory.AwsCloudProvider, - Region: "global", - Account: inventory.AssetCloudAccount{ - Id: "123", - Name: "alias", - }, - Service: &inventory.AssetCloudService{ - Name: "AWS IAM", - }, + cloudField := inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + Region: "global", + AccountID: "123", + AccountName: "alias", + ServiceName: "AWS IAM", } expected := []inventory.AssetEvent{ inventory.NewAssetEvent( inventory.AssetClassificationAwsIamPolicy, - []string{"arn:aws:iam::0000:policy/policy-1", "178263"}, + "arn:aws:iam::0000:policy/policy-1", "policy-1", + inventory.WithRelatedAssetIds([]string{"178263"}), inventory.WithRawAsset(policy1), inventory.WithCloud(cloudField), - inventory.WithTags(map[string]string{ + inventory.WithLabels(map[string]string{ "key-1": "value-1", "key-2": "value-2", }), - inventory.WithResourcePolicies(inventory.AssetResourcePolicy{ - Version: pointers.Ref("2012-10-17"), - Effect: "Allow", - Action: []string{"read", "update", "delete"}, - Resource: []string{"s3/bucket", "s3/bucket/*"}, - }, inventory.AssetResourcePolicy{ - Version: pointers.Ref("2012-10-17"), - Effect: "Deny", - Action: []string{"delete"}, - Resource: []string{"s3/bucket"}, - }), ), inventory.NewAssetEvent( inventory.AssetClassificationAwsIamPolicy, - []string{"arn:aws:iam::0000:policy/policy-2"}, + "arn:aws:iam::0000:policy/policy-2", "policy-2", inventory.WithRawAsset(policy2), inventory.WithCloud(cloudField), - inventory.WithTags(map[string]string{ + inventory.WithLabels(map[string]string{ "key-1": "value-1", }), - inventory.WithResourcePolicies(inventory.AssetResourcePolicy{ - Version: pointers.Ref("2012-10-17"), - Effect: "Allow", - Action: []string{"read"}, - Resource: []string{"*"}, - }), ), inventory.NewAssetEvent( inventory.AssetClassificationAwsIamPolicy, - []string{"arn:aws:iam::0000:policy/policy-3"}, + "arn:aws:iam::0000:policy/policy-3", "policy-3", inventory.WithRawAsset(policy3), inventory.WithCloud(cloudField), diff --git a/internal/inventory/awsfetcher/fetcher_iam_role.go b/internal/inventory/awsfetcher/fetcher_iam_role.go index fbd700cfa9..32e9c09716 100644 --- a/internal/inventory/awsfetcher/fetcher_iam_role.go +++ b/internal/inventory/awsfetcher/fetcher_iam_role.go @@ -68,20 +68,21 @@ func (i *iamRoleFetcher) Fetch(ctx context.Context, assetChannel chan<- inventor assetChannel <- inventory.NewAssetEvent( inventory.AssetClassificationAwsIamRole, - []string{pointers.Deref(role.Arn), pointers.Deref(role.RoleId)}, + pointers.Deref(role.Arn), pointers.Deref(role.RoleName), + inventory.WithRelatedAssetIds([]string{pointers.Deref(role.RoleId)}), inventory.WithRawAsset(*role), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AwsCloudProvider, - Region: awslib.GlobalRegion, - Account: inventory.AssetCloudAccount{ - Id: i.AccountId, - Name: i.AccountName, - }, - Service: &inventory.AssetCloudService{ - Name: "AWS IAM", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + Region: awslib.GlobalRegion, + AccountID: i.AccountId, + AccountName: i.AccountName, + ServiceName: "AWS IAM", + }), + inventory.WithUser(inventory.User{ + ID: pointers.Deref(role.Arn), + Name: pointers.Deref(role.RoleName), }), ) } diff --git a/internal/inventory/awsfetcher/fetcher_iam_role_test.go b/internal/inventory/awsfetcher/fetcher_iam_role_test.go index 6d2fc5d364..2e26be1ece 100644 --- a/internal/inventory/awsfetcher/fetcher_iam_role_test.go +++ b/internal/inventory/awsfetcher/fetcher_iam_role_test.go @@ -63,7 +63,7 @@ func TestIAMRoleFetcher_Fetch(t *testing.T) { AssumeRolePolicyDocument: pointers.Ref("document"), Description: pointers.Ref("EKS managed node group IAM role"), Path: pointers.Ref("/"), - RoleId: pointers.Ref("17823618723"), + RoleId: pointers.Ref("17823618724"), }, } @@ -72,37 +72,39 @@ func TestIAMRoleFetcher_Fetch(t *testing.T) { expected := []inventory.AssetEvent{ inventory.NewAssetEvent( inventory.AssetClassificationAwsIamRole, - []string{"arn:aws:iam::0000:role/role-name-1", "17823618723"}, + "arn:aws:iam::0000:role/role-name-1", "role-name-1", + inventory.WithRelatedAssetIds([]string{"17823618723"}), inventory.WithRawAsset(role1), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AwsCloudProvider, - Region: "global", - Account: inventory.AssetCloudAccount{ - Id: "123", - Name: "alias", - }, - Service: &inventory.AssetCloudService{ - Name: "AWS IAM", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + Region: "global", + AccountID: "123", + AccountName: "alias", + ServiceName: "AWS IAM", + }), + inventory.WithUser(inventory.User{ + ID: "arn:aws:iam::0000:role/role-name-1", + Name: "role-name-1", }), ), inventory.NewAssetEvent( inventory.AssetClassificationAwsIamRole, - []string{"arn:aws:iam::0000:role/role-name-2", "17823618723"}, + "arn:aws:iam::0000:role/role-name-2", "role-name-2", + inventory.WithRelatedAssetIds([]string{"17823618724"}), inventory.WithRawAsset(role2), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AwsCloudProvider, - Region: "global", - Account: inventory.AssetCloudAccount{ - Id: "123", - Name: "alias", - }, - Service: &inventory.AssetCloudService{ - Name: "AWS IAM", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + Region: "global", + AccountID: "123", + AccountName: "alias", + ServiceName: "AWS IAM", + }), + inventory.WithUser(inventory.User{ + ID: "arn:aws:iam::0000:role/role-name-2", + Name: "role-name-2", }), ), } diff --git a/internal/inventory/awsfetcher/fetcher_iam_user.go b/internal/inventory/awsfetcher/fetcher_iam_user.go index ff992a8a74..75dfc6081c 100644 --- a/internal/inventory/awsfetcher/fetcher_iam_user.go +++ b/internal/inventory/awsfetcher/fetcher_iam_user.go @@ -73,20 +73,21 @@ func (i *iamUserFetcher) Fetch(ctx context.Context, assetChannel chan<- inventor assetChannel <- inventory.NewAssetEvent( inventory.AssetClassificationAwsIamUser, - []string{user.GetResourceArn(), user.UserId}, + user.GetResourceArn(), user.GetResourceName(), + inventory.WithRelatedAssetIds([]string{user.UserId}), inventory.WithRawAsset(user), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AwsCloudProvider, - Region: user.GetRegion(), - Account: inventory.AssetCloudAccount{ - Id: i.AccountId, - Name: i.AccountName, - }, - Service: &inventory.AssetCloudService{ - Name: "AWS IAM", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + Region: user.GetRegion(), + AccountID: i.AccountId, + AccountName: i.AccountName, + ServiceName: "AWS IAM", + }), + inventory.WithUser(inventory.User{ + ID: user.GetResourceArn(), + Name: user.GetResourceName(), }), ) } diff --git a/internal/inventory/awsfetcher/fetcher_iam_user_test.go b/internal/inventory/awsfetcher/fetcher_iam_user_test.go index c08c3014a2..c7f1246d78 100644 --- a/internal/inventory/awsfetcher/fetcher_iam_user_test.go +++ b/internal/inventory/awsfetcher/fetcher_iam_user_test.go @@ -87,37 +87,38 @@ func TestIAMUserFetcher_Fetch(t *testing.T) { expected := []inventory.AssetEvent{ inventory.NewAssetEvent( inventory.AssetClassificationAwsIamUser, - []string{"arn:aws:iam::000:user/user-1", "u-123123"}, + "arn:aws:iam::000:user/user-1", "user-1", + inventory.WithRelatedAssetIds([]string{"u-123123"}), inventory.WithRawAsset(user1), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AwsCloudProvider, - Region: "global", - Account: inventory.AssetCloudAccount{ - Id: "123", - Name: "alias", - }, - Service: &inventory.AssetCloudService{ - Name: "AWS IAM", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + Region: "global", + AccountID: "123", + AccountName: "alias", + ServiceName: "AWS IAM", + }), + inventory.WithUser(inventory.User{ + ID: "arn:aws:iam::000:user/user-1", + Name: "user-1", }), ), inventory.NewAssetEvent( inventory.AssetClassificationAwsIamUser, - []string{"arn:aws:iam::000:user/user-2"}, + "arn:aws:iam::000:user/user-2", "user-2", inventory.WithRawAsset(user2), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AwsCloudProvider, - Region: "global", - Account: inventory.AssetCloudAccount{ - Id: "123", - Name: "alias", - }, - Service: &inventory.AssetCloudService{ - Name: "AWS IAM", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + Region: "global", + AccountID: "123", + AccountName: "alias", + ServiceName: "AWS IAM", + }), + inventory.WithUser(inventory.User{ + ID: "arn:aws:iam::000:user/user-2", + Name: "user-2", }), ), } diff --git a/internal/inventory/awsfetcher/fetcher_lambda.go b/internal/inventory/awsfetcher/fetcher_lambda.go index 3c195078a8..25503b5804 100644 --- a/internal/inventory/awsfetcher/fetcher_lambda.go +++ b/internal/inventory/awsfetcher/fetcher_lambda.go @@ -85,19 +85,15 @@ func (s *lambdaFetcher) fetch(ctx context.Context, resourceName string, function } assetChannel <- inventory.NewAssetEvent( classification, - []string{id}, + id, item.GetResourceName(), inventory.WithRawAsset(item), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AwsCloudProvider, - Region: item.GetRegion(), - Account: inventory.AssetCloudAccount{ - Id: s.AccountId, - Name: s.AccountName, - }, - Service: &inventory.AssetCloudService{ - Name: "AWS Lambda", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + Region: item.GetRegion(), + AccountID: s.AccountId, + AccountName: s.AccountName, + ServiceName: "AWS Lambda", }), ) } diff --git a/internal/inventory/awsfetcher/fetcher_lambda_test.go b/internal/inventory/awsfetcher/fetcher_lambda_test.go index f0b4806ee2..f307d15c57 100644 --- a/internal/inventory/awsfetcher/fetcher_lambda_test.go +++ b/internal/inventory/awsfetcher/fetcher_lambda_test.go @@ -55,18 +55,14 @@ func TestLambdaFunction_Fetch(t *testing.T) { expected := []inventory.AssetEvent{ inventory.NewAssetEvent( inventory.AssetClassificationAwsLambdaFunction, - []string{"arn:aws:lambda:us-east-1:378890115541:function:kuba-test-func"}, + "arn:aws:lambda:us-east-1:378890115541:function:kuba-test-func", "kuba-test-func", inventory.WithRawAsset(function1), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AwsCloudProvider, - Account: inventory.AssetCloudAccount{ - Id: "123", - Name: "alias", - }, - Service: &inventory.AssetCloudService{ - Name: "AWS Lambda", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + AccountID: "123", + AccountName: "alias", + ServiceName: "AWS Lambda", }), ), } diff --git a/internal/inventory/awsfetcher/fetcher_networking.go b/internal/inventory/awsfetcher/fetcher_networking.go index af016d2479..e9e2db0319 100644 --- a/internal/inventory/awsfetcher/fetcher_networking.go +++ b/internal/inventory/awsfetcher/fetcher_networking.go @@ -96,111 +96,21 @@ func (s *networkingFetcher) fetch(ctx context.Context, resourceName string, func for _, item := range awsResources { assetChannel <- inventory.NewAssetEvent( classification, - []string{item.GetResourceArn(), pointers.Deref(s.retrieveId(item))}, + item.GetResourceArn(), item.GetResourceName(), + inventory.WithRelatedAssetIds([]string{pointers.Deref(s.retrieveId(item))}), inventory.WithRawAsset(item), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AwsCloudProvider, - Region: item.GetRegion(), - Account: inventory.AssetCloudAccount{ - Id: s.AccountId, - Name: s.AccountName, - }, - Service: &inventory.AssetCloudService{ - Name: "AWS Networking", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + Region: item.GetRegion(), + AccountID: s.AccountId, + AccountName: s.AccountName, + ServiceName: "AWS Networking", }), - s.networkEnricher(item), ) } } -//nolint:revive -func (s *networkingFetcher) networkEnricher(item awslib.AwsResource) inventory.AssetEnricher { - var enricher inventory.AssetEnricher - - switch obj := item.(type) { - case *ec2.InternetGatewayInfo: - vpcIds := []string{} - for _, attachment := range obj.InternetGateway.Attachments { - id := pointers.Deref(attachment.VpcId) - if id != "" { - vpcIds = append(vpcIds, id) - } - } - enricher = inventory.WithNetwork(inventory.AssetNetwork{ - VpcIds: vpcIds, - }) - case *ec2.NatGatewayInfo: - ifaceIds := []string{} - for _, iface := range obj.NatGateway.NatGatewayAddresses { - id := pointers.Deref(iface.NetworkInterfaceId) - if id != "" { - ifaceIds = append(ifaceIds, id) - } - } - enricher = inventory.WithNetwork(inventory.AssetNetwork{ - NetworkInterfaceIds: ifaceIds, - SubnetIds: []string{pointers.Deref(obj.NatGateway.SubnetId)}, - VpcIds: []string{pointers.Deref(obj.NatGateway.VpcId)}, - }) - case *ec2.NACLInfo: - subnetIds := []string{} - for _, association := range obj.NetworkAcl.Associations { - id := pointers.Deref(association.SubnetId) - if id != "" { - subnetIds = append(subnetIds, id) - } - } - enricher = inventory.WithNetwork(inventory.AssetNetwork{ - SubnetIds: subnetIds, - VpcIds: []string{pointers.Deref(obj.NetworkAcl.VpcId)}, - }) - case *ec2.NetworkInterfaceInfo: - secGroupIds := []string{} - for _, secGroup := range obj.NetworkInterface.Groups { - id := pointers.Deref(secGroup.GroupId) - secGroupIds = append(secGroupIds, id) - } - enricher = inventory.WithNetwork(inventory.AssetNetwork{ - SecurityGroupIds: secGroupIds, - SubnetIds: []string{pointers.Deref(obj.NetworkInterface.SubnetId)}, - VpcIds: []string{pointers.Deref(obj.NetworkInterface.VpcId)}, - }) - case *ec2.SecurityGroup: - enricher = inventory.WithNetwork(inventory.AssetNetwork{ - VpcIds: []string{pointers.Deref(obj.VpcId)}, - }) - case *ec2.SubnetInfo: - enricher = inventory.WithNetwork(inventory.AssetNetwork{ - VpcIds: []string{pointers.Deref(obj.Subnet.VpcId)}, - }) - case *ec2.TransitGatewayAttachmentInfo: - routeTableId := "" - if obj.TransitGatewayAttachment.Association != nil { - routeTableId = pointers.Deref(obj.TransitGatewayAttachment.Association.TransitGatewayRouteTableId) - } - enricher = inventory.WithNetwork(inventory.AssetNetwork{ - RouteTableIds: []string{routeTableId}, - TransitGatewayIds: []string{pointers.Deref(obj.TransitGatewayAttachment.TransitGatewayId)}, - }) - case *ec2.VpcPeeringConnectionInfo: - enricher = inventory.WithNetwork(inventory.AssetNetwork{ - VpcIds: []string{ - pointers.Deref(obj.VpcPeeringConnection.AccepterVpcInfo.VpcId), - pointers.Deref(obj.VpcPeeringConnection.RequesterVpcInfo.VpcId), - }, - }) - case *ec2.VpcInfo: - enricher = inventory.EmptyEnricher() - default: - s.logger.Warnf("Unsupported Networking Fetcher type %T (enricher)", obj) - enricher = inventory.EmptyEnricher() - } - - return enricher -} - func (s *networkingFetcher) retrieveId(awsResource awslib.AwsResource) *string { switch resource := awsResource.(type) { case *ec2.InternetGatewayInfo: diff --git a/internal/inventory/awsfetcher/fetcher_rds.go b/internal/inventory/awsfetcher/fetcher_rds.go index 590a6e812d..d9c21ccc71 100644 --- a/internal/inventory/awsfetcher/fetcher_rds.go +++ b/internal/inventory/awsfetcher/fetcher_rds.go @@ -68,19 +68,16 @@ func (s *rdsFetcher) Fetch(ctx context.Context, assetChannel chan<- inventory.As for _, item := range rdsInstances { assetChannel <- inventory.NewAssetEvent( inventory.AssetClassificationAwsRds, - []string{item.GetResourceArn(), item.Identifier}, + item.GetResourceArn(), item.GetResourceName(), + inventory.WithRelatedAssetIds([]string{item.Identifier}), inventory.WithRawAsset(item), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AwsCloudProvider, - Region: item.GetRegion(), - Account: inventory.AssetCloudAccount{ - Id: s.AccountId, - Name: s.AccountName, - }, - Service: &inventory.AssetCloudService{ - Name: "RDS", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + Region: item.GetRegion(), + AccountID: s.AccountId, + AccountName: s.AccountName, + ServiceName: "AWS RDS", }), ) } diff --git a/internal/inventory/awsfetcher/fetcher_rds_test.go b/internal/inventory/awsfetcher/fetcher_rds_test.go index dcca017e81..96426460b3 100644 --- a/internal/inventory/awsfetcher/fetcher_rds_test.go +++ b/internal/inventory/awsfetcher/fetcher_rds_test.go @@ -84,34 +84,28 @@ func TestRDSInstanceFetcher_Fetch(t *testing.T) { expected := []inventory.AssetEvent{ inventory.NewAssetEvent( inventory.AssetClassificationAwsRds, - []string{"arn:aws:rds:eu-west-1:123:db:db1", "db1"}, + "arn:aws:rds:eu-west-1:123:db:db1", "db1", + inventory.WithRelatedAssetIds([]string{"db1"}), inventory.WithRawAsset(instance1), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AwsCloudProvider, - Account: inventory.AssetCloudAccount{ - Id: "123", - Name: "alias", - }, - Service: &inventory.AssetCloudService{ - Name: "RDS", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + AccountID: "123", + AccountName: "alias", + ServiceName: "AWS RDS", }), ), inventory.NewAssetEvent( inventory.AssetClassificationAwsRds, - []string{"arn:aws:rds:eu-west-1:123:db:db2", "db2"}, + "arn:aws:rds:eu-west-1:123:db:db2", "db2", + inventory.WithRelatedAssetIds([]string{"db2"}), inventory.WithRawAsset(instance2), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AwsCloudProvider, - Account: inventory.AssetCloudAccount{ - Id: "123", - Name: "alias", - }, - Service: &inventory.AssetCloudService{ - Name: "RDS", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + AccountID: "123", + AccountName: "alias", + ServiceName: "AWS RDS", }), ), } diff --git a/internal/inventory/awsfetcher/fetcher_s3_bucket.go b/internal/inventory/awsfetcher/fetcher_s3_bucket.go index 7c6744da73..0b366a1fdb 100644 --- a/internal/inventory/awsfetcher/fetcher_s3_bucket.go +++ b/internal/inventory/awsfetcher/fetcher_s3_bucket.go @@ -36,13 +36,6 @@ type s3BucketFetcher struct { AccountName string } -var s3BucketClassification = inventory.AssetClassification{ - Category: inventory.CategoryInfrastructure, - SubCategory: inventory.SubCategoryStorage, - Type: inventory.TypeObjectStorage, - SubType: inventory.SubTypeS3, -} - type s3BucketProvider interface { DescribeBuckets(ctx context.Context) ([]awslib.AwsResource, error) } @@ -74,23 +67,18 @@ func (s *s3BucketFetcher) Fetch(ctx context.Context, assetChannel chan<- invento for _, bucket := range buckets { assetChannel <- inventory.NewAssetEvent( - s3BucketClassification, - []string{bucket.GetResourceArn()}, + inventory.AssetClassificationAwsS3Bucket, + bucket.GetResourceArn(), bucket.GetResourceName(), inventory.WithRawAsset(bucket), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AwsCloudProvider, - Region: bucket.Region, - Account: inventory.AssetCloudAccount{ - Id: s.AccountId, - Name: s.AccountName, - }, - Service: &inventory.AssetCloudService{ - Name: "AWS S3", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + Region: bucket.Region, + AccountID: s.AccountId, + AccountName: s.AccountName, + ServiceName: "AWS S3", }), - inventory.WithResourcePolicies(convertPolicy(bucket.BucketPolicy)...), ) } } diff --git a/internal/inventory/awsfetcher/fetcher_s3_bucket_test.go b/internal/inventory/awsfetcher/fetcher_s3_bucket_test.go index e9d7cb7529..e06f22872b 100644 --- a/internal/inventory/awsfetcher/fetcher_s3_bucket_test.go +++ b/internal/inventory/awsfetcher/fetcher_s3_bucket_test.go @@ -104,62 +104,28 @@ func TestS3BucketFetcher_Fetch(t *testing.T) { expected := []inventory.AssetEvent{ inventory.NewAssetEvent( inventory.AssetClassificationAwsS3Bucket, - []string{"arn:aws:s3:::bucket-1"}, + "arn:aws:s3:::bucket-1", "bucket-1", inventory.WithRawAsset(bucket1), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AwsCloudProvider, - Region: "europe-west-1", - Account: inventory.AssetCloudAccount{ - Id: "123", - Name: "alias", - }, - Service: &inventory.AssetCloudService{ - Name: "AWS S3", - }, - }), - inventory.WithResourcePolicies(inventory.AssetResourcePolicy{ - Version: pointers.Ref("2012-10-17"), - Id: pointers.Ref("Test 1"), - Effect: "Allow", - Principal: map[string]any{ - "AWS": "dima", - "service": "aws.com", - }, - Action: []string{"read", "update", "delete"}, - Resource: []string{"s3/bucket", "s3/bucket/*"}, - }, inventory.AssetResourcePolicy{ - Version: pointers.Ref("2012-10-17"), - Id: pointers.Ref("Test 2"), - Effect: "Deny", - Principal: map[string]any{"AWS": "romulo"}, - Action: []string{"delete"}, - Resource: []string{"s3/bucket"}, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + Region: "europe-west-1", + AccountID: "123", + AccountName: "alias", + ServiceName: "AWS S3", }), ), inventory.NewAssetEvent( inventory.AssetClassificationAwsS3Bucket, - []string{"arn:aws:s3:::bucket-2"}, + "arn:aws:s3:::bucket-2", "bucket-2", inventory.WithRawAsset(bucket2), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AwsCloudProvider, - Region: "europe-west-1", - Account: inventory.AssetCloudAccount{ - Id: "123", - Name: "alias", - }, - Service: &inventory.AssetCloudService{ - Name: "AWS S3", - }, - }), - inventory.WithResourcePolicies(inventory.AssetResourcePolicy{ - Version: pointers.Ref("2012-10-17"), - Id: pointers.Ref("Test 1"), - Effect: "Allow", - Principal: map[string]any{"*": "*"}, - Action: []string{"read"}, - Resource: []string{"s3/bucket"}, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + Region: "europe-west-1", + AccountID: "123", + AccountName: "alias", + ServiceName: "AWS S3", }), ), } diff --git a/internal/inventory/awsfetcher/fetcher_sns.go b/internal/inventory/awsfetcher/fetcher_sns.go index 86c082d531..d81b357f80 100644 --- a/internal/inventory/awsfetcher/fetcher_sns.go +++ b/internal/inventory/awsfetcher/fetcher_sns.go @@ -60,19 +60,15 @@ func (s *snsFetcher) Fetch(ctx context.Context, assetChannel chan<- inventory.As for _, item := range awsResources { assetChannel <- inventory.NewAssetEvent( inventory.AssetClassificationAwsSnsTopic, - []string{item.GetResourceArn()}, + item.GetResourceArn(), item.GetResourceName(), inventory.WithRawAsset(item), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AwsCloudProvider, - Region: item.GetRegion(), - Account: inventory.AssetCloudAccount{ - Id: s.AccountId, - Name: s.AccountName, - }, - Service: &inventory.AssetCloudService{ - Name: "AWS SNS", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + Region: item.GetRegion(), + AccountID: s.AccountId, + AccountName: s.AccountName, + ServiceName: "AWS SNS", }), ) } diff --git a/internal/inventory/awsfetcher/fetcher_sns_test.go b/internal/inventory/awsfetcher/fetcher_sns_test.go index 5ab22226cb..8c6a490b8e 100644 --- a/internal/inventory/awsfetcher/fetcher_sns_test.go +++ b/internal/inventory/awsfetcher/fetcher_sns_test.go @@ -45,18 +45,14 @@ func TestSNSFetcher_Fetch(t *testing.T) { expected := []inventory.AssetEvent{ inventory.NewAssetEvent( inventory.AssetClassificationAwsSnsTopic, - []string{"topic:arn:test-topic"}, + "topic:arn:test-topic", "test-topic", inventory.WithRawAsset(awsResource), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AwsCloudProvider, - Account: inventory.AssetCloudAccount{ - Id: "123", - Name: "alias", - }, - Service: &inventory.AssetCloudService{ - Name: "AWS SNS", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AwsCloudProvider, + AccountID: "123", + AccountName: "alias", + ServiceName: "AWS SNS", }), ), } diff --git a/internal/inventory/azurefetcher/fetcher_account.go b/internal/inventory/azurefetcher/fetcher_account.go index f30244586b..e5568e8e3a 100644 --- a/internal/inventory/azurefetcher/fetcher_account.go +++ b/internal/inventory/azurefetcher/fetcher_account.go @@ -73,17 +73,13 @@ func (f *accountFetcher) fetch(ctx context.Context, resourceName string, functio for _, item := range azureAssets { assetChan <- inventory.NewAssetEvent( classification, - []string{item.Id}, + item.Id, item.DisplayName, inventory.WithRawAsset(item), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AzureCloudProvider, - Account: inventory.AssetCloudAccount{ - Id: item.TenantId, - }, - Service: &inventory.AssetCloudService{ - Name: "Azure", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AzureCloudProvider, + AccountID: item.TenantId, + ServiceName: "Azure", }), ) } diff --git a/internal/inventory/azurefetcher/fetcher_account_test.go b/internal/inventory/azurefetcher/fetcher_account_test.go index 514b8107be..20b7519647 100644 --- a/internal/inventory/azurefetcher/fetcher_account_test.go +++ b/internal/inventory/azurefetcher/fetcher_account_test.go @@ -40,17 +40,13 @@ func TestAccountFetcher_Fetch_Tenants(t *testing.T) { expected := []inventory.AssetEvent{ inventory.NewAssetEvent( inventory.AssetClassificationAzureTenant, - []string{"/tenants/"}, + "/tenants/", "Mario", inventory.WithRawAsset(azureAssets[0]), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AzureCloudProvider, - Account: inventory.AssetCloudAccount{ - Id: "", - }, - Service: &inventory.AssetCloudService{ - Name: "Azure", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AzureCloudProvider, + AccountID: "", + ServiceName: "Azure", }), ), } @@ -77,17 +73,13 @@ func TestAccountFetcher_Fetch_Subscriptions(t *testing.T) { expected := []inventory.AssetEvent{ inventory.NewAssetEvent( inventory.AssetClassificationAzureSubscription, - []string{"/subscriptions/"}, + "/subscriptions/", "Luigi", inventory.WithRawAsset(azureAssets[0]), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AzureCloudProvider, - Account: inventory.AssetCloudAccount{ - Id: "", - }, - Service: &inventory.AssetCloudService{ - Name: "Azure", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AzureCloudProvider, + AccountID: "", + ServiceName: "Azure", }), ), } diff --git a/internal/inventory/azurefetcher/fetcher_activedirectory.go b/internal/inventory/azurefetcher/fetcher_activedirectory.go index fb121dc68a..a2352fdc00 100644 --- a/internal/inventory/azurefetcher/fetcher_activedirectory.go +++ b/internal/inventory/azurefetcher/fetcher_activedirectory.go @@ -65,19 +65,15 @@ func (f *activedirectoryFetcher) fetchServicePrincipals(ctx context.Context, ass } assetChan <- inventory.NewAssetEvent( inventory.AssetClassificationAzureServicePrincipal, - []string{pointers.Deref(item.GetId())}, + pointers.Deref(item.GetId()), pointers.Deref(item.GetDisplayName()), inventory.WithRawAsset( item.GetBackingStore().Enumerate(), ), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AzureCloudProvider, - Account: inventory.AssetCloudAccount{ - Id: tenantId, - }, - Service: &inventory.AssetCloudService{ - Name: "Azure", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AzureCloudProvider, + AccountID: tenantId, + ServiceName: "Azure", }), ) } diff --git a/internal/inventory/azurefetcher/fetcher_activedirectory_test.go b/internal/inventory/azurefetcher/fetcher_activedirectory_test.go index bb142da58f..6c427511fb 100644 --- a/internal/inventory/azurefetcher/fetcher_activedirectory_test.go +++ b/internal/inventory/azurefetcher/fetcher_activedirectory_test.go @@ -59,17 +59,13 @@ func TestActiveDirectoryFetcher_Fetch(t *testing.T) { expected := []inventory.AssetEvent{ inventory.NewAssetEvent( inventory.AssetClassificationAzureServicePrincipal, - []string{"id"}, + "id", "dn", inventory.WithRawAsset(values), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AzureCloudProvider, - Account: inventory.AssetCloudAccount{ - Id: appOwnerOrganizationId.String(), - }, - Service: &inventory.AssetCloudService{ - Name: "Azure", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AzureCloudProvider, + AccountID: appOwnerOrganizationId.String(), + ServiceName: "Azure", }), ), } diff --git a/internal/inventory/azurefetcher/fetcher_resource_graph.go b/internal/inventory/azurefetcher/fetcher_resource_graph.go index 2cb877cd04..8e65e7e2bd 100644 --- a/internal/inventory/azurefetcher/fetcher_resource_graph.go +++ b/internal/inventory/azurefetcher/fetcher_resource_graph.go @@ -86,17 +86,13 @@ func (f *resourceGraphFetcher) fetch(ctx context.Context, resourceName, resource } assetChan <- inventory.NewAssetEvent( classification, - []string{item.Id}, + item.Id, name, inventory.WithRawAsset(item), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AzureCloudProvider, - Account: inventory.AssetCloudAccount{ - Id: item.TenantId, - }, - Service: &inventory.AssetCloudService{ - Name: "Azure", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AzureCloudProvider, + AccountID: item.TenantId, + ServiceName: "Azure", }), ) } diff --git a/internal/inventory/azurefetcher/fetcher_resource_graph_test.go b/internal/inventory/azurefetcher/fetcher_resource_graph_test.go index bdf7118339..f3d92ab5f8 100644 --- a/internal/inventory/azurefetcher/fetcher_resource_graph_test.go +++ b/internal/inventory/azurefetcher/fetcher_resource_graph_test.go @@ -45,32 +45,24 @@ func TestResourceGraphFetcher_Fetch(t *testing.T) { expected := []inventory.AssetEvent{ inventory.NewAssetEvent( inventory.AssetClassificationAzureAppService, - []string{appService.Id}, + appService.Id, appService.Name, inventory.WithRawAsset(appService), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AzureCloudProvider, - Account: inventory.AssetCloudAccount{ - Id: "", - }, - Service: &inventory.AssetCloudService{ - Name: "Azure", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AzureCloudProvider, + AccountID: "", + ServiceName: "Azure", }), ), inventory.NewAssetEvent( inventory.AssetClassificationAzureDisk, - []string{disk.Id}, + disk.Id, disk.Name, inventory.WithRawAsset(disk), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AzureCloudProvider, - Account: inventory.AssetCloudAccount{ - Id: "", - }, - Service: &inventory.AssetCloudService{ - Name: "Azure", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AzureCloudProvider, + AccountID: "", + ServiceName: "Azure", }), ), } diff --git a/internal/inventory/azurefetcher/fetcher_storage.go b/internal/inventory/azurefetcher/fetcher_storage.go index 8676f8c662..d06d8c80f9 100644 --- a/internal/inventory/azurefetcher/fetcher_storage.go +++ b/internal/inventory/azurefetcher/fetcher_storage.go @@ -104,17 +104,13 @@ func (f *storageFetcher) fetch(ctx context.Context, storageAccounts []azurelib.A for _, item := range azureAssets { assetChan <- inventory.NewAssetEvent( classification, - []string{item.Id}, + item.Id, item.DisplayName, inventory.WithRawAsset(item), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AzureCloudProvider, - Account: inventory.AssetCloudAccount{ - Id: item.TenantId, - }, - Service: &inventory.AssetCloudService{ - Name: "Azure", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AzureCloudProvider, + AccountID: item.TenantId, + ServiceName: "Azure", }), ) } diff --git a/internal/inventory/azurefetcher/fetcher_storage_test.go b/internal/inventory/azurefetcher/fetcher_storage_test.go index 05a6fdb7cf..078ff94641 100644 --- a/internal/inventory/azurefetcher/fetcher_storage_test.go +++ b/internal/inventory/azurefetcher/fetcher_storage_test.go @@ -55,38 +55,32 @@ func TestStorageFetcher_Fetch(t *testing.T) { expected := []inventory.AssetEvent{ inventory.NewAssetEvent( inventory.AssetClassificationAzureStorageBlobService, - []string{azureBlobService.Id}, + azureBlobService.Id, azureBlobService.Name, inventory.WithRawAsset(azureBlobService), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AzureCloudProvider, - Service: &inventory.AssetCloudService{ - Name: "Azure", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AzureCloudProvider, + ServiceName: "Azure", }), ), inventory.NewAssetEvent( inventory.AssetClassificationAzureStorageQueueService, - []string{azureQueueService.Id}, + azureQueueService.Id, azureQueueService.Name, inventory.WithRawAsset(azureQueueService), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AzureCloudProvider, - Service: &inventory.AssetCloudService{ - Name: "Azure", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AzureCloudProvider, + ServiceName: "Azure", }), ), inventory.NewAssetEvent( inventory.AssetClassificationAzureStorageQueue, - []string{azureQueue.Id}, + azureQueue.Id, azureQueue.Name, inventory.WithRawAsset(azureQueue), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.AzureCloudProvider, - Service: &inventory.AssetCloudService{ - Name: "Azure", - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.AzureCloudProvider, + ServiceName: "Azure", }), ), } diff --git a/internal/inventory/cloud_assets.xlsx b/internal/inventory/cloud_assets.xlsx index 5f231b0b3c45b86dd5aab8d7a9ce14b11a960f00..0a00779f6b3fe1e6d16144d7ab5bd1decf63e1ab 100644 GIT binary patch literal 174786 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%0A5QoXGln$LpU0oqkKqZgLt9oLn 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 Yr_F1=3Mgz0n+1O2!E>*}eo&y;zeF6U&;S4c literal 89134 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 diff --git a/internal/inventory/gcpfetcher/fetcher_assets.go b/internal/inventory/gcpfetcher/fetcher_assets.go index 374a8db1c0..f7f59b062d 100644 --- a/internal/inventory/gcpfetcher/fetcher_assets.go +++ b/internal/inventory/gcpfetcher/fetcher_assets.go @@ -85,45 +85,39 @@ func (f *assetsInventory) fetch(ctx context.Context, assetChan chan<- inventory. for _, item := range gcpAssets { assetChan <- inventory.NewAssetEvent( classification, - []string{item.Name}, + item.Name, item.Name, inventory.WithRawAsset(item), inventory.WithRelatedAssetIds( - f.findRelatedAssetIds(classification.SubType, item), + f.findRelatedAssetIds(classification.Type, item), ), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.GcpCloudProvider, - Account: inventory.AssetCloudAccount{ - Id: item.CloudAccount.AccountId, - Name: item.CloudAccount.AccountName, - }, - Organization: inventory.AssetCloudOrganization{ - Id: item.CloudAccount.OrganisationId, - Name: item.CloudAccount.OrganizationName, - }, - Service: &inventory.AssetCloudService{ - Name: assetType, - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.GcpCloudProvider, + AccountID: item.CloudAccount.AccountId, + AccountName: item.CloudAccount.AccountName, + ProjectID: item.CloudAccount.OrganisationId, + ProjectName: item.CloudAccount.OrganizationName, + ServiceName: assetType, }), ) } } -func (f *assetsInventory) findRelatedAssetIds(subType inventory.AssetSubType, item *gcpinventory.ExtendedGcpAsset) []string { +func (f *assetsInventory) findRelatedAssetIds(t inventory.AssetType, item *gcpinventory.ExtendedGcpAsset) []string { ids := []string{} ids = append(ids, item.Ancestors...) if item.Resource != nil { ids = append(ids, item.Resource.Parent) } - ids = append(ids, f.findRelatedAssetIdsForSubType(subType, item)...) + ids = append(ids, f.findRelatedAssetIdsForType(t, item)...) ids = lo.Compact(ids) ids = lo.Uniq(ids) return ids } -func (f *assetsInventory) findRelatedAssetIdsForSubType(subType inventory.AssetSubType, item *gcpinventory.ExtendedGcpAsset) []string { +func (f *assetsInventory) findRelatedAssetIdsForType(t inventory.AssetType, item *gcpinventory.ExtendedGcpAsset) []string { ids := []string{} var fields map[string]*structpb.Value @@ -131,8 +125,8 @@ func (f *assetsInventory) findRelatedAssetIdsForSubType(subType inventory.AssetS fields = item.GetResource().GetData().GetFields() } - switch subType { - case inventory.SubTypeGcpInstance: + switch t { + case inventory.AssetClassificationGcpInstance.Type: if v, ok := fields["networkInterfaces"]; ok { for _, networkInterface := range v.GetListValue().GetValues() { networkInterfaceFields := networkInterface.GetStructValue().GetFields() @@ -154,9 +148,9 @@ func (f *assetsInventory) findRelatedAssetIdsForSubType(subType inventory.AssetS } ids = appendIfExists(ids, fields, "machineType") ids = appendIfExists(ids, fields, "zone") - case inventory.SubTypeGcpFirewall, inventory.SubTypeGcpSubnet: + case inventory.AssetClassificationGcpFirewall.Type, inventory.AssetClassificationGcpSubnet.Type: ids = appendIfExists(ids, fields, "network") - case inventory.SubTypeGcpProject, inventory.SubTypeGcpBucket: + case inventory.AssetClassificationGcpProject.Type, inventory.AssetClassificationGcpBucket.Type: if item.IamPolicy == nil { break } diff --git a/internal/inventory/gcpfetcher/fetcher_assets_test.go b/internal/inventory/gcpfetcher/fetcher_assets_test.go index 5e8b39c3c8..4e96d4d024 100644 --- a/internal/inventory/gcpfetcher/fetcher_assets_test.go +++ b/internal/inventory/gcpfetcher/fetcher_assets_test.go @@ -50,23 +50,17 @@ func TestAccountFetcher_Fetch_Assets(t *testing.T) { expected := lo.Map(ResourcesToFetch, func(r ResourcesClassification, _ int) inventory.AssetEvent { return inventory.NewAssetEvent( r.classification, - []string{"/projects//some_resource"}, + "/projects//some_resource", "/projects//some_resource", inventory.WithRawAsset(assets[0]), inventory.WithRelatedAssetIds([]string{}), - inventory.WithCloud(inventory.AssetCloud{ - Provider: inventory.GcpCloudProvider, - Account: inventory.AssetCloudAccount{ - Id: "", - Name: "", - }, - Organization: inventory.AssetCloudOrganization{ - Id: "", - Name: "", - }, - Service: &inventory.AssetCloudService{ - Name: r.assetType, - }, + inventory.WithCloud(inventory.Cloud{ + Provider: inventory.GcpCloudProvider, + AccountID: "", + AccountName: "", + ProjectID: "", + ProjectName: "", + ServiceName: r.assetType, }), ) }) diff --git a/internal/inventory/inventory.go b/internal/inventory/inventory.go index 3cb6d88c12..dd989ccaad 100644 --- a/internal/inventory/inventory.go +++ b/internal/inventory/inventory.go @@ -20,6 +20,7 @@ package inventory import ( "context" "fmt" + "strings" "time" "github.com/elastic/beats/v7/libbeat/beat" @@ -30,7 +31,7 @@ import ( ) const ( - indexTemplate = "logs-cloud_asset_inventory.asset_inventory-%s_%s_%s_%s-default" + indexTemplate = "logs-cloud_asset_inventory.asset_inventory-%s_%s-default" minimalPeriod = 30 * time.Second ) @@ -123,21 +124,23 @@ func (a *AssetInventory) runAllFetchersOnce(ctx context.Context) { func (a *AssetInventory) publish(assets []AssetEvent) { events := lo.Map(assets, func(e AssetEvent, _ int) beat.Event { var relatedEntity []string - relatedEntity = append(relatedEntity, e.Asset.Id...) - if len(e.Asset.RelatedEntityId) > 0 { - relatedEntity = append(relatedEntity, e.Asset.RelatedEntityId...) + relatedEntity = append(relatedEntity, e.Entity.Id) + if len(e.Entity.relatedEntityId) > 0 { + relatedEntity = append(relatedEntity, e.Entity.relatedEntityId...) } return beat.Event{ - Meta: mapstr.M{libevents.FieldMetaIndex: generateIndex(e.Asset)}, + Meta: mapstr.M{libevents.FieldMetaIndex: generateIndex(e.Entity)}, Timestamp: a.now(), Fields: mapstr.M{ - "asset": e.Asset, - "cloud": e.Cloud, - "host": e.Host, - "network": e.Network, - "iam": e.IAM, - "resource_policies": e.ResourcePolicies, - "related.entity": relatedEntity, + "entity": e.Entity, + "event": e.Event, + "cloud": e.Cloud, + "host": e.Host, + "network": e.Network, + "user": e.User, + "Attributes": e.RawAttributes, + "labels": e.Labels, + "related.entity": relatedEntity, }, } }) @@ -145,14 +148,19 @@ func (a *AssetInventory) publish(assets []AssetEvent) { a.publisher.PublishAll(events) } -func generateIndex(a Asset) string { - return fmt.Sprintf(indexTemplate, a.Category, a.SubCategory, a.Type, a.SubType) +func generateIndex(a Entity) string { + return fmt.Sprintf(indexTemplate, slugfy(string(a.Category)), slugfy(string(a.Type))) } -func (a *AssetInventory) Stop() { - close(a.assetCh) +func slugfy(s string) string { + chunks := strings.Split(s, " ") + clean := make([]string, len(chunks)) + for i, c := range chunks { + clean[i] = strings.ToLower(c) + } + return strings.Join(clean, "_") } -func removeEmpty(list []string) []string { - return lo.Filter(list, func(item string, _ int) bool { return item != "" }) +func (a *AssetInventory) Stop() { + close(a.assetCh) } diff --git a/internal/inventory/inventory_test.go b/internal/inventory/inventory_test.go index 30e4bb3deb..89c412b4a9 100644 --- a/internal/inventory/inventory_test.go +++ b/internal/inventory/inventory_test.go @@ -31,63 +31,47 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "github.com/elastic/cloudbeat/internal/resources/utils/pointers" "github.com/elastic/cloudbeat/internal/resources/utils/testhelper" ) func TestAssetInventory_Run(t *testing.T) { + var emptyRef *any now := func() time.Time { return time.Date(2024, 1, 1, 1, 1, 1, 0, time.Local) } expected := []beat.Event{ { - Meta: mapstr.M{libevents.FieldMetaIndex: "logs-cloud_asset_inventory.asset_inventory-infrastructure_compute_virtual-machine_ec2-instance-default"}, + Meta: mapstr.M{libevents.FieldMetaIndex: "logs-cloud_asset_inventory.asset_inventory-infrastructure_virtual_machine-default"}, Timestamp: now(), Fields: mapstr.M{ - "asset": Asset{ - Id: []string{"arn:aws:ec2:us-east::ec2/234567890"}, + "entity": Entity{ + Id: "arn:aws:ec2:us-east::ec2/234567890", Name: "test-server", AssetClassification: AssetClassification{ - Category: CategoryInfrastructure, - SubCategory: SubCategoryCompute, - Type: TypeVirtualMachine, - SubType: SubTypeEC2, + Category: CategoryInfrastructure, + Type: "Virtual Machine", }, - Tags: map[string]string{"Name": "test-server", "key": "value"}, }, - "cloud": &AssetCloud{ + "event": Event{ + Kind: "asset", + }, + "labels": map[string]string{"Name": "test-server", "key": "value"}, + "cloud": &Cloud{ Provider: AwsCloudProvider, Region: "us-east", }, - "host": &AssetHost{ - Architecture: string(types.ArchitectureValuesX8664), - ImageId: pointers.Ref("image-id"), - InstanceType: "instance-type", - Platform: "linux", - PlatformDetails: pointers.Ref("ubuntu"), - }, - "network": &AssetNetwork{ - NetworkId: pointers.Ref("vpc-id"), - SubnetIds: []string{"subnetId"}, - Ipv6Address: pointers.Ref("ipv6"), - PublicIpAddress: pointers.Ref("public-ip-addr"), - PrivateIpAddress: pointers.Ref("private-ip-addre"), - PublicDnsName: pointers.Ref("public-dns"), - PrivateDnsName: pointers.Ref("private-dns"), + "host": &Host{ + Architecture: string(types.ArchitectureValuesX8664), + Type: "instance-type", + ID: "i-a2", }, - "iam": &AssetIAM{ - Id: pointers.Ref("a123123"), - Arn: pointers.Ref("123123:123123:123123"), + "network": &Network{ + Name: "vpc-id", }, - "resource_policies": []AssetResourcePolicy{ - { - Version: pointers.Ref("2012-10-17"), - Id: pointers.Ref("Test 1"), - Effect: "Allow", - Principal: map[string]any{"*": "*"}, - Action: []string{"read"}, - Resource: []string{"s3/bucket"}, - }, + "user": &User{ + ID: "a123123", + Name: "name", }, "related.entity": []string{"arn:aws:ec2:us-east::ec2/234567890"}, + "Attributes": emptyRef, }, }, } @@ -102,45 +86,27 @@ func TestAssetInventory_Run(t *testing.T) { fetcher.EXPECT().Fetch(mock.Anything, mock.Anything).Run(func(_ context.Context, assetChannel chan<- AssetEvent) { assetChannel <- NewAssetEvent( AssetClassification{ - Category: CategoryInfrastructure, - SubCategory: SubCategoryCompute, - Type: TypeVirtualMachine, - SubType: SubTypeEC2, + Category: CategoryInfrastructure, + Type: "Virtual Machine", }, - []string{"arn:aws:ec2:us-east::ec2/234567890"}, + "arn:aws:ec2:us-east::ec2/234567890", "test-server", - WithTags(map[string]string{"Name": "test-server", "key": "value"}), - WithCloud(AssetCloud{ + WithLabels(map[string]string{"Name": "test-server", "key": "value"}), + WithCloud(Cloud{ Provider: AwsCloudProvider, Region: "us-east", }), - WithHost(AssetHost{ - Architecture: string(types.ArchitectureValuesX8664), - ImageId: pointers.Ref("image-id"), - InstanceType: "instance-type", - Platform: "linux", - PlatformDetails: pointers.Ref("ubuntu"), - }), - WithIAM(AssetIAM{ - Id: pointers.Ref("a123123"), - Arn: pointers.Ref("123123:123123:123123"), + WithHost(Host{ + Architecture: string(types.ArchitectureValuesX8664), + Type: "instance-type", + ID: "i-a2", }), - WithNetwork(AssetNetwork{ - NetworkId: pointers.Ref("vpc-id"), - SubnetIds: []string{"subnetId"}, - Ipv6Address: pointers.Ref("ipv6"), - PublicIpAddress: pointers.Ref("public-ip-addr"), - PrivateIpAddress: pointers.Ref("private-ip-addre"), - PublicDnsName: pointers.Ref("public-dns"), - PrivateDnsName: pointers.Ref("private-dns"), + WithUser(User{ + ID: "a123123", + Name: "name", }), - WithResourcePolicies(AssetResourcePolicy{ - Version: pointers.Ref("2012-10-17"), - Id: pointers.Ref("Test 1"), - Effect: "Allow", - Principal: map[string]any{"*": "*"}, - Action: []string{"read"}, - Resource: []string{"s3/bucket"}, + WithNetwork(Network{ + Name: "vpc-id", }), ) }) diff --git a/scripts/update_assets_md/main.go b/scripts/update_assets_md/main.go index 1f23982f6d..8925e2ef90 100644 --- a/scripts/update_assets_md/main.go +++ b/scripts/update_assets_md/main.go @@ -43,18 +43,21 @@ const ( ) type Classification struct { - Category string - SubCategory string - Type string - SubType string + Category string + OldType string + Type string } func (item Classification) ID() string { - return fmt.Sprintf("%s_%s_%s_%s", + if item.Type != "" { + return fmt.Sprintf("%s_%s", + strcase.ToKebab(item.Category), + strcase.ToKebab(item.Type), + ) + } + return fmt.Sprintf("%s_%s", strcase.ToKebab(item.Category), - strcase.ToKebab(item.SubCategory), - strcase.ToKebab(item.Type), - strcase.ToKebab(item.SubType), + strcase.ToKebab(item.OldType), ) } @@ -162,7 +165,7 @@ func loadClassificationsFromExcel(filepath string) (*ByProvider, error) { return nil, fmt.Errorf("failed to open Excel file: %w", err) } - sheets := map[int]string{1: AWS_PREFIX, 2: AZURE_PREFIX, 3: GCP_PREFIX} + sheets := map[int]string{3: AWS_PREFIX, 4: AZURE_PREFIX, 5: GCP_PREFIX} for sheetNo, provider := range sheets { sheetName := f.GetSheetName(sheetNo) rows, err := f.GetRows(sheetName) @@ -173,10 +176,9 @@ func loadClassificationsFromExcel(filepath string) (*ByProvider, error) { headers := rows[0] for _, row := range rows[1:] { cl := Classification{ - Category: row[getColumnIndex(headers, "asset.category")], - SubCategory: row[getColumnIndex(headers, "asset.subcategory")], - Type: row[getColumnIndex(headers, "asset.type")], - SubType: row[getColumnIndex(headers, "asset.subtype")], + Category: row[getColumnIndex(headers, "Category")], + OldType: row[getColumnIndex(headers, "(current) Type")], + Type: row[getColumnIndex(headers, "Updated Type")], } output.Assign(provider, cl) } @@ -205,8 +207,8 @@ func writeSummary(plannedByProvider, implementedByProvider *ByProvider, filepath // table of assets table := []string{ "
Full table\n", - "| Category | SubCategory | Type | SubType | Implemented? |", - "|---|---|---|---|---|", + "| Category | Old Type | Type | Implemented? |", + "|---|---|---|---|", } for _, key := range sortedKeys { @@ -221,8 +223,8 @@ func writeSummary(plannedByProvider, implementedByProvider *ByProvider, filepath } table = append(table, fmt.Sprintf( - "| %s | %s | %s | %s | %s |", - item.Category, item.SubCategory, item.Type, item.SubType, status, + "| %s | %s | %s | %s |", + item.Category, item.OldType, item.Type, status, ), ) } @@ -290,8 +292,8 @@ func extractClassification(expr ast.Expr) (Classification, error) { return output, fmt.Errorf("value is not a composite literal, skipping") } - if len(compLit.Elts) != 4 { - return output, fmt.Errorf("expected full, 4-field classification; got %d", len(compLit.Elts)) + if len(compLit.Elts) != 2 { + return output, fmt.Errorf("expected full, 2-field classification; got %d", len(compLit.Elts)) } classification := []string{} @@ -304,9 +306,7 @@ func extractClassification(expr ast.Expr) (Classification, error) { } output.Category = classification[0] - output.SubCategory = classification[1] - output.Type = classification[2] - output.SubType = classification[3] + output.Type = classification[1] return output, nil } diff --git a/tests/commonlib/io_utils.py b/tests/commonlib/io_utils.py index 4567a5e81e..6e10db97a2 100644 --- a/tests/commonlib/io_utils.py +++ b/tests/commonlib/io_utils.py @@ -59,28 +59,22 @@ def get_events_from_index( def get_assets_from_index( elastic_client, category: str, - sub_category: str, type_: str, - sub_type: str, time_after: datetime, ) -> list[Munch]: """ Resturns assets from a given index matching given classification. @param elastic_client: Client to connect to Elasticsearch. - @param category: Asset category used as a filter - @param sub_category: Asset subcategory used as a filter - @param type: Asset type used as a filter - @param sub_type: Asset subtype used as a filter + @param category: Entity category used as a filter + @param type: Entity type used as a filter @param time_after: Filter events having timestamp > time_after @return: List of Munch objects """ query = { "bool": { "must": [ - {"match": {"asset.category": category}}, - {"match": {"asset.sub_category": sub_category}}, - {"match": {"asset.type": type_}}, - {"match": {"asset.sub_type": sub_type}}, + {"match": {"entity.category": category}}, + {"match": {"entity.type": type_}}, ], "filter": [ { diff --git a/tests/commonlib/utils.py b/tests/commonlib/utils.py index d7d350f849..24a55015d7 100644 --- a/tests/commonlib/utils.py +++ b/tests/commonlib/utils.py @@ -84,9 +84,7 @@ def get_ES_assets( elastic_client, timeout, category, - sub_category, type_, - sub_type, exec_timestamp, resource_identifier=lambda r: True, ) -> Union[List[munch.Munch], None]: @@ -99,9 +97,7 @@ def get_ES_assets( assets = get_assets_from_index( elastic_client, category, - sub_category, type_, - sub_type, latest_timestamp, ) except Exception as e: diff --git a/tests/product/tests/data/asset_inventory_test_case.py b/tests/product/tests/data/asset_inventory_test_case.py index 7fc58157db..84ac563fcf 100644 --- a/tests/product/tests/data/asset_inventory_test_case.py +++ b/tests/product/tests/data/asset_inventory_test_case.py @@ -12,9 +12,7 @@ class AssetInventoryCase: """ category: str - sub_category: str type_: str - sub_type: str def __iter__(self): return iter(astuple(self)) diff --git a/tests/product/tests/data/aws_asset_inventory/test_cases.py b/tests/product/tests/data/aws_asset_inventory/test_cases.py index 3b308bbca8..41083fb5c4 100644 --- a/tests/product/tests/data/aws_asset_inventory/test_cases.py +++ b/tests/product/tests/data/aws_asset_inventory/test_cases.py @@ -7,135 +7,91 @@ test_cases = { "[Asset Inventory][AWS][EC2] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="compute", - type_="virtual-machine", - sub_type="ec2-instance", + category="Host", + type_="AWS EC2 Instance", ), "[Asset Inventory][AWS][IAM Role] assets found": AssetInventoryCase( - category="identity", - sub_category="digital-identity", - type_="role", - sub_type="iam-role", + category="Service Account", + type_="AWS IAM Role", ), "[Asset Inventory][AWS][ELBv1] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="network", - type_="load-balancer", - sub_type="elastic-load-balancer", + category="Load Balancer", + type_="AWS Elastic Load Balancer", ), "[Asset Inventory][AWS][ELBv2] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="network", - type_="load-balancer", - sub_type="elastic-load-balancer-v2", + category="Load Balancer", + type_="AWS Elastic Load Balancer v2", ), "[Asset Inventory][AWS][IAM Policy] assets found": AssetInventoryCase( - category="identity", - sub_category="digital-identity", - type_="policy", - sub_type="iam-policy", + category="Access Management", + type_="AWS IAM Policy", ), "[Asset Inventory][AWS][IAM User] assets found": AssetInventoryCase( - category="identity", - sub_category="digital-identity", - type_="user", - sub_type="iam-user", + category="Identity", + type_="AWS IAM User", ), "[Asset Inventory][AWS][Lambda Event Source Mapping] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="integration", - type_="event-source", - sub_type="lambda-event-source-mapping", + category="FaaS", + type_="AWS Lambda Event Source Mapping", ), "[Asset Inventory][AWS][Lambda Function] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="compute", - type_="serverless", - sub_type="lambda-function", + category="FaaS", + type_="AWS Lambda Function", ), "[Asset Inventory][AWS][Lambda Layer] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="compute", - type_="serverless", - sub_type="lambda-layer", + category="FaaS", + type_="AWS Lambda Layer", ), "[Asset Inventory][AWS][Internet Gateway] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="network", - type_="gateway", - sub_type="internet-gateway", + category="Gateway", + type_="AWS Internet Gateway", ), "[Asset Inventory][AWS][NAT Gateway] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="network", - type_="gateway", - sub_type="nat-gateway", + category="Gateway", + type_="AWS NAT Gateway", ), - "[Asset Inventory][AWS][VPC ACL] assets found": AssetInventoryCase( - category="identity", - sub_category="authorization", - type_="acl", - sub_type="s3-access-control-list", + "[Asset Inventory][AWS][EC2 Network ACL] assets found": AssetInventoryCase( + category="Networking", + type_="AWS EC2 Network ACL", ), "[Asset Inventory][AWS][EC2 Network Interface] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="network", - type_="interface", - sub_type="ec2-network-interface", + category="Networking", + type_="AWS EC2 Network Interface", ), "[Asset Inventory][AWS][Security Group] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="network", - type_="firewall", - sub_type="ec2-security-group", + category="Firewall", + type_="AWS EC2 Security Group", ), "[Asset Inventory][AWS][EC2 Subnet] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="network", - type_="subnet", - sub_type="ec2-subnet", + category="Networking", + type_="AWS EC2 Subnet", ), "[Asset Inventory][AWS][Transit Gateway] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="network", - type_="virtual-network", - sub_type="transit-gateway", + category="Gateway", + type_="AWS Transit Gateway", ), "[Asset Inventory][AWS][Transit Gateway Attachment] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="network", - type_="virtual-network", - sub_type="transit-gateway-attachment", + category="Gateway", + type_="AWS Transit Gateway Attachment", ), "[Asset Inventory][AWS][VPC Peering Connection] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="network", - type_="peering", - sub_type="vpc-peering-connection", + category="Networking", + type_="AWS VPC Peering Connection", ), "[Asset Inventory][AWS][VPC] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="network", - type_="virtual-network", - sub_type="vpc", + category="Networking", + type_="AWS VPC", ), "[Asset Inventory][AWS][RDS] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="database", - type_="relational", - sub_type="rds-instance", + category="Database", + type_="AWS RDS Instance", ), "[Asset Inventory][AWS][S3] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="storage", - type_="object-storage", - sub_type="s3-bucket", + category="Storage Bucket", + type_="AWS S3 Bucket", ), "[Asset Inventory][AWS][SNS Topic] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="messaging", - type_="notification-service", - sub_type="sns-topic", + category="Messaging Service", + type_="AWS SNS Topic", ), } diff --git a/tests/product/tests/data/azure_asset_inventory/test_cases.py b/tests/product/tests/data/azure_asset_inventory/test_cases.py index ae5162b05d..5bb20df53e 100644 --- a/tests/product/tests/data/azure_asset_inventory/test_cases.py +++ b/tests/product/tests/data/azure_asset_inventory/test_cases.py @@ -6,93 +6,75 @@ test_cases = { "[Asset Inventory][Azure][Azure App Service] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="application", - type_="web-application", - sub_type="azure-app-service", + category="Web Service", + type_="Azure App Service", ), "[Asset Inventory][Azure][Azure Virtual Machine] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="compute", - type_="virtual-machine", - sub_type="azure-virtual-machine", + category="Host", + type_="Azure Virtual Machine", ), "[Asset Inventory][Azure][Azure Container Registry] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="container", - type_="registry", - sub_type="azure-container-registry", + category="Container Registry", + type_="Azure Container Registry", ), + "[Asset Inventory][Azure][Azure Cosmos DB Account] assets found": AssetInventoryCase( + category="Infrastructure", + type_="Azure Cosmos DB Account", + ), + # "[Asset Inventory][Azure][Azure Cosmos DB SQL Database] assets found": AssetInventoryCase( + # category="Infrastructure", + # type_="Azure Cosmos DB SQL Database", + # ), "[Asset Inventory][Azure][Azure SQL Database] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="database", - type_="relational", - sub_type="azure-sql-database", + category="Database", + type_="Azure SQL Database", ), "[Asset Inventory][Azure][Azure SQL Server] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="database", - type_="relational", - sub_type="azure-sql-server", + category="Database", + type_="Azure SQL Server", + ), + "[Asset Inventory][Azure][Azure Principal] assets found": AssetInventoryCase( + category="Identity", + type_="Azure Principal", ), "[Asset Inventory][Azure][Azure Elastic Pool] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="database", - type_="scalability", - sub_type="azure-elastic-pool", + category="Database", + type_="Azure Elastic Pool", ), "[Asset Inventory][Azure][Azure Subscription] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="management", - type_="cloud-account", - sub_type="azure-subscription", + category="Access Management", + type_="Azure Subscription", ), "[Asset Inventory][Azure][Azure Tenant] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="management", - type_="cloud-account", - sub_type="azure-tenant", + category="Access Management", + type_="Azure Tenant", ), "[Asset Inventory][Azure][Azure Resource Group] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="management", - type_="resource-group", - sub_type="azure-resource-group", + category="Access Management", + type_="Azure Resource Group", ), "[Asset Inventory][Azure][Azure Disk] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="storage", - type_="disk", - sub_type="azure-disk", + category="Volume", + type_="Azure Disk", ), "[Asset Inventory][Azure][Azure Snapshot] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="storage", - type_="snapshot", - sub_type="azure-snapshot", + category="Snapshot", + type_="Azure Snapshot", ), "[Asset Inventory][Azure][Azure Storage Account] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="storage", - type_="storage", - sub_type="azure-storage-account", + category="Private Endpoint", + type_="Azure Storage Account", ), "[Asset Inventory][Azure][Azure Storage Queue] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="application-integration", - type_="message-queue", - sub_type="azure-storage-queue", + category="Messaging Service", + type_="Azure Storage Queue", ), "[Asset Inventory][Azure][Azure Storage Queue Service] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="application-integration", - type_="message-queue", - sub_type="azure-storage-queue-service", + category="Messaging Service", + type_="Azure Storage Queue Service", ), "[Asset Inventory][Azure][Azure Storage Blob Service] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="storage", - type_="object-storage", - sub_type="azure-storage-blob-service", + category="Storage Bucket", + type_="Azure Storage Blob Service", ), } diff --git a/tests/product/tests/data/gcp_asset_inventory/test_cases.py b/tests/product/tests/data/gcp_asset_inventory/test_cases.py index 4b9f83f039..f3541b1c6f 100644 --- a/tests/product/tests/data/gcp_asset_inventory/test_cases.py +++ b/tests/product/tests/data/gcp_asset_inventory/test_cases.py @@ -6,87 +6,59 @@ test_cases = { "[Asset Inventory][GCP][Service Account] assets found": AssetInventoryCase( - category="identity", - sub_category="service-identity", - type_="service-account", - sub_type="gcp-service-account", + category="Access Management", + type_="GCP Service Account", ), "[Asset Inventory][GCP][Service Account Key] assets found": AssetInventoryCase( - category="identity", - sub_category="service-identity", - type_="service-account-key", - sub_type="gcp-service-account-key", + category="Access Management", + type_="GCP Service Account Key", ), "[Asset Inventory][GCP][Instance] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="compute", - type_="virtual-machine", - sub_type="gcp-instance", + category="Host", + type_="GCP Compute Instance", ), "[Asset Inventory][GCP][Subnet] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="network", - type_="subnet", - sub_type="gcp-subnet", + category="Subnet", + type_="GCP Subnet", ), "[Asset Inventory][GCP][Project] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="management", - type_="cloud-account", - sub_type="gcp-project", + category="Account", + type_="GCP Project", ), # "[Asset Inventory][GCP][Organization] assets found": AssetInventoryCase( - # category="infrastructure", - # sub_category="management", + # category="Infrastructure", # type_="cloud-account", - # sub_type="gcp-organization", # ), # "[Asset Inventory][GCP][Folder] assets found": AssetInventoryCase( - # category="infrastructure", - # sub_category="management", + # category="Infrastructure", # type_="resource-hierarchy", - # sub_type="gcp-folder", # ), "[Asset Inventory][GCP][Bucket] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="storage", - type_="object-storage", - sub_type="gcp-bucket", + category="Storage Bucket", + type_="GCP Bucket", ), "[Asset Inventory][GCP][Firewall] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="network", - type_="firewall", - sub_type="gcp-firewall", + category="Firewall", + type_="GCP Firewall", ), # "[Asset Inventory][GCP][GKE Cluster] assets found": AssetInventoryCase( - # category="infrastructure", - # sub_category="container", + # category="Infrastructure", # type_="orchestration", - # sub_type="gcp-gke-cluster", # ), # "[Asset Inventory][GCP][Forwarding Rule] assets found": AssetInventoryCase( - # category="infrastructure", - # sub_category="network", + # category="Infrastructure", # type_="load-balancing", - # sub_type="gcp-forwarding-rule", # ), "[Asset Inventory][GCP][IAM Role] assets found": AssetInventoryCase( - category="identity", - sub_category="access-management", - type_="iam-role", - sub_type="gcp-iam-role", + category="Service Usage Technology", + type_="GCP IAM Role", ), # "[Asset Inventory][GCP][Cloud Function] assets found": AssetInventoryCase( - # category="infrastructure", - # sub_category="serverless", + # category="Infrastructure", # type_="function", - # sub_type="gcp-cloud-function", # ), "[Asset Inventory][GCP][Cloud Run Service] assets found": AssetInventoryCase( - category="infrastructure", - sub_category="container", - type_="serverless", - sub_type="gcp-cloud-run-service", + category="Container Service", + type_="GCP Cloud Run Service", ), } diff --git a/tests/product/tests/test_aws_asset_inventory.py b/tests/product/tests/test_aws_asset_inventory.py index 3b359b7f6f..1fa0eefb80 100644 --- a/tests/product/tests/test_aws_asset_inventory.py +++ b/tests/product/tests/test_aws_asset_inventory.py @@ -1,6 +1,6 @@ """ AWS Asset Inventory Elastic Compute Cloud verification. -This module verifies presence and correctness of retrieved assets +This module verifies presence and correctness of retrieved entities """ from datetime import datetime, timedelta @@ -16,37 +16,33 @@ def test_aws_asset_inventory( asset_inventory_client, category, - sub_category, type_, - sub_type, ): """ - This data driven test verifies assets published by cloudbeat agent. + This data driven test verifies entities published by cloudbeat agent. """ - assets = get_ES_assets( + entities = get_ES_assets( asset_inventory_client, timeout=10, category=category, - sub_category=sub_category, type_=type_, - sub_type=sub_type, exec_timestamp=datetime.utcnow() - timedelta(minutes=30), ) - assert assets is not None, "Expected a list of assets, got None" - assert isinstance(assets, list) and len(assets) > 0, "Expected the list to be non-empty" - for asset in assets: - assert asset.cloud, "Expected .cloud section" - assert asset.cloud.provider == "aws", f'Expected "aws" provider, got {asset.cloud.provider}' - assert len(asset.asset.id) > 0, "Expected .asset.id list to contain an ID" - assert len(asset.asset.id[0]) > 0, "Expected the ID to be non-empty" - assert asset.asset.raw, "Expected the resource under .asset.raw" + assert entities is not None, "Expected a list of entities, got None" + assert isinstance(entities, list) and len(entities) > 0, "Expected the list to be non-empty" + for entity in entities: + assert entity.cloud, "Expected .cloud section" + assert entity.cloud.Provider == "aws", f'Expected "aws" provider, got {entity.cloud.Provider}' + assert len(entity.entity.id) > 0, "Expected .entity.id list to contain an ID" + assert len(entity.entity.id[0]) > 0, "Expected the ID to be non-empty" + assert entity.Attributes, "Expected the resource under .Attributes" register_params( test_aws_asset_inventory, Parameters( - ("category", "sub_category", "type_", "sub_type"), + ("category", "type_"), [*aws_tc.test_cases.values()], ids=[*aws_tc.test_cases.keys()], ), diff --git a/tests/product/tests/test_azure_asset_inventory.py b/tests/product/tests/test_azure_asset_inventory.py index b249e47f1c..7b957fe7eb 100644 --- a/tests/product/tests/test_azure_asset_inventory.py +++ b/tests/product/tests/test_azure_asset_inventory.py @@ -1,6 +1,6 @@ """ Azure Asset Inventory Elastic Compute Cloud verification. -This module verifies presence and correctness of retrieved assets +This module verifies presence and correctness of retrieved entities """ from datetime import datetime, timedelta @@ -17,37 +17,33 @@ def test_azure_asset_inventory( asset_inventory_client, category, - sub_category, type_, - sub_type, ): """ - This data driven test verifies assets published by cloudbeat agent. + This data driven test verifies entities published by cloudbeat agent. """ - assets = get_ES_assets( + entities = get_ES_assets( asset_inventory_client, timeout=10, category=category, - sub_category=sub_category, type_=type_, - sub_type=sub_type, exec_timestamp=datetime.utcnow() - timedelta(minutes=30), ) - assert assets is not None, "Expected a list of assets, got None" - assert isinstance(assets, list) and len(assets) > 0, "Expected the list to be non-empty" - for asset in assets: - assert asset.cloud, "Expected .cloud section" - assert asset.cloud.provider == "azure", f'Expected "aws" provider, got {asset.cloud.provider}' - assert len(asset.asset.id) > 0, "Expected .asset.id list to contain an ID" - assert len(asset.asset.id[0]) > 0, "Expected the ID to be non-empty" - assert asset.asset.raw, "Expected the resource under .asset.raw" + assert entities is not None, "Expected a list of entities, got None" + assert isinstance(entities, list) and len(entities) > 0, "Expected the list to be non-empty" + for entity in entities: + assert entity.cloud, "Expected .cloud section" + assert entity.cloud.Provider == "azure", f'Expected "aws" provider, got {entity.cloud.Provider}' + assert len(entity.entity.id) > 0, "Expected .entity.id list to contain an ID" + assert len(entity.entity.id[0]) > 0, "Expected the ID to be non-empty" + assert entity.Attributes, "Expected the resource under .Attributes" register_params( test_azure_asset_inventory, Parameters( - ("category", "sub_category", "type_", "sub_type"), + ("category", "type_"), [*azure_tc.test_cases.values()], ids=[*azure_tc.test_cases.keys()], ), diff --git a/tests/product/tests/test_gcp_asset_inventory.py b/tests/product/tests/test_gcp_asset_inventory.py index 0706ff73cf..f24c260b88 100644 --- a/tests/product/tests/test_gcp_asset_inventory.py +++ b/tests/product/tests/test_gcp_asset_inventory.py @@ -1,6 +1,6 @@ """ GCP Asset Inventory Elastic Compute Cloud verification. -This module verifies presence and correctness of retrieved assets +This module verifies presence and correctness of retrieved entities """ from datetime import datetime, timedelta @@ -16,38 +16,34 @@ def test_gcp_asset_inventory( asset_inventory_client, category, - sub_category, type_, - sub_type, ): """ - This data driven test verifies assets published by cloudbeat agent. + This data driven test verifies entities published by cloudbeat agent. """ # pylint: disable=duplicate-code - assets = get_ES_assets( + entities = get_ES_assets( asset_inventory_client, timeout=10, category=category, - sub_category=sub_category, type_=type_, - sub_type=sub_type, exec_timestamp=datetime.utcnow() - timedelta(minutes=30), ) - assert assets is not None, "Expected a list of assets, got None" - assert isinstance(assets, list) and len(assets) > 0, "Expected the list to be non-empty" - for asset in assets: - assert asset.cloud, "Expected .cloud section" - assert asset.cloud.provider == "gcp", f'Expected "gcp" provider, got {asset.cloud.provider}' - assert len(asset.asset.id) > 0, "Expected .asset.id list to contain an ID" - assert len(asset.asset.id[0]) > 0, "Expected the ID to be non-empty" - assert asset.asset.raw, "Expected the resource under .asset.raw" + assert entities is not None, "Expected a list of entities, got None" + assert isinstance(entities, list) and len(entities) > 0, "Expected the list to be non-empty" + for entity in entities: + assert entity.cloud, "Expected .cloud section" + assert entity.cloud.Provider == "gcp", f'Expected "gcp" provider, got {entity.cloud.Provider}' + assert len(entity.entity.id) > 0, "Expected .entity.id list to contain an ID" + assert len(entity.entity.id[0]) > 0, "Expected the ID to be non-empty" + assert entity.Attributes, "Expected the resource under .Attributes" register_params( test_gcp_asset_inventory, Parameters( - ("category", "sub_category", "type_", "sub_type"), + ("category", "type_"), [*gcp_tc.test_cases.values()], ids=[*gcp_tc.test_cases.keys()], ),