Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -33,64 +33,25 @@ def _get_client_args(**kwargs):
# type: (dict) -> Optional[dict]
identity_config = kwargs.pop("identity_config", None) or {}

url = os.environ.get(EnvironmentVariables.MSI_ENDPOINT)
secret = os.environ.get(EnvironmentVariables.MSI_SECRET)
url = os.environ.get(EnvironmentVariables.IDENTITY_ENDPOINT)
secret = os.environ.get(EnvironmentVariables.IDENTITY_HEADER)
if not (url and secret):
# App Service managed identity isn't available in this environment
return None

if kwargs.get("client_id"):
identity_config["clientid"] = kwargs.pop("client_id")
if kwargs.get("resource_id"):
identity_config["mi_res_id"] = kwargs.pop("resource_id")

return dict(
kwargs,
_content_callback=_parse_app_service_expires_on,
identity_config=identity_config,
base_headers={"secret": secret},
base_headers={"X-IDENTITY-HEADER": secret},
request_factory=functools.partial(_get_request, url),
)


def _get_request(url, scope, identity_config):
# type: (str, str, dict) -> HttpRequest
request = HttpRequest("GET", url)
request.format_parameters(dict({"api-version": "2017-09-01", "resource": scope}, **identity_config))
request.format_parameters(dict({"api-version": "2019-08-01", "resource": scope}, **identity_config))
return request


def _parse_app_service_expires_on(content):
# type: (dict) -> None
"""Parse an App Service MSI version 2017-09-01 expires_on value to epoch seconds.

This version of the API returns expires_on as a UTC datetime string rather than epoch seconds. The string's
format depends on the OS. Responses on Windows include AM/PM, for example "1/16/2020 5:24:12 AM +00:00".
Responses on Linux do not, for example "06/20/2019 02:57:58 +00:00".

:raises ValueError: ``expires_on`` didn't match an expected format
"""

# Azure ML sets the same environment variables as App Service but returns expires_on as an integer.
# That means we could have an Azure ML response here, so let's first try to parse expires_on as an int.
try:
content["expires_on"] = int(content["expires_on"])
return
except ValueError:
pass

import calendar
import time

expires_on = content["expires_on"]
if expires_on.endswith(" +00:00"):
date_string = expires_on[: -len(" +00:00")]
for format_string in ("%m/%d/%Y %H:%M:%S", "%m/%d/%Y %I:%M:%S %p"): # (Linux, Windows)
try:
t = time.strptime(date_string, format_string)
content["expires_on"] = calendar.timegm(t)
return
except ValueError:
pass

raise ValueError("'{}' doesn't match the expected format".format(expires_on))
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# ------------------------------------
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# ------------------------------------
import functools
import os
from typing import TYPE_CHECKING

from azure.core.pipeline.transport import HttpRequest

from .._constants import EnvironmentVariables
from .._internal.managed_identity_base import ManagedIdentityBase
from .._internal.managed_identity_client import ManagedIdentityClient

if TYPE_CHECKING:
from typing import Any, Optional


class AzureMLCredential(ManagedIdentityBase):
def get_client(self, **kwargs):
# type: (**Any) -> Optional[ManagedIdentityClient]
client_args = _get_client_args(**kwargs)
if client_args:
return ManagedIdentityClient(**client_args)
return None

def get_unavailable_message(self):
# type: () -> str
return "Azure ML managed identity configuration not found in environment"


def _get_client_args(**kwargs):
# type: (dict) -> Optional[dict]
identity_config = kwargs.pop("identity_config", None) or {}

url = os.environ.get(EnvironmentVariables.MSI_ENDPOINT)
secret = os.environ.get(EnvironmentVariables.MSI_SECRET)
if not (url and secret):
# Azure ML managed identity isn't available in this environment
return None

if kwargs.get("client_id"):
identity_config["clientid"] = kwargs.pop("client_id")
if kwargs.get("resource_id"):
identity_config["mi_res_id"] = kwargs.pop("resource_id")

