azure-discovery 0.1.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 (32) hide show
  1. azure_discovery-0.1.0/LICENSE +21 -0
  2. azure_discovery-0.1.0/PKG-INFO +335 -0
  3. azure_discovery-0.1.0/README.md +299 -0
  4. azure_discovery-0.1.0/azure_discovery/__init__.py +33 -0
  5. azure_discovery-0.1.0/azure_discovery/adt_types/__init__.py +18 -0
  6. azure_discovery-0.1.0/azure_discovery/adt_types/errors.py +13 -0
  7. azure_discovery-0.1.0/azure_discovery/adt_types/models.py +121 -0
  8. azure_discovery-0.1.0/azure_discovery/api.py +45 -0
  9. azure_discovery-0.1.0/azure_discovery/cli.py +132 -0
  10. azure_discovery-0.1.0/azure_discovery/enumerators/__init__.py +3 -0
  11. azure_discovery-0.1.0/azure_discovery/enumerators/azure_resources.py +134 -0
  12. azure_discovery-0.1.0/azure_discovery/orchestrator.py +24 -0
  13. azure_discovery-0.1.0/azure_discovery/reporting/__init__.py +4 -0
  14. azure_discovery-0.1.0/azure_discovery/reporting/console.py +28 -0
  15. azure_discovery-0.1.0/azure_discovery/reporting/html.py +79 -0
  16. azure_discovery-0.1.0/azure_discovery/utils/__init__.py +10 -0
  17. azure_discovery-0.1.0/azure_discovery/utils/azure_clients.py +182 -0
  18. azure_discovery-0.1.0/azure_discovery/utils/graph_helpers.py +42 -0
  19. azure_discovery-0.1.0/azure_discovery/utils/logging.py +59 -0
  20. azure_discovery-0.1.0/azure_discovery.egg-info/PKG-INFO +335 -0
  21. azure_discovery-0.1.0/azure_discovery.egg-info/SOURCES.txt +30 -0
  22. azure_discovery-0.1.0/azure_discovery.egg-info/dependency_links.txt +1 -0
  23. azure_discovery-0.1.0/azure_discovery.egg-info/entry_points.txt +2 -0
  24. azure_discovery-0.1.0/azure_discovery.egg-info/requires.txt +15 -0
  25. azure_discovery-0.1.0/azure_discovery.egg-info/top_level.txt +1 -0
  26. azure_discovery-0.1.0/pyproject.toml +116 -0
  27. azure_discovery-0.1.0/setup.cfg +4 -0
  28. azure_discovery-0.1.0/tests/test_azure_resources.py +152 -0
  29. azure_discovery-0.1.0/tests/test_graph_helpers.py +59 -0
  30. azure_discovery-0.1.0/tests/test_orchestrator.py +70 -0
  31. azure_discovery-0.1.0/tests/test_reporting_console.py +60 -0
  32. azure_discovery-0.1.0/tests/test_reporting_html.py +89 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Azure Discovery Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,335 @@
