# Flightglass collector for Microsoft Azure

A single, read-only Python script that reads configuration metadata from one Microsoft Entra
tenant and writes it to a JSON file (the "bundle"). You send that file to your Flightglass partner,
who uploads it to produce an agentic AI readiness assessment.

It is open source (Apache-2.0) so your security team can read exactly what it does before
running it. Everything it reads is listed below.

## What it does and doesn't do

**It does:** sign in with the Azure CLI you're already signed in to in Cloud Shell (it asks
`az account get-access-token` for short-lived tokens and keeps them in memory only), make
read-only `GET` requests to Azure Resource Manager and Microsoft Graph, run read-only Azure
Resource Graph queries, keep only the fields listed in the code, and write one file on the machine
where it runs.

**It never:**

- changes anything: no `PUT`, `PATCH` or `DELETE`, and no `POST` except Resource Graph and Cost
  Management queries (which only read) — the code refuses any other request;
- calls `listKeys`, `listSecrets`, `listCredentials`, `regenerateKey` or anything similar;
- reads Key Vault secrets, keys or certificates (vault settings only);
- reads storage or database contents, prompts, completions or logs;
- lists users, groups or sign-in records (only policy settings and counts);
- stores client secrets (only their start and end dates), your token, or your name or email;
- uses an app registration or service principal;
- sends anything anywhere. The file stays on your machine until you choose to share it.

For usage, it keeps totals only: model calls and input and output tokens per deployment over the
last 30 days (from Azure Monitor metrics, never prompts, completions or who made the calls), and
the last three full months' spend on Azure AI services (Foundry, Azure OpenAI, Machine Learning,
AI Search). Spend on every other service is filtered out on your machine before the file is
written.

To spot AI outside Microsoft Foundry, it counts Azure Machine Learning endpoints and Azure AI
services accounts of other kinds (such as Vision or Speech), by kind and location. It doesn't
read App Service settings or Key Vault secret names, so keys for other AI providers stored there
can't be seen.

## Running it

1. **Roles.** The person running it needs Reader, Security Reader and Cost Management Reader at
   the tenant root management group (or on each subscription), and Global Reader in Microsoft
   Entra ID. All are read-only. See [required-roles.md](required-roles.md) for step-by-step
   instructions and what happens without Global Reader.
2. **Open Azure Cloud Shell (Bash)** from the Azure portal. Python 3 and the Azure CLI are
   already installed and signed in. Check `az account show` shows the right tenant.
3. **Download and run** (your Flightglass partner's instructions page gives the exact command):

   ```bash
   python3 flightglass_collector_azure.py --regions uksouth,westeurope --output flightglass-azure-bundle.json
   ```

   It lists the tenant and subscriptions it will read and asks you to confirm before reading
   anything.

4. **Download the file** from Cloud Shell (**Manage files → Download**,
   `flightglass-azure-bundle.json`) and send it to your Flightglass partner. One file covers the
   whole tenant.

At the end it prints how many items it read, any it couldn't read (usually a missing role; the
assessment reports these separately), the file's location and its SHA-256 hash.

### Options