return dict(
kwargs,
identity_config=identity_config,
base_headers={"secret": secret},
request_factory=functools.partial(_get_request, url),
)


def _get_request(url, scope, identity_config):
# type: (str, str, dict) -> HttpRequest
request = HttpRequest("GET", url)
request.format_parameters(dict({"api-version": "2017-09-01", "resource": scope}, **identity_config))
return request
Original file line number Diff line number Diff line change
Expand Up @@ -45,30 +45,34 @@ class ManagedIdentityCredential(object):
def __init__(self, **kwargs):
# type: (**Any) -> None
self._credential = None # type: Optional[TokenCredential]
if os.environ.get(EnvironmentVariables.MSI_ENDPOINT):
if os.environ.get(EnvironmentVariables.IDENTITY_ENDPOINT):
if os.environ.get(EnvironmentVariables.IDENTITY_HEADER):
if os.environ.get(EnvironmentVariables.IDENTITY_SERVER_THUMBPRINT):
_LOGGER.info("%s will use Service Fabric managed identity", self.__class__.__name__)
from .service_fabric import ServiceFabricCredential

self._credential = ServiceFabricCredential(**kwargs)
else:
_LOGGER.info("%s will use App Service managed identity", self.__class__.__name__)
from .app_service import AppServiceCredential

self._credential = AppServiceCredential(**kwargs)
elif os.environ.get(EnvironmentVariables.IMDS_ENDPOINT):
_LOGGER.info("%s will use Azure Arc managed identity", self.__class__.__name__)
from .azure_arc import AzureArcCredential

self._credential = AzureArcCredential(**kwargs)
elif os.environ.get(EnvironmentVariables.MSI_ENDPOINT):
if os.environ.get(EnvironmentVariables.MSI_SECRET):
_LOGGER.info("%s will use App Service managed identity", self.__class__.__name__)
from .app_service import AppServiceCredential
_LOGGER.info("%s will use Azure ML managed identity", self.__class__.__name__)
from .azure_ml import AzureMLCredential

self._credential = AppServiceCredential(**kwargs)
self._credential = AzureMLCredential(**kwargs)
else:
_LOGGER.info("%s will use Cloud Shell managed identity", self.__class__.__name__)
from .cloud_shell import CloudShellCredential

self._credential = CloudShellCredential(**kwargs)
elif os.environ.get(EnvironmentVariables.IDENTITY_ENDPOINT):
if os.environ.get(EnvironmentVariables.IDENTITY_HEADER) and os.environ.get(
EnvironmentVariables.IDENTITY_SERVER_THUMBPRINT
):
_LOGGER.info("%s will use Service Fabric managed identity", self.__class__.__name__)
from .service_fabric import ServiceFabricCredential

self._credential = ServiceFabricCredential(**kwargs)
elif os.environ.get(EnvironmentVariables.IMDS_ENDPOINT):
_LOGGER.info("%s will use Azure Arc managed identity", self.__class__.__name__)
from .azure_arc import AzureArcCredential