1
+ Metadata-Version: 2.4
2
+ Name: azure-discovery
3
+ Version: 0.1.0
4
+ Summary: Lightweight Azure tenant discovery and visualization via Resource Graph. Enumerates subscriptions and resources, normalizes results, and renders interactive dependency graphs. Supports public and sovereign clouds (Gov, China, Germany, Azure Stack).
5
+ Author: David Frazer <david.frazer336@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Documentation, https://github.com/maravedi/AzureDiscovery#readme
8
+ Project-URL: Repository, https://github.com/maravedi/AzureDiscovery
9
+ Project-URL: Bug Tracker, https://github.com/maravedi/AzureDiscovery/issues
10
+ Keywords: azure,resource-graph,discovery,inventory,sovereign-cloud,visualization
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: System :: Systems Administration
18
+ Requires-Python: >=3.11
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: pydantic>=2.7
22
+ Requires-Dist: fastapi>=0.110
23
+ Requires-Dist: uvicorn>=0.30
24
+ Requires-Dist: typer>=0.12
25
+ Requires-Dist: pyvis>=0.3.2
26
+ Requires-Dist: azure-identity>=1.17
27
+ Requires-Dist: azure-mgmt-resourcegraph>=8.0
28
+ Requires-Dist: azure-mgmt-subscription>=3.1
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=8.2; extra == "dev"
31
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
32
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
33
+ Requires-Dist: ruff>=0.4.0; extra == "dev"
34
+ Requires-Dist: mypy>=1.10; extra == "dev"
35
+ Dynamic: license-file
36
+
37
+ # Azure Discovery
38
+
39
+ Azure Discovery is a lightweight Azure tenant mapper that enumerates subscriptions and resources via Azure Resource Graph, normalizes the results, and renders an interactive dependency graph. The tool exposes both a Typer-based CLI (`azure-discovery`) and a FastAPI surface so the same discovery workflow can be automated or embedded in other services.
40
+
41
+ The package is published on [PyPI](https://pypi.org/project/azure-discovery/) as **azure-discovery** and can be installed with `pip install azure-discovery`.
42
+
43
+ ## Core capabilities
44
+
45
+ - Builds environment-aware credential chains (Azure CLI + DefaultAzureCredential) with guardrails for unsupported clouds.
46
+ - Queries Azure Resource Graph with include/exclude filters, tag constraints, and resource group scopes.
47
+ - Resolves subscriptions automatically when not provided and de-duplicates resources for consistent graph IDs.
48
+ - Produces JSON summaries, console metrics, and PyVis HTML graphs for quick triage.
49
+ - Offers identical request/response contracts (Pydantic models) across CLI and API, following the Receive an Object, Return an Object (RORO) pattern.
50
+ - Supports all Azure clouds: public, Government (GCC/GCC-H), China, Germany, and Azure Stack.
51
+
52
+ ## Installation
53
+
54
+ **From PyPI (recommended):**
55
+
56
+ ```bash
57
+ pip install azure-discovery
58
+ ```
59
+
60
+ **With optional development dependencies:**
61
+
62
+ ```bash
63
+ pip install azure-discovery[dev]
64
+ ```
65
+
66
+ **From source (e.g. for development or when embedded in another repo):**
67
+
68
+ ```bash
69
+ git clone https://github.com/maravedi/AzureDiscovery.git
70
+ cd AzureDiscovery
71
+ pip install -e .[dev]
72
+ ```
73
+
74
+ ## Package layout
75
+
76
+ When installed, the package provides the **azure_discovery** Python package:
77
+
78
+ ```
79
+ azure_discovery/
80
+ __init__.py # run_discovery, AzureDiscoveryRequest, AzureDiscoveryResponse, etc.
81
+ cli.py # Typer command surface (entry point: azure-discovery)
82
+ api.py # FastAPI app for /discover and visualization endpoints
83
+ orchestrator.py # Async coordinator for enumeration + visualization
84
+ adt_types/ # Pydantic models and custom exceptions
85
+ enumerators/ # Resource Graph query builder and normalization
86
+ reporting/ # Console logging and HTML/PyVis graph generation
87
+ utils/ # Azure SDK clients, graph helpers, structured logging
88
+ ```
89
+
90
+ **Programmatic usage:**
91
+
92
+ ```python
93
+ from azure_discovery import run_discovery
94
+ from azure_discovery.adt_types import (
95
+ AzureDiscoveryRequest,
96
+ AzureDiscoveryResponse,
97
+ AzureEnvironment,
98
+ DiscoveryFilter,
99
+ )
100
+
101
+ request = AzureDiscoveryRequest(
102
+ tenant_id="<tenant-guid>",
103
+ environment=AzureEnvironment.AZURE_GOV,
104
+ subscriptions=["<sub-id>"],
105
+ filter=DiscoveryFilter(include_types=["Microsoft.Compute/virtualMachines"]),
106
+ )
107
+ response = await run_discovery(request)
108
+ # response.nodes, response.relationships, response.html_report_path
109
+ ```
110
+
111
+ To pass your own credential (e.g. for sovereign cloud from another app):
112
+
113
+ ```python
114
+ from azure_discovery.enumerators.azure_resources import enumerate_azure_resources
115
+
116
+ response = await enumerate_azure_resources(request, credential=my_credential)
117
+ ```
118
+
119
+ ## Prerequisites
120
+
121
+ - Python 3.11+
122
+ - Azure CLI 2.60+ (optional, used when `--prefer-cli` is set) or service principal credentials exported as `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, and `AZURE_CLIENT_SECRET`
123
+ - Azure Resource Graph access (Reader or above on the subscriptions you plan to scan)
124
+ - Network egress to `management.azure.com` and `api.azure.com` (or the sovereign cloud endpoints you select)
125
+
126
+ ## Required Azure permissions
127
+
128
+ | Capability | Minimum RBAC role | Scope recommendation |
129
+ | ---------- | ----------------- | -------------------- |
130
+ | Run Resource Graph queries | `Reader`, `Resource Graph Reader`, or any custom role with `Microsoft.ResourceGraph/*/read` | Every subscription you plan to inventory or the parent management group |
131
+ | Auto-discover subscriptions (when `--subscription` is omitted) | `Reader` on the management group or `Directory.Read.All` consent for service principals | Tenant root (`/providers/Microsoft.Management/managementGroups/<root>`) |
132
+ | Register Microsoft.ResourceGraph (one-time) | `Contributor` or `Owner` | Each subscription being scanned |
133
+
134
+ The tool never mutates resources, but it cannot enumerate subscriptions or call Resource Graph unless the identity has at least `Reader` at the relevant scope. Grant the narrowest scope that still covers your target estate (for example, a dedicated management group for security tooling).
135
+
136
+ ### Service principal flow (CLI based)
137
+
138
+ ```
139
+ az ad sp create-for-rbac \
140
+ --name azure-discovery-sp \
141
+ --role "Reader" \
142
+ --scopes /subscriptions/<sub-id-1> /subscriptions/<sub-id-2> \
143
+ --years 1
144
+
145
+ az role assignment create \
146
+ --assignee <appId> \
147
+ --role "Resource Graph Reader" \
148
+ --scope /subscriptions/<sub-id-1>
149
+ ```
150
+
151
+ Export the emitted `appId`, `tenant`, and `password` as `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, and `AZURE_CLIENT_SECRET`. Repeat the role assignment command for every subscription or assign at the management group scope (`/providers/Microsoft.Management/managementGroups/<mg-id>`) to cover multiple subscriptions at once.
152
+
153
+ ### User-assigned permissions (Portal)
154
+
155
+ 1. Open Azure Portal → **Subscriptions** → select each target subscription.
156
+ 2. Navigate to **Access control (IAM)** → **Add** → **Add role assignment**.
157
+ 3. Pick the `Reader` (or `Resource Graph Reader`) role, then select the user or managed identity that will run AzureDiscovery.
158
+ 4. If you want automatic subscription discovery, repeat the assignment at the tenant root management group (visible under **Management groups**). Users need the **Azure RBAC Reader** role there.
159
+
160
+ ### Provider registration and validation
161
+
162
+ Run the following once per subscription to ensure the Resource Graph service is registered and the identity can query it:
163
+
164
+ ```
165
+ az account set --subscription <sub-id>
166
+ az provider register --namespace Microsoft.ResourceGraph
167
+ az graph query -q "Resources | take 1"
168
+ ```
169
+
170
+ Successful output from `az graph query` confirms both the provider registration and the assigned role. If the command fails with `AuthorizationFailed`, double-check the scope of the role assignments and replicate them for every subscription you intend to scan.
171
+
172
+ ## Getting started
173
+
174
+ 1. **Install** (see [Installation](#installation) above).
175
+
176
+ 2. **Authenticate to Azure**
177
+
178
+ - Interactive: `az login --tenant <tenant-id>` and, if multiple tenants, `az account set --subscription <sub-id>`.
179
+ - Service principal: export `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_SECRET`, and (optionally) `AZURE_SUBSCRIPTION_ID`.
180
+ - For sovereign clouds (e.g. Azure Government): set `az cloud set --name AzureUSGovernment` and log in; or use a service principal with the appropriate authority.
181
+
182
+ 3. **Run the CLI**
183
+
184
+ After install, the `azure-discovery` console script is on your PATH:
185
+
186
+ ```bash
187
+ azure-discovery discover \
188
+ --tenant-id <tenant-guid> \
189
+ --subscription <sub-id-1> --subscription <sub-id-2> \
190
+ --include-type "Microsoft.Compute/virtualMachines" \
191
+ --resource-group core-infra \
192
+ --required-tag environment=prod \
193
+ --visualization-output-dir artifacts/graphs
194
+ ```
195
+
196
+ **Run as a module from source** (from the repo root):
197
+
198
+ ```bash
199
+ python -m azure_discovery.cli discover --help
200
+ python -m azure_discovery.cli discover --tenant-id <tenant-guid> --environment azure_gov [options...]
201
+ ```
202
+
203
+ The command prints the structured `AzureDiscoveryResponse` JSON and writes an interactive HTML graph under `artifacts/graphs/`.
204
+
205
+ 4. **Run the API (optional)**
206
+
207
+ ```bash
208
+ uvicorn azure_discovery.api:app --host 0.0.0.0 --port 8000 --reload
209
+ ```
210
+
211
+ - Health check: `curl http://localhost:8000/healthz`
212
+ - Discovery: `curl -X POST http://localhost:8000/discover -H "Content-Type: application/json" -d '{"tenant_id": "...", ... }'`
213
+ - Download visualization: `curl http://localhost:8000/visuals/<file-name> --output graph.html`
214
+
215
+ ## Configuration reference
216
+
217
+ | Option | Description |
218
+ | ------ | ----------- |
219
+ | `--tenant-id` | Required Entra ID tenant GUID. |
220
+ | `--environment` | Azure cloud (`azure_public`, `azure_gov`, `azure_china`, `azure_germany`, `azure_stack`). |
221
+ | `--subscription/-s` | Repeatable flag to scope runs to explicit subscription IDs. Omit to auto-resolve. |
222
+ | `--include-type` / `--exclude-type` | Filter resource types (case-insensitive). |
223
+ | `--resource-group` | Restrict discovery to named resource groups. |
224
+ | `--required-tag` | Enforce tag key=value pairs (repeatable). |
225
+ | `--prefer-cli` | Place Azure CLI credentials at the front of the chain. |
226
+ | `--visualization-output-dir` | Directory for PyVis HTML output (default `artifacts/graphs`). |
227
+ | `--visualization-file` | Override the generated HTML file name. |
228
+ | `--output/-o` | Write JSON output to file instead of stdout. |
229
+ | `--quiet/-q` | Suppress all logs except errors. |
230
+ | `--format/-f` | Output format: `json` (default) or `json-compact`. |
231
+
232
+ Programmatic workflows can instantiate `AzureDiscoveryRequest` directly and call `orchestrator.run_discovery`, receiving an `AzureDiscoveryResponse` that contains resolved subscriptions, normalized nodes, inferred relationships, and an optional `html_report_path`.
233
+
234
+ ### Output and logging separation
235
+
236
+ By default, the CLI writes JSON results to stdout and logs to stderr. This allows clean piping:
237
+
238
+ ```bash
239
+ # Pipe JSON output to jq for filtering
240
+ azure-discovery discover --tenant-id <id> | jq '.discovered_subscriptions'
241
+
242
+ # Write output to file and suppress logs
243
+ azure-discovery discover --tenant-id <id> --output results.json --quiet
244
+
245
+ # Compact JSON output for scripting
246
+ azure-discovery discover --tenant-id <id> --format json-compact
247
+ ```
248
+
249
+ ## Development
250
+
251
+ ### Quick start
252
+
253
+ ```bash
254
+ # Install with development dependencies
255
+ make install-dev
256
+
257
+ # Run tests
258
+ make test
259
+
260
+ # Format code
261
+ make format
262
+
263
+ # Run linting
264
+ make lint
265
+
266
+ # Type checking
267
+ make typecheck
268
+
269
+ # Generate coverage report
270
+ make coverage
271
+ ```
272
+
273
+ ### Available make commands
274
+
275
+ Run `make help` to see all available commands:
276
+ - `make install` - Install package dependencies
277
+ - `make install-dev` - Install with development dependencies
278
+ - `make test` - Run tests with pytest
279
+ - `make lint` - Run ruff linter
280
+ - `make format` - Format code with ruff
281
+ - `make typecheck` - Run mypy type checking
282
+ - `make coverage` - Generate test coverage report
283
+ - `make clean` - Remove build artifacts and cache
284
+ - `make run-api` - Run FastAPI server locally
285
+
286
+ ### Pre-commit hooks
287
+
288
+ Install pre-commit hooks to automatically run linting and formatting on commit:
289
+
290
+ ```bash
291
+ pip install pre-commit
292
+ pre-commit install
293
+ ```
294
+
295
+ This will run ruff formatting, linting, and mypy type checking before each commit.
296
+
297
+ ### Environment variables
298
+
299
+ Copy `.env.example` to `.env` and configure your Azure credentials:
300
+
301
+ ```bash
302
+ cp .env.example .env
303
+ # Edit .env with your credentials
304
+ ```
305
+
306
+ For detailed contributing guidelines, see [CONTRIBUTING.md](CONTRIBUTING.md).
307
+
308
+ ## Troubleshooting
309
+
310
+ - **`AzureClientError: Unable to enumerate subscriptions`** – ensure the identity has at least `Reader` on one subscription and that the Resource Graph service is registered (`az provider register --namespace Microsoft.ResourceGraph`).
311
+ - **`Resource Graph query failure`** – check that the tenant/subscription pair belongs to the same cloud you selected, and verify network egress to the relevant `resource_manager` endpoint (see `_ENVIRONMENT_MAP` in `azure_discovery.utils.azure_clients`).
312
+ - **`VisualizationError: Failed to render HTML graph`** – confirm the `--visualization-output-dir` path exists and is writable; the PyVis writer does not auto-create directories unless it has permissions on each parent.
313
+ - **`401` or `interaction_required` errors** – when running non-interactively, use a service principal credential chain and set `AZURE_CLIENT_SECRET`; the default chain will otherwise attempt to launch an interactive browser flow.
314
+ - **Empty graph output** – verify filters are not mutually exclusive (e.g., mixing include/exclude for the same type) and that `--max-batch-size` in the request (via API) is not set below the minimum (100).
315
+
316
+ ## Known gaps and future opportunities
317
+
318
+ - **Identity & RBAC coverage** – the tool currently inventories Azure Resource Manager assets only; Entra ID objects, role assignments, and policy definitions are not ingested or visualized.
319
+ - **Change tracking** – runs are stateless; there is no persistence layer or diffing between historical discoveries.
320
+ - **Security context** – vulnerability, compliance, and workload insights (e.g., Defender for Cloud signals) are not integrated.
321
+ - **API hardening** – the FastAPI surface is unprotected (no authN/Z, rate limiting, or request quotas); deploy behind an API gateway or add middleware before production use.
322
+ - **Scale controls** – back-off and throttling are minimal (`throttle_delay_seconds` only); there is no adaptive rate control or batching heuristics for very large tenants.
323
+
324
+ These items are valuable next steps if you intend to operationalize Azure Discovery beyond ad-hoc investigations.
325
+
326
+ ## Publishing to PyPI (maintainers)
327
+
328
+ To publish a new version to PyPI:
329
+
330
+ 1. Bump `version` in `pyproject.toml`.
331
+ 2. Ensure tests pass: `pip install -e .[dev] && pytest`.
332
+ 3. Build: `python -m build`.
333
+ 4. Upload: `twine upload dist/azure-discovery-<version>*` (requires PyPI credentials or token).
334
+
335
+ The package uses a single top-level package **azure_discovery** to avoid namespace conflicts on install. The console script **azure-discovery** is provided by the `[project.scripts]` entry in `pyproject.toml`.
@@ -0,0 +1,299 @@
1
+ # Azure Discovery
2
+
3
+ Azure Discovery is a lightweight Azure tenant mapper that enumerates subscriptions and resources via Azure Resource Graph, normalizes the results, and renders an interactive dependency graph. The tool exposes both a Typer-based CLI (`azure-discovery`) and a FastAPI surface so the same discovery workflow can be automated or embedded in other services.
4
+
5
+ The package is published on [PyPI](https://pypi.org/project/azure-discovery/) as **azure-discovery** and can be installed with `pip install azure-discovery`.
6
+
7
+ ## Core capabilities
8
+
9
+ - Builds environment-aware credential chains (Azure CLI + DefaultAzureCredential) with guardrails for unsupported clouds.
10
+ - Queries Azure Resource Graph with include/exclude filters, tag constraints, and resource group scopes.
11
+ - Resolves subscriptions automatically when not provided and de-duplicates resources for consistent graph IDs.
12
+ - Produces JSON summaries, console metrics, and PyVis HTML graphs for quick triage.
13
+ - Offers identical request/response contracts (Pydantic models) across CLI and API, following the Receive an Object, Return an Object (RORO) pattern.
14
+ - Supports all Azure clouds: public, Government (GCC/GCC-H), China, Germany, and Azure Stack.
15
+
16
+ ## Installation
17
+
18
+ **From PyPI (recommended):**
19
+
20
+ ```bash
21
+ pip install azure-discovery
22
+ ```
23
+
24
+ **With optional development dependencies:**
25
+
26
+ ```bash
27
+ pip install azure-discovery[dev]
28
+ ```
29
+
30
+ **From source (e.g. for development or when embedded in another repo):**
31
+
32
+ ```bash
33
+ git clone https://github.com/maravedi/AzureDiscovery.git
34
+ cd AzureDiscovery
35
+ pip install -e .[dev]
36
+ ```
37
+
38
+ ## Package layout
39
+
40
+ When installed, the package provides the **azure_discovery** Python package:
41
+
42
+ ```
43
+ azure_discovery/
44
+ __init__.py # run_discovery, AzureDiscoveryRequest, AzureDiscoveryResponse, etc.
45
+ cli.py # Typer command surface (entry point: azure-discovery)
46
+ api.py # FastAPI app for /discover and visualization endpoints
47
+ orchestrator.py # Async coordinator for enumeration + visualization
48
+ adt_types/ # Pydantic models and custom exceptions
49
+ enumerators/ # Resource Graph query builder and normalization
50
+ reporting/ # Console logging and HTML/PyVis graph generation
51
+ utils/ # Azure SDK clients, graph helpers, structured logging
52
+ ```
53
+
54
+ **Programmatic usage:**
55
+
56
+ ```python
57
+ from azure_discovery import run_discovery
58
+ from azure_discovery.adt_types import (
59
+ AzureDiscoveryRequest,
60
+ AzureDiscoveryResponse,
61
+ AzureEnvironment,
62
+ DiscoveryFilter,
63
+ )
64
+
65
+ request = AzureDiscoveryRequest(
66
+ tenant_id="<tenant-guid>",
67
+ environment=AzureEnvironment.AZURE_GOV,
68
+ subscriptions=["<sub-id>"],
69
+ filter=DiscoveryFilter(include_types=["Microsoft.Compute/virtualMachines"]),
70
+ )
71
+ response = await run_discovery(request)
72
+ # response.nodes, response.relationships, response.html_report_path
73
+ ```
74
+
75
+ To pass your own credential (e.g. for sovereign cloud from another app):
76
+
77
+ ```python
78
+ from azure_discovery.enumerators.azure_resources import enumerate_azure_resources
79
+
80
+ response = await enumerate_azure_resources(request, credential=my_credential)
81
+ ```
82
+
83
+ ## Prerequisites
84
+
85
+ - Python 3.11+
86
+ - Azure CLI 2.60+ (optional, used when `--prefer-cli` is set) or service principal credentials exported as `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, and `AZURE_CLIENT_SECRET`
87
+ - Azure Resource Graph access (Reader or above on the subscriptions you plan to scan)
88
+ - Network egress to `management.azure.com` and `api.azure.com` (or the sovereign cloud endpoints you select)
89
+
90
+ ## Required Azure permissions
91
+
92
+ | Capability | Minimum RBAC role | Scope recommendation |
93
+ | ---------- | ----------------- | -------------------- |
94
+ | Run Resource Graph queries | `Reader`, `Resource Graph Reader`, or any custom role with `Microsoft.ResourceGraph/*/read` | Every subscription you plan to inventory or the parent management group |
95
+ | Auto-discover subscriptions (when `--subscription` is omitted) | `Reader` on the management group or `Directory.Read.All` consent for service principals | Tenant root (`/providers/Microsoft.Management/managementGroups/<root>`) |
96
+ | Register Microsoft.ResourceGraph (one-time) | `Contributor` or `Owner` | Each subscription being scanned |
97
+
98
+ The tool never mutates resources, but it cannot enumerate subscriptions or call Resource Graph unless the identity has at least `Reader` at the relevant scope. Grant the narrowest scope that still covers your target estate (for example, a dedicated management group for security tooling).
99
+
100
+ ### Service principal flow (CLI based)
101
+
102
+ ```
103
+ az ad sp create-for-rbac \
104
+ --name azure-discovery-sp \
105
+ --role "Reader" \
106
+ --scopes /subscriptions/<sub-id-1> /subscriptions/<sub-id-2> \
107
+ --years 1
108
+
109
+ az role assignment create \
110
+ --assignee <appId> \
111
+ --role "Resource Graph Reader" \
112
+ --scope /subscriptions/<sub-id-1>
113
+ ```
114
+
115
+ Export the emitted `appId`, `tenant`, and `password` as `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, and `AZURE_CLIENT_SECRET`. Repeat the role assignment command for every subscription or assign at the management group scope (`/providers/Microsoft.Management/managementGroups/<mg-id>`) to cover multiple subscriptions at once.
116
+
117
+ ### User-assigned permissions (Portal)
118
+
119
+ 1. Open Azure Portal → **Subscriptions** → select each target subscription.
120
+ 2. Navigate to **Access control (IAM)** → **Add** → **Add role assignment**.
121
+ 3. Pick the `Reader` (or `Resource Graph Reader`) role, then select the user or managed identity that will run AzureDiscovery.
122
+ 4. If you want automatic subscription discovery, repeat the assignment at the tenant root management group (visible under **Management groups**). Users need the **Azure RBAC Reader** role there.
123
+
124
+ ### Provider registration and validation
125
+
126
+ Run the following once per subscription to ensure the Resource Graph service is registered and the identity can query it:
127
+
128
+ ```
129
+ az account set --subscription <sub-id>
130
+ az provider register --namespace Microsoft.ResourceGraph
131
+ az graph query -q "Resources | take 1"
132
+ ```
133
+
134
+ Successful output from `az graph query` confirms both the provider registration and the assigned role. If the command fails with `AuthorizationFailed`, double-check the scope of the role assignments and replicate them for every subscription you intend to scan.
135
+
136
+ ## Getting started
137
+
138
+ 1. **Install** (see [Installation](#installation) above).
139
+
140
+ 2. **Authenticate to Azure**
141
+
142
+ - Interactive: `az login --tenant <tenant-id>` and, if multiple tenants, `az account set --subscription <sub-id>`.
143
+ - Service principal: export `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_SECRET`, and (optionally) `AZURE_SUBSCRIPTION_ID`.
144
+ - For sovereign clouds (e.g. Azure Government): set `az cloud set --name AzureUSGovernment` and log in; or use a service principal with the appropriate authority.
145
+
146
+ 3. **Run the CLI**
147
+
148
+ After install, the `azure-discovery` console script is on your PATH:
149
+
150
+ ```bash
151
+ azure-discovery discover \
152
+ --tenant-id <tenant-guid> \
153
+ --subscription <sub-id-1> --subscription <sub-id-2> \
154
+ --include-type "Microsoft.Compute/virtualMachines" \
155
+ --resource-group core-infra \
156
+ --required-tag environment=prod \
157
+ --visualization-output-dir artifacts/graphs
158
+ ```
159
+
160
+ **Run as a module from source** (from the repo root):
161
+
162
+ ```bash
163
+ python -m azure_discovery.cli discover --help
164
+ python -m azure_discovery.cli discover --tenant-id <tenant-guid> --environment azure_gov [options...]
165
+ ```
166
+
167
+ The command prints the structured `AzureDiscoveryResponse` JSON and writes an interactive HTML graph under `artifacts/graphs/`.
168
+
169
+ 4. **Run the API (optional)**
170
+
171
+ ```bash
172
+ uvicorn azure_discovery.api:app --host 0.0.0.0 --port 8000 --reload
173
+ ```
174
+
175
+ - Health check: `curl http://localhost:8000/healthz`
176
+ - Discovery: `curl -X POST http://localhost:8000/discover -H "Content-Type: application/json" -d '{"tenant_id": "...", ... }'`
177
+ - Download visualization: `curl http://localhost:8000/visuals/<file-name> --output graph.html`
178
+
179
+ ## Configuration reference
180
+
181
+ | Option | Description |
182
+ | ------ | ----------- |
183
+ | `--tenant-id` | Required Entra ID tenant GUID. |
184
+ | `--environment` | Azure cloud (`azure_public`, `azure_gov`, `azure_china`, `azure_germany`, `azure_stack`). |
185
+ | `--subscription/-s` | Repeatable flag to scope runs to explicit subscription IDs. Omit to auto-resolve. |
186
+ | `--include-type` / `--exclude-type` | Filter resource types (case-insensitive). |
187
+ | `--resource-group` | Restrict discovery to named resource groups. |
188
+ | `--required-tag` | Enforce tag key=value pairs (repeatable). |
189
+ | `--prefer-cli` | Place Azure CLI credentials at the front of the chain. |
190
+ | `--visualization-output-dir` | Directory for PyVis HTML output (default `artifacts/graphs`). |
191
+ | `--visualization-file` | Override the generated HTML file name. |
192
+ | `--output/-o` | Write JSON output to file instead of stdout. |
193
+ | `--quiet/-q` | Suppress all logs except errors. |
194
+ | `--format/-f` | Output format: `json` (default) or `json-compact`. |
195
+
196
+ Programmatic workflows can instantiate `AzureDiscoveryRequest` directly and call `orchestrator.run_discovery`, receiving an `AzureDiscoveryResponse` that contains resolved subscriptions, normalized nodes, inferred relationships, and an optional `html_report_path`.
197
+
198
+ ### Output and logging separation
199
+
200
+ By default, the CLI writes JSON results to stdout and logs to stderr. This allows clean piping:
201
+
202
+ ```bash
203
+ # Pipe JSON output to jq for filtering
204
+ azure-discovery discover --tenant-id <id> | jq '.discovered_subscriptions'
205
+
206
+ # Write output to file and suppress logs
207
+ azure-discovery discover --tenant-id <id> --output results.json --quiet
208
+
209
+ # Compact JSON output for scripting
210
+ azure-discovery discover --tenant-id <id> --format json-compact
211
+ ```
212
+
213
+ ## Development
214
+
215
+ ### Quick start
216
+
217
+ ```bash
218
+ # Install with development dependencies
219
+ make install-dev
220
+
221
+ # Run tests
222
+ make test
223
+
224
+ # Format code
225
+ make format
226
+
227
+ # Run linting
228
+ make lint
229
+
230
+ # Type checking
231
+ make typecheck
232
+
233
+ # Generate coverage report
234
+ make coverage
235
+ ```
236
+
237
+ ### Available make commands
238
+
239
+ Run `make help` to see all available commands:
240
+ - `make install` - Install package dependencies
241
+ - `make install-dev` - Install with development dependencies
242
+ - `make test` - Run tests with pytest
243
+ - `make lint` - Run ruff linter
244
+ - `make format` - Format code with ruff
245
+ - `make typecheck` - Run mypy type checking
246
+ - `make coverage` - Generate test coverage report
247
+ - `make clean` - Remove build artifacts and cache
248
+ - `make run-api` - Run FastAPI server locally
249
+
250
+ ### Pre-commit hooks
251
+
252
+ Install pre-commit hooks to automatically run linting and formatting on commit:
253
+
254
+ ```bash
255
+ pip install pre-commit
256
+ pre-commit install
257
+ ```
258
+
259
+ This will run ruff formatting, linting, and mypy type checking before each commit.
260
+
261
+ ### Environment variables
262
+
263
+ Copy `.env.example` to `.env` and configure your Azure credentials:
264
+
265
+ ```bash
266
+ cp .env.example .env
267
+ # Edit .env with your credentials
268
+ ```
269
+
270
+ For detailed contributing guidelines, see [CONTRIBUTING.md](CONTRIBUTING.md).
271
+
272
+ ## Troubleshooting
273
+
274
+ - **`AzureClientError: Unable to enumerate subscriptions`** – ensure the identity has at least `Reader` on one subscription and that the Resource Graph service is registered (`az provider register --namespace Microsoft.ResourceGraph`).
275
+ - **`Resource Graph query failure`** – check that the tenant/subscription pair belongs to the same cloud you selected, and verify network egress to the relevant `resource_manager` endpoint (see `_ENVIRONMENT_MAP` in `azure_discovery.utils.azure_clients`).
276
+ - **`VisualizationError: Failed to render HTML graph`** – confirm the `--visualization-output-dir` path exists and is writable; the PyVis writer does not auto-create directories unless it has permissions on each parent.
277
+ - **`401` or `interaction_required` errors** – when running non-interactively, use a service principal credential chain and set `AZURE_CLIENT_SECRET`; the default chain will otherwise attempt to launch an interactive browser flow.
278
+ - **Empty graph output** – verify filters are not mutually exclusive (e.g., mixing include/exclude for the same type) and that `--max-batch-size` in the request (via API) is not set below the minimum (100).
279
+
280
+ ## Known gaps and future opportunities
281
+
282
+ - **Identity & RBAC coverage** – the tool currently inventories Azure Resource Manager assets only; Entra ID objects, role assignments, and policy definitions are not ingested or visualized.
283
+ - **Change tracking** – runs are stateless; there is no persistence layer or diffing between historical discoveries.
284
+ - **Security context** – vulnerability, compliance, and workload insights (e.g., Defender for Cloud signals) are not integrated.
285
+ - **API hardening** – the FastAPI surface is unprotected (no authN/Z, rate limiting, or request quotas); deploy behind an API gateway or add middleware before production use.
286
+ - **Scale controls** – back-off and throttling are minimal (`throttle_delay_seconds` only); there is no adaptive rate control or batching heuristics for very large tenants.
287
+
288
+ These items are valuable next steps if you intend to operationalize Azure Discovery beyond ad-hoc investigations.
289
+
290
+ ## Publishing to PyPI (maintainers)
291
+
292
+ To publish a new version to PyPI:
293
+
294
+ 1. Bump `version` in `pyproject.toml`.
295
+ 2. Ensure tests pass: `pip install -e .[dev] && pytest`.
296
+ 3. Build: `python -m build`.
297
+ 4. Upload: `twine upload dist/azure-discovery-<version>*` (requires PyPI credentials or token).
298
+
299
+ The package uses a single top-level package **azure_discovery** to avoid namespace conflicts on install. The console script **azure-discovery** is provided by the `[project.scripts]` entry in `pyproject.toml`.
@@ -0,0 +1,33 @@
1
+ """Azure Discovery: Azure tenant discovery and visualization via Resource Graph.
2
+
3
+ This package can be installed from PyPI as ``azure-discovery`` and used as:
4
+
5
+ pip install azure-discovery
6
+ azure-discovery discover --tenant-id <id> --subscription <sub-id>
7
+
8
+ Programmatic usage:
9
+
10
+ from azure_discovery import run_discovery
11
+ from azure_discovery.adt_types import AzureDiscoveryRequest, AzureDiscoveryResponse
12
+ """
13
+
14
+ from .orchestrator import run_discovery
15
+ from .adt_types import (
16
+ AzureDiscoveryRequest,
17
+ AzureDiscoveryResponse,
18
+ AzureEnvironment,
19
+ DiscoveryFilter,
20
+ ResourceNode,
21
+ ResourceRelationship,
22
+ )
23
+
24
+ __all__ = [
25
+ "run_discovery",
26
+ "AzureDiscoveryRequest",
27
+ "AzureDiscoveryResponse",
28
+ "AzureEnvironment",
29
+ "DiscoveryFilter",
30
+ "ResourceNode",
31
+ "ResourceRelationship",
32
+ ]
33
+ __version__ = "0.1.0"