| Option            | What it does                                                                                                                                               |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--regions`       | Comma-separated regions in scope. Default: the regions where your AI accounts are (noted in the report).                                                   |
| `--subscriptions` | Comma-separated subscription IDs. Default: every subscription you can read in this tenant.                                                                 |
| `--output`        | Output file. Default: `flightglass-azure-bundle.json`.                                                                                                     |
| `--yes`           | Skip the confirmation prompt.                                                                                                                              |
| `--dry-run`       | List every call it would make, without calling Azure. Needs no sign-in.                                                                                    |
| `--redact-ids`    | Replace resource, resource group and management group names with short, stable hashes. Tenant and subscription IDs are kept because the report needs them. |

**Cost:** nothing. Every call it makes is free.

Requirements: Python 3.9 or newer and the Azure CLI. It uses only Python's standard library.

## Every call it makes

Calls marked `{AI account}` run once per Microsoft Foundry or Azure OpenAI account found;
`/subscriptions/{id}` calls run once per subscription in scope. Requests are paged and
retried if Azure asks it to slow down (HTTP 429, honouring `Retry-After`).

| Call                                                                        | Bundle item                                       |
| --------------------------------------------------------------------------- | ------------------------------------------------- |
| `az account show`, `az account get-access-token`                            | Tenant ID and short-lived tokens (kept in memory) |
| GET `/subscriptions`                                                        | Subscriptions in scope                            |
| POST Resource Graph: AI accounts (Foundry, Azure OpenAI)                    | `arg.ai_accounts`                                 |
| POST Resource Graph: Foundry projects                                       | `arg.ai_projects`                                 |
| POST Resource Graph: Azure AI Search services                               | `arg.search_services`                             |
| GET {AI account}/deployments                                                | `arm.ai_deployments`                              |
| GET {AI account}/raiPolicies                                                | `arm.ai_rai_policies`                             |
| POST Resource Graph: policy assignments                                     | `arg.policy_assignments`                          |
| GET {policy or initiative definition}                                       | `arg.policy_assignments`                          |
| GET Graph /identity/conditionalAccess/policies                              | `graph.conditional_access_policies`               |
| GET Graph /policies/identitySecurityDefaultsEnforcementPolicy               | `graph.security_defaults`                         |
| GET Graph /roleManagement/directory/roleEligibilitySchedules                | `graph.pim_role_eligibility`                      |
| POST Resource Graph: Owner and User Access Administrator assignments        | `arg.privileged_role_assignments`                 |
| GET Graph /applications (password credential dates only)                    | `graph.application_credentials`                   |
| POST Resource Graph: Microsoft Purview accounts                             | `arg.purview_accounts`                            |
| POST Resource Graph: storage account settings                               | `arg.storage_accounts`                            |
| POST Resource Graph: Key Vault settings                                     | `arg.key_vaults`                                  |
| POST Resource Graph: Defender for Cloud plans                               | `arg.defender_plans`                              |
| POST Resource Graph: API Management instances                               | `arg.apim_services`                               |
| GET /subscriptions/{id}/providers/Microsoft.Insights/diagnosticSettings     | `arm.activity_log_diagnostic_settings`            |
| GET {AI account}/providers/Microsoft.Insights/diagnosticSettings            | `arm.ai_diagnostic_settings`                      |
| GET /providers/microsoft.aadiam/diagnosticSettings                          | `arm.entra_diagnostic_settings`                   |
| POST Resource Graph: Log Analytics workspace retention                      | `arg.log_analytics_workspaces`                    |
| POST Resource Graph: Application Insights resources                         | `arg.app_insights`                                |
| GET /subscriptions/{id}/providers/Microsoft.Consumption/budgets             | `arm.budgets`                                     |
| GET /subscriptions/{id}/providers/Microsoft.CostManagement/scheduledActions | `arm.cost_anomaly_alerts`                         |
| GET {AI account}/providers/Microsoft.Insights/metrics (30 days)             | `arm.ai_usage_metrics`                            |
| POST /subscriptions/{id}/providers/Microsoft.CostManagement/query           | `arm.ai_spend`                                    |
| POST Resource Graph: Azure Machine Learning online and serverless endpoints | `arg.ml_endpoints`                                |
| POST Resource Graph: other Azure AI services accounts                       | `arg.other_ai_accounts`                           |
| POST Resource Graph: tag keys on resources                                  | `arg.resource_tags`                               |
| POST Resource Graph: management groups and subscription placement           | `arm.management_groups`                           |

[docs/COVERAGE-azure.md](../../docs/COVERAGE-azure.md) shows which checks use each item and the
role each needs.

## Data handling

The bundle holds configuration metadata only: tenant, subscription and resource IDs (needed as
evidence in the report), settings and counts. Use `--redact-ids` if you'd rather not share
resource names. Flightglass stores uploaded bundles in the UK (London) and deletes raw bundles 90
days after upload. How it stores, protects and deletes them is in
[SECURITY.md](../../docs/SECURITY.md).

## Development

```bash
python3 -m venv collector/azure/.venv
collector/azure/.venv/bin/pip install jsonschema
collector/azure/.venv/bin/python -m unittest discover -s collector/azure/tests -v
```

Tests run against a fake Azure that serves Resource Graph, Resource Manager and Microsoft Graph
responses for a fictional tenant, so no Azure account or network is needed. The output is
validated against the published bundle schema (`packages/schema/azure-bundle.schema.json`), and
the rules engine's tests evaluate the same output.

## Licence

Apache License 2.0. See [LICENSE](LICENSE).
