Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sdk/identity/azure-identity/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Features Added

- Added `AzureDeveloperCredential` for Azure Developer CLI. ([#27916](https://github.com/Azure/azure-sdk-for-python/pull/27916))
- Added `WorkloadIdentityCredential` for Workload Identity Federation on Kubernetes ([#28536](https://github.com/Azure/azure-sdk-for-python/pull/28536))
- Added support to use "TryAutoDetect" as the value for `AZURE_REGIONAL_AUTHORITY_NAME` to enable auto detecting the appropriate authority ([#526](https://github.com/AzureAD/microsoft-authentication-library-for-python/issues/526))

## 1.13.0b1 (2023-01-10)
Expand Down
13 changes: 7 additions & 6 deletions sdk/identity/azure-identity/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,11 @@ authentication flow. This can also be selected manually by running `az login --u

#### Authenticate via the Azure Developer CLI

`DefaultAzureCredential` and `AzureDeveloperCliCredential` can authenticate as the user
signed in to the [Azure Developer CLI][azd_cli]. To sign in to the Azure Developer CLI, run
`azd login`. On a system with a default web browser, the Azure Developer CLI will launch
the browser to authenticate a user.
Developers coding outside of an IDE can also use the [Azure Developer CLI][azure_developer_cli] to authenticate. Applications using the `DefaultAzureCredential` or the `AzureDeveloperCliCredential` can then use this account to authenticate calls in their application when running locally.

To authenticate with the [Azure Developer CLI][azure_developer_cli], users can run the command `azd login`. For users running on a system with a default web browser, the Azure Developer CLI will launch the browser to authenticate the user.

When no default browser is available, `azd login` will use the device code
authentication flow. This can also be selected manually by running `azd login --use-device-code`.
For systems without a default web browser, the `azd login --use-device-code` command will use the device code authentication flow.

## Key concepts

Expand All @@ -82,7 +80,9 @@ this library's credential classes.
![DefaultAzureCredential authentication flow](https://raw.githubusercontent.com/Azure/azure-sdk-for-python/main/sdk/identity/azure-identity/images/mermaidjs/DefaultAzureCredentialAuthFlow.svg)

1. **Environment** - `DefaultAzureCredential` will read account information specified via [environment variables](#environment-variables "environment variables") and use it to authenticate.
1. **Workload Identity** - If the application is deployed to an Azure Kubernetes service with Managed Identity enabled, `DefaultAzureCredential` will authenticate with it.
1. **Managed Identity** - If the application is deployed to an Azure host with Managed Identity enabled, `DefaultAzureCredential` will authenticate with it.
1. **Azure Developer CLI** - If the developer has authenticated via the Azure Developer CLI `azd login` command, the `DefaultAzureCredential` will authenticate with that account.
1. **Azure CLI** - If a user has signed in via the Azure CLI `az login` command, `DefaultAzureCredential` will authenticate as that user.
1. **Azure PowerShell** - If a user has signed in via Azure PowerShell's `Connect-AzAccount` command, `DefaultAzureCredential` will authenticate as that user.
1. **Interactive browser** - If enabled, `DefaultAzureCredential` will interactively authenticate a user via the default browser. This is disabled by default.
Expand Down Expand Up @@ -400,6 +400,7 @@ additional questions or comments.
[auth_code_cred_ref]: https://aka.ms/azsdk/python/identity/authorizationcodecredential
[azure_appconfiguration]: https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/appconfiguration/azure-appconfiguration
[azure_cli]: https://learn.microsoft.com/cli/azure
[azure_developer_cli]:https://aka.ms/azure-dev
[azure_core_transport_doc]: https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/core/azure-core/CLIENT_LIBRARY_DEVELOPER.md#transport
[azure_eventhub]: https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/eventhub/azure-eventhub
[azure_keyvault_certificates]: https://github.com/Azure/azure-sdk-for-python/blob/main/sdk//keyvault/azure-keyvault-certificates
Expand Down
2 changes: 2 additions & 0 deletions sdk/identity/azure-identity/azure/identity/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
SharedTokenCacheCredential,
UsernamePasswordCredential,
VisualStudioCodeCredential,
WorkloadIdentityCredential,
)
from ._persistent_cache import TokenCachePersistenceOptions

Expand Down Expand Up @@ -53,6 +54,7 @@
"TokenCachePersistenceOptions",
"UsernamePasswordCredential",
"VisualStudioCodeCredential",
"WorkloadIdentityCredential",
]

from ._version import VERSION
Expand Down
2 changes: 1 addition & 1 deletion sdk/identity/azure-identity/azure/identity/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,4 @@ class EnvironmentVariables:
AZURE_REGIONAL_AUTHORITY_NAME = "AZURE_REGIONAL_AUTHORITY_NAME"

AZURE_FEDERATED_TOKEN_FILE = "AZURE_FEDERATED_TOKEN_FILE"
TOKEN_EXCHANGE_VARS = (AZURE_AUTHORITY_HOST, AZURE_TENANT_ID, AZURE_FEDERATED_TOKEN_FILE)
WORKLOAD_IDENTITY_VARS = (AZURE_AUTHORITY_HOST, AZURE_TENANT_ID, AZURE_FEDERATED_TOKEN_FILE)
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from .user_password import UsernamePasswordCredential
from .vscode import VisualStudioCodeCredential
from .client_assertion import ClientAssertionCredential
from .workload_identity import WorkloadIdentityCredential


__all__ = [
Expand All @@ -39,5 +40,6 @@
"SharedTokenCacheCredential",
"AzureCliCredential",
"UsernamePasswordCredential",
"WorkloadIdentityCredential",
"VisualStudioCodeCredential",
]
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# ------------------------------------
import logging
import os
from typing import List, TYPE_CHECKING, Any
from typing import List, TYPE_CHECKING, Any, cast

from azure.core.credentials import AccessToken
from .._constants import EnvironmentVariables
Expand All @@ -18,6 +18,7 @@
from .azure_cli import AzureCliCredential
from .azd_cli import AzureDeveloperCliCredential
from .vscode import VisualStudioCodeCredential
from .workload_identity import WorkloadIdentityCredential

if TYPE_CHECKING:
from azure.core.credentials import TokenCredential
Expand All @@ -33,12 +34,15 @@ class DefaultAzureCredential(ChainedTokenCredential):

1. A service principal configured by environment variables. See :class:`~azure.identity.EnvironmentCredential` for
more details.
2. An Azure managed identity. See :class:`~azure.identity.ManagedIdentityCredential` for more details.
3. On Windows only: a user who has signed in with a Microsoft application, such as Visual Studio. If multiple
2. WorkloadIdentityCredential if environment variable configuration is set by the Azure workload
identity webhook.
3. An Azure managed identity. See :class:`~azure.identity.ManagedIdentityCredential` for more details.
4. The identity currently logged in to the Azure Developer CLI.
5. On Windows only: a user who has signed in with a Microsoft application, such as Visual Studio. If multiple
identities are in the cache, then the value of the environment variable ``AZURE_USERNAME`` is used to select
which identity to use. See :class:`~azure.identity.SharedTokenCacheCredential` for more details.
4. The identity currently logged in to the Azure CLI.
5. The identity currently logged in to Azure PowerShell.
6. The identity currently logged in to the Azure CLI.
7. The identity currently logged in to Azure PowerShell.

This default behavior is configurable with keyword arguments.

Expand All @@ -64,6 +68,8 @@ class DefaultAzureCredential(ChainedTokenCredential):
AZURE_TENANT_ID, if any. If unspecified, users will authenticate in their home tenants.
:keyword str managed_identity_client_id: The client ID of a user-assigned managed identity. Defaults to the value
of the environment variable AZURE_CLIENT_ID, if any. If not specified, a system-assigned identity will be used.
:keyword str workload_identity_client_id: The client ID of a user-assigned managed identity. Defaults to the value
of the environment variable AZURE_CLIENT_ID, if any. If not specified, a system-assigned identity will be used.
Comment thread
xiangyan99 marked this conversation as resolved.
Outdated
:keyword str interactive_browser_client_id: The client ID to be used in interactive browser credential. If not
specified, users will authenticate to an Azure development application.
:keyword str shared_cache_username: Preferred username for :class:`~azure.identity.SharedTokenCacheCredential`.
Expand All @@ -76,7 +82,7 @@ class DefaultAzureCredential(ChainedTokenCredential):
Directory work or school accounts.
Comment thread
xiangyan99 marked this conversation as resolved.
"""

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

Expand All @@ -100,6 +106,9 @@ def __init__(self, **kwargs: Any) -> None:
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
)
interactive_browser_client_id = kwargs.pop("interactive_browser_client_id", None)

shared_cache_username = kwargs.pop("shared_cache_username", os.environ.get(EnvironmentVariables.AZURE_USERNAME))
Expand All @@ -119,6 +128,13 @@ def __init__(self, **kwargs: Any) -> None:
credentials = [] # type: List[TokenCredential]
if not exclude_environment_credential:
credentials.append(EnvironmentCredential(authority=authority, **kwargs))
if all(os.environ.get(var) for var in EnvironmentVariables.WORKLOAD_IDENTITY_VARS):
client_id = workload_identity_client_id
credentials.append(WorkloadIdentityCredential(
client_id=cast(str, client_id),
tenant_id=os.environ[EnvironmentVariables.AZURE_TENANT_ID],
file=os.environ[EnvironmentVariables.AZURE_FEDERATED_TOKEN_FILE],
**kwargs))
if not exclude_managed_identity_credential:
credentials.append(ManagedIdentityCredential(client_id=managed_identity_client_id, **kwargs))
if not exclude_azd_cli_credential:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,18 @@ def __init__(self, **kwargs: Any) -> None:
from .cloud_shell import CloudShellCredential

self._credential = CloudShellCredential(**kwargs)
elif all(os.environ.get(var) for var in EnvironmentVariables.TOKEN_EXCHANGE_VARS):
_LOGGER.info("%s will use token exchange", self.__class__.__name__)
from .token_exchange import TokenExchangeCredential
elif all(os.environ.get(var) for var in EnvironmentVariables.WORKLOAD_IDENTITY_VARS):
_LOGGER.info("%s will use workload identity", self.__class__.__name__)
from .workload_identity import WorkloadIdentityCredential

client_id = kwargs.pop("client_id", None) or os.environ.get(EnvironmentVariables.AZURE_CLIENT_ID)
if not client_id:
raise ValueError('Configure the environment with a client ID or pass a value for "client_id" argument')

self._credential = TokenExchangeCredential(
self._credential = WorkloadIdentityCredential(
tenant_id=os.environ[EnvironmentVariables.AZURE_TENANT_ID],
client_id=client_id,
token_file_path=os.environ[EnvironmentVariables.AZURE_FEDERATED_TOKEN_FILE],
file=os.environ[EnvironmentVariables.AZURE_FEDERATED_TOKEN_FILE],
**kwargs
)
else:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,44 @@
class TokenFileMixin:
def __init__(
self,
token_file_path: str,
file: str,
**_: Any
) -> None:
super(TokenFileMixin, self).__init__()
self._jwt = ""
self._last_read_time = 0
self._token_file_path = token_file_path
self._file = file

def get_service_account_token(self) -> str:
now = int(time.time())
if now - self._last_read_time > 300:
with open(self._token_file_path) as f:
if now - self._last_read_time > 600:
with open(self._file) as f:
self._jwt = f.read()
self._last_read_time = now
return self._jwt


class TokenExchangeCredential(ClientAssertionCredential, TokenFileMixin):
class WorkloadIdentityCredential(ClientAssertionCredential, TokenFileMixin):
"""WorkloadIdentityCredential supports Azure workload identity on Kubernetes.
See the `workload identity overview <https://learn.microsoft.com/azure/aks/workload-identity-overview>`_
for more information.

:param str tenant_id: ID of the application's Azure Active Directory tenant. Also called its "directory" ID.
:param str client_id: The client ID of an Azure AD app registration.
:param str file: The path to a file containing a Kubernetes service account token that authenticates the identity.
"""

def __init__(
self,
tenant_id: str,
client_id: str,
token_file_path: str,
file: str,
**kwargs: Any
) -> None:
super(TokenExchangeCredential, self).__init__(
super(WorkloadIdentityCredential, self).__init__(
tenant_id=tenant_id,
client_id=client_id,
func=self.get_service_account_token,
token_file_path=token_file_path,
file=file,
**kwargs
)
2 changes: 2 additions & 0 deletions sdk/identity/azure-identity/azure/identity/aio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
SharedTokenCacheCredential,
VisualStudioCodeCredential,
ClientAssertionCredential,
WorkloadIdentityCredential,
)


Expand All @@ -37,4 +38,5 @@
"SharedTokenCacheCredential",
"VisualStudioCodeCredential",
"ClientAssertionCredential",
"WorkloadIdentityCredential",
]
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from .azd_cli import AzureDeveloperCliCredential
from .vscode import VisualStudioCodeCredential
from .client_assertion import ClientAssertionCredential
from .workload_identity import WorkloadIdentityCredential


__all__ = [
Expand All @@ -33,4 +34,5 @@
"SharedTokenCacheCredential",
"VisualStudioCodeCredential",
"ClientAssertionCredential",
"WorkloadIdentityCredential",
]
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# ------------------------------------
import logging
import os
from typing import List, TYPE_CHECKING, Any
from typing import List, TYPE_CHECKING, Any, cast

from azure.core.credentials import AccessToken
from ..._constants import EnvironmentVariables
Expand All @@ -17,6 +17,7 @@
from .managed_identity import ManagedIdentityCredential
from .shared_cache import SharedTokenCacheCredential
from .vscode import VisualStudioCodeCredential
from .workload_identity import WorkloadIdentityCredential

if TYPE_CHECKING:
from azure.core.credentials_async import AsyncTokenCredential
Expand All @@ -32,12 +33,15 @@ class DefaultAzureCredential(ChainedTokenCredential):

1. A service principal configured by environment variables. See :class:`~azure.identity.aio.EnvironmentCredential`
for more details.
2. An Azure managed identity. See :class:`~azure.identity.aio.ManagedIdentityCredential` for more details.
3. On Windows only: a user who has signed in with a Microsoft application, such as Visual Studio. If multiple
2. WorkloadIdentityCredential if environment variable configuration is set by the Azure workload
identity webhook.
3. An Azure managed identity. See :class:`~azure.identity.aio.ManagedIdentityCredential` for more details.
4. The identity currently logged in to the Azure Developer CLI.
5. On Windows only: a user who has signed in with a Microsoft application, such as Visual Studio. If multiple
identities are in the cache, then the value of the environment variable ``AZURE_USERNAME`` is used to select
which identity to use. See :class:`~azure.identity.aio.SharedTokenCacheCredential` for more details.
4. The identity currently logged in to the Azure CLI.
5. The identity currently logged in to Azure PowerShell.
6. The identity currently logged in to the Azure CLI.
7. The identity currently logged in to Azure PowerShell.

This default behavior is configurable with keyword arguments.

Expand All @@ -58,6 +62,8 @@ class DefaultAzureCredential(ChainedTokenCredential):
**False**.
:keyword str managed_identity_client_id: The client ID of a user-assigned managed identity. Defaults to the value
of the environment variable AZURE_CLIENT_ID, if any. If not specified, a system-assigned identity will be used.
:keyword str workload_identity_client_id: The client ID of a user-assigned managed identity. Defaults to the value
of the environment variable AZURE_CLIENT_ID, if any. If not specified, a system-assigned identity will be used.
Comment thread
xiangyan99 marked this conversation as resolved.
Outdated
:keyword str shared_cache_username: Preferred username for :class:`~azure.identity.aio.SharedTokenCacheCredential`.
Defaults to the value of environment variable AZURE_USERNAME, if any.
:keyword str shared_cache_tenant_id: Preferred tenant for :class:`~azure.identity.aio.SharedTokenCacheCredential`.
Expand Down Expand Up @@ -93,6 +99,9 @@ def __init__(self, **kwargs: Any) -> None:
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
)

vscode_tenant_id = kwargs.pop(
"visual_studio_code_tenant_id", os.environ.get(EnvironmentVariables.AZURE_TENANT_ID)
Expand All @@ -109,6 +118,13 @@ def __init__(self, **kwargs: Any) -> None:
credentials = [] # type: List[AsyncTokenCredential]
if not exclude_environment_credential:
credentials.append(EnvironmentCredential(authority=authority, **kwargs))
if all(os.environ.get(var) for var in EnvironmentVariables.WORKLOAD_IDENTITY_VARS):
client_id = workload_identity_client_id
credentials.append(WorkloadIdentityCredential(
client_id=cast(str, client_id),
tenant_id=os.environ[EnvironmentVariables.AZURE_TENANT_ID],
file=os.environ[EnvironmentVariables.AZURE_FEDERATED_TOKEN_FILE],
**kwargs))
if not exclude_managed_identity_credential:
credentials.append(ManagedIdentityCredential(client_id=managed_identity_client_id, **kwargs))
if not exclude_azd_cli_credential:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,18 @@ def __init__(self, **kwargs: Any) -> None:
from .cloud_shell import CloudShellCredential

self._credential = CloudShellCredential(**kwargs)
elif all(os.environ.get(var) for var in EnvironmentVariables.TOKEN_EXCHANGE_VARS):
_LOGGER.info("%s will use token exchange", self.__class__.__name__)
from .token_exchange import TokenExchangeCredential
elif all(os.environ.get(var) for var in EnvironmentVariables.WORKLOAD_IDENTITY_VARS):
_LOGGER.info("%s will use workload identity", self.__class__.__name__)
from .workload_identity import WorkloadIdentityCredential

client_id = kwargs.pop("client_id", None) or os.environ.get(EnvironmentVariables.AZURE_CLIENT_ID)
if not client_id:
raise ValueError('Configure the environment with a client ID or pass a value for "client_id" argument')

self._credential = TokenExchangeCredential(
self._credential = WorkloadIdentityCredential(
tenant_id=os.environ[EnvironmentVariables.AZURE_TENANT_ID],
client_id=client_id,
token_file_path=os.environ[EnvironmentVariables.AZURE_FEDERATED_TOKEN_FILE],
file=os.environ[EnvironmentVariables.AZURE_FEDERATED_TOKEN_FILE],
**kwargs
)
else:
Expand Down
Loading