cloudg 0.3.2__tar.gz → 0.4.0__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.
Files changed (91) hide show
  1. cloudg-0.4.0/PKG-INFO +693 -0
  2. {cloudg-0.3.2 → cloudg-0.4.0}/README.md +13 -3
  3. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/__init__.py +1 -1
  4. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/api.py +121 -0
  5. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/cli.py +150 -0
  6. cloudg-0.4.0/cloudg/ingest.py +93 -0
  7. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/scanners/checkov.py +28 -0
  8. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/scanners/prowler.py +22 -1
  9. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/scanners/scoutsuite.py +23 -2
  10. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/scanners/trivy.py +41 -0
  11. cloudg-0.4.0/docs/DOCUMENTATION.md +621 -0
  12. {cloudg-0.3.2 → cloudg-0.4.0}/pyproject.toml +6 -3
  13. cloudg-0.4.0/tests/test_ingest.py +337 -0
  14. cloudg-0.3.2/PKG-INFO +0 -327
  15. {cloudg-0.3.2 → cloudg-0.4.0}/.gitignore +0 -0
  16. {cloudg-0.3.2 → cloudg-0.4.0}/LICENSE +0 -0
  17. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/collectors/__init__.py +0 -0
  18. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/collectors/aws.py +0 -0
  19. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/collectors/azure.py +0 -0
  20. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/collectors/base.py +0 -0
  21. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/collectors/gcp.py +0 -0
  22. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/collectors/multi.py +0 -0
  23. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/config.py +0 -0
  24. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/coverage.py +0 -0
  25. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/credentials.py +0 -0
  26. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/graph/__init__.py +0 -0
  27. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/graph/builder.py +0 -0
  28. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/graph/ontology.py +0 -0
  29. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/graph/rag_export.py +0 -0
  30. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/graph/reachability.py +0 -0
  31. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/normaliser.py +0 -0
  32. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/policies/custodian-aws-security.yml +0 -0
  33. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/policies/custodian-azure.yml +0 -0
  34. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/policies/custodian-gcp.yml +0 -0
  35. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/policies/custodian.yml +0 -0
  36. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/region_discovery.py +0 -0
  37. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/registry.py +0 -0
  38. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/renderers/__init__.py +0 -0
  39. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/renderers/html_report.py +0 -0
  40. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/renderers/json_export.py +0 -0
  41. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/renderers/svg.py +0 -0
  42. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/renderers/terraform_export.py +0 -0
  43. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/retry.py +0 -0
  44. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/check_equivalence.yaml +0 -0
  45. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/cis_aws_v3.yaml +0 -0
  46. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/cis_azure_v2.yaml +0 -0
  47. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/cis_gcp_v3.yaml +0 -0
  48. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/frameworks/aws_foundational_security_best_practices_aws.yaml +0 -0
  49. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/frameworks/cis_5.0_aws.yaml +0 -0
  50. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/frameworks/cis_5.0_azure.yaml +0 -0
  51. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/frameworks/cis_5.0_gcp.yaml +0 -0
  52. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/frameworks/gdpr_aws.yaml +0 -0
  53. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/frameworks/hipaa_aws.yaml +0 -0
  54. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/frameworks/hipaa_azure.yaml +0 -0
  55. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/frameworks/hipaa_gcp.yaml +0 -0
  56. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/frameworks/iso27001_2022_aws.yaml +0 -0
  57. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/frameworks/iso27001_2022_azure.yaml +0 -0
  58. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/frameworks/iso27001_2022_gcp.yaml +0 -0
  59. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/frameworks/mitre_attack_aws.yaml +0 -0
  60. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/frameworks/mitre_attack_azure.yaml +0 -0
  61. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/frameworks/mitre_attack_gcp.yaml +0 -0
  62. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/frameworks/nist_800_53_revision_5_aws.yaml +0 -0
  63. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/frameworks/nist_csf_2.0_aws.yaml +0 -0
  64. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/frameworks/pci_4.0_aws.yaml +0 -0
  65. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/frameworks/pci_4.0_azure.yaml +0 -0
  66. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/frameworks/pci_4.0_gcp.yaml +0 -0
  67. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/frameworks/soc2_aws.yaml +0 -0
  68. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/frameworks/soc2_azure.yaml +0 -0
  69. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/frameworks/soc2_gcp.yaml +0 -0
  70. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/gdpr.yaml +0 -0
  71. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/hipaa_security_rule.yaml +0 -0
  72. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/iso_27001_2022.yaml +0 -0
  73. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/nist_800_53.yaml +0 -0
  74. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/pci_dss_v4.yaml +0 -0
  75. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/rules/soc2_tsc.yaml +0 -0
  76. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/scanners/__init__.py +0 -0
  77. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/scanners/iam_linter.py +0 -0
  78. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/schema/__init__.py +0 -0
  79. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/schema/models.py +0 -0
  80. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/templates/report.html.j2 +0 -0
  81. {cloudg-0.3.2 → cloudg-0.4.0}/cloudg/ui.py +0 -0
  82. {cloudg-0.3.2 → cloudg-0.4.0}/tests/__init__.py +0 -0
  83. {cloudg-0.3.2 → cloudg-0.4.0}/tests/conftest.py +0 -0
  84. {cloudg-0.3.2 → cloudg-0.4.0}/tests/test_api.py +0 -0
  85. {cloudg-0.3.2 → cloudg-0.4.0}/tests/test_collectors.py +0 -0
  86. {cloudg-0.3.2 → cloudg-0.4.0}/tests/test_normaliser.py +0 -0
  87. {cloudg-0.3.2 → cloudg-0.4.0}/tests/test_ontology.py +0 -0
  88. {cloudg-0.3.2 → cloudg-0.4.0}/tests/test_rag_export.py +0 -0
  89. {cloudg-0.3.2 → cloudg-0.4.0}/tests/test_region_discovery.py +0 -0
  90. {cloudg-0.3.2 → cloudg-0.4.0}/tests/test_scanner_orchestration.py +0 -0
  91. {cloudg-0.3.2 → cloudg-0.4.0}/tests/test_terraform_export.py +0 -0
