Skip to main content

azure-tenant-isolation

Multi-tenant Azure CLI and AZD isolation for concurrent terminal sessions. Index-file driven, bare `az` CLI commands only — no wrapper scripts, no PowerShell modules. Mandatory two-layer guard: per-tenant `AZURE_CONFIG_DIR` is the foundation, and `az account show` assertion is the extra gate that verifies tenant + subscription before destructive operations. USE FOR: az login, azd up, azd deploy, az account set, Azure subscription, Azure tenant, AZURE_CONFIG_DIR, AZD_CONFIG_DIR, multi-tenant, switch tenant, deploy to Azure, Bicep deploy, az deployment, azd auth, ChainedTokenCredential, Azure identity, verify subscription, confirm tenant, tenant index, prevent cross-tenant deployment. DO NOT USE FOR: provisioning Azure resources (use azd-patterns), deploying Foundry agents (use foundry-hosted-agents), deploying Citadel gateway (use citadel-hub-deploy).

Zur Installation springen

Quellinformationen

Repository
aiappsgbb/awesome-gbb
Letzte Quellaktivität
23. September 2026 um 21:36
Erkannte Sprache von SKILL.md
Englisch
Sterne
6
Forks
3

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
6 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
azure-tenant-isolation
description
Multi-tenant Azure CLI and AZD isolation for concurrent terminal sessions. Index-file driven, bare `az` CLI commands only — no wrapper scripts, no PowerShell modules. Mandatory two-layer guard: per-tenant `AZURE_CONFIG_DIR` is the foundation, and `az account show` assertion is the extra gate that verifies tenant + subscription before destructive operations. USE FOR: az login, azd up, azd deploy, az account set, Azure subscription, Azure tenant, AZURE_CONFIG_DIR, AZD_CONFIG_DIR, multi-tenant, switch tenant, deploy to Azure, Bicep deploy, az deployment, azd auth, ChainedTokenCredential, Azure identity, verify subscription, confirm tenant, tenant index, prevent cross-tenant deployment. DO NOT USE FOR: provisioning Azure resources (use azd-patterns), deploying Foundry agents (use foundry-hosted-agents), deploying Citadel gateway (use citadel-hub-deploy).
metadata
{"version":"1.2.1"}
# Multi-Tenant Azure CLI & AZD Isolation Run multiple `az` / `azd` workflows against different Azure tenants and subscriptions **at the same time** without crossing wires. This skill ships **two files only**: this document and a JSON schema example. There are **no wrapper scripts, no PowerShell modules, no helper functions**. Every Azure operation in this skill is a **literal `az` (or `azd`) CLI command**. The only shell-specific bits are the unavoidable env-var one-liners (`export VAR=…` on Unix, `$env:VAR=…` on Windows). --- ## Problem `az` and `azd` keep session state (tokens, active subscription, environments) in a **single shared directory** (`~/.azure` and `~/.azd` by default). When two terminals or scripts target different tenants concurrently they collide: - `az login --tenant X` in terminal A overwrites the token used by terminal B. - `az account set -s <sub>` mutates **global** state — every shell sees it. - `azd env select` in one shell leaks into another. - A subprocess that forgets `AZURE_CONFIG_DIR` silently hits the wrong tenant. This is the #1 cause of "I deployed to the wrong subscription" incidents. ### Concrete failure Three concurrent shells targeting three different aliases — without isolation, terminal C's `az account set` silently rewrites the active subscription that terminal A is about to deploy with: ``` Terminal A (prod) Terminal B (dev) Terminal C (partner) ───────────────── ───────────────── ─────────────────── az login --tenant <prod> az login --tenant <dev> az login --tenant <partner> az account set -s partner-shared az account show ← shows partner-shared (!!) azd up ← deploys prod env to partner sub ``` With per-terminal `AZURE_CONFIG_DIR` set to `~/.azure-tenants/{prod,dev,partner}`, each shell has its own token cache and active sub — the writes in C never reach A. --- ## Design — two layered guards Tenant isolation here is built from **two stacked guards**. They are **complementary**, not alternatives. ``` +-----------------------------------------+ per-tenant | AZURE_CONFIG_DIR=~/.azure-tenants/prod | ← FOUNDATION isolation | AZD_CONFIG_DIR =~/.azd-tenants/prod | never replaced +-----------------------------------------+ | v +-----------------------------------------+ subscription | az account show --query tenantId -o tsv| ← EXTRA GATE assertion via | az account show --query name -o tsv| compare → exit 1 az CLI | | on mismatch +-----------------------------------------+ | v +-----------------------------------------+ | destructive op (azd up, az deployment) | +-----------------------------------------+ ``` **The assertion does NOT replace the env-var setup.** Both must be present before any destructive operation. The env vars provide isolation; the assertion catches drift inside an isolated config dir (e.g. a stale default subscription, or another shell that ran `az account set` against the same config dir). --- ## Mandatory rules These are non-negotiable. Every shell, every script, every agent session that touches Azure must follow them. 1. **Per-tenant `AZURE_CONFIG_DIR` and `AZD_CONFIG_DIR` are mandatory.** Set them before the first `az` / `azd` command in every new shell. They are the foundation guard and are **never** replaced by anything downstream (assertions, `--subscription` flags, etc.). 2. **Always verify subscription before destructive operations.** Run the `az account show --query tenantId/name -o tsv` assertion immediately before `azd up`, `azd deploy`, `az deployment ... create`, `az group create`, image updates, or any `az ... delete`. The assertion is the second guard — it does **not** replace rule 1. 3. **Never `az account set` without `AZURE_CONFIG_DIR` set first.** Without isolation, you mutate the global default subscription that every other shell on the machine reads. 4. **Never `az login` without `--tenant <id>`.** A bare `az login` opens the browser picker and may pick the wrong tenant — silently. Always pass `--tenant <id>`. Same goes for `azd auth login --tenant-id <id>`. **AND**: `azd` has its own auth chain — `az login` alone does NOT satisfy `azd ai agent show` / `azd deploy`, even with `AZD_CONFIG_DIR` set. Run **both** logins per shell: ```bash az login --tenant "$TENANT_ID" az account set --subscription "$DEFAULT_SUB" # MANDATORY for multi-sub tenants — see rule 4a azd auth login --tenant-id "$TENANT_ID" ``` 4a. **Multi-sub tenants: `az login --tenant <id>` defaults to whichever sub was last touched, NOT to `default_subscription`.** When a single tenant covers multiple subscriptions (e.g., `acme-prod-east` and `acme-prod-west`), `az login --tenant <id>` populates the token cache with all of them and silently leaves the active subscription on whatever was set last in the global cache. **🛑 DO NOT auto-switch to `default_subscription` blindly.** The `default_subscription` field in the index file is a **hint**, not a rule the tooling should silently enforce — a real SE may have run `az account set --subscription <other-allowed-sub>` immediately before launching the agent, and your bootstrap script should respect that intent. Doing otherwise overrides the user's explicit choice and re-creates the exact bug this skill exists to prevent (verified live in the 2026-05-28 agentic-loop bootstrap.sh retrospective). **DO assert membership in `allowed_subscriptions`** instead. The correct flow is: 1. After `az login --tenant <id>`, read `ACTUAL_SUB = az account show --query name -o tsv`. 2. If `ACTUAL_SUB` ∈ `allowed_subscriptions`, accept it. Done. 3. If `ACTUAL_SUB` ∉ `allowed_subscriptions`, **fail loud** with a message listing the allowed values; do NOT silently switch. Only set `default_subscription` explicitly when `ACTUAL_SUB` is empty (first login) or unknown. Fast check after login: ```bash az account show --query "{tenant:tenantId, sub:name}" -o table # Verify sub is one of `allowed_subscriptions` for this alias; # if not, decide explicitly with: az account set --subscription <one-of-allowed> ``` > **🔴 DO NOT** let `az account set` auto-switch to a subscription outside `allowed_subscriptions`. The default behavior after `az login --tenant` is to activate whichever sub was last touched — verify with `az account show` and reject if not in the whitelist. See § Assertion variants — strict vs whitelist for the canonical membership-check snippet. 5. **The index file is personal.** It lists your tenant ids. Gitignore `~/.azure-tenants/` and `~/.azd-tenants/` globally. Never commit them. 6. **Subprocess inherits env, not isolation state.** Set `AZURE_CONFIG_DIR` / `AZD_CONFIG_DIR` in the **parent** before spawning anything that calls `az` / `azd` (Python `subprocess`, Node `child_process`, `azure.yaml` hooks, GitHub Actions steps, …). 7. **Application code uses `ChainedTokenCredential`, never API keys.** Keys bypass the `AZURE_CONFIG_DIR` indirection and break the isolation guarantees. ### Agent preflight (Copilot CLI / automated sessions) Before running **any** `az` / `azd` command in a session: 1. **CHECK** that `AZURE_CONFIG_DIR` (and `AZD_CONFIG_DIR` if `azd` is involved) is already set in the current shell. 2. **If NOT set → STOP.** Ask the user which alias from the index this session targets. Do not guess. Do not fall back to `~/.azure`. 3. **If set → check if token is still valid** with `az account show`. - **If `az account show` succeeds** → verify tenantId matches the expected alias. If it does, the token is valid — **do NOT re-login.** - **If `az account show` fails** (exit code ≠ 0) → token expired. Only THEN prompt login: ```bash az login --tenant "$TENANT_ID" az account set --subscription "$DEFAULT_SUB" azd auth login --tenant-id "$TENANT_ID" # if azd is involved ``` 4. **Also check `azd auth`** if the session will use `azd`: `azd auth login --check-status`. If "Not logged in", prompt `azd auth login --tenant-id "$TENANT_ID"`. 5. Only then proceed. > ⚠️ **Never force `az login` when the token is still valid.** `az login` > opens a browser (or device-code prompt) which blocks automated sessions. > `az account show` is a zero-cost check — use it every time before > deciding whether login is needed. ### Tenant index = single source of truth Rule 1 above requires per-tenant `AZURE_CONFIG_DIR` / `AZD_CONFIG_DIR`. The next section describes the JSON index file that holds the alias→tenant_id+subscription mapping these env vars are derived from. --- ## The tenant index file Instead of hard-coding tenant ids in shells, scripts, or docs, keep a small JSON file describing every tenant you work with. By default it lives at: - `$env:AZURE_TENANT_INDEX` if set - otherwise `~/.azure-tenants/index.json` This file is **personal data** — it lists the tenant ids you work with and their friendly aliases. **Gitignore it. Never commit it.** A starter copy lives in [`references/index.example.json`](references/index.example.json). Schema (**structural excerpt** — the canonical 2-tenant example with full field coverage lives in [`references/index.example.json`](references/index.example.json); copy from there, don't retype from the snippet below): ```json { "version": 1, "default_alias": "prod", "tenants": { "prod": { "tenant_id": "00000000-0000-0000-0000-000000000001", "description": "Production tenant", "config_dir": null, "azd_config_dir": null, "default_subscription": "acme-prod", "allowed_subscriptions": ["acme-prod"] } } } ``` | Field | Type | Meaning | |-------|------|---------| | `version` | int | Schema version. Currently `1`. | | `default_alias` | string | Alias used when none is specified. | | `tenants.<alias>.tenant_id` | string | Azure AD tenant GUID. | | `tenants.<alias>.description` | string | Human note (free text). | | `tenants.<alias>.config_dir` | string\|null | Override for `AZURE_CONFIG_DIR` (used by `az`). `null` → derive `~/.azure-tenants/<alias>`. **`~` is NOT expanded automatically by JSON readers — consumers must expand it themselves** (see "How to read values from the index" below). | | `tenants.<alias>.azd_config_dir` | string\|null | Override for `AZD_CONFIG_DIR` (used by `azd`). `null` → derive `~/.azd-tenants/<alias>`. Same `~`-expansion caveat as `config_dir`. **Keep this in lock-step with `config_dir`** — overriding one without the other splits the alias's two halves into different folders, which is almost never what you want. | | `tenants.<alias>.default_subscription` | string | Subscription name (or id) passed to `az account set` after login. | | `tenants.<alias>.allowed_subscriptions` | string[] | Whitelist of subscription names or IDs (exact membership test — see "Assertion variants" below). Empty/missing → only `default_subscription` is accepted, by name or ID. A non-empty list does not implicitly include the default hint. | ### Bootstrap — manual, no scripts Unix: ```bash mkdir -p ~/.azure-tenants ~/.azd-tenants curl -fsSL -o ~/.azure-tenants/index.json \ https://raw.githubusercontent.com/aiappsgbb/awesome-gbb/main/skills/azure-tenant-isolation/references/index.example.json $EDITOR ~/.azure-tenants/index.json # replace placeholder GUIDs / sub names ``` Windows (PowerShell): ```powershell New-Item -ItemType Directory -Force "$env:USERPROFILE\.azure-tenants" | Out-Null New-Item -ItemType Directory -Force "$env:USERPROFILE\.azd-tenants" | Out-Null Invoke-WebRequest ` -Uri 'https://raw.githubusercontent.com/aiappsgbb/awesome-gbb/main/skills/azure-tenant-isolation/references/index.example.json' ` -OutFile "$env:USERPROFILE\.azure-tenants\index.json" notepad "$env:USERPROFILE\.azure-tenants\index.json" ``` Both `~/.azure-tenants/<alias>/` and `~/.azd-tenants/<alias>/` are created on demand the first time you `az login` / `azd auth login` against them, so you don't have to pre-create per-alias subfolders. The two top-level folders above just hold the index file and serve as the parent for those auto-created per-alias dirs. Make sure your global `.gitignore_global` (or repo-level `.gitignore`) excludes both `.azure-tenants/` and `.azd-tenants/` so the files (and tokens!) never land in a repo. --- ## Canonical `az` CLI flow All commands below are bare `az` invocations. The only shell-specific lines are the env-var exports (which cannot be wrapped). ### How to read values from the index The skill ships **no wrapper scripts**, but reading values out of a JSON file is a pure read — not a wrapper — and it's needed every time you set up a shell. Use the platform's built-in JSON reader:
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen