-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Description
- Package Name: azure-identity
- Package Version: 1.25.1 (also present on
mainat commitb21e2fa) - Operating System: Any (bug is in SDK logic, not platform-specific)
- Python Version: 3.9+
Describe the bug
In DefaultAzureCredential.__init__, the workload_identity_client_id parameter silently inherits its default value from the already-resolved managed_identity_client_id instead of independently reading the AZURE_CLIENT_ID environment variable. This contradicts the documentation and creates a hidden coupling between two unrelated credentials.
The documented behavior (API reference, docstring):
workload_identity_client_id(str) - The client ID of an identity assigned to the pod. Defaults to the value of the environment variable AZURE_CLIENT_ID, if any.
The actual code at default.py line 157:
managed_identity_client_id = kwargs.pop(
"managed_identity_client_id", os.environ.get(EnvironmentVariables.AZURE_CLIENT_ID)
)
workload_identity_client_id = kwargs.pop("workload_identity_client_id", managed_identity_client_id)
# ^^^^^^^^^^^^^^^^^^^^^^^^^^
# BUG: defaults to managed_identity_client_id
# DOCS SAY: defaults to os.environ.get("AZURE_CLIENT_ID")The default for workload_identity_client_id is managed_identity_client_id (the already-resolved variable), not os.environ.get(EnvironmentVariables.AZURE_CLIENT_ID) as the documentation states.
To Reproduce
- Set the
AZURE_CLIENT_IDenvironment variable to a known value (e.g.,"env-client-id") - Create a
DefaultAzureCredentialwith an explicitmanaged_identity_client_idbut without specifyingworkload_identity_client_id:
import os
os.environ["AZURE_CLIENT_ID"] = "env-client-id"
from azure.identity import DefaultAzureCredential
# Pass a DIFFERENT managed_identity_client_id than what's in AZURE_CLIENT_ID
credential = DefaultAzureCredential(managed_identity_client_id="custom-mi-id")- Observe that
workload_identity_client_idinternally resolves to"custom-mi-id"(inherited frommanaged_identity_client_id) instead of"env-client-id"(fromAZURE_CLIENT_ID).
This can be verified by stepping through the constructor or by examining the WorkloadIdentityCredential that gets added to the chain - it will receive client_id="custom-mi-id" instead of client_id="env-client-id".
The issue is even worse when managed_identity_client_id=None is explicitly passed (see "Additional context" below):
# This makes BOTH credentials lose their AZURE_CLIENT_ID fallback
credential = DefaultAzureCredential(managed_identity_client_id=None)
# managed_identity_client_id = None (kwargs.pop returns None, skips env var)
# workload_identity_client_id = None (inherited None, also skips env var)Minimal standalone reproduction (no Azure environment needed):
import os
os.environ["AZURE_CLIENT_ID"] = "env-client-id"
# Simulates the exact kwargs.pop logic from DefaultAzureCredential.__init__
# --- Scenario 1: managed_identity_client_id explicitly set ---
kwargs = {"managed_identity_client_id": "custom-mi-id"}
managed_identity_client_id = kwargs.pop(
"managed_identity_client_id", os.environ.get("AZURE_CLIENT_ID")
)
workload_identity_client_id = kwargs.pop(
"workload_identity_client_id", managed_identity_client_id # <-- current (buggy) code
)
print(f"managed_identity_client_id: {managed_identity_client_id}")
# Output: "custom-mi-id" (correct)
print(f"workload_identity_client_id: {workload_identity_client_id}")
# Output: "custom-mi-id" (BUG - docs say this should be "env-client-id")
# --- Scenario 2: managed_identity_client_id=None cascades ---
kwargs2 = {"managed_identity_client_id": None}
managed_identity_client_id2 = kwargs2.pop(
"managed_identity_client_id", os.environ.get("AZURE_CLIENT_ID")
)
workload_identity_client_id2 = kwargs2.pop(
"workload_identity_client_id", managed_identity_client_id2
)
print(f"\nmanaged_identity_client_id: {managed_identity_client_id2}")
# Output: None (separate kwargs.pop issue)
print(f"workload_identity_client_id: {workload_identity_client_id2}")
# Output: None (BUG - cascading None, should be "env-client-id")Expected behavior
workload_identity_client_id should independently default to os.environ.get("AZURE_CLIENT_ID") as the documentation states, regardless of what value managed_identity_client_id was set to.
The fix is a one-line change in default.py line 157:
# Before (buggy):
workload_identity_client_id = kwargs.pop("workload_identity_client_id", managed_identity_client_id)
# After (fixed):
workload_identity_client_id = kwargs.pop(
"workload_identity_client_id", os.environ.get(EnvironmentVariables.AZURE_CLIENT_ID)
)Screenshots
N/A - code-level bug.
Additional context
This bug compounds with a related issue where DefaultAzureCredential(managed_identity_client_id=None) does not fall back to AZURE_CLIENT_ID either (because kwargs.pop returns the explicit None instead of the default). When combined, explicitly passing managed_identity_client_id=None causes both managed_identity_client_id AND workload_identity_client_id to become None, losing the AZURE_CLIENT_ID fallback for the entire credential chain.
This is especially problematic during migration from ManagedIdentityCredential(client_id=client_id) to DefaultAzureCredential(managed_identity_client_id=client_id) where client_id may be None at runtime.
Related issue: #36365 (ManagedIdentityCredential class doesn't use AZURE_CLIENT_ID environment variable)
Documentation sources confirming the expected behavior:
- API reference (sync): "workload_identity_client_id (str) - Defaults to the value of the environment variable AZURE_CLIENT_ID"
- API reference (async): Same wording
- Versioned docs: Same wording
- Source docstring: Same wording
Metadata
Metadata
Assignees
Labels
Type
Projects
Status