cloudg-0.4.0/PKG-INFO ADDED
@@ -0,0 +1,693 @@
1
+ Metadata-Version: 2.5
2
+ Name: cloudg
3
+ Version: 0.4.0
4
+ Summary: cloudg (cloud graphing) — multi-cloud infrastructure mapping, knowledge graphs and security intelligence for AWS, Azure and GCP
5
+ Project-URL: Homepage, https://github.com/morpheuslord/cloudg
6
+ Project-URL: Repository, https://github.com/morpheuslord/cloudg
7
+ Project-URL: Documentation, https://morpheuslord.github.io/cloudg/
8
+ Project-URL: Changelog, https://github.com/morpheuslord/cloudg/blob/main/CHANGELOG.md
9
+ Author-email: morpheuslord <chiranjeevi.naidu@proton.me>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: audit,aws,azure,cloud,gcp,graph,ontology,security,terraform
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Information Technology
16
+ Classifier: Intended Audience :: System Administrators
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Security
21
+ Classifier: Topic :: System :: Systems Administration
22
+ Requires-Python: >=3.11
23
+ Requires-Dist: aiofiles>=23.0
24
+ Requires-Dist: click>=8.0
25
+ Requires-Dist: jinja2>=3.1
26
+ Requires-Dist: networkx>=3.1
27
+ Requires-Dist: parliament>=1.6
28
+ Requires-Dist: pydantic<3.0,>=2.0
29
+ Requires-Dist: python-louvain>=0.16
30
+ Requires-Dist: pyyaml>=6.0
31
+ Requires-Dist: rdflib>=7.0
32
+ Requires-Dist: rich>=13.0
33
+ Requires-Dist: svgwrite>=1.4
34
+ Provides-Extra: all
35
+ Requires-Dist: aioboto3>=12.0; extra == 'all'
36
+ Requires-Dist: azure-identity>=1.14; extra == 'all'
37
+ Requires-Dist: azure-mgmt-compute>=30.0; extra == 'all'
38
+ Requires-Dist: azure-mgmt-keyvault>=10.0; extra == 'all'
39
+ Requires-Dist: azure-mgmt-network>=25.0; extra == 'all'
40
+ Requires-Dist: azure-mgmt-resource>=23.0; extra == 'all'
41
+ Requires-Dist: azure-mgmt-sql>=3.0; extra == 'all'
42
+ Requires-Dist: azure-mgmt-storage>=21.0; extra == 'all'
43
+ Requires-Dist: boto3>=1.28; extra == 'all'
44
+ Requires-Dist: google-auth>=2.22; extra == 'all'
45
+ Requires-Dist: google-cloud-asset>=3.0; extra == 'all'
46
+ Provides-Extra: aws
47
+ Requires-Dist: aioboto3>=12.0; extra == 'aws'
48
+ Requires-Dist: boto3>=1.28; extra == 'aws'
49
+ Provides-Extra: azure
50
+ Requires-Dist: azure-identity>=1.14; extra == 'azure'
51
+ Requires-Dist: azure-mgmt-compute>=30.0; extra == 'azure'
52
+ Requires-Dist: azure-mgmt-keyvault>=10.0; extra == 'azure'
53
+ Requires-Dist: azure-mgmt-network>=25.0; extra == 'azure'
54
+ Requires-Dist: azure-mgmt-resource>=23.0; extra == 'azure'
55
+ Requires-Dist: azure-mgmt-sql>=3.0; extra == 'azure'
56
+ Requires-Dist: azure-mgmt-storage>=21.0; extra == 'azure'
57
+ Provides-Extra: dev
58
+ Requires-Dist: aioboto3>=12.0; extra == 'dev'
59
+ Requires-Dist: boto3>=1.28; extra == 'dev'
60
+ Requires-Dist: moto[all]>=5.0; extra == 'dev'
61
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
62
+ Requires-Dist: pytest>=7.0; extra == 'dev'
63
+ Requires-Dist: ruff>=0.1; extra == 'dev'
64
+ Provides-Extra: full
65
+ Requires-Dist: diagrams>=0.23; extra == 'full'
66
+ Requires-Dist: policy-sentry>=0.12; extra == 'full'
67
+ Requires-Dist: weasyprint>=60.0; extra == 'full'
68
+ Provides-Extra: gcp
69
+ Requires-Dist: google-auth>=2.22; extra == 'gcp'
70
+ Requires-Dist: google-cloud-asset>=3.0; extra == 'gcp'
71
+ Description-Content-Type: text/markdown
72
+
73
+ <p align="center">
74
+ <img src="https://raw.githubusercontent.com/morpheuslord/cloudg/main/assets/cloudg_animated_logo.gif" width="480" alt="cloudg animated logo">
75
+ </p>
76
+
77
+ # cloudg reference
78
+
79
+ cloudg (cloud graphing) maps AWS, Azure and GCP infrastructure into a graph, runs security scanners over that inventory, and merges every finding into one deduplicated, compliance-mapped result set. It can also skip the scanning entirely and aggregate outputs you already have.
80
+
81
+ This is the complete reference: every CLI command, the exact input and output formats, the data models, the full Python API with integration examples, configuration, plugins and authentication. Release history lives in the [changelog](https://github.com/morpheuslord/cloudg/blob/main/CHANGELOG.md).
82
+
83
+ Contents:
84
+
85
+ - [Installation](#installation)
86
+ - [The pipeline](#the-pipeline)
87
+ - [CLI reference](#cli-reference)
88
+ - [Input requirements](#input-requirements)
89
+ - [Output contract](#output-contract)
90
+ - [Data models](#data-models)
91
+ - [Configuration](#configuration)
92
+ - [Python API](#python-api)
93
+ - [Integration recipes](#integration-recipes)
94
+ - [Plugin system](#plugin-system)
95
+ - [Authentication](#authentication)
96
+ - [Docker](#docker)
97
+
98
+ ## Installation
99
+
100
+ From PyPI:
101
+
102
+ ```bash
103
+ pip install cloudg # core
104
+ pip install cloudg[aws] # + boto3, aioboto3
105
+ pip install cloudg[azure] # + Azure management SDKs
106
+ pip install cloudg[gcp] # + google-cloud-asset, google-auth
107
+ pip install cloudg[all] # every provider SDK
108
+ ```
109
+
110
+ The cloud SDKs are optional extras, pulled in only when you collect from that provider. The core package alone ships the graph engine, the ontology builder, the findings normaliser, the report renderers and `cloudg ingest`. That is enough to aggregate existing scan results on a machine with no cloud access at all.
111
+
112
+ The external scanners are separate executables, not Python dependencies: [Prowler](https://github.com/prowler-cloud/prowler) (`pip install prowler`), [ScoutSuite](https://github.com/nccgroup/ScoutSuite) (`pip install scoutsuite`), [Checkov](https://github.com/bridgecrewio/checkov) (`pip install checkov`) and [Trivy](https://trivy.dev/) (binary install). Install whichever subset you want. cloudg checks `PATH` at run time and skips anything missing with a warning; the rest of the pipeline is unaffected. `install.sh` (Linux/macOS) and `install.bat` (Windows) set up all of them plus the cloud CLIs, and the Docker image bundles the four scanner binaries.
113
+
114
+ Requires Python 3.11 or newer.
115
+
116
+ ## The pipeline
117
+
118
+ ```
119
+ collect -> graph -> scan -> normalise -> report
120
+ \-> ontology / RAG / terraform
121
+ ```
122
+
123
+ Collection runs all configured providers concurrently with asyncio, iterating accounts and regions per provider. Regions auto-discover when you pass `--regions all`. Assets and network edges go into a directed NetworkX graph: BFS from the internet node finds exposed resources, and blast-radius scoring estimates what an attacker could reach from each node.
124
+
125
+ The enabled scanners then run in parallel over the same inventory, each in its own thread with its own timeout. Their findings, plus the graph's own reachability findings and the built-in IAM linter's results, all flow into the normaliser, which deduplicates within and across scanners, rescores against CVSS, and maps everything to compliance controls. The renderers turn the final `ScanResult` into the report files listed under [Output contract](#output-contract).
126
+
127
+ Every phase degrades gracefully. A missing scanner binary, an unreachable provider or a failed exporter logs a warning; nothing else stops.
128
+
129
+ ## CLI reference
130
+
131
+ Global options go before the subcommand: `cloudg -v -c my-config.yaml run ...`
132
+
133
+ | Option | Meaning |
134
+ |---|---|
135
+ | `-v, --verbose` | debug logging |
136
+ | `-c, --config PATH` | path to `config.yaml` |
137
+ | `--log-file PATH` | also write logs to a file |
138
+
139
+ ### cloudg run
140
+
141
+ The full pipeline: collect, graph, scan, normalise, report.
142
+
143
+ ```bash
144
+ cloudg run -p aws --regions us-east-1
145
+ cloudg run -p all --regions all
146
+ cloudg run -p aws --scanners prowler,trivy
147
+ cloudg run -p aws --regions us-east-1 --terraform
148
+ ```
149
+
150
+ | Flag | Meaning |
151
+ |---|---|
152
+ | `-p, --provider` | `aws`, `azure`, `gcp` or `all`; repeatable |
153
+ | `--regions` | `all` for auto-discovery, or a comma-separated list |
154
+ | `--scanners` | subset of `prowler,scoutsuite,checkov,trivy,iam`; defaults to `scanners.enabled` in config |
155
+ | `--iac-dir` | directory for the IaC scanners (Checkov, Trivy fs) |
156
+ | `--images` | comma-separated container images for Trivy |
157
+ | `--ontology / --no-ontology` | RDF export, on by default |
158
+ | `--rag-export / --no-rag-export` | RAG chunks, on by default |
159
+ | `--terraform / --no-terraform` | Terraform recreation, off by default |
160
+ | `-o, --output` | output directory, `./reports` by default |
161
+
162
+ Credential flags are listed under [Authentication](#authentication).
163
+
164
+ A detail worth knowing: when no IaC directory is configured but `--terraform` is on, the IaC scanners target the generated Terraform recreation of the live infrastructure. Checkov then audits your actual cloud rather than whatever directory cloudg happens to run from. There is deliberately no fallback to `.`: a zero-finding scan of an unrelated directory reads like a clean bill of health, and cloudg refuses to produce one.
165
+
166
+ ### cloudg collect
167
+
168
+ Asset collection alone, one provider at a time. Writes assets and edges without running any scanner.
169
+
170
+ ```bash
171
+ cloudg collect -p aws --profile prod --region eu-west-1 -o ./reports
172
+ cloudg collect -p azure --subscription-id <id>
173
+ cloudg collect -p gcp --project-id <id>
174
+ ```
175
+
176
+ ### cloudg scan
177
+
178
+ Scanners alone, no collection. Writes `raw-findings.json`.
179
+
180
+ ```bash
181
+ cloudg scan -p aws --scanners prowler,checkov --iac-dir ./terraform
182
+ ```
183
+
184
+ ### cloudg ingest
185
+
186
+ Aggregation without execution. Feed cloudg the native output files of scans that already ran (in CI, on another host, on a schedule) and it runs the normalisation half of the pipeline: cross-scanner dedupe via the check-equivalence rulesets, compliance mapping and report generation. No scanner executes and no cloud credentials are needed.
187
+
188
+ ```bash
189
+ cloudg ingest \
190
+ --prowler ./prowler-output/ \
191
+ --scoutsuite ./scoutsuite-report/ \
192
+ --checkov ./results_json.json \
193
+ --trivy ./trivy-image.json --trivy ./trivy-fs.json \
194
+ -o ./reports
195
+ ```
196
+
197
+ | Flag | Accepts |
198
+ |---|---|
199
+ | `--prowler` | ASFF JSON or JSONL file, or Prowler's `-o` output directory (searched recursively for `*.json`) |
200
+ | `--scoutsuite` | the `scoutsuite_results_*.js` file, or the `--report-dir` directory |
201
+ | `--checkov` | a `checkov --output json` file (or `results_json.json`), or a directory containing it |
202
+ | `--trivy` | a `trivy image` or `trivy fs` JSON file, or a directory of them |
203
+ | `--format` | `html`, `json` or `all` (default) |
204
+ | `-o, --output` | output directory |
205
+
206
+ Every input flag is repeatable and any combination or subset of tools works: one tool alone, or all four together. See [Input requirements](#input-requirements) for the exact formats.
207
+
208
+ ### cloudg report
209
+
210
+ Re-render reports from a previous run's `findings.json`. No cloud, no scanners.
211
+
212
+ ```bash
213
+ cloudg report -i ./reports/findings.json --format html
214
+ ```
215
+
216
+ `--format` takes `html`, `json`, `svg` or `all`.
217
+
218
+ ## Input requirements
219
+
220
+ This section pins down exactly what each ingest path expects, and the command that produces it.
221
+
222
+ ### Prowler
223
+
224
+ Produce:
225
+
226
+ ```bash
227
+ prowler aws -M json-asff -o ./prowler-output
228
+ ```
229
+
230
+ cloudg accepts the output directory itself or any single file from it. Files may be a JSON array or JSONL (one ASFF object per line); both parse. The fields cloudg reads from each ASFF finding:
231
+
232
+ ```json
233
+ {
234
+ "Id": "prowler-aws-s3_bucket_default_encryption-123456789012-eu-west-1-...",
235
+ "Title": "S3 bucket default encryption",
236
+ "Description": "...",
237
+ "Severity": {"Label": "HIGH"},
238
+ "Resources": [{"Id": "arn:aws:s3:::my-bucket"}],
239
+ "Compliance": {"Status": "FAILED", "RelatedRequirements": ["CIS 2.1.1"]},
240
+ "Remediation": {"Recommendation": {"Text": "..."}}
241
+ }
242
+ ```
243
+
244
+ Findings with `Compliance.Status` of `PASSED` are skipped. The check name embedded in `Id` (the third dash-separated token) becomes the dedupe key.
245
+
246
+ ### ScoutSuite
247
+
248
+ Produce:
249
+
250
+ ```bash
251
+ scout aws --report-dir ./scoutsuite-report --no-browser
252
+ ```
253
+
254
+ cloudg accepts the report directory (searched recursively for `scoutsuite_results*.js`) or the file itself. The file is JavaScript, a single JSON object assigned to a variable; cloudg strips the assignment and parses the JSON. Findings come from `services.<service>.findings.<key>`, where each entry with `flagged_items > 0` yields one finding per item in `items`. The `level` field maps to severity: `danger` becomes CRITICAL, `warning` HIGH, `caution` MEDIUM.
255
+
256
+ ### Checkov
257
+
258
+ Produce:
259
+
260
+ ```bash
261
+ checkov -d ./iac --output json > results_json.json
262
+ ```
263
+
264
+ cloudg accepts the JSON file or a directory containing `results_json.json` (falling back to any `*.json` in the directory). Both shapes Checkov emits parse: a single `{check_type, results}` object, or a list of them when several frameworks ran. Findings come from `results.failed_checks[]`, reading `check_id`, `check_name`, `severity`, `resource`, `file_path`, `file_line_range` and `guideline`.
265
+
266
+ ### Trivy
267
+
268
+ Produce either or both:
269
+
270
+ ```bash
271
+ trivy image --format json myrepo/app:latest > trivy-image.json
272
+ trivy fs --format json --scanners vuln,misconfig,secret ./iac > trivy-fs.json
273
+ ```
274
+
275
+ cloudg accepts a single JSON file or a directory of them. Each report's top-level `ArtifactType` decides the parser: `container_image` goes through the image parser (vulnerabilities and secrets), everything else (`filesystem`, `repository`) goes through the filesystem parser, which also handles misconfigurations. `ArtifactName` becomes the resource identifier. From each vulnerability cloudg reads `VulnerabilityID`, `PkgName`, `InstalledVersion`, `FixedVersion`, `Severity`, `Description` and the CVSS v3 score when present.
276
+
277
+ ## Output contract
278
+
279
+ All commands write into the output directory (`./reports` by default).
280
+
281
+ | File | Contents |
282
+ |---|---|
283
+ | `report.html` | interactive report: D3 topology, findings table, compliance matrix. Self-contained, works offline. |
284
+ | `findings.json` | the machine-readable result, structure below |
285
+ | `raw-findings.json` | pre-normalisation findings (from `scan` and `ingest`) |
286
+ | `topology.svg`, `topology.graphml`, `topology-cytoscape.json` | the graph in three formats |
287
+ | `ontology.ttl`, `ontology.jsonld` | RDF ontology, around 62 inferred relation types, SPARQL-queryable |
288
+ | `rag_chunks.jsonl`, `rag_metadata_index.json` | retrieval-ready chunks, one JSON object per line |
289
+ | `terraform/*.tf.json`, `terraform/import.sh` | Terraform recreation of live infrastructure, 25+ asset types |
290
+
291
+ `findings.json` has this shape:
292
+
293
+ ```json
294
+ {
295
+ "metadata": {
296
+ "scan_id": "uuid",
297
+ "provider": "AWS",
298
+ "account_id": "...",
299
+ "region": "...",
300
+ "started_at": "...",
301
+ "completed_at": "..."
302
+ },
303
+ "summary": {
304
+ "total_assets": 0,
305
+ "total_findings": 0,
306
+ "total_edges": 0,
307
+ "severity_breakdown": {"CRITICAL": 0, "HIGH": 0},
308
+ "compliance_frameworks": ["CIS", "NIST-800-53"]
309
+ },
310
+ "assets": [],
311
+ "findings": [],
312
+ "compliance": [],
313
+ "graph": {"nodes": [], "links": []}
314
+ }
315
+ ```
316
+
317
+ `assets`, `findings` and `compliance` are lists of the models below, serialised with Pydantic's JSON mode. `graph` is D3 force-layout data. `cloudg report -i` accepts this same file back, so the format round-trips.
318
+
319
+ ## Data models
320
+
321
+ Everything flowing through cloudg is a Pydantic v2 model from `cloudg.schema.models`. All models allow extra fields, so scanner-specific attributes survive serialisation.
322
+
323
+ ### Finding
324
+
325
+ The unit every scanner and parser produces.
326
+
327
+ | Field | Type | Notes |
328
+ |---|---|---|
329
+ | `id` | `str` | auto-generated UUID |
330
+ | `resource_id` | `str` | internal asset ID or native identifier |
331
+ | `resource_arn` | `str \| None` | cloud-native identifier when known |
332
+ | `severity` | `Severity` | `CRITICAL`, `HIGH`, `MEDIUM`, `LOW`, `INFO` |
333
+ | `title` | `str` | display title; scanner tags such as `[Checkov/terraform]` are stripped during cross-scanner comparison |
334
+ | `description` | `str` | |
335
+ | `evidence` | `str \| None` | file path, package version, raw product fields |
336
+ | `remediation` | `str \| None` | |
337
+ | `source_tool` | `str` | `"prowler"`, `"checkov"`, ...; becomes `"prowler, checkov"` after a merge |
338
+ | `source_finding_id` | `str \| None` | the scanner's own check or CVE ID; drives dedupe |
339
+ | `compliance_frameworks` | `list[str]` | unioned on merge |
340
+ | `cvss_score` | `float \| None` | 0.0 to 10.0 |
341
+ | `detected_at` | `datetime` | |
342
+ | `is_suppressed` | `bool` | |
343
+ | `risk_score` | computed `float` | severity base averaged with CVSS when present |
344
+
345
+ ### CloudAsset
346
+
347
+ | Field | Type | Notes |
348
+ |---|---|---|
349
+ | `id` | `str` | auto UUID |
350
+ | `arn` | `str \| None` | native identifier |
351
+ | `name` | `str` | |
352
+ | `asset_type` | `AssetType` | 45-value taxonomy: `EC2`, `S3_BUCKET`, `IAM_ROLE`, `VPC`, `KMS_KEY`, ... |
353
+ | `provider` | `CloudProvider` | `AWS`, `AZURE`, `GCP` |
354
+ | `region` | `str` | `"global"` for regionless resources |
355
+ | `account_id` | `str \| None` | |
356
+ | `tags` | `dict[str, str]` | |
357
+ | `metadata` | `dict[str, Any]` | normalised extra attributes |
358
+ | `is_internet_exposed` | `bool` | set by the graph analysis |
359
+
360
+ ### NetworkEdge
361
+
362
+ Directed edge between two asset IDs. `edge_type` is one of `SECURITY_GROUP_RULE`, `NACL_RULE`, `ROUTE`, `IAM_TRUST`, `IAM_POLICY_ATTACHMENT`, `CONTAINS`, `PEERING`, `LOAD_BALANCER_TARGET`, `INTERNET_EXPOSED`; the model also carries `ports`, `port_range`, `protocol`, `cidr` and `direction`.
363
+
364
+ ### ComplianceResult
365
+
366
+ One control-level verdict: `framework` (for example `CIS`, `NIST-800-53`), `control_id`, `control_title`, `status` (`PASS`, `FAIL`, `NOT_APPLICABLE`, `MANUAL`) and the `finding_ids` behind it.
367
+
368
+ ### ScanResult
369
+
370
+ The aggregate container the normaliser returns and the renderers consume: `assets`, `findings`, `edges`, `compliance`, timing fields, and a computed `summary` dict with totals and the severity breakdown.
371
+
372
+ ## Configuration
373
+
374
+ `config.yaml` mirrors every CLI flag and adds persistent settings; pass it with `-c` or keep it next to where you run. Everything is optional, defaults are sensible. The shipped [config.yaml](https://github.com/morpheuslord/cloudg/blob/main/config.yaml) documents each field inline. The structure, with defaults:
375
+
376
+ ```yaml
377
+ providers: [aws] # aws, azure, gcp; several at once is fine
378
+
379
+ aws:
380
+ regions: [us-east-1] # or [all]
381
+ accounts: [] # multi-account fan-out
382
+ role_name: null # role to assume in each account
383
+ role_arn: null # single-account AssumeRole or OIDC target
384
+ external_id: null
385
+ web_identity_token_file: null
386
+ profile: null
387
+ max_retries: 10
388
+ retry_mode: adaptive
389
+
390
+ azure:
391
+ subscription_ids: []
392
+ tenant_id: null
393
+ client_id: null
394
+ client_secret: null
395
+ certificate_path: null
396
+ federated_token_file: null
397
+ use_managed_identity: false
398
+ regions: [ALL]
399
+
400
+ gcp:
401
+ project_ids: []
402
+ credentials_file: null
403
+ impersonate_service_account: null
404
+ regions: [ALL]
405
+
406
+ scanners:
407
+ enabled: [prowler, scoutsuite, checkov, trivy, iam]
408
+ timeout_seconds: 3600
409
+ iac_directories: [] # targets for Checkov and Trivy fs
410
+ trivy_images: []
411
+ checkov_frameworks: [] # empty = auto-detect
412
+ prowler_extra_args: [] # passthrough args, per scanner
413
+
414
+ graph:
415
+ persist_graphml: true
416
+ compute_attack_paths: true
417
+
418
+ ontology:
419
+ enabled: true
420
+ export_formats: [turtle, json-ld]
421
+
422
+ rag:
423
+ enabled: true
424
+ chunk_strategy: hybrid # entity | community | relation_group | hybrid
425
+ max_chunk_tokens: 2000
426
+
427
+ terraform:
428
+ enabled: false
429
+ output_dir: ./reports/terraform
430
+
431
+ report:
432
+ formats: [html, json, svg]
433
+ inline_js: true # air-gapped friendly
434
+
435
+ rulesets:
436
+ rules_dir: null # defaults to the packaged cloudg/rules/
437
+
438
+ concurrency_limit: 5
439
+ ```
440
+
441
+ In code, the same structure is `CloudGConfig`, a Pydantic model:
442
+
443
+ ```python
444
+ from cloudg import CloudGConfig, load_config
445
+
446
+ config = load_config("config.yaml") # file, validated
447
+ config = CloudGConfig(providers=["aws"]) # or programmatic
448
+ config.scanners.enabled = ["prowler", "iam"]
449
+ config.aws.regions = ["eu-west-1", "eu-central-1"]
450
+ ```
451
+
452
+ ## Python API
453
+
454
+ `CloudGEngine` in `cloudg.api` is the integration entry point: async-first, sync wrappers included, built for embedding in larger systems (SIEM pipelines, orchestration platforms, command extensions such as [hol-guard](https://github.com/hashgraph-online/hol-guard)). The package root re-exports the essentials: `CloudGEngine`, `CloudGConfig`, `load_config`, `PipelineResult`, `CollectionResult`, `AnalysisResult`.
455
+
456
+ ### Construction and hooks
457
+
458
+ ```python
459
+ from cloudg import CloudGConfig, CloudGEngine
460
+
461
+ engine = CloudGEngine(CloudGConfig(providers=["aws"]))
462
+
463
+ engine.on_phase_start = lambda phase: print(f"phase: {phase}")
464
+ engine.on_finding = lambda finding: siem.send(finding.model_dump(mode="json"))
465
+ engine.on_error = lambda phase, exc: alerting.notify(phase, exc)
466
+ ```
467
+
468
+ Hook signatures:
469
+
470
+ | Hook | Signature | Fires |
471
+ |---|---|---|
472
+ | `on_phase_start` | `(phase: str) -> None` | at each phase: `collection`, `scanning`, `ingest`, `analysis`, `normalisation`, `reporting` |
473
+ | `on_collection_complete` | `(result: CollectionResult) -> None` | after collection |
474
+ | `on_finding` | `(finding: Finding) -> None` | once per finding, after scan or ingest |
475
+ | `on_scan_complete` | `(findings: list[Finding]) -> None` | after scan or ingest |
476
+ | `on_analysis_complete` | `(result: AnalysisResult) -> None` | after analysis |
477
+ | `on_error` | `(phase: str, exc: Exception) -> None` | on any phase failure |
478
+
479
+ Exceptions raised inside a hook are swallowed, so a broken callback cannot take the pipeline down.
480
+
481
+ ### Methods
482
+
483
+ `run_pipeline(output_dir="./reports") -> PipelineResult` and its sync twin `run_pipeline_sync()` run everything: collect, scan, analyse, normalise, report.
484
+
485
+ `collect() -> CollectionResult` runs multi-provider collection alone. The result carries `assets` (`list[CloudAsset]`), `edges` (`list[NetworkEdge]`), per-service `coverage` records, `providers_scanned`, `regions_scanned` and `duration_ms`.
486
+
487
+ `scan(assets, edges, iac_dir=None, images=None, profile=None, output_dir="./reports") -> list[Finding]` runs the scanners from `config.scanners.enabled` in parallel, plus graph reachability analysis and the IAM linter. Missing binaries are skipped.
488
+
489
+ `ingest_reports(reports: dict[str, list[str | Path]]) -> list[Finding]` parses existing scanner outputs instead of running anything. Keys are tool names (`prowler`, `scoutsuite`, `checkov`, `trivy`), values are lists of report paths, file or directory. Unparseable paths are logged and skipped rather than raising.
490
+
491
+ `normalise_findings(findings, assets=None) -> ScanResult` applies the full dedupe, cross-scanner merge, CVSS rescoring and compliance mapping. This is the same code path `run_pipeline` uses.
492
+
493
+ `analyze(assets, edges, findings, output_dir="./reports") -> AnalysisResult` builds the graph and produces the ontology, RAG chunks, Terraform files, attack paths and reachability findings, gated by the corresponding config sections.
494
+
495
+ `run_from_reports(reports, output_dir="./reports") -> PipelineResult` and `run_from_reports_sync(...)` chain ingest, normalise and report generation in one call.
496
+
497
+ ### PipelineResult
498
+
499
+ | Field | Type |
500
+ |---|---|
501
+ | `assets`, `edges`, `findings` | the collected and normalised data |
502
+ | `scan_result` | `ScanResult \| None` |
503
+ | `graph_nodes`, `graph_edges`, `ontology_triples` | `int` |
504
+ | `rag_chunks_path`, `terraform_paths`, `report_paths` | output locations |
505
+ | `attack_paths` | lateral movement paths from the graph |
506
+ | `providers_scanned`, `regions_scanned`, `coverage` | collection metadata |
507
+ | `duration_ms`, `errors` | run metadata |
508
+ | `total_assets`, `total_findings`, `severity_breakdown` | computed properties |
509
+ | `to_summary()` | one dict with the headline numbers, ready to serialise |
510
+
511
+ ## Integration recipes
512
+
513
+ Working examples against the real API. Each is self-contained.
514
+
515
+ ### Aggregate existing scan results, get reports
516
+
517
+ ```python
518
+ from cloudg import CloudGConfig, CloudGEngine
519
+
520
+ engine = CloudGEngine(CloudGConfig())
521
+ result = engine.run_from_reports_sync(
522
+ {
523
+ "prowler": ["./prowler-output/"],
524
+ "trivy": ["./trivy-image.json", "./trivy-fs.json"],
525
+ },
526
+ output_dir="./reports",
527
+ )
528
+
529
+ print(result.total_findings, result.severity_breakdown)
530
+ print(result.report_paths["html"]) # ./reports/report.html
531
+ for f in result.findings:
532
+ if f.severity.value in ("CRITICAL", "HIGH"):
533
+ print(f.risk_score, f.title, f.resource_arn)
534
+ ```
535
+
536
+ ### Parse first, decide later
537
+
538
+ ```python
539
+ from cloudg import CloudGConfig, CloudGEngine
540
+ from cloudg.ingest import parse_report, ingest_reports
541
+
542
+ findings = parse_report("prowler", "./prowler-output/")
543
+ findings += parse_report("checkov", "./results_json.json")
544
+ # or in one call:
545
+ findings = ingest_reports({"prowler": ["./out/"], "checkov": ["results_json.json"]})
546
+
547
+ engine = CloudGEngine(CloudGConfig())
548
+ scan_result = engine.normalise_findings(findings)
549
+
550
+ print(scan_result.summary)
551
+ for c in scan_result.compliance:
552
+ if c.status.value == "FAIL":
553
+ print(c.framework, c.control_id, len(c.finding_ids))
554
+ ```
555
+
556
+ The per-scanner parsers are also public, if you want a single tool with no engine:
557
+
558
+ ```python
559
+ from cloudg.scanners.trivy import TrivyScanner
560
+
561
+ findings = TrivyScanner.parse_report("./trivy-image.json")
562
+ ```
563
+
564
+ ### Stream findings into a SIEM as they arrive
565
+
566
+ ```python
567
+ import asyncio
568
+ from cloudg import CloudGConfig, CloudGEngine
569
+
570
+ engine = CloudGEngine(CloudGConfig(providers=["aws"]))
571
+ engine.on_finding = lambda f: queue.put(f.model_dump(mode="json"))
572
+ engine.on_error = lambda phase, exc: log.warning("cloudg %s failed: %s", phase, exc)
573
+
574
+ result = asyncio.run(engine.run_pipeline(output_dir="/var/lib/cloudg/reports"))
575
+ ```
576
+
577
+ ### Run the phases yourself
578
+
579
+ ```python
580
+ import asyncio
581
+ from cloudg import CloudGConfig, CloudGEngine
582
+
583
+ async def main():
584
+ engine = CloudGEngine(CloudGConfig(providers=["aws"]))
585
+
586
+ collection = await engine.collect()
587
+ findings = await engine.scan(collection.assets, collection.edges)
588
+ analysis = await engine.analyze(collection.assets, collection.edges, findings)
589
+ scan_result = engine.normalise_findings(findings, assets=collection.assets)
590
+
591
+ print(analysis.graph_nodes, "nodes,", len(analysis.attack_paths), "attack paths")
592
+ return scan_result
593
+
594
+ asyncio.run(main())
595
+ ```
596
+
597
+ ### Combine live collection with ingested reports
598
+
599
+ Collection needs credentials; ingest does not. Together they give you the graph outputs and the aggregated findings in one result:
600
+
601
+ ```python
602
+ import asyncio
603
+ from cloudg import CloudGConfig, CloudGEngine
604
+
605
+ async def main():
606
+ engine = CloudGEngine(CloudGConfig(providers=["aws"]))
607
+
608
+ collection = await engine.collect()
609
+ ingested = engine.ingest_reports({"prowler": ["./ci-prowler-output/"]})
610
+ scan_result = engine.normalise_findings(ingested, assets=collection.assets)
611
+ await engine.analyze(collection.assets, collection.edges, scan_result.findings)
612
+
613
+ asyncio.run(main())
614
+ ```
615
+
616
+ ### Gate a CI job on severity
617
+
618
+ ```python
619
+ import sys
620
+ from cloudg import CloudGConfig, CloudGEngine
621
+
622
+ result = CloudGEngine(CloudGConfig()).run_from_reports_sync(
623
+ {"checkov": ["results_json.json"], "trivy": ["trivy.json"]}
624
+ )
625
+ critical = result.severity_breakdown.get("CRITICAL", 0)
626
+ if critical:
627
+ print(f"{critical} critical findings, failing the build")
628
+ sys.exit(1)
629
+ ```
630
+
631
+ ## Plugin system
632
+
633
+ Custom collectors and scanners register through setuptools entry points; no core changes needed. `PluginRegistry` (in `cloudg.registry`) discovers them at startup and falls back to the built-ins for any name not overridden.
634
+
635
+ A minimal scanner plugin:
636
+
637
+ ```python
638
+ # my_package/scanner.py
639
+ from cloudg.schema.models import Finding, Severity
640
+
641
+ class MyScanner:
642
+ def __init__(self, **kwargs):
643
+ ...
644
+
645
+ def run(self) -> list[Finding]:
646
+ return [
647
+ Finding(
648
+ resource_id="arn:aws:s3:::example",
649
+ severity=Severity.HIGH,
650
+ title="Example finding",
651
+ description="What was found and why it matters",
652
+ source_tool="myscanner",
653
+ source_finding_id="MY_CHECK_001",
654
+ )
655
+ ]
656
+ ```
657
+
658
+ Registered in the plugin's own `pyproject.toml`:
659
+
660
+ ```toml
661
+ [project.entry-points."cloudg.scanners"]
662
+ myscanner = "my_package.scanner:MyScanner"
663
+
664
+ [project.entry-points."cloudg.collectors"]
665
+ mycloud = "my_package.collector:MyCollector"
666
+ ```
667
+
668
+ After `pip install`, the name is available in `scanners.enabled` and `--scanners`. Collectors follow the base interface in `cloudg/collectors/base.py`. If your scanner emits a stable check ID in `source_finding_id`, you can add it to `rules/check_equivalence.yaml` so its findings merge with equivalent checks from other tools.
669
+
670
+ ## Authentication
671
+
672
+ Every provider supports several methods, resolved in a fixed priority order, so the same config works on a laptop, in CI and on cloud compute.
673
+
674
+ AWS, in priority order: direct keys (`--aws-key`, `--aws-secret`, `--aws-session-token` or the standard env vars); OIDC web identity federation (`--aws-role-arn` with `--aws-web-identity-token-file`, the GitHub Actions, GitLab CI and EKS pattern with no long-lived keys); a named CLI profile via `--profile`, SSO included; or nothing, letting the default chain pick up env vars, cached SSO or the instance role. On top of any of these, STS role assumption with `--aws-role-arn` and, for the third-party auditor pattern, `--aws-external-id`. Multi-account fan-out uses `accounts` plus `role_name` in the config, assuming that role in each account before collecting.
675
+
676
+ Azure, in priority order: workload identity federation (`--azure-tenant-id`, `--azure-client-id`, `--azure-federated-token-file`); a service principal with a client secret (`--azure-client-secret`) or a certificate (`--azure-cert-path`); managed identity (`--azure-managed-identity`, with `managed_identity_client_id` in config for user-assigned identities); or the `DefaultAzureCredential` chain, which covers `az login`.
677
+
678
+ GCP: a credentials file via `--gcp-credentials-file` (service account key JSON or a workload identity federation `external_account` config), or application default credentials. `--gcp-impersonate-sa` layers service account impersonation on either.
679
+
680
+ ## Docker
681
+
682
+ The image bundles all four scanner binaries, so it is the shortest path to the full pipeline:
683
+
684
+ ```bash
685
+ docker pull ghcr.io/morpheuslord/cloudg:latest
686
+ docker compose run --rm cloudg run -p aws --regions us-east-1
687
+ ```
688
+
689
+ Or build locally with `docker build -t cloudg:latest .`
690
+
691
+ ---
692
+
693
+ Found a gap in this reference? [Open an issue](https://github.com/morpheuslord/cloudg/issues).