Skip to content

Commit fb3837b

Browse files
authored
[Identity] Update AZURE_TOKEN_CREDENTIALS to allow specific creds (#41709)
Now users of DefaultAzureCredential can specify a specific credential in the DAC chain when using the AZURE_TOKEN_CREDENTIALS environment variable. Signed-off-by: Paul Van Eck <paulvaneck@microsoft.com>
1 parent 23db1c8 commit fb3837b

File tree

9 files changed

+493
-88
lines changed

9 files changed

+493
-88
lines changed

sdk/identity/azure-identity/CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
# Release History
22

3-
## 1.23.1 (Unreleased)
3+
## 1.24.0b1 (Unreleased)
44

55
### Features Added
66

7+
- Expanded the set of acceptable values for environment variable `AZURE_TOKEN_CREDENTIALS` to allow for selection of a specific credential in the `DefaultAzureCredential` chain. At runtime, only the specified credential will be used when acquiring tokens with `DefaultAzureCredential`. For example, setting `AZURE_TOKEN_CREDENTIALS=WorkloadIdentityCredential` will make `DefaultAzureCredential` use only `WorkloadIdentityCredential`.
8+
- Valid values are `EnvironmentCredential`, `WorkloadIdentityCredential`, `ManagedIdentityCredential`, `AzureCliCredential`, `AzurePowershellCredential`, `AzureDeveloperCliCredential`, and `InteractiveBrowserCredential`. ([#41709](https://github.com/Azure/azure-sdk-for-python/pull/41709))
9+
710
### Breaking Changes
811

912
### Bugs Fixed

sdk/identity/azure-identity/azure/identity/_credentials/default.py

Lines changed: 69 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from azure.core.credentials import AccessToken, AccessTokenInfo, TokenRequestOptions, SupportsTokenInfo, TokenCredential
1010
from .._constants import EnvironmentVariables
11-
from .._internal import get_default_authority, normalize_authority, within_dac
11+
from .._internal import get_default_authority, normalize_authority, within_dac, process_credential_exclusions
1212
from .azure_powershell import AzurePowerShellCredential
1313
from .browser import InteractiveBrowserCredential
1414
from .chained import ChainedTokenCredential
@@ -133,36 +133,74 @@ def __init__(self, **kwargs: Any) -> None: # pylint: disable=too-many-statement
133133

134134
process_timeout = kwargs.pop("process_timeout", 10)
135135

136-
token_credentials_env = os.environ.get(EnvironmentVariables.AZURE_TOKEN_CREDENTIALS, "").strip().lower()
137-
exclude_workload_identity_credential = kwargs.pop("exclude_workload_identity_credential", False)
138-
exclude_environment_credential = kwargs.pop("exclude_environment_credential", False)
139-
exclude_managed_identity_credential = kwargs.pop("exclude_managed_identity_credential", False)
140-
exclude_shared_token_cache_credential = kwargs.pop("exclude_shared_token_cache_credential", False)
141-
exclude_visual_studio_code_credential = kwargs.pop("exclude_visual_studio_code_credential", True)
142-
exclude_developer_cli_credential = kwargs.pop("exclude_developer_cli_credential", False)
143-
exclude_cli_credential = kwargs.pop("exclude_cli_credential", False)
144-
exclude_interactive_browser_credential = kwargs.pop("exclude_interactive_browser_credential", True)
145-
exclude_powershell_credential = kwargs.pop("exclude_powershell_credential", False)
146-
147-
if token_credentials_env == "dev":
148-
# In dev mode, use only developer credentials
149-
exclude_environment_credential = True
150-
exclude_managed_identity_credential = True
151-
exclude_workload_identity_credential = True
152-
elif token_credentials_env == "prod":
153-
# In prod mode, use only production credentials
154-
exclude_shared_token_cache_credential = True
155-
exclude_visual_studio_code_credential = True
156-
exclude_cli_credential = True
157-
exclude_developer_cli_credential = True
158-
exclude_powershell_credential = True
159-
exclude_interactive_browser_credential = True
160-
elif token_credentials_env != "":
161-
# If the environment variable is set to something other than dev or prod, raise an error
162-
raise ValueError(
163-
f"Invalid value for {EnvironmentVariables.AZURE_TOKEN_CREDENTIALS}: {token_credentials_env}. "
164-
"Valid values are 'dev' or 'prod'."
165-
)
136+
# Define credential configuration mapping
137+
credential_config = {
138+
"environment": {
139+
"exclude_param": "exclude_environment_credential",
140+
"env_name": "environmentcredential",
141+
"default_exclude": False,
142+
},
143+
"workload_identity": {
144+
"exclude_param": "exclude_workload_identity_credential",
145+
"env_name": "workloadidentitycredential",
146+
"default_exclude": False,
147+
},
148+
"managed_identity": {
149+
"exclude_param": "exclude_managed_identity_credential",
150+
"env_name": "managedidentitycredential",
151+
"default_exclude": False,
152+
},
153+
"shared_token_cache": {
154+
"exclude_param": "exclude_shared_token_cache_credential",
155+
"default_exclude": False,
156+
},
157+
"visual_studio_code": {
158+
"exclude_param": "exclude_visual_studio_code_credential",
159+
"default_exclude": True,
160+
},
161+
"cli": {
162+
"exclude_param": "exclude_cli_credential",
163+
"env_name": "azureclicredential",
164+
"default_exclude": False,
165+
},
166+
"developer_cli": {
167+
"exclude_param": "exclude_developer_cli_credential",
168+
"env_name": "azuredeveloperclicredential",
169+
"default_exclude": False,
170+
},
171+
"powershell": {
172+
"exclude_param": "exclude_powershell_credential",
173+
"env_name": "azurepowershellcredential",
174+
"default_exclude": False,
175+
},
176+
"interactive_browser": {
177+
"exclude_param": "exclude_interactive_browser_credential",
178+
"env_name": "interactivebrowsercredential",
179+
"default_exclude": True,
180+
},
181+
}
182+
183+
# Extract user-provided exclude flags and set defaults
184+
exclude_flags = {}
185+
user_excludes = {}
186+
for cred_key, config in credential_config.items():
187+
param_name = cast(str, config["exclude_param"])
188+
user_excludes[cred_key] = kwargs.pop(param_name, None)
189+
exclude_flags[cred_key] = config["default_exclude"]
190+
191+
# Process AZURE_TOKEN_CREDENTIALS environment variable and apply user overrides
192+
exclude_flags = process_credential_exclusions(credential_config, exclude_flags, user_excludes)
193+
194+
# Extract individual exclude flags for backward compatibility
195+
exclude_environment_credential = exclude_flags["environment"]
196+
exclude_workload_identity_credential = exclude_flags["workload_identity"]
197+
exclude_managed_identity_credential = exclude_flags["managed_identity"]
198+
exclude_shared_token_cache_credential = exclude_flags["shared_token_cache"]
199+
exclude_visual_studio_code_credential = exclude_flags["visual_studio_code"]
200+
exclude_cli_credential = exclude_flags["cli"]
201+
exclude_developer_cli_credential = exclude_flags["developer_cli"]
202+
exclude_powershell_credential = exclude_flags["powershell"]
203+
exclude_interactive_browser_credential = exclude_flags["interactive_browser"]
166204

167205
credentials: List[SupportsTokenInfo] = []
168206
within_dac.set(True)

sdk/identity/azure-identity/azure/identity/_internal/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from .utils import (
1212
get_default_authority,
1313
normalize_authority,
14+
process_credential_exclusions,
1415
resolve_tenant,
1516
validate_scope,
1617
validate_subscription,
@@ -48,6 +49,7 @@ def _scopes_to_resource(*scopes) -> str:
4849
"get_default_authority",
4950
"InteractiveCredential",
5051
"normalize_authority",
52+
"process_credential_exclusions",
5153
"resolve_tenant",
5254
"validate_scope",
5355
"validate_subscription",

sdk/identity/azure-identity/azure/identity/_internal/utils.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,65 @@ def resolve_tenant(
133133
'when creating the credential, or add "*" to additionally_allowed_tenants to allow '
134134
"acquiring tokens for any tenant.".format(tenant_id)
135135
)
136+
137+
138+
def process_credential_exclusions(credential_config: dict, exclude_flags: dict, user_excludes: dict) -> dict:
139+
"""Process credential exclusions based on environment variable and user overrides.
140+
141+
This method handles the AZURE_TOKEN_CREDENTIALS environment variable to determine
142+
which credentials should be excluded from the credential chain, and then applies
143+
any user-provided exclude overrides which take precedence over environment settings.
144+
145+
:param credential_config: Configuration mapping for all available credentials, containing
146+
exclude parameter names, environment names, and default exclude settings
147+
:type credential_config: dict
148+
:param exclude_flags: Dictionary of exclude flags for each credential (will be modified)
149+
:type exclude_flags: dict
150+
:param user_excludes: User-provided exclude overrides from constructor kwargs
151+
:type user_excludes: dict
152+
153+
:return: Dictionary of final exclude flags for each credential
154+
:rtype: dict
155+
156+
:raises ValueError: If token_credentials_env contains an invalid credential name
157+
"""
158+
# Handle AZURE_TOKEN_CREDENTIALS environment variable
159+
token_credentials_env = os.environ.get(EnvironmentVariables.AZURE_TOKEN_CREDENTIALS, "").strip().lower()
160+
161+
if token_credentials_env == "dev":
162+
# In dev mode, use only developer credentials
163+
dev_credentials = {"cli", "developer_cli", "powershell", "shared_token_cache"}
164+
for cred_key in credential_config:
165+
exclude_flags[cred_key] = cred_key not in dev_credentials
166+
elif token_credentials_env == "prod":
167+
# In prod mode, use only production credentials
168+
prod_credentials = {"environment", "workload_identity", "managed_identity"}
169+
for cred_key in credential_config:
170+
exclude_flags[cred_key] = cred_key not in prod_credentials
171+
elif token_credentials_env:
172+
# If a specific credential is specified, exclude all others except the specified one
173+
valid_credentials = {config["env_name"] for config in credential_config.values() if "env_name" in config}
174+
175+
if token_credentials_env not in valid_credentials:
176+
valid_values = ["dev", "prod"] + sorted(valid_credentials)
177+
raise ValueError(
178+
f"Invalid value for {EnvironmentVariables.AZURE_TOKEN_CREDENTIALS}: {token_credentials_env}. "
179+
f"Valid values are: {', '.join(valid_values)}."
180+
)
181+
182+
# Find which credential was selected and exclude all others
183+
selected_cred_key = None
184+
for cred_key, config in credential_config.items():
185+
if config.get("env_name") == token_credentials_env:
186+
selected_cred_key = cred_key
187+
break
188+
189+
for cred_key in credential_config:
190+
exclude_flags[cred_key] = cred_key != selected_cred_key
191+
192+
# Apply user-provided exclude flags (these override environment variable settings)
193+
for cred_key, user_value in user_excludes.items():
194+
if user_value is not None:
195+
exclude_flags[cred_key] = user_value
196+
197+
return exclude_flags

sdk/identity/azure-identity/azure/identity/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
# Copyright (c) Microsoft Corporation.
33
# Licensed under the MIT License.
44
# ------------------------------------
5-
VERSION = "1.23.1"
5+
VERSION = "1.24.0b1"

sdk/identity/azure-identity/azure/identity/aio/_credentials/default.py

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from azure.core.credentials import AccessToken, AccessTokenInfo, TokenRequestOptions
1010
from azure.core.credentials_async import AsyncTokenCredential, AsyncSupportsTokenInfo
1111
from ..._constants import EnvironmentVariables
12-
from ..._internal import get_default_authority, normalize_authority, within_dac
12+
from ..._internal import get_default_authority, normalize_authority, within_dac, process_credential_exclusions
1313
from .azure_cli import AzureCliCredential
1414
from .azd_cli import AzureDeveloperCliCredential
1515
from .azure_powershell import AzurePowerShellCredential
@@ -89,7 +89,7 @@ class DefaultAzureCredential(ChainedTokenCredential):
8989
:caption: Create a DefaultAzureCredential.
9090
"""
9191

92-
def __init__(self, **kwargs: Any) -> None: # pylint: disable=too-many-statements
92+
def __init__(self, **kwargs: Any) -> None: # pylint: disable=too-many-statements, too-many-locals
9393
if "tenant_id" in kwargs:
9494
raise TypeError("'tenant_id' is not supported in DefaultAzureCredential.")
9595

@@ -125,34 +125,68 @@ def __init__(self, **kwargs: Any) -> None: # pylint: disable=too-many-statement
125125

126126
process_timeout = kwargs.pop("process_timeout", 10)
127127

128-
token_credentials_env = os.environ.get(EnvironmentVariables.AZURE_TOKEN_CREDENTIALS, "").strip().lower()
129-
exclude_workload_identity_credential = kwargs.pop("exclude_workload_identity_credential", False)
130-
exclude_visual_studio_code_credential = kwargs.pop("exclude_visual_studio_code_credential", True)
131-
exclude_developer_cli_credential = kwargs.pop("exclude_developer_cli_credential", False)
132-
exclude_cli_credential = kwargs.pop("exclude_cli_credential", False)
133-
exclude_environment_credential = kwargs.pop("exclude_environment_credential", False)
134-
exclude_managed_identity_credential = kwargs.pop("exclude_managed_identity_credential", False)
135-
exclude_shared_token_cache_credential = kwargs.pop("exclude_shared_token_cache_credential", False)
136-
exclude_powershell_credential = kwargs.pop("exclude_powershell_credential", False)
137-
138-
if token_credentials_env == "dev":
139-
# In dev mode, use only developer credentials
140-
exclude_environment_credential = True
141-
exclude_managed_identity_credential = True
142-
exclude_workload_identity_credential = True
143-
elif token_credentials_env == "prod":
144-
# In prod mode, use only production credentials
145-
exclude_shared_token_cache_credential = True
146-
exclude_visual_studio_code_credential = True
147-
exclude_cli_credential = True
148-
exclude_developer_cli_credential = True
149-
exclude_powershell_credential = True
150-
elif token_credentials_env != "":
151-
# If the environment variable is set to something other than dev or prod, raise an error
152-
raise ValueError(
153-
f"Invalid value for {EnvironmentVariables.AZURE_TOKEN_CREDENTIALS}: {token_credentials_env}. "
154-
"Valid values are 'dev' or 'prod'."
155-
)
128+
# Define credential configuration mapping (async version)
129+
credential_config = {
130+
"environment": {
131+
"exclude_param": "exclude_environment_credential",
132+
"env_name": "environmentcredential",
133+
"default_exclude": False,
134+
},
135+
"workload_identity": {
136+
"exclude_param": "exclude_workload_identity_credential",
137+
"env_name": "workloadidentitycredential",
138+
"default_exclude": False,
139+
},
140+
"managed_identity": {
141+
"exclude_param": "exclude_managed_identity_credential",
142+
"env_name": "managedidentitycredential",
143+
"default_exclude": False,
144+
},
145+
"shared_token_cache": {
146+
"exclude_param": "exclude_shared_token_cache_credential",
147+
"default_exclude": False,
148+
},
149+
"visual_studio_code": {
150+
"exclude_param": "exclude_visual_studio_code_credential",
151+
"default_exclude": True,
152+
},
153+
"cli": {
154+
"exclude_param": "exclude_cli_credential",
155+
"env_name": "azureclicredential",
156+
"default_exclude": False,
157+
},
158+
"developer_cli": {
159+
"exclude_param": "exclude_developer_cli_credential",
160+
"env_name": "azuredeveloperclicredential",
161+
"default_exclude": False,
162+
},
163+
"powershell": {
164+
"exclude_param": "exclude_powershell_credential",
165+
"env_name": "azurepowershellcredential",
166+
"default_exclude": False,
167+
},
168+
}
169+
170+
# Extract user-provided exclude flags and set defaults
171+
exclude_flags = {}
172+
user_excludes = {}
173+
for cred_key, config in credential_config.items():
174+
param_name = cast(str, config["exclude_param"])
175+
user_excludes[cred_key] = kwargs.pop(param_name, None)
176+
exclude_flags[cred_key] = config["default_exclude"]
177+
178+
# Process AZURE_TOKEN_CREDENTIALS environment variable and apply user overrides
179+
exclude_flags = process_credential_exclusions(credential_config, exclude_flags, user_excludes)
180+
181+
# Extract individual exclude flags for backward compatibility
182+
exclude_environment_credential = exclude_flags["environment"]
183+
exclude_workload_identity_credential = exclude_flags["workload_identity"]
184+
exclude_managed_identity_credential = exclude_flags["managed_identity"]
185+
exclude_shared_token_cache_credential = exclude_flags["shared_token_cache"]
186+
exclude_visual_studio_code_credential = exclude_flags["visual_studio_code"]
187+
exclude_cli_credential = exclude_flags["cli"]
188+
exclude_developer_cli_credential = exclude_flags["developer_cli"]
189+
exclude_powershell_credential = exclude_flags["powershell"]
156190

157191
credentials: List[AsyncSupportsTokenInfo] = []
158192
within_dac.set(True)

sdk/identity/azure-identity/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
url="https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/identity/azure-identity",
3939
keywords="azure, azure sdk",
4040
classifiers=[
41-
"Development Status :: 5 - Production/Stable",
41+
"Development Status :: 4 - Beta",
4242
"Programming Language :: Python",
4343
"Programming Language :: Python :: 3 :: Only",
4444
"Programming Language :: Python :: 3",

0 commit comments

Comments
 (0)