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
35 changes: 32 additions & 3 deletions src/azure-cli-core/azure/cli/core/auth/adal_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

class MSIAuthenticationWrapper(MSIAuthentication):
# This method is exposed for Azure Core. Add *scopes, **kwargs to fit azure.core requirement
# pylint: disable=line-too-long
def get_token(self, *scopes, **kwargs): # pylint:disable=unused-argument
logger.debug("MSIAuthenticationWrapper.get_token invoked by Track 2 SDK with scopes=%s", scopes)

Expand All @@ -27,18 +28,29 @@ def get_token(self, *scopes, **kwargs): # pylint:disable=unused-argument
# If available, use resource provided by SDK
self.resource = resource
self.set_token()
# Managed Identity token entry sample:
# VM managed identity endpoint 2018-02-01 token entry sample:
# curl "http://169.254.169.254:80/metadata/identity/oauth2/token?resource=https://management.core.windows.net/&api-version=2018-02-01" -H "Metadata: true"
# {
# "access_token": "eyJ0eXAiOiJKV...",
# "client_id": "da95e381-d7ab-4fdc-8047-2457909c723b",
# "expires_in": "86386",
# "expires_on": "1605238724",
# "ext_expires_in": "86399",
# "not_before": "1605152024",
# "resource": "https://management.azure.com/",
# "resource": "https://management.core.windows.net/",
# "token_type": "Bearer"
# }
return AccessToken(self.token['access_token'], int(self.token['expires_on']))

# App Service managed identity endpoint 2017-09-01 token entry sample:
# curl "${MSI_ENDPOINT}?resource=https://management.core.windows.net/&api-version=2017-09-01" -H "secret: ${MSI_SECRET}"
# {
# "access_token": "eyJ0eXAiOiJKV...",
# "expires_on":"11/05/2021 15:18:31 +00:00",
Copy link
Member Author

@jiasli jiasli Aug 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example response was retrieved on a Linux App Service.

It has been reported that managed identity on Windows App Service returns expires_on in a different format:

"expires_on":"8/5/2023 9:13:43 AM +00:00"

causing Azure CLI to fail with

time data '8/3/2023 9:26:33 AM +0000' does not match format '%m/%d/%Y %H:%M:%S %z'

In my opinion, App Service should guarantee the responses of managed identity get token requests targeting the same API version 2017-09-01 are consistent between Windows and Linux.

# "resource":"https://management.core.windows.net/",
# "token_type":"Bearer",
# "client_id":"df45d93a-de31-47ca-acef-081ca60d1a83"
# }
return AccessToken(self.token['access_token'], _normalize_expires_on(self.token['expires_on']))

def set_token(self):
import traceback
Expand Down Expand Up @@ -69,3 +81,20 @@ def set_token(self):
def signed_session(self, session=None):
logger.debug("MSIAuthenticationWrapper.signed_session invoked by Track 1 SDK")
super().signed_session(session)


def _normalize_expires_on(expires_on):
"""
The expires_on field returned by managed identity differs on Azure VM (epoch str) and App Service (datetime str).
Normalize to epoch int.
"""
try:
# Treat as epoch string "1605238724"
expires_on_epoch_int = int(expires_on)
except ValueError:
import datetime
# Treat as datetime string "11/05/2021 15:18:31 +00:00"
expires_on_epoch_int = int(datetime.datetime.strptime(expires_on, '%m/%d/%Y %H:%M:%S %z').timestamp())

logger.debug("Normalize expires_on: %r -> %r", expires_on, expires_on_epoch_int)
return expires_on_epoch_int
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

import unittest

from azure.cli.core.auth.adal_authentication import _normalize_expires_on


class TestUtil(unittest.TestCase):
def test_normalize_expires_on(self):
assert _normalize_expires_on("11/05/2021 15:18:31 +00:00") == 1636125511
assert _normalize_expires_on('1636125511') == 1636125511


if __name__ == '__main__':
unittest.main()