# Connect Azure to Prodpeek Profile: `azure/read-only` · connection kind: `azure` · tier 2 Credential: A service principal with the Reader role Suggested URL: `https://management.azure.com` Subscriptions, the activity log, Resource Health, alerts, VMs, App Service, Container Apps, AKS, databases, networking, Key Vault metadata, metrics and budgets — forty-one tools, one Reader service principal, nothing to install. Prodpeek asks Azure every hour whether the principal is still read-only. ## What you must not do You cannot create this credential on the user's behalf — it needs their login and, usually, an approval step. Walk them through it and verify the result. Never ask them to paste the credential into the chat; it goes straight into Prodpeek's console, which encrypts it at rest. Prodpeek has a native Azure adapter; the upstream is https://management.azure.com (or the sovereign-cloud equivalent) and should not be changed to a portal URL. The credential is the JSON that `az ad sp create-for-rbac --role Reader` prints. If the user offers a Contributor or Owner principal, or their own user token, refuse it and walk them through creating a Reader service principal. ## Grant exactly these permissions - `A service principal of its own, named `prodpeek`` — Not your user, and not a principal shared with a deployment pipeline. One identity per purpose is what makes the activity log and the scope probe mean something. - `The Reader role, on each subscription it should see` — Reader is read-only at the control plane, and every Azure credential dispenser — storage keys, kubeconfigs, app settings — is a POST action Reader cannot call. Scope it to the subscriptions (or resource groups) this connection should reach; that fence is Azure's and the gateway cannot widen it. - `A client secret with an expiry` — `az ad sp create-for-rbac` sets one year by default. Shorter is better; a rotation is a paste into Prodpeek's credential drop. ## Refuse these, and say why if the user asks for them - `Contributor or Owner` — Every write in the subscription. The profile denies every write and the adapter issues only GET, so it would change nothing about what Prodpeek does — but it removes Azure's half of the fence. The scope probe will notice within the hour and demote the service. - `Any custom role with `*` in its actions` — `*` is every write on every provider. The scope probe treats it exactly as Contributor. - `User Access Administrator` — It can assign roles — including Owner, to itself. A read-only integration that can grant itself write is not read-only. - `Key Vault Secrets User (or any data-plane role)` — Data-plane roles read secret values, blob contents and queue messages. The adapter never talks to those hosts, so the role buys nothing, and the credential becomes a secret-reader if it ever leaks. The probe reports any dataActions it finds. ## Verify before the credential is used - `az role assignment list --assignee --all -o table` shows only Reader. - The principal is used by nothing but Prodpeek. - The client secret has an expiry you will see coming. - Test connection shows list_activity_log under Allowed. - Prove shows `scope: read_only` for the service. ## Then, in Prodpeek 1. Services → Add a service → choose the profile `azure/read-only`. 2. Connection kind `azure`, URL `https://management.azure.com`. 3. Paste the credential. It is encrypted in the store and never shown again. 4. Run **Test connection**. It lists every tool the upstream advertises and how the profile classifies each one. Anything under "not in the policy" is denied by default — report that list rather than assuming it is fine. ## Full human walkthrough ## The whole setup With the Azure CLI, one command: ```bash az ad sp create-for-rbac --name prodpeek --role Reader \ --scopes /subscriptions/ ``` It prints a small JSON object with `appId`, `password` and `tenant`. That JSON, pasted whole, is the credential. Add `--scopes` more than once to cover several subscriptions. Or in the portal: 1. **Microsoft Entra ID → App registrations → New registration**. Name it `prodpeek`; nothing else needs setting. 2. In the new app, **Certificates & secrets → New client secret**, with an expiry. Copy the secret **value** (not its id), the **Application (client) ID** and the **Directory (tenant) ID**. 3. **Subscriptions → your subscription → Access control (IAM) → Add role assignment → Reader**, and pick the `prodpeek` app as the member. 4. The credential is `tenant-id:client-id:secret`, or the three on separate lines. Then in Prodpeek: **Services → Add a service**, pick `azure/read-only`, leave the upstream as `https://management.azure.com`, paste the credential, **Test connection**. On Azure Government or Azure China, use `https://management.usgovcloudapi.net` or `https://management.chinacloudapi.cn`; Prodpeek picks the matching sign-in authority itself. !!! danger "Reader. Never Contributor, never Owner." Contributor is the role tutorials reach for, and it can restart, scale or delete anything in the subscription. The scope probe will catch it — `prove` reads the principal's own role assignments every hour and demotes a service that can write — but by then it has been sitting in your gateway with write. For a five-minute test you can paste a raw token from `az account get-access-token --query accessToken -o tsv` instead. It is your own identity and it expires within the hour; do not leave it there. ## Why there is no MCP server to install There is no hosted Azure MCP server a server-side gateway can hold a stored team credential for. Azure Resource Manager takes a bearer token issued to a service principal — the shape Prodpeek is built around — so it speaks ARM directly. The adapter exchanges the principal for a token at Entra ID, caches it until shortly before it expires, and uses it for GET requests to ARM. That exchange is the only request it makes that is not a GET, and it goes only to Entra. ## What your agent can then do ``` azure__list_activity_log what changed just before this broke, and who did it azure__list_resource_health whether Azure says the fault is on its side azure__list_alerts which Azure Monitor alerts fired, and when azure__get_web_app is the app running, and when did it last change azure__list_web_app_deployments what was deployed to it, when, by whom azure__list_container_app_revisions did the new revision come up, and how is traffic split azure__get_aks_cluster cluster version, power state, node pools azure__get_metrics CPU, requests, errors and latency over a window azure__list_deployments which ARM deployment failed, and its error azure__list_network_security_groups why the app cannot reach the database azure__list_dns_records why it still resolves to the old address azure__list_subscriptions where every subscription id comes from ``` `list_activity_log` is the one nobody thinks to ask for and the one that most often ends the discussion: a scale-down, a deleted rule or a redeploy, with the caller's name and a timestamp. `list_resource_health` is second — it is Azure saying, per resource, whether the problem is theirs. ## What it refuses, and the ones that matter **Every credential dispenser is a POST, and none has a tool.** Storage account keys (`listKeys`), an AKS kubeconfig (`listClusterUserCredential`), a web app's app settings and connection strings (`config/appsettings/list`), deployment credentials (`publishxml`). There is no code path in the adapter that builds those paths, and a Reader principal cannot call them either. **Key Vault secret values are out of reach.** They live on `*.vault.azure.net`, a host the adapter never talks to. `list_key_vaults` returns vault metadata, network rules and access policies. **Inline secrets are removed.** Some ARM answers carry a secret under a predictable name — a VM's `customData` (cloud-init, often full of tokens), an `adminPassword`, a `connectionString`. The adapter replaces the value under every such key, at any depth, and leaves names and diagnostics alone. ARM deployment parameter and output **values** go the same way; their **names** stay. **Restarting a web app has no tool, and it is the one people ask for.** "Turn it off and on again" is still a production change, and during an incident it destroys the state that explains the incident. ## Why Tier 2 The Tier 1 half is real: Reader refuses every write in this profile *at Azure*, independently of the gateway. Keep it that way. It is Tier 2 anyway, because Reader also reads every network security group rule, every role assignment and every Key Vault access policy in its scope — a complete map of what is exposed and who may reach it — and Azure has no role meaning "may list resources but not their network rules". The roll-up decides the label. Unlike most vendors, Azure can be **asked**. This profile carries the `azure_rbac` scope probe: [`prove`](../use/prove.md) reads the principal's own role assignments on every subscription it can see, and each role's permissions, every hour. A write permission on anything this adapter reads — or `*` — demotes the service and names the role. Write permissions elsewhere (Cost Management Reader can open support tickets) are reported, not counted, and any data-plane permission is reported. ## Checking the credential really cannot write ```bash APP_ID= # Should list Reader, and only Reader az role assignment list --assignee "$APP_ID" --all -o table # Should list no data-plane role either az role assignment list --assignee "$APP_ID" --all \ --query "[].roleDefinitionName" -o tsv | sort -u ``` Only `Reader` in both is the vendor fence doing its half. In Prodpeek, **Prove** shows `scope: read_only` for the service once the hourly run has happened — or run it by hand from the console. Anything else there names the role that is too much; remove that assignment.