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).

Ir a la instalación

Datos de origen

Repositorio
aiappsgbb/awesome-gbb
Última actividad en el origen
23 de septiembre de 2026 a las 21:36
Idioma detectado de SKILL.md
inglés
Estrellas
6
Forks
3

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Explorador de archivos
6 archivos

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
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:
Ver en GitHub
Este SKILL.md es muy grande, por eso SkillsMP muestra aqui solo la primera seccion. Ver en GitHub