diff --git a/.github/skills/find-package-skill/SKILL.md b/.github/skills/find-package-skill/SKILL.md index 514cef4492e4..6bfaec79be64 100644 --- a/.github/skills/find-package-skill/SKILL.md +++ b/.github/skills/find-package-skill/SKILL.md @@ -22,4 +22,5 @@ you already know the package well. | Package | Path | | ------------------------- | --------------------------------------------------------------------------- | -| `azure-search-documents` | `sdk/search/azure-search-documents/.github/skills/azure-search-documents/SKILL.md` | \ No newline at end of file +| `azure-search-documents` | `sdk/search/azure-search-documents/.github/skills/azure-search-documents/SKILL.md` | +| `azure-identity` | `sdk/identity/azure-identity/.github/skills/azure-identity/SKILL.md` | \ No newline at end of file diff --git a/sdk/identity/azure-identity/.github/skills/azure-identity/SKILL.md b/sdk/identity/azure-identity/.github/skills/azure-identity/SKILL.md new file mode 100644 index 000000000000..d3a0d62b359d --- /dev/null +++ b/sdk/identity/azure-identity/.github/skills/azure-identity/SKILL.md @@ -0,0 +1,53 @@ +--- +name: azure-identity +description: 'Domain knowledge for azure-identity. Covers architecture, credential chain, MSAL integration, sync/async split, managed identity, token APIs, and common pitfalls. WHEN: modify azure-identity; fix azure-identity bug; add azure-identity credential; add azure-identity feature; change azure-identity DefaultAzureCredential; update azure-identity managed identity.' +--- + +# azure-identity — Package Skill + +`azure-identity` is **entirely hand-authored**: no TypeSpec, no `_generated/`, no `_patch.py`, and no regeneration step. Every changed file is production source, so first verify public API exports, sync/async parity, credential-chain behavior, and tests. + +``` +azure/identity/ +├── __init__.py # public sync exports; __all__ is the contract +├── _credentials/ # sync credentials, DAC, managed identity dispatcher +├── _internal/ # shared auth plumbing; not public API +└── aio/ # async mirror, with fewer public credentials +``` + +**Extension package:** `azure-identity-broker` provides `InteractiveBrowserBrokerCredential` (sync only) for broker auth on Windows, macOS, Linux, and WSL, with non-Windows/WSL fallback behavior. `VisualStudioCodeCredential` requires this package and only works on Windows/WSL. + +## Common Pitfalls + +1. **Public symbols require `__all__`.** Add new sync public symbols to `azure/identity/__init__.py`; add async-applicable symbols to `azure/identity/aio/__init__.py`. +2. **Three credentials are sync-only.** Do not add async `DeviceCodeCredential`, `InteractiveBrowserCredential`, or `UsernamePasswordCredential`. +3. **MSAL is sync-only.** Async credentials may reuse MSAL `TokenCache`, but never instantiate `msal.PublicClientApplication`, `ConfidentialClientApplication`, or `ManagedIdentityClient`. +4. **DAC chain order is contract-sensitive.** Sync DAC has Environment, WorkloadIdentity, ManagedIdentity, SharedTokenCache, VS Code, CLI, PowerShell, Developer CLI, InteractiveBrowser (excluded by default), Broker. Async DAC stops after Developer CLI. +5. **`within_dac` is intentional.** DAC sets this `ContextVar` while iterating so child credentials suppress noisy errors and exit fast on `CredentialUnavailableError`. +6. **Managed identity dispatch is environment-driven.** Cloud Shell uses the in-house `ManagedIdentityClient`; most other managed-identity hosts use `MsalManagedIdentityClient`; Workload Identity dispatches to `WorkloadIdentityCredential`. +7. **Never hardcode authorities.** Use `AzureAuthorityHosts`, `get_default_authority()`, and `normalize_authority()` so sovereign clouds work. + +## Token API Rule + +Every credential supports `get_token()` and `get_token_info()` through `GetTokenMixin`. `get_token()` is legacy but supported; new token-response features such as PoP, `refresh_on`, or non-Bearer token types belong on `get_token_info()` / `TokenRequestOptions`. + +## Change Checklist + +1. Mirror sync changes under `aio/` unless the credential is one of the three sync-only credentials. +2. Keep shared helpers in sync `_internal/` and import them from async; do not duplicate logic. +3. If DAC changes, update both sync and async defaults, exclude kwargs, `AZURE_TOKEN_CREDENTIALS` behavior, and tests. +4. If token acquisition changes, preserve CAE/non-CAE cache separation and multi-tenant `additionally_allowed_tenants` checks. +5. Update `CHANGELOG.md`, README snippets, and troubleshooting links when public behavior changes. + +After code changes, run the package validation tool: + +``` +azsdk_package_run_check with packagePath="sdk/identity/azure-identity" and checkType="All" +``` + +For targeted local checks from `sdk/identity/azure-identity`, use `azpysdk pytest .`, `azpysdk pylint .`, `azpysdk mypy .`, and `azpysdk sphinx .`. + +## References + +- Credential taxonomy, MSAL tiers, managed identity, DAC details: `references/architecture.md` +- Test commands and patterns: `references/testing.md` diff --git a/sdk/identity/azure-identity/.github/skills/azure-identity/references/architecture.md b/sdk/identity/azure-identity/.github/skills/azure-identity/references/architecture.md new file mode 100644 index 000000000000..fe1bd32daa38 --- /dev/null +++ b/sdk/identity/azure-identity/.github/skills/azure-identity/references/architecture.md @@ -0,0 +1,149 @@ +# Architecture Reference for azure-identity + +## Credential Taxonomy + +### By OAuth Application Type + +This is the OAuth/MSAL distinction that drives most of the credential design. + +**Public client application** — cannot keep a secret; authenticates a _user_. This is the OAuth category; implementation varies by credential, so check the MSAL tiers below before changing internals: +- `InteractiveBrowserCredential`, `DeviceCodeCredential`, `UsernamePasswordCredential` +- `AuthorizationCodeCredential`, `SharedTokenCacheCredential` +- `VisualStudioCodeCredential` (delegates to broker) +- `InteractiveBrowserBrokerCredential` (broker package) + +**Confidential client application** — runs on a server, holds a secret/certificate/assertion. This is the OAuth category; not every credential here instantiates `msal.ConfidentialClientApplication` directly: +- `ClientSecretCredential`, `CertificateCredential`, `ClientAssertionCredential` +- `OnBehalfOfCredential` +- `WorkloadIdentityCredential` (built on `ClientAssertionCredential`) +- `AzurePipelinesCredential` (composes `ClientAssertionCredential`) +- `EnvironmentCredential` (resolves to one of the above) + +**Neither (managed identity)** — authenticates the compute resource via local IMDS/metadata endpoint: +- `ManagedIdentityCredential` and backends: `ImdsCredential`, `AppServiceCredential`, `AzureMLCredential`, `AzureArcCredential`, `ServiceFabricCredential`, `CloudShellCredential` + +**Subprocess-based** — shells out to a developer tool that has already authenticated (no MSAL): +- `AzureCliCredential`, `AzureDeveloperCliCredential`, `AzurePowerShellCredential` + +### By Production vs Developer (DAC Classification) + +Used by `DefaultAzureCredential` and the `AZURE_TOKEN_CREDENTIALS` env var: + +| Category | Credentials | +|----------|------------| +| **Production** | `EnvironmentCredential`, `WorkloadIdentityCredential`, `ManagedIdentityCredential` | +| **Developer** | `SharedTokenCacheCredential`, `VisualStudioCodeCredential`, `AzureCliCredential`, `AzurePowerShellCredential`, `AzureDeveloperCliCredential`, `BrokerCredential` | +| **Other** (explicit use only) | `ClientSecretCredential`, `CertificateCredential`, `ClientAssertionCredential`, `OnBehalfOfCredential`, `AuthorizationCodeCredential`, `DeviceCodeCredential`, `UsernamePasswordCredential`, `AzurePipelinesCredential`, `InteractiveBrowserCredential` (DAC-excluded by default) | + +## MSAL Integration — Three Tiers + +Understanding the three tiers of MSAL usage is critical for making changes: + +### Tier 1: Full MSAL client app (sync only) + +These instantiate `msal.PublicClientApplication`, `msal.ConfidentialClientApplication`, or `msal.ManagedIdentityClient`: +- `ClientSecretCredential`, `CertificateCredential` → `ConfidentialClientApplication` +- `OnBehalfOfCredential` → `ConfidentialClientApplication.acquire_token_on_behalf_of` +- `InteractiveBrowserCredential`, `DeviceCodeCredential`, `UsernamePasswordCredential` → `PublicClientApplication` +- `ManagedIdentityCredential` (most backends) → `MsalManagedIdentityClient` → `msal.ManagedIdentityClient` + - **Exception:** `CloudShellCredential` does NOT use `msal.ManagedIdentityClient` — uses in-house `ManagedIdentityClient` + +### Tier 2: MSAL TokenCache only + +The in-house `AadClient` performs raw OAuth HTTP requests but stores results in `msal.TokenCache` for cache compatibility: +- `AuthorizationCodeCredential`, `ClientAssertionCredential`, `WorkloadIdentityCredential`, `AzurePipelinesCredential` +- `SharedTokenCacheCredential` — reads MSAL's persisted cache, redeems refresh tokens via `AadClient` + +### Tier 3: No MSAL at all + +Subprocess-based credentials that parse stdout JSON: +- `AzureCliCredential` (`az account get-access-token`) +- `AzureDeveloperCliCredential` (`azd auth token`) +- `AzurePowerShellCredential` (`Get-AzAccessToken` via `pwsh`) + +### Sync vs Async — The Big Rule + +**MSAL is a sync-only library.** No async credential ever instantiates a MSAL client application. Async credentials inherit from sync base classes that import MSAL, but token acquisition on the async side is implemented in-house: +- Async `AadClient` performs raw OAuth HTTP requests via azure-core's async pipeline +- Async managed-identity sources hit IMDS/app-host endpoints directly +- MSAL's `TokenCache` data structure IS reused for cache compatibility + +So Tier 1 sync credentials do not stay Tier 1 in async: their async counterparts either use `AadClient` / `TokenCache` compatibility or direct async managed-identity HTTP. + +## Managed Identity Backends + +`ManagedIdentityCredential` sniffs the environment and picks ONE backend: + +| Environment | Detection | Uses MSAL MI Client? | +|------------|-----------|---------------------| +| App Service | `IDENTITY_ENDPOINT` + `IDENTITY_HEADER` | Yes | +| Service Fabric | `IDENTITY_ENDPOINT` + `IDENTITY_SERVER_THUMBPRINT` | Yes | +| Azure Arc | `IDENTITY_ENDPOINT` + `IMDS_ENDPOINT` | Yes | +| Azure ML | `MSI_ENDPOINT` + `MSI_SECRET` | Yes | +| Cloud Shell | `MSI_ENDPOINT` (no secret) | **No** — uses in-house client | +| Workload Identity | `AZURE_FEDERATED_TOKEN_FILE` + `AZURE_TENANT_ID` + `AZURE_CLIENT_ID` | No — dispatches to `WorkloadIdentityCredential` / `AadClient` | +| IMDS (fallback) | None of the above | Yes | + +## DefaultAzureCredential Chain + +### Sync chain (10 entries) + +1. `EnvironmentCredential` +2. `WorkloadIdentityCredential` +3. `ManagedIdentityCredential` +4. `SharedTokenCacheCredential` +5. `VisualStudioCodeCredential` +6. `AzureCliCredential` +7. `AzurePowerShellCredential` +8. `AzureDeveloperCliCredential` +9. `InteractiveBrowserCredential` (**excluded by default** — `exclude_interactive_browser_credential=True`) +10. `BrokerCredential` (no-op unless `azure-identity-broker` installed + Windows/WSL) + +### Async chain (8 entries) + +Same as sync entries 1–8. **No** `InteractiveBrowserCredential` or `BrokerCredential`. + +### `FailedDACCredential` pattern + +If a credential fails to **initialize** (not at token time), it's replaced with `FailedDACCredential` (sync) or `AsyncFailedDACCredential` (async). This lets the chain continue and reports the init error only if ALL credentials fail. + +### `AZURE_TOKEN_CREDENTIALS` env var + +Narrows DAC before `exclude_*` flags: +- `prod` → keeps only Environment, WorkloadIdentity, ManagedIdentity +- `dev` → keeps developer credentials + broker +- Specific credential name → selects one (but `SharedTokenCacheCredential` and `BrokerCredential` have no `env_name` and can't be selected this way) +- `require_envvar=True` + unset var → `ValueError` + +### `within_dac` ContextVar + +Set while DAC iterates its chain. Child credentials check this to: +- Suppress noisy error logging (use DEBUG instead of WARNING) +- Exit fast on `CredentialUnavailableError` + +## Authentication Flows + +| Flow | Credentials | +|------|------------| +| Client credentials — secret | `ClientSecretCredential`, `EnvironmentCredential` (with `AZURE_CLIENT_SECRET`) | +| Client credentials — certificate | `CertificateCredential`, `EnvironmentCredential` (with `AZURE_CLIENT_CERTIFICATE_PATH`) | +| Client credentials — federated assertion | `ClientAssertionCredential` | +| Workload identity federation | `WorkloadIdentityCredential` | +| Azure Pipelines OIDC | `AzurePipelinesCredential` | +| On-Behalf-Of | `OnBehalfOfCredential` | +| IMDS / app-host metadata | `ManagedIdentityCredential` (auto-detects source) | +| Authorization code + PKCE | `InteractiveBrowserCredential`, `InteractiveBrowserBrokerCredential` | +| Authorization code redemption | `AuthorizationCodeCredential` | +| Device code | `DeviceCodeCredential` | +| ROPC (discouraged) | `UsernamePasswordCredential` | +| Silent / refresh-token from cache | `SharedTokenCacheCredential`, silent step in interactive credentials | +| Native broker (WAM) | `InteractiveBrowserBrokerCredential`, `VisualStudioCodeCredential` | +| Subprocess delegation | `AzureCliCredential`, `AzurePowerShellCredential`, `AzureDeveloperCliCredential` | + +## Cross-Cutting Behaviors + +- **CAE**: Credentials that proxy MSAL pass `enable_cae` through. `AadClient` keeps a separate `cae_cache`. CAE and non-CAE tokens must not collide. +- **Multi-tenant**: Per-call `tenant_id` overrides constructor tenant, subject to `additionally_allowed_tenants` allow-list. +- **Token caching**: In-memory by default. Persistent via `TokenCachePersistenceOptions` → `msal-extensions` (DPAPI/Keychain/libsecret, plaintext fallback only if explicitly allowed). +- **Authorities/clouds**: Controlled by `authority=` or `AZURE_AUTHORITY_HOST`. `AzureAuthorityHosts` enumerates Public, US Gov, China. +- **Logging**: Every credential logs through `azure.identity` logger. `TROUBLESHOOTING.md` is the canonical troubleshooting reference. diff --git a/sdk/identity/azure-identity/.github/skills/azure-identity/references/testing.md b/sdk/identity/azure-identity/.github/skills/azure-identity/references/testing.md new file mode 100644 index 000000000000..1e2827e11b0d --- /dev/null +++ b/sdk/identity/azure-identity/.github/skills/azure-identity/references/testing.md @@ -0,0 +1,61 @@ +# Testing Guide for azure-identity + +## Running Tests + +```bash +cd sdk/identity/azure-identity + +# Run unit tests (playback mode, no credentials needed) +azpysdk pytest . + +# Run a specific test file +azpysdk pytest tests/test_default.py + +# Run async tests +azpysdk pytest tests/test_default_async.py + +# Run with keyword filter +azpysdk pytest tests/ -k "test_default_credential" +``` + +## Test Structure + +Tests generally mirror the credential layout, but sync-only credentials do not have async test pairs: + +``` +tests/ +├── test_default.py / test_default_async.py +├── test_managed_identity.py / test_managed_identity_async.py +├── test_certificate_credential.py / test_certificate_credential_async.py +├── test_cli_credential.py / test_cli_credential_async.py +├── test_browser_credential.py # sync-only credential +├── test_chained_credential.py / test_chained_credential_async.py +├── managed-identity-live/ # Live MI tests (require Azure environment) +├── helpers.py # Shared test utilities +└── conftest.py # Fixtures, sanitizers, env-gated marks +``` + +## Key Testing Patterns + +- **`time.sleep` and `asyncio.sleep` are patched to no-op** in non-live mode (`conftest.py`). Don't rely on timing in unit tests. +- **Custom markers**: `@pytest.mark.manual` (requires human interaction), `@pytest.mark.prints` (outputs to terminal). +- **Live tests are env-gated**: Service principal creds, certificate paths, and username/password must be in env vars. Tests skip if vars are missing. +- **Managed identity live tests** are in a separate `managed-identity-live/` directory with their own `conftest.py`. They require Key Vault access and specific environment configuration. +- **MSAL app behavior is often mocked** with `unittest.mock.patch`, while HTTP transports are also used where request/response behavior matters (for example, token endpoint flows and managed identity clients). +- **Async tests** use `pytest-asyncio`. The event loop policy is overridden on Windows to `WindowsSelectorEventLoopPolicy` in `conftest.py`. + +## Validation + +```bash +# Run targeted local checks +azpysdk pytest . +azpysdk pylint . +azpysdk mypy . +azpysdk sphinx . +``` + +For full package validation through the Azure SDK release tooling, run: + +``` +azsdk_package_run_check with packagePath="sdk/identity/azure-identity" and checkType="All" +```