nuvu-scan 2.1.2__tar.gz → 2.1.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/.gitignore +3 -0
- nuvu_scan-2.1.6/DEVELOPMENT_STATUS.md +216 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/PKG-INFO +5 -1
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/README.md +4 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/cli/commands/scan.py +2 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/core/base.py +10 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/core/providers/aws/aws_scanner.py +135 -6
- nuvu_scan-2.1.6/nuvu_scan/core/providers/aws/collectors/apigateway.py +197 -0
- nuvu_scan-2.1.6/nuvu_scan/core/providers/aws/collectors/backup.py +252 -0
- nuvu_scan-2.1.6/nuvu_scan/core/providers/aws/collectors/cloudfront.py +132 -0
- nuvu_scan-2.1.6/nuvu_scan/core/providers/aws/collectors/cloudtrail.py +189 -0
- nuvu_scan-2.1.6/nuvu_scan/core/providers/aws/collectors/cloudwatch.py +163 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/core/providers/aws/collectors/cost_explorer.py +90 -0
- nuvu_scan-2.1.6/nuvu_scan/core/providers/aws/collectors/dynamodb.py +236 -0
- nuvu_scan-2.1.6/nuvu_scan/core/providers/aws/collectors/ec2.py +572 -0
- nuvu_scan-2.1.6/nuvu_scan/core/providers/aws/collectors/ecs.py +243 -0
- nuvu_scan-2.1.6/nuvu_scan/core/providers/aws/collectors/eks.py +246 -0
- nuvu_scan-2.1.6/nuvu_scan/core/providers/aws/collectors/elasticache.py +325 -0
- nuvu_scan-2.1.6/nuvu_scan/core/providers/aws/collectors/elb.py +198 -0
- nuvu_scan-2.1.6/nuvu_scan/core/providers/aws/collectors/iam.py +593 -0
- nuvu_scan-2.1.6/nuvu_scan/core/providers/aws/collectors/kinesis.py +174 -0
- nuvu_scan-2.1.6/nuvu_scan/core/providers/aws/collectors/kms.py +186 -0
- nuvu_scan-2.1.6/nuvu_scan/core/providers/aws/collectors/lakeformation.py +303 -0
- nuvu_scan-2.1.6/nuvu_scan/core/providers/aws/collectors/lambda_collector.py +224 -0
- nuvu_scan-2.1.6/nuvu_scan/core/providers/aws/collectors/misc_services.py +320 -0
- nuvu_scan-2.1.6/nuvu_scan/core/providers/aws/collectors/rds.py +405 -0
- nuvu_scan-2.1.6/nuvu_scan/core/providers/aws/collectors/route53.py +183 -0
- nuvu_scan-2.1.6/nuvu_scan/core/providers/aws/collectors/secrets.py +178 -0
- nuvu_scan-2.1.6/nuvu_scan/core/providers/aws/collectors/security_services.py +329 -0
- nuvu_scan-2.1.6/nuvu_scan/core/providers/aws/collectors/sns_sqs.py +284 -0
- nuvu_scan-2.1.6/nuvu_scan/core/providers/aws/collectors/vpc_costs.py +296 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/pyproject.toml +1 -1
- nuvu_scan-2.1.2/DEVELOPMENT_STATUS.md +0 -377
- nuvu_scan-2.1.2/nuvu_scan/core/providers/aws/collectors/iam.py +0 -277
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/.cursorrules +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/.github/workflows/ci.yml +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/.github/workflows/release.yml +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/.pre-commit-config.yaml +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/CONTRIBUTING.md +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/Makefile +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/RELEASE.md +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/__init__.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/cli/__init__.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/cli/commands/__init__.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/cli/formatters/__init__.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/cli/formatters/csv.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/cli/formatters/html.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/cli/formatters/json.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/cli/main.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/core/__init__.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/core/analyzers/__init__.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/core/models/__init__.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/core/providers/__init__.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/core/providers/aws/__init__.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/core/providers/aws/collectors/__init__.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/core/providers/aws/collectors/athena.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/core/providers/aws/collectors/glue.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/core/providers/aws/collectors/mwaa.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/core/providers/aws/collectors/redshift.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/core/providers/aws/collectors/s3.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/core/providers/gcp/__init__.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/core/providers/gcp/collectors/__init__.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/core/providers/gcp/collectors/bigquery.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/core/providers/gcp/collectors/billing.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/core/providers/gcp/collectors/dataproc.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/core/providers/gcp/collectors/gcs.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/core/providers/gcp/collectors/gemini.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/core/providers/gcp/collectors/iam.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/core/providers/gcp/collectors/pubsub.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/nuvu_scan/core/providers/gcp/gcp_scanner.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/tests/__init__.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/tests/test_base.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/tests/test_cli.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/tests/test_formatters.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/tests/test_push_payload.py +0 -0
- {nuvu_scan-2.1.2 → nuvu_scan-2.1.6}/tests/test_scanners.py +0 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Nuvu Scan - Development Status
|
|
2
|
+
|
|
3
|
+
**Multi-Cloud Data Asset Scanner** - Open-source scanner designed to discover and inventory cloud data assets across AWS, GCP, Azure, and Databricks.
|
|
4
|
+
|
|
5
|
+
> **Note**: nuvu-scan is a read-only scanner that collects asset metadata. For governance, policy enforcement, and decision-making, see [nuvu-cloud](https://github.com/nuvudev/nuvu-cloud).
|
|
6
|
+
|
|
7
|
+
## ✅ Available Collectors (v2.1.0)
|
|
8
|
+
|
|
9
|
+
### AWS Collectors (18 collectors)
|
|
10
|
+
|
|
11
|
+
| Collector | Command Flag | What It Scans | Key Metrics |
|
|
12
|
+
|-----------|--------------|---------------|-------------|
|
|
13
|
+
| **S3** | `s3` | Buckets, policies, encryption | Size, public access, versioning |
|
|
14
|
+
| **Glue** | `glue` | Databases, tables, crawlers, jobs, connections | Table counts, crawl status, job runs |
|
|
15
|
+
| **Athena** | `athena` | Workgroups, query history | Query stats, failure rates |
|
|
16
|
+
| **Redshift** | `redshift` | Clusters, serverless, snapshots, datashares, reserved nodes | CPU, connections, WLM, costs |
|
|
17
|
+
| **IAM** | `iam` | Roles, users, groups, access keys | Permissions, MFA, key age, last used |
|
|
18
|
+
| **MWAA** | `mwaa` | Apache Airflow environments | Environment class, worker counts |
|
|
19
|
+
| **EC2/VPC** | `ec2` | Security groups, VPCs, instances, EBS volumes, Elastic IPs | Open ports, public IPs, volume encryption |
|
|
20
|
+
| **KMS** | `kms` | Customer-managed encryption keys | Rotation status, key state |
|
|
21
|
+
| **RDS** | `rds` | RDS instances, Aurora clusters, snapshots | Encryption, multi-AZ, backup retention |
|
|
22
|
+
| **DynamoDB** | `dynamodb` | DynamoDB tables | PITR, encryption, capacity mode |
|
|
23
|
+
| **Lambda** | `lambda` | Lambda functions | Runtime, code size, VPC config |
|
|
24
|
+
| **Secrets Manager** | `secrets` | Secrets | Rotation, last accessed, age |
|
|
25
|
+
| **AWS Backup** | `backup` | Backup vaults, backup plans | Recovery points, lifecycle |
|
|
26
|
+
| **EKS** | `eks` | EKS clusters, node groups | K8s version, endpoint access |
|
|
27
|
+
| **SNS/SQS** | `sns_sqs` | SNS topics, SQS queues | Encryption, DLQ, message counts |
|
|
28
|
+
| **Lake Formation** | `lakeformation` | Data lake settings, permissions, LF-Tags | Permission grants, admin count |
|
|
29
|
+
| **CloudTrail** | `cloudtrail` | CloudTrail trails | Multi-region, encryption, logging status |
|
|
30
|
+
| **CloudWatch** | `cloudwatch` | CloudWatch log groups | Retention, encryption, size |
|
|
31
|
+
|
|
32
|
+
### GCP Collectors (6 collectors)
|
|
33
|
+
|
|
34
|
+
| Collector | Command Flag | What It Scans | Key Metrics |
|
|
35
|
+
|-----------|--------------|---------------|-------------|
|
|
36
|
+
| **GCS** | `gcs` | Cloud Storage buckets | Size, public access, lifecycle |
|
|
37
|
+
| **BigQuery** | `bigquery` | Datasets, tables, query history | Table sizes, query costs |
|
|
38
|
+
| **Dataproc** | `dataproc` | Dataproc clusters | Cluster config, job history |
|
|
39
|
+
| **Pub/Sub** | `pubsub` | Topics, subscriptions | Message counts |
|
|
40
|
+
| **IAM** | `iam` | Service accounts | Roles, permissions |
|
|
41
|
+
| **Gemini** | `gemini` | Gemini API usage | API costs |
|
|
42
|
+
|
|
43
|
+
## 📋 Usage
|
|
44
|
+
|
|
45
|
+
### Basic Scan
|
|
46
|
+
```bash
|
|
47
|
+
# Scan all AWS collectors
|
|
48
|
+
nuvu-scan aws
|
|
49
|
+
|
|
50
|
+
# Scan all GCP collectors
|
|
51
|
+
nuvu-scan gcp --credentials /path/to/key.json
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Selective Scanning
|
|
55
|
+
```bash
|
|
56
|
+
# Scan specific collectors
|
|
57
|
+
nuvu-scan aws --collectors s3,rds,iam,kms
|
|
58
|
+
|
|
59
|
+
# List available collectors
|
|
60
|
+
nuvu-scan aws --list-collectors
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Output Formats
|
|
64
|
+
```bash
|
|
65
|
+
# HTML report (default)
|
|
66
|
+
nuvu-scan aws -o report.html
|
|
67
|
+
|
|
68
|
+
# JSON output
|
|
69
|
+
nuvu-scan aws -o assets.json
|
|
70
|
+
|
|
71
|
+
# CSV output
|
|
72
|
+
nuvu-scan aws -o assets.csv
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Push to Nuvu Cloud
|
|
76
|
+
```bash
|
|
77
|
+
# Push results to nuvu-cloud for governance
|
|
78
|
+
nuvu-scan aws --push --api-key YOUR_API_KEY
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 🔒 IAM Permissions
|
|
82
|
+
|
|
83
|
+
The complete IAM policy is in `aws-iam-policy.json` (60+ permission statements).
|
|
84
|
+
|
|
85
|
+
### Permission Categories
|
|
86
|
+
|
|
87
|
+
| Category | Services | Example Actions |
|
|
88
|
+
|----------|----------|-----------------|
|
|
89
|
+
| **Storage** | S3, EBS | `s3:GetBucket*`, `ec2:DescribeVolumes` |
|
|
90
|
+
| **Compute** | EC2, Lambda, EKS | `ec2:DescribeInstances`, `lambda:ListFunctions` |
|
|
91
|
+
| **Database** | RDS, DynamoDB, Redshift | `rds:DescribeDB*`, `dynamodb:DescribeTable` |
|
|
92
|
+
| **Data Analytics** | Glue, Athena, Lake Formation | `glue:GetTables`, `athena:ListWorkGroups` |
|
|
93
|
+
| **Security** | IAM, KMS, Secrets Manager | `iam:ListRoles`, `kms:DescribeKey` |
|
|
94
|
+
| **Networking** | VPC, Security Groups | `ec2:DescribeSecurityGroups`, `ec2:DescribeVpcs` |
|
|
95
|
+
| **Messaging** | SNS, SQS | `sns:GetTopicAttributes`, `sqs:GetQueueAttributes` |
|
|
96
|
+
| **Observability** | CloudWatch, CloudTrail | `logs:DescribeLogGroups`, `cloudtrail:DescribeTrails` |
|
|
97
|
+
| **Resilience** | AWS Backup | `backup:ListBackupVaults`, `backup:ListBackupPlans` |
|
|
98
|
+
| **Cost** | Cost Explorer | `ce:GetCostAndUsage` |
|
|
99
|
+
|
|
100
|
+
All permissions are **read-only** following the principle of least privilege.
|
|
101
|
+
|
|
102
|
+
## 📊 Asset Types Collected
|
|
103
|
+
|
|
104
|
+
### Compute
|
|
105
|
+
- EC2 instances, EBS volumes, Elastic IPs
|
|
106
|
+
- Lambda functions
|
|
107
|
+
- EKS clusters, node groups
|
|
108
|
+
|
|
109
|
+
### Storage
|
|
110
|
+
- S3 buckets
|
|
111
|
+
- EBS volumes
|
|
112
|
+
|
|
113
|
+
### Databases
|
|
114
|
+
- RDS instances, Aurora clusters
|
|
115
|
+
- DynamoDB tables
|
|
116
|
+
- Redshift clusters (provisioned & serverless)
|
|
117
|
+
|
|
118
|
+
### Data Catalog
|
|
119
|
+
- Glue databases, tables, crawlers, jobs
|
|
120
|
+
- Lake Formation settings, permissions, LF-Tags
|
|
121
|
+
|
|
122
|
+
### Security
|
|
123
|
+
- IAM roles, users, groups, access keys
|
|
124
|
+
- KMS keys
|
|
125
|
+
- Secrets Manager secrets
|
|
126
|
+
- Security groups, VPCs
|
|
127
|
+
|
|
128
|
+
### Observability
|
|
129
|
+
- CloudWatch log groups
|
|
130
|
+
- CloudTrail trails
|
|
131
|
+
|
|
132
|
+
### Messaging
|
|
133
|
+
- SNS topics
|
|
134
|
+
- SQS queues
|
|
135
|
+
|
|
136
|
+
### Backup
|
|
137
|
+
- AWS Backup vaults and plans
|
|
138
|
+
|
|
139
|
+
## 🏷️ Risk Flags
|
|
140
|
+
|
|
141
|
+
nuvu-scan identifies potential issues by flagging assets:
|
|
142
|
+
|
|
143
|
+
| Category | Example Flags |
|
|
144
|
+
|----------|---------------|
|
|
145
|
+
| **Security** | `unencrypted`, `publicly_accessible`, `mfa_disabled`, `open_to_internet` |
|
|
146
|
+
| **Access** | `unused_role`, `old_key`, `overly_permissive`, `public_access` |
|
|
147
|
+
| **Operations** | `no_backups`, `stale_crawler`, `deprecated_runtime`, `logging_disabled` |
|
|
148
|
+
| **Cost** | `unattached_volume`, `old_snapshot`, `unused_eip` |
|
|
149
|
+
| **Compliance** | `no_retention_policy`, `rotation_disabled`, `pitr_disabled` |
|
|
150
|
+
|
|
151
|
+
## 🧪 Testing
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# Run tests
|
|
155
|
+
uv run pytest
|
|
156
|
+
|
|
157
|
+
# Run with coverage
|
|
158
|
+
uv run pytest --cov=nuvu_scan
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## 📦 Installation
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# From PyPI
|
|
165
|
+
pip install nuvu-scan
|
|
166
|
+
|
|
167
|
+
# From source
|
|
168
|
+
git clone https://github.com/nuvudev/nuvu-scan
|
|
169
|
+
cd nuvu-scan
|
|
170
|
+
uv sync
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## 📋 Roadmap
|
|
174
|
+
|
|
175
|
+
### Additional AWS Collectors
|
|
176
|
+
- [ ] OpenSearch collector
|
|
177
|
+
- [ ] EMR collector
|
|
178
|
+
- [ ] SageMaker collector
|
|
179
|
+
- [ ] Bedrock collector
|
|
180
|
+
- [ ] MSK (Kafka) collector
|
|
181
|
+
- [ ] Kinesis collector
|
|
182
|
+
- [ ] Step Functions collector
|
|
183
|
+
- [ ] EventBridge collector
|
|
184
|
+
|
|
185
|
+
### Additional GCP Collectors
|
|
186
|
+
- [ ] Cloud SQL collector
|
|
187
|
+
- [ ] Cloud Spanner collector
|
|
188
|
+
- [ ] Bigtable collector
|
|
189
|
+
- [ ] Firestore collector
|
|
190
|
+
- [ ] Vertex AI collector
|
|
191
|
+
- [ ] Dataflow collector
|
|
192
|
+
- [ ] Cloud Composer collector
|
|
193
|
+
|
|
194
|
+
### Azure Provider
|
|
195
|
+
- [ ] Blob Storage collector
|
|
196
|
+
- [ ] Data Lake collector
|
|
197
|
+
- [ ] Synapse collector
|
|
198
|
+
- [ ] Azure Databricks collector
|
|
199
|
+
|
|
200
|
+
### Databricks Provider
|
|
201
|
+
- [ ] Workspace discovery
|
|
202
|
+
- [ ] Unity Catalog
|
|
203
|
+
|
|
204
|
+
### Enhancements
|
|
205
|
+
- [ ] Parallel collection for faster scans
|
|
206
|
+
- [ ] Progress bars with ETA
|
|
207
|
+
- [ ] Incremental scanning (delta detection)
|
|
208
|
+
- [ ] Schema-level inventory (Redshift Data API)
|
|
209
|
+
|
|
210
|
+
## 🤝 Contributing
|
|
211
|
+
|
|
212
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
213
|
+
|
|
214
|
+
## 📄 License
|
|
215
|
+
|
|
216
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nuvu-scan
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.6
|
|
4
4
|
Summary: Multi-Cloud Data Asset Control - Discover, govern, and optimize your cloud data assets across AWS and GCP
|
|
5
5
|
Project-URL: Homepage, https://nuvu.dev
|
|
6
6
|
Project-URL: Documentation, https://github.com/nuvudev/nuvu-scan#readme
|
|
@@ -48,6 +48,10 @@ Description-Content-Type: text/markdown
|
|
|
48
48
|
|
|
49
49
|
# Nuvu Scan
|
|
50
50
|
|
|
51
|
+
[](https://pypistats.org/packages/nuvu-scan)
|
|
52
|
+
[](https://github.com/nuvudev/nuvu-scan)
|
|
53
|
+
[](https://pypi.org/project/nuvu-scan/)
|
|
54
|
+
|
|
51
55
|
**Take Control of Your Cloud Data Estate**
|
|
52
56
|
Discover, govern, and optimize your cloud data assets across **AWS and GCP** — reduce wasted spend, enforce compliance, and gain full visibility into unused, idle, or risky resources.
|
|
53
57
|
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Nuvu Scan
|
|
2
2
|
|
|
3
|
+
[](https://pypistats.org/packages/nuvu-scan)
|
|
4
|
+
[](https://github.com/nuvudev/nuvu-scan)
|
|
5
|
+
[](https://pypi.org/project/nuvu-scan/)
|
|
6
|
+
|
|
3
7
|
**Take Control of Your Cloud Data Estate**
|
|
4
8
|
Discover, govern, and optimize your cloud data assets across **AWS and GCP** — reduce wasted spend, enforce compliance, and gain full visibility into unused, idle, or risky resources.
|
|
5
9
|
|
|
@@ -331,6 +331,7 @@ def scan_command(
|
|
|
331
331
|
"total_cost_estimate_usd": result.total_cost_estimate_usd,
|
|
332
332
|
"scan_regions": scan_regions if scan_regions else None,
|
|
333
333
|
"scan_all_regions": not bool(region),
|
|
334
|
+
"summary": result.summary, # Include cost data from Cost Explorer
|
|
334
335
|
"assets": [
|
|
335
336
|
{
|
|
336
337
|
"provider": asset.provider,
|
|
@@ -353,6 +354,7 @@ def scan_command(
|
|
|
353
354
|
"risk_flags": asset.risk_flags,
|
|
354
355
|
"ownership_confidence": asset.ownership_confidence or "unknown",
|
|
355
356
|
"suggested_owner": asset.suggested_owner,
|
|
357
|
+
"underlying_cloud_account_id": asset.underlying_cloud_account_id,
|
|
356
358
|
}
|
|
357
359
|
for asset in result.assets
|
|
358
360
|
],
|
|
@@ -28,6 +28,16 @@ class NormalizedCategory(str, Enum):
|
|
|
28
28
|
DATABASE = "database"
|
|
29
29
|
SECURITY = "security"
|
|
30
30
|
BILLING = "billing"
|
|
31
|
+
# Additional categories for comprehensive coverage
|
|
32
|
+
NETWORKING = "networking" # VPC, Load Balancers, Route 53, CloudFront
|
|
33
|
+
CACHING = "caching" # ElastiCache, DAX
|
|
34
|
+
CONTAINER = "container" # ECS, ECR, Fargate
|
|
35
|
+
SERVERLESS = "serverless" # Lambda, Step Functions, API Gateway
|
|
36
|
+
STORAGE = "storage" # EBS, EFS, FSx
|
|
37
|
+
MESSAGING = "messaging" # SNS, SQS, EventBridge
|
|
38
|
+
OBSERVABILITY = "observability" # CloudWatch, X-Ray
|
|
39
|
+
RESILIENCE = "resilience" # Backup, DR
|
|
40
|
+
GOVERNANCE = "governance" # Config, CloudTrail, Organizations
|
|
31
41
|
|
|
32
42
|
|
|
33
43
|
@dataclass
|
|
@@ -15,15 +15,37 @@ from nuvu_scan.core.base import (
|
|
|
15
15
|
ScanConfig,
|
|
16
16
|
)
|
|
17
17
|
|
|
18
|
+
from .collectors.apigateway import APIGatewayCollector
|
|
18
19
|
from .collectors.athena import AthenaCollector
|
|
20
|
+
from .collectors.backup import BackupCollector
|
|
21
|
+
from .collectors.cloudfront import CloudFrontCollector
|
|
22
|
+
from .collectors.cloudtrail import CloudTrailCollector
|
|
23
|
+
from .collectors.cloudwatch import CloudWatchLogsCollector
|
|
19
24
|
from .collectors.cost_explorer import CostExplorerCollector
|
|
25
|
+
from .collectors.dynamodb import DynamoDBCollector
|
|
26
|
+
from .collectors.ec2 import EC2Collector
|
|
27
|
+
from .collectors.ecs import ECSCollector
|
|
28
|
+
from .collectors.eks import EKSCollector
|
|
29
|
+
from .collectors.elasticache import ElastiCacheCollector
|
|
30
|
+
from .collectors.elb import ELBCollector
|
|
20
31
|
from .collectors.glue import GlueCollector
|
|
21
32
|
from .collectors.iam import IAMCollector
|
|
33
|
+
from .collectors.kinesis import KinesisFirehoseCollector
|
|
34
|
+
from .collectors.kms import KMSCollector
|
|
35
|
+
from .collectors.lakeformation import LakeFormationCollector
|
|
36
|
+
from .collectors.lambda_collector import LambdaCollector
|
|
37
|
+
from .collectors.misc_services import EFSCollector, StepFunctionsCollector, SystemsManagerCollector
|
|
22
38
|
from .collectors.mwaa import MWAACollector
|
|
39
|
+
from .collectors.rds import RDSCollector
|
|
23
40
|
from .collectors.redshift import RedshiftCollector
|
|
24
|
-
|
|
25
|
-
# Import collectors
|
|
41
|
+
from .collectors.route53 import Route53Collector
|
|
26
42
|
from .collectors.s3 import S3Collector
|
|
43
|
+
from .collectors.secrets import SecretsManagerCollector
|
|
44
|
+
from .collectors.security_services import SecurityServicesCollector
|
|
45
|
+
from .collectors.sns_sqs import SNSSQSCollector
|
|
46
|
+
|
|
47
|
+
# New collectors for comprehensive coverage
|
|
48
|
+
from .collectors.vpc_costs import VPCCostsCollector
|
|
27
49
|
|
|
28
50
|
|
|
29
51
|
class AWSScanner(CloudProviderScan):
|
|
@@ -166,12 +188,38 @@ class AWSScanner(CloudProviderScan):
|
|
|
166
188
|
|
|
167
189
|
# Map of collector names to their classes for filtering
|
|
168
190
|
COLLECTOR_MAP = {
|
|
191
|
+
# Original collectors
|
|
169
192
|
"s3": S3Collector,
|
|
170
193
|
"glue": GlueCollector,
|
|
171
194
|
"athena": AthenaCollector,
|
|
172
195
|
"redshift": RedshiftCollector,
|
|
173
196
|
"iam": IAMCollector,
|
|
174
197
|
"mwaa": MWAACollector,
|
|
198
|
+
"ec2": EC2Collector,
|
|
199
|
+
"kms": KMSCollector,
|
|
200
|
+
"rds": RDSCollector,
|
|
201
|
+
"dynamodb": DynamoDBCollector,
|
|
202
|
+
"lambda": LambdaCollector,
|
|
203
|
+
"secrets": SecretsManagerCollector,
|
|
204
|
+
"backup": BackupCollector,
|
|
205
|
+
"eks": EKSCollector,
|
|
206
|
+
"sns_sqs": SNSSQSCollector,
|
|
207
|
+
"lakeformation": LakeFormationCollector,
|
|
208
|
+
"cloudtrail": CloudTrailCollector,
|
|
209
|
+
"cloudwatch": CloudWatchLogsCollector,
|
|
210
|
+
# New collectors for comprehensive coverage
|
|
211
|
+
"vpc_costs": VPCCostsCollector,
|
|
212
|
+
"elb": ELBCollector,
|
|
213
|
+
"elasticache": ElastiCacheCollector,
|
|
214
|
+
"route53": Route53Collector,
|
|
215
|
+
"kinesis": KinesisFirehoseCollector,
|
|
216
|
+
"apigateway": APIGatewayCollector,
|
|
217
|
+
"cloudfront": CloudFrontCollector,
|
|
218
|
+
"ecs": ECSCollector,
|
|
219
|
+
"security": SecurityServicesCollector,
|
|
220
|
+
"ssm": SystemsManagerCollector,
|
|
221
|
+
"stepfunctions": StepFunctionsCollector,
|
|
222
|
+
"efs": EFSCollector,
|
|
175
223
|
}
|
|
176
224
|
|
|
177
225
|
@classmethod
|
|
@@ -179,6 +227,22 @@ class AWSScanner(CloudProviderScan):
|
|
|
179
227
|
"""Return list of available collector names."""
|
|
180
228
|
return list(cls.COLLECTOR_MAP.keys())
|
|
181
229
|
|
|
230
|
+
# Collectors that require account_id as a parameter
|
|
231
|
+
COLLECTORS_NEEDING_ACCOUNT_ID = {
|
|
232
|
+
"vpc_costs",
|
|
233
|
+
"elb",
|
|
234
|
+
"elasticache",
|
|
235
|
+
"route53",
|
|
236
|
+
"kinesis",
|
|
237
|
+
"apigateway",
|
|
238
|
+
"cloudfront",
|
|
239
|
+
"ecs",
|
|
240
|
+
"security",
|
|
241
|
+
"ssm",
|
|
242
|
+
"stepfunctions",
|
|
243
|
+
"efs",
|
|
244
|
+
}
|
|
245
|
+
|
|
182
246
|
def _initialize_collectors(self) -> list:
|
|
183
247
|
"""Initialize AWS service collectors based on config."""
|
|
184
248
|
collectors = []
|
|
@@ -189,15 +253,22 @@ class AWSScanner(CloudProviderScan):
|
|
|
189
253
|
# Normalize to lowercase
|
|
190
254
|
requested_lower = [c.lower() for c in requested]
|
|
191
255
|
|
|
256
|
+
def create_collector(name, collector_cls):
|
|
257
|
+
"""Create a collector with appropriate parameters."""
|
|
258
|
+
if name in self.COLLECTORS_NEEDING_ACCOUNT_ID:
|
|
259
|
+
return collector_cls(self.session, self.config.regions, self.config.account_id)
|
|
260
|
+
else:
|
|
261
|
+
return collector_cls(self.session, self.config.regions)
|
|
262
|
+
|
|
192
263
|
# If no specific collectors requested, use all
|
|
193
264
|
if not requested_lower:
|
|
194
|
-
for collector_cls in self.COLLECTOR_MAP.
|
|
195
|
-
collectors.append(
|
|
265
|
+
for name, collector_cls in self.COLLECTOR_MAP.items():
|
|
266
|
+
collectors.append(create_collector(name, collector_cls))
|
|
196
267
|
else:
|
|
197
268
|
# Filter to only requested collectors
|
|
198
269
|
for name, collector_cls in self.COLLECTOR_MAP.items():
|
|
199
270
|
if name in requested_lower:
|
|
200
|
-
collectors.append(
|
|
271
|
+
collectors.append(create_collector(name, collector_cls))
|
|
201
272
|
|
|
202
273
|
# Warn about unknown collectors
|
|
203
274
|
known = set(self.COLLECTOR_MAP.keys())
|
|
@@ -243,18 +314,68 @@ class AWSScanner(CloudProviderScan):
|
|
|
243
314
|
|
|
244
315
|
end_date = datetime.utcnow()
|
|
245
316
|
start_date = end_date - timedelta(days=30)
|
|
246
|
-
|
|
317
|
+
|
|
318
|
+
# Get comprehensive cost data including Savings Plans
|
|
319
|
+
cost_data = self.cost_explorer.get_all_costs_with_savings(start_date, end_date)
|
|
320
|
+
service_costs = cost_data["service_costs"]
|
|
321
|
+
savings_plans = cost_data["savings_plans"]
|
|
247
322
|
print(" → Cost data retrieved", file=sys.stderr)
|
|
323
|
+
if savings_plans.get("total_savings", 0) > 0:
|
|
324
|
+
print(
|
|
325
|
+
f" → Savings Plans: ${savings_plans['total_savings']:.2f} saved ({savings_plans['utilization_percent']:.1f}% utilization)",
|
|
326
|
+
file=sys.stderr,
|
|
327
|
+
)
|
|
248
328
|
|
|
249
329
|
if service_costs:
|
|
250
330
|
# Map collectors to AWS service names in Cost Explorer
|
|
251
331
|
collector_to_services = {
|
|
332
|
+
# Original collectors
|
|
252
333
|
"s3": ["Amazon Simple Storage Service"],
|
|
253
334
|
"glue": ["AWS Glue"],
|
|
254
335
|
"athena": ["Amazon Athena"],
|
|
255
336
|
"redshift": ["Amazon Redshift"],
|
|
256
337
|
"iam": [], # IAM is free
|
|
257
338
|
"mwaa": ["Amazon Managed Workflows for Apache Airflow"],
|
|
339
|
+
"ec2": [
|
|
340
|
+
"Amazon Elastic Compute Cloud - Compute",
|
|
341
|
+
"EC2 - Other",
|
|
342
|
+
"Amazon EC2 Container Registry (ECR)",
|
|
343
|
+
],
|
|
344
|
+
"kms": ["AWS Key Management Service"],
|
|
345
|
+
"rds": ["Amazon Relational Database Service"],
|
|
346
|
+
"dynamodb": ["Amazon DynamoDB"],
|
|
347
|
+
"lambda": ["AWS Lambda"],
|
|
348
|
+
"secrets": ["AWS Secrets Manager"],
|
|
349
|
+
"backup": ["AWS Backup"],
|
|
350
|
+
"eks": [
|
|
351
|
+
"Amazon Elastic Kubernetes Service",
|
|
352
|
+
"Amazon Elastic Container Service for Kubernetes",
|
|
353
|
+
],
|
|
354
|
+
"sns_sqs": [
|
|
355
|
+
"Amazon Simple Notification Service",
|
|
356
|
+
"Amazon Simple Queue Service",
|
|
357
|
+
],
|
|
358
|
+
"lakeformation": ["AWS Lake Formation"],
|
|
359
|
+
"cloudtrail": ["AWS CloudTrail"],
|
|
360
|
+
"cloudwatch": ["AmazonCloudWatch", "CloudWatch Events"],
|
|
361
|
+
# New collectors
|
|
362
|
+
"vpc_costs": ["Amazon Virtual Private Cloud"],
|
|
363
|
+
"elb": ["Amazon Elastic Load Balancing"],
|
|
364
|
+
"elasticache": ["Amazon ElastiCache"],
|
|
365
|
+
"route53": ["Amazon Route 53"],
|
|
366
|
+
"kinesis": ["Amazon Kinesis Firehose", "Amazon Kinesis"],
|
|
367
|
+
"apigateway": ["Amazon API Gateway"],
|
|
368
|
+
"cloudfront": ["Amazon CloudFront"],
|
|
369
|
+
"ecs": ["Amazon Elastic Container Service"],
|
|
370
|
+
"security": [
|
|
371
|
+
"Amazon GuardDuty",
|
|
372
|
+
"Amazon Inspector",
|
|
373
|
+
"AWS Security Hub",
|
|
374
|
+
"AWS Config",
|
|
375
|
+
],
|
|
376
|
+
"ssm": ["AWS Systems Manager"],
|
|
377
|
+
"stepfunctions": ["AWS Step Functions"],
|
|
378
|
+
"efs": ["Amazon Elastic File System"],
|
|
258
379
|
}
|
|
259
380
|
|
|
260
381
|
# Filter costs based on active collectors
|
|
@@ -309,6 +430,14 @@ class AWSScanner(CloudProviderScan):
|
|
|
309
430
|
"estimated_monthly_cost": monthly_estimate,
|
|
310
431
|
"scope": scope_note,
|
|
311
432
|
"note": "Actual costs from AWS Cost Explorer API for the last 30 days.",
|
|
433
|
+
# Savings Plans data
|
|
434
|
+
"savings_plans": {
|
|
435
|
+
"total_savings": savings_plans.get("total_savings", 0),
|
|
436
|
+
"utilization_percent": savings_plans.get("utilization_percent", 0),
|
|
437
|
+
"coverage_percent": savings_plans.get("coverage_percent", 0),
|
|
438
|
+
"amortized_commitment": savings_plans.get("amortized_commitment", 0),
|
|
439
|
+
"on_demand_equivalent": savings_plans.get("on_demand_equivalent", 0),
|
|
440
|
+
},
|
|
312
441
|
},
|
|
313
442
|
)
|
|
314
443
|
all_assets.append(cost_summary_asset)
|