4
4
# This code is licensed under the MIT License.
5
5
import json
6
6
import logging
7
+ import os
8
+ import time
7
9
try : # Python 2
8
10
from urlparse import urlparse
9
11
except : # Python 3
@@ -17,8 +19,18 @@ def _scope_to_resource(scope): # This is an experimental reasonable-effort appr
17
19
return "{}://{}" .format (u .scheme , u .netloc )
18
20
return scope # There is no much else we can do here
19
21
22
+
20
23
def _obtain_token (http_client , resource , client_id = None ):
24
+ if "IDENTITY_ENDPOINT" in os .environ and "IDENTITY_HEADER" in os .environ :
25
+ return _obtain_token_on_app_service (
26
+ http_client , os .environ ["IDENTITY_ENDPOINT" ], os .environ ["IDENTITY_HEADER" ],
27
+ resource , client_id = client_id )
28
+ return _obtain_token_on_azure_vm (http_client , resource , client_id = client_id )
29
+
30
+
31
+ def _obtain_token_on_azure_vm (http_client , resource , client_id = None ):
21
32
# Based on https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http
33
+ logger .debug ("Obtaining token via managed identity on Azure VM" )
22
34
params = {
23
35
"api-version" : "2018-02-01" ,
24
36
"resource" : resource ,
@@ -44,3 +56,42 @@ def _obtain_token(http_client, resource, client_id=None):
44
56
logger .debug ("IMDS emits unexpected payload: %s" , resp .text )
45
57
raise
46
58
59
+ def _obtain_token_on_app_service (http_client , endpoint , identity_header , resource , client_id = None ):
60
+ # Prerequisite: Create your app service https://docs.microsoft.com/en-us/azure/app-service/quickstart-python
61
+ # Assign it a managed identity https://docs.microsoft.com/en-us/azure/app-service/overview-managed-identity?tabs=portal%2Chttp
62
+ # SSH into your container for testing https://docs.microsoft.com/en-us/azure/app-service/configure-linux-open-ssh-session
63
+ logger .debug ("Obtaining token via managed identity on Azure App Service" )
64
+ params = {
65
+ "api-version" : "2019-08-01" ,
66
+ "resource" : resource ,
67
+ }
68
+ if client_id :
69
+ params ["client_id" ] = client_id
70
+ resp = http_client .get (
71
+ endpoint ,
72
+ params = params ,
73
+ headers = {
74
+ "X-IDENTITY-HEADER" : identity_header ,
75
+ "Metadata" : "true" , # Unnecessary yet harmless for App Service,
76
+ # It will be needed by Azure Automation
77
+ # https://docs.microsoft.com/en-us/azure/automation/enable-managed-identity-for-automation#get-access-token-for-system-assigned-managed-identity-using-http-get
78
+ },
79
+ )
80
+ try :
81
+ payload = json .loads (resp .text )
82
+ if payload .get ("access_token" ) and payload .get ("expires_on" ):
83
+ return { # Normalizing the payload into OAuth2 format
84
+ "access_token" : payload ["access_token" ],
85
+ "expires_in" : int (payload ["expires_on" ]) - int (time .time ()),
86
+ "resource" : payload .get ("resource" ),
87
+ "token_type" : payload .get ("token_type" , "Bearer" ),
88
+ }
89
+ return {
90
+ "error" : "invalid_scope" , # Empirically, wrong resource ends up with a vague statusCode=500
91
+ "error_description" : "{}, {}" .format (
92
+ payload .get ("statusCode" ), payload .get ("message" )),
93
+ }
94
+ except ValueError :
95
+ logger .debug ("IMDS emits unexpected payload: %s" , resp .text )
96
+ raise
97
+
0 commit comments