self._credential = AzureArcCredential(**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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# ------------------------------------
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# ------------------------------------
from typing import TYPE_CHECKING

from .._internal.managed_identity_base import AsyncManagedIdentityBase
from .._internal.managed_identity_client import AsyncManagedIdentityClient
from ..._credentials.azure_ml import _get_client_args

if TYPE_CHECKING:
from typing import Any, Optional


class AzureMLCredential(AsyncManagedIdentityBase):
def get_client(self, **kwargs: "Any") -> "Optional[AsyncManagedIdentityClient]":
client_args = _get_client_args(**kwargs)
if client_args:
return AsyncManagedIdentityClient(**client_args)
return None

def get_unavailable_message(self) -> str:
return "Azure ML managed identity configuration not found in environment"
Original file line number Diff line number Diff line change
Expand Up @@ -38,30 +38,39 @@ class ManagedIdentityCredential(AsyncContextManager):
def __init__(self, **kwargs: "Any") -> None:
self._credential = None # type: Optional[AsyncTokenCredential]

if os.environ.get(EnvironmentVariables.MSI_ENDPOINT):
if os.environ.get(EnvironmentVariables.MSI_SECRET):
_LOGGER.info("%s will use App Service managed identity", self.__class__.__name__)
from .app_service import AppServiceCredential
if os.environ.get(EnvironmentVariables.IDENTITY_ENDPOINT):
if os.environ.get(EnvironmentVariables.IDENTITY_HEADER):
if os.environ.get(EnvironmentVariables.IDENTITY_SERVER_THUMBPRINT):
_LOGGER.info("%s will use Service Fabric managed identity", self.__class__.__name__)
from .service_fabric import ServiceFabricCredential

self._credential = ServiceFabricCredential(**kwargs)
else:
_LOGGER.info("%s will use App Service managed identity", self.__class__.__name__)
from .app_service import AppServiceCredential

self._credential = AppServiceCredential(**kwargs)
elif os.environ.get(EnvironmentVariables.IMDS_ENDPOINT):
_LOGGER.info("%s will use Azure Arc managed identity", self.__class__.__name__)
from .azure_arc import AzureArcCredential

self._credential = AppServiceCredential(**kwargs)
self._credential = AzureArcCredential(**kwargs)
else:
_LOGGER.info("%s will use Cloud Shell managed identity", self.__class__.__name__)
from .cloud_shell import CloudShellCredential

self._credential = CloudShellCredential(**kwargs)
elif os.environ.get(EnvironmentVariables.IDENTITY_ENDPOINT):
if os.environ.get(EnvironmentVariables.IDENTITY_HEADER) and os.environ.get(
EnvironmentVariables.IDENTITY_SERVER_THUMBPRINT
):
_LOGGER.info("%s will use Service Fabric managed identity", self.__class__.__name__)
from .service_fabric import ServiceFabricCredential

self._credential = ServiceFabricCredential(**kwargs)
elif os.environ.get(EnvironmentVariables.IMDS_ENDPOINT):
_LOGGER.info("%s will use Azure Arc managed identity", self.__class__.__name__)
from .azure_arc import AzureArcCredential
elif os.environ.get(EnvironmentVariables.MSI_ENDPOINT):
if os.environ.get(EnvironmentVariables.MSI_SECRET):
_LOGGER.info("%s will use Azure ML managed identity", self.__class__.__name__)
from .azure_ml import AzureMLCredential

self._credential = AzureArcCredential(**kwargs)
self._credential = AzureMLCredential(**kwargs)
else:
_LOGGER.info("%s will use Cloud Shell managed identity", self.__class__.__name__)
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ interactions:
secret:
- redacted
method: GET
uri: https://msi-endpoint/token?api-version=2017-09-01&resource=https://management.azure.com
uri: https://msi-endpoint/token?api-version=2019-08-01&resource=https://management.azure.com
response:
body:
string: '{"access_token": "redacted", "expires_on": "05/22/2021 17:14:33 +00:00",
string: '{"access_token": "redacted", "expires_on": "1646265298",
"resource": "https://management.azure.com", "token_type": "Bearer", "client_id":
"6d807b1c-2a6f-41a0-b428-d0e3a08382a0"}'
headers:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ interactions:
secret:
- redacted
method: GET
uri: https://msi-endpoint/token?api-version=2017-09-01&resource=https://management.azure.com
uri: https://msi-endpoint/token?api-version=2019-08-01&resource=https://management.azure.com
response:
body:
string: '{"access_token": "redacted", "expires_on": "05/22/2021 17:14:33 +00:00",
string: '{"access_token": "redacted", "expires_on": "1646265298",
"resource": "https://management.azure.com", "token_type": "Bearer", "client_id":
"6d807b1c-2a6f-41a0-b428-d0e3a08382a0"}'
headers:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ interactions:
secret:
- redacted
method: GET
uri: https://msi-endpoint/token?api-version=2017-09-01&resource=https://management.azure.com&clientid=client-id
uri: https://msi-endpoint/token?api-version=2019-08-01&resource=https://management.azure.com&client_id=client-id
response:
body:
string: '{"access_token": "redacted", "expires_on": "05/22/2021 17:18:21 +00:00",
string: '{"access_token": "redacted", "expires_on": "1646265298",
"resource": "https://management.azure.com", "token_type": "Bearer", "client_id":
"client-id"}'
headers:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ interactions:
secret:
- redacted
method: GET
uri: https://msi-endpoint/token?api-version=2017-09-01&resource=https://management.azure.com&clientid=client-id
uri: https://msi-endpoint/token?api-version=2019-08-01&resource=https://management.azure.com&client_id=client-id
response:
body:
string: '{"access_token": "redacted", "expires_on": "05/22/2021 17:18:21 +00:00",
string: '{"access_token": "redacted", "expires_on": "1646265298",
"resource": "https://management.azure.com", "token_type": "Bearer", "client_id":
"client-id"}'
headers:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ interactions:
secret:
- redacted
method: GET
uri: https://msi-endpoint/token?api-version=2017-09-01&resource=https://management.azure.com
uri: https://msi-endpoint/token?api-version=2019-08-01&resource=https://management.azure.com
response:
body:
string: '{"access_token": "redacted", "expires_on": "05/22/2021 17:18:20 +00:00",
string: '{"access_token": "redacted", "expires_on": "1646265298",
"resource": "https://management.azure.com", "token_type": "Bearer", "client_id":
"6d807b1c-2a6f-41a0-b428-d0e3a08382a0"}'
headers:
Expand All @@ -21,5 +21,5 @@ interactions:
status:
code: 200
message: OK
url: http://172.16.7.2:8081/msi/token?api-version=2017-09-01&resource=https://management.azure.com
url: http://172.16.7.2:8081/msi/token?api-version=2019-08-01&resource=https://management.azure.com
version: 1
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ interactions:
secret:
- redacted
method: GET
uri: https://msi-endpoint/token?api-version=2017-09-01&resource=https://management.azure.com
uri: https://msi-endpoint/token?api-version=2019-08-01&resource=https://management.azure.com
response:
body:
string: '{"access_token": "redacted", "expires_on": "05/22/2021 17:18:20 +00:00",
string: '{"access_token": "redacted", "expires_on": "1646265298",
"resource": "https://management.azure.com", "token_type": "Bearer", "client_id":
"6d807b1c-2a6f-41a0-b428-d0e3a08382a0"}'
headers:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ interactions:
secret:
- redacted
method: GET
uri: https://msi-endpoint/token?api-version=2017-09-01&resource=https://management.azure.com&clientid=client-id
uri: https://msi-endpoint/token?api-version=2019-08-01&resource=https://management.azure.com&client_id=client-id
response:
body:
string: '{"access_token": "redacted", "expires_on": "05/22/2021 17:18:21 +00:00",
string: '{"access_token": "redacted", "expires_on": "1646265298",
"resource": "https://management.azure.com", "token_type": "Bearer", "client_id":
"client-id"}'
headers:
Expand All @@ -21,5 +21,5 @@ interactions:
status:
code: 200
message: OK
url: http://172.16.7.2:8081/msi/token?api-version=2017-09-01&resource=https://management.azure.com&clientid=b48ae5dc-70d0-488a-8ef5-f30d905cd5ec
url: http://172.16.7.2:8081/msi/token?api-version=2019-08-01&resource=https://management.azure.com&clientid=b48ae5dc-70d0-488a-8ef5-f30d905cd5ec
version: 1
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ interactions:
secret:
- redacted
method: GET
uri: https://msi-endpoint/token?api-version=2017-09-01&resource=https://management.azure.com&clientid=client-id
uri: https://msi-endpoint/token?api-version=2019-08-01&resource=https://management.azure.com&client_id=client-id
response:
body:
string: '{"access_token": "redacted", "expires_on": "05/22/2021 17:18:21 +00:00",
string: '{"access_token": "redacted", "expires_on": "1646265298",
"resource": "https://management.azure.com", "token_type": "Bearer", "client_id":
"client-id"}'
headers:
Expand Down
Loading