Add managed identity E2E tests + self-hosted CI stages (MSAL Go #641 parity) - #948
Add managed identity E2E tests + self-hosted CI stages (MSAL Go #641 parity)#948gladjohn wants to merge 3 commits into
Conversation
Mirror the MSAL Go MI E2E setup (#641). Add tests/test_mi_e2e.py, which acquires ARM tokens for the system-assigned identity and each user-assigned binding (client id / resource id / object id) over IMDS, plus the system-assigned identity over Azure Arc, asserting the first call reaches the identity provider and the second is served from the cache (token_source). Uses the same lab identities as the Go / .NET MI E2E tests. Wire two OneBranch stages that run the test on the self-hosted lab pools (MISEManagedIdentity VM/IMDS and MISEAZUREARC), gated so IMDS cases run only on the IMDS pool (MSAL_TEST_MI_IMDS) and the Arc case only on an Arc machine; the test self-skips everywhere else. Exclude the E2E file from the unit stage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5ad57850-7d1f-4b9a-bf9f-723d69a9687c
There was a problem hiding this comment.
Pull request overview
This PR introduces real end-to-end (E2E) Managed Identity (MI) token acquisition tests for MSAL Python and wires them into the shared Azure DevOps pipeline template via two new self-hosted stages (IMDS VM + Azure Arc), aiming for parity with the MSAL Go MI E2E setup.
Changes:
- Added
tests/test_mi_e2e.pywith MI E2E test cases that acquire an ARM token twice and assert cache behavior viatoken_source. - Excluded the new MI E2E file from the unit-test stage.
- Added two new self-hosted pipeline stages to run the MI E2E tests on the appropriate pools.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
tests/test_mi_e2e.py |
Adds real MI E2E tests for IMDS (system + UAMI variants) and Azure Arc (system only), including cache assertions. |
.Pipelines/template-pipeline-stages.yml |
Excludes MI E2E tests from unit stage and adds two self-hosted stages to execute MI E2E tests and publish results. |
Suppressed comments (1)
.Pipelines/template-pipeline-stages.yml:355
- Same as the IMDS MI stage: this stage will also run in runPublish (release) builds because it only checks UnitTests success. If these MI E2E stages are intended for CI parity runs only, gate them behind not(${{ parameters.runPublish }}) to avoid adding self-hosted pool requirements to publish pipelines.
displayName: 'MI E2E - Azure Arc'
dependsOn: UnitTests
condition: eq(dependencies.UnitTests.result, 'Succeeded')
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| second = client.acquire_token_for_client(resource=_ARM_RESOURCE) | ||
| test.assertNotIn("error", second, "second acquisition failed: {}".format(second)) | ||
| test.assertEqual( |
| _UAMI_CLIENT_ID = "6325cd32-9911-41f3-819c-416cdf9104e7" | ||
| _UAMI_OBJECT_ID = "ecb2ad92-3e30-4505-b79f-ac640d069f24" | ||
| _UAMI_RESOURCE_ID = ( | ||
| "/subscriptions/c1686c51-b717-4fe0-9af3-24a20a41fb0c/resourcegroups/" | ||
| "MSIV2-Testing-MSALNET/providers/Microsoft.ManagedIdentity/userAssignedIdentities/msiv2uami" | ||
| ) |
| - stage: MIE2EImds | ||
| displayName: 'MI E2E - IMDS' | ||
| dependsOn: UnitTests | ||
| condition: eq(dependencies.UnitTests.result, 'Succeeded') |
…p install The self-hosted MI E2E pools carry the toolchain (like Go/.NET); pre-provision msal's deps + pytest on them instead of installing at run time. This removes the runtime "pip install", which the Azure Arc pool cannot do anyway - its egress TLS inspection blocks files.pythonhosted.org (the same restriction MSAL Go documented). Also drop --timeout so pytest-timeout is not required. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5ad57850-7d1f-4b9a-bf9f-723d69a9687c
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (4)
.Pipelines/template-pipeline-stages.yml:343
- The Azure Arc stage header still discusses "pip install" as if it were part of this stage, but the steps below require dependencies to be pre-provisioned and do not install anything. Rewording the comment will better reflect the actual constraint on the Arc pool.
# Pool note: as observed for MSAL Go, the Arc machine's egress inspection can
# reset TLS for package downloads. If "pip install" cannot reach PyPI, pre-provision
# the dependencies on the pool (or use an offline wheel cache) and drop the install step.
tests/test_mi_e2e.py:71
- requests.Session() is created but never closed. Even in short-lived E2E runs this can leave open connections/sockets and may trigger ResourceWarning on some Python versions. Close the session explicitly (try/finally) around the two token acquisitions.
client = ManagedIdentityClient(managed_identity, http_client=requests.Session())
.Pipelines/template-pipeline-stages.yml:291
- The stage header says the IMDS pool needs outbound access to PyPI for "pip install", but this stage intentionally does no runtime pip install (dependencies are described as pre-provisioned below). Updating the comment avoids confusing future maintainers when diagnosing pool requirements.
This issue also appears on line 341 of the same file.
# Pool assumptions (self-hosted): Python 3.x on PATH and outbound access to
# PyPI for "pip install". Adjust if the pool differs.
.Pipelines/template-pipeline-stages.yml:372
- As written, this stage can succeed while running 0 effective MI tests (e.g., if Arc source detection fails and both test classes self-skip). Add a small pre-flight check that fails the job when the agent is not detected as Azure Arc, so pool misconfiguration is surfaced immediately.
$ErrorActionPreference = 'Stop'
python --version
New-Item -ItemType Directory -Force -Path test-results | Out-Null
python -m pytest -vv --junitxml=test-results/junit-mi-e2e-arc.xml tests/test_mi_e2e.py
…d on the agent PATH Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5ad57850-7d1f-4b9a-bf9f-723d69a9687c
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
Suppressed comments (4)
.Pipelines/template-pipeline-stages.yml:356
- Same as the IMDS stage: this stage targets a self-hosted pool (
MISEAZUREARC) but does not currently skip forked PRs. Add a fork guard to avoid running untrusted code on self-hosted infrastructure.
- stage: MIE2EAzureArc
displayName: 'MI E2E - Azure Arc'
dependsOn: UnitTests
condition: eq(dependencies.UnitTests.result, 'Succeeded')
jobs:
.Pipelines/template-pipeline-stages.yml:382
- Same issue as the IMDS stage: the script may pick up a non-Python-3
python.exefrom PATH. Validate the major version and only proceed with Python 3.x.
$py = (Get-Command python.exe -ErrorAction SilentlyContinue).Source
if (-not $py) {
$py = Get-ChildItem 'C:\Program Files\Python3*\python.exe','C:\Program Files (x86)\Python3*\python.exe','C:\Python3*\python.exe' -ErrorAction SilentlyContinue |
Select-Object -First 1 -ExpandProperty FullName
}
if (-not $py) { throw 'Python not found on this agent. Install Python 3.x on the pool machine.' }
tests/test_mi_e2e.py:25
- The suggested SHA-256 comparison relies on
hashlib, but it isn't currently imported in this module.
import os
import unittest
import requests
.Pipelines/template-pipeline-stages.yml:323
- The PowerShell script assumes
python.exeon PATH is Python 3.x. If the agent has a Python 2python.exeearlier on PATH, this will select it and the tests will fail in a non-obvious way.
Consider validating the major version (and falling back to C:\Program Files\Python3* search when it's not 3).
This issue also appears on line 377 of the same file.
$py = (Get-Command python.exe -ErrorAction SilentlyContinue).Source
if (-not $py) {
$py = Get-ChildItem 'C:\Program Files\Python3*\python.exe','C:\Program Files (x86)\Python3*\python.exe','C:\Python3*\python.exe' -ErrorAction SilentlyContinue |
Select-Object -First 1 -ExpandProperty FullName
}
if (-not $py) { throw 'Python not found on this agent. Install Python 3.x on the pool machine.' }
| first = client.acquire_token_for_client(resource=_ARM_RESOURCE) | ||
| test.assertNotIn("error", first, "first acquisition failed: {}".format(first)) | ||
| test.assertIn("access_token", first) | ||
| test.assertEqual( | ||
| "identity_provider", first.get("token_source"), | ||
| "first call should reach the identity provider") | ||
|
|
||
| second = client.acquire_token_for_client(resource=_ARM_RESOURCE) | ||
| test.assertNotIn("error", second, "second acquisition failed: {}".format(second)) | ||
| test.assertEqual( | ||
| "cache", second.get("token_source"), | ||
| "second call should be served from the token cache") | ||
| test.assertEqual( | ||
| first["access_token"], second["access_token"], | ||
| "cached token should match the original token") |
| - stage: MIE2EImds | ||
| displayName: 'MI E2E - IMDS' | ||
| dependsOn: UnitTests | ||
| condition: eq(dependencies.UnitTests.result, 'Succeeded') | ||
| jobs: |
Summary — Phase 1: managed identity E2E tests in CI
Mirrors the MSAL Go MI E2E setup (#641) for MSAL Python: add real managed-identity E2E tests and run them in CI on the self-hosted lab machines. This is the infrastructure step; behavior changes (e.g. Azure Arc UAMI) will follow separately.
Test —
tests/test_mi_e2e.pyReal token acquisition against
https://management.azure.com, assertingtoken_sourcegoesidentity_provider→cacheon the second call (the Python equivalent of Go'sacquireTokenTwiceAssertCaching):ManagedIdentityImdsE2ETestCase): system-assigned + user-assigned by client id / resource id / object id.ManagedIdentityAzureArcE2ETestCase): system-assigned only.Uses the same lab identities and ARM resource as the Go / .NET MI E2E tests, so all SDKs exercise the same lab configuration on the same machines. The tests self-skip off-pool (IMDS gated on
MSAL_TEST_MI_IMDS; Arc on Arc source detection), so they're safe on hosted agents and locally.Pipeline —
.Pipelines/template-pipeline-stages.ymlTwo OneBranch stages on the self-hosted pools, mirroring Go's
MI E2E – IMDS/MI E2E – Azure Arc:MISEManagedIdentity(Windows Azure VM); setsMSAL_TEST_MI_IMDS.MISEAZUREARC(Azure Arc machine).No lab certificate is needed (the machine's own managed identity is used).
test_mi_e2e.pyis also excluded from the unit stage (consistent with the other*_e2e.pyfiles).Assumptions to validate on the pools
pip installcan reach PyPI. Note: as observed with MSAL Go, the Arc machine's egress inspection can reset TLS for package downloads — ifpip installcan't reach PyPI onMISEAZUREARC, we'll pre-provision dependencies / use an offline wheel cache and drop the install step (documented inline in the stage).Not in this PR (Phase 2)
The Azure Arc user-assigned managed identity fail-closed behavior (ported to MSAL.NET #6128 and MSAL Go #644) is a separate follow-up.