Skip to content

Commit 816abf8

Browse files
Enhance Azure Maps integration by implementing user-assigned managed identity and SAS token caching in policies
1 parent b191db1 commit 816abf8

File tree

3 files changed

+163
-166
lines changed

3 files changed

+163
-166
lines changed

samples/azure-maps/create.ipynb

Lines changed: 20 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,10 @@
1515
},
1616
{
1717
"cell_type": "code",
18-
"execution_count": 25,
18+
"execution_count": null,
1919
"metadata": {},
20-
"outputs": [
21-
{
22-
"name": "stdout",
23-
"output_type": "stream",
24-
"text": [
25-
"👉🏽 \u001b[1;34mResource group name : apim-infra-simple-apim-1\u001b[0m \n",
26-
"\n",
27-
"\u001b[1;32mNotebook initialized\u001b[0m ⌚ 22:31:28.796962 \n"
28-
]
29-
}
30-
],
20+
"outputs": [],
3121
"source": [
32-
"import subprocess\n",
3322
"import utils\n",
3423
"from apimtypes import *\n",
3524
"\n",
@@ -55,7 +44,7 @@
5544
"map_geocode_v2_aad_get_xml = utils.read_policy_xml('./map_geocode_v2_aad_get.xml')\n",
5645
"\n",
5746
"# Map API \n",
58-
"mapApi_v2_default_get = GET_APIOperation2('get-default-route','Get default route','/*','This is the default route that will allow all requests to go through to the backend api',map_default_route_v2_aad_get_xml)\n",
47+
"mapApi_v2_default_get = GET_APIOperation2('get-default-route','Get default route','/default/*','This is the default route that will allow all requests to go through to the backend api',map_default_route_v2_aad_get_xml)\n",
5948
"mapApi_v1_async_post = APIOperation('async-geocode-batch','Async Geocode Batch','/geocode/batch/async',HTTP_VERB.POST, 'Post geocode batch async endpoint',map_async_geocode_batch_v1_keyauth_post_xml)\n",
6049
"mapApi_v2_geocode_get = GET_APIOperation2('get-geocode','Get Geocode','/geocode','Get geocode endpoint',map_geocode_v2_aad_get_xml)\n",
6150
"api1 = API('map-api', 'Map API', '/map', 'This is the proxy for Azure Maps', operations=[mapApi_v2_default_get, mapApi_v1_async_post,mapApi_v2_geocode_get], tags = tags, serviceUrl=azure_maps_url)\n",
@@ -69,6 +58,7 @@
6958
"\n",
7059
"# 4) Set up the named values\n",
7160
"nvs: List[NamedValue] = [\n",
61+
" NamedValue('azure-maps-arm-api-version','2023-06-01')\n",
7262
"]\n",
7363
"\n",
7464
"utils.print_ok('Notebook initialized')"
@@ -85,23 +75,9 @@
8575
},
8676
{
8777
"cell_type": "code",
88-
"execution_count": 27,
78+
"execution_count": null,
8979
"metadata": {},
90-
"outputs": [
91-
{
92-
"name": "stdout",
93-
"output_type": "stream",
94-
"text": [
95-
"⚙️ \u001b[1;34maz group show --name apim-infra-simple-apim-1\u001b[0m \n",
96-
"⚙️ \u001b[1;34maz group show --name apim-infra-simple-apim-1\u001b[0m \n",
97-
"📝 Updated the policy XML in the bicep parameters file 'params.json'\n",
98-
"⚙️ \u001b[1;34maz deployment group create --name simple-apim --resource-group apim-infra-simple-apim-1 --template-file main.bicep --parameters params.json --query \"properties.outputs\"\u001b[0m \n",
99-
"👉🏽 \u001b[1;34mAPIM API Gateway URL : https://apim-w6pw4mtuew6ga.azure-api.net\u001b[0m \n",
100-
"\n",
101-
"\u001b[1;32mDeployment completed\u001b[0m ⌚ 22:44:24.722798 \n"
102-
]
103-
}
104-
],
80+
"outputs": [],
10581
"source": [
10682
"import utils\n",
10783
"\n",
@@ -142,129 +118,9 @@
142118
},
143119
{
144120
"cell_type": "code",
145-
"execution_count": 28,
121+
"execution_count": null,
146122
"metadata": {},
147-
"outputs": [
148-
{
149-
"name": "stdout",
150-
"output_type": "stream",
151-
"text": [
152-
"\n",
153-
"ℹ️ \u001b[1;32mCalling Hello World (Root) API\u001b[0m ⌚ 22:50:43.490816 \n",
154-
"👉🏽 \u001b[1;34mGET https://apim-w6pw4mtuew6ga.azure-api.net/\u001b[0m \n",
155-
"👉🏽 \u001b[1;34mResponse status : \u001b[1;32m200 - OK\u001b[0m\u001b[0m \n",
156-
"👉🏽 \u001b[1;34mResponse headers :\n",
157-
"{'Content-Length': '32', 'Date': 'Thu, 12 Jun 2025 03:50:44 GMT', 'Request-Context': 'appId=cid-v1:788e87f2-9135-4c09-896d-02870631a447'}\u001b[0m \n",
158-
"👉🏽 \u001b[1;34mResponse body :\n",
159-
"Hello World from API Management!\u001b[0m \n",
160-
"\n",
161-
"ℹ️ \u001b[1;32mCalling Default Route with AAD Auth API\u001b[0m ⌚ 22:50:44.075994 \n",
162-
"👉🏽 \u001b[1;34mGET https://apim-w6pw4mtuew6ga.azure-api.net/map/default/geocode?query=15127%20NE%2024th%20Street%20Redmond%20WA/\u001b[0m \n",
163-
"👉🏽 \u001b[1;34mResponse status : \u001b[1;32m200 - OK\u001b[0m\u001b[0m \n",
164-
"👉🏽 \u001b[1;34mResponse headers :\n",
165-
"{'Content-Type': 'application/json; charset=utf-8', 'Date': 'Thu, 12 Jun 2025 03:50:46 GMT', 'Content-Encoding': 'gzip', 'Transfer-Encoding': 'chunked', 'Vary': 'Accept-Encoding', 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains', 'x-ms-azuremaps-region': 'East US', 'X-Content-Type-Options': 'nosniff', 'X-Cache': 'CONFIG_NOCACHE', 'X-MSEdge-Ref': 'Ref A: 3E47F785F9DE487E9C530EC0D2124173 Ref B: BL2AA2030110019 Ref C: 2025-06-12T03:50:46Z', 'Request-Context': 'appId=cid-v1:788e87f2-9135-4c09-896d-02870631a447'}\u001b[0m \n",
166-
"👉🏽 \u001b[1;34mResponse body :\n",
167-
"{\n",
168-
" \"type\": \"FeatureCollection\",\n",
169-
" \"features\": [\n",
170-
" {\n",
171-
" \"type\": \"Feature\",\n",
172-
" \"geometry\": {\n",
173-
" \"type\": \"Point\",\n",
174-
" \"coordinates\": [\n",
175-
" -122.138669,\n",
176-
" 47.630359\n",
177-
" ]\n",
178-
" },\n",
179-
" \"bbox\": [\n",
180-
" -122.14631082421619,\n",
181-
" 47.62649628242932,\n",
182-
" -122.1310271757838,\n",
183-
" 47.634221717570675\n",
184-
" ],\n",
185-
" \"properties\": {\n",
186-
" \"type\": \"Address\",\n",
187-
" \"confidence\": \"High\",\n",
188-
" \"matchCodes\": [\n",
189-
" \"Good\"\n",
190-
" ],\n",
191-
" \"geocodePoints\": [\n",
192-
" {\n",
193-
" \"calculationMethod\": \"Rooftop\",\n",
194-
" \"usageTypes\": [\n",
195-
" \"Display\"\n",
196-
" ],\n",
197-
" \"geometry\": {\n",
198-
" \"type\": \"Point\",\n",
199-
" \"coordinates\": [\n",
200-
" -122.138669,\n",
201-
" 47.630359\n",
202-
" ]\n",
203-
" }\n",
204-
" },\n",
205-
" {\n",
206-
" \"calculationMethod\": \"Rooftop\",\n",
207-
" \"usageTypes\": [\n",
208-
" \"Route\"\n",
209-
" ],\n",
210-
" \"geometry\": {\n",
211-
" \"type\": \"Point\",\n",
212-
" \"coordinates\": [\n",
213-
" -122.1386667,\n",
214-
" 47.630218\n",
215-
" ]\n",
216-
" }\n",
217-
" }\n",
218-
" ],\n",
219-
" \"address\": {\n",
220-
" \"addressLine\": \"15127 NE 24th St\",\n",
221-
" \"streetName\": \"NE 24th St\",\n",
222-
" \"streetNumber\": \"15127\",\n",
223-
" \"postalCode\": \"98052\",\n",
224-
" \"locality\": \"Redmond\",\n",
225-
" \"formattedAddress\": \"15127 NE 24th St, Redmond, WA 98052\",\n",
226-
" \"countryRegion\": {\n",
227-
" \"name\": \"United States\",\n",
228-
" \"ISO\": \"US\"\n",
229-
" },\n",
230-
" \"adminDistricts\": [\n",
231-
" {\n",
232-
" \"shortName\": \"WA\"\n",
233-
" },\n",
234-
" {\n",
235-
" \"shortName\": \"King County\"\n",
236-
" }\n",
237-
" ]\n",
238-
" }\n",
239-
" }\n",
240-
" }\n",
241-
" ]\n",
242-
"}\u001b[0m \n",
243-
"\n",
244-
"ℹ️ \u001b[1;32mCalling Hello World (ACA Backend 2) API\u001b[0m ⌚ 22:50:47.215265 \n",
245-
"👉🏽 \u001b[1;34mGET https://apim-w6pw4mtuew6ga.azure-api.net/aca-2/\u001b[0m \n",
246-
"👉🏽 \u001b[1;34mResponse status : \u001b[1;31m404 - Resource Not Found\u001b[0m\u001b[0m \n",
247-
"👉🏽 \u001b[1;34mResponse headers :\n",
248-
"{'Content-Length': '54', 'Content-Type': 'application/json', 'Date': 'Thu, 12 Jun 2025 03:50:47 GMT', 'Request-Context': 'appId=cid-v1:788e87f2-9135-4c09-896d-02870631a447'}\u001b[0m \n",
249-
"👉🏽 \u001b[1;34mResponse body :\n",
250-
"{ \"statusCode\": 404, \"message\": \"Resource not found\" }\u001b[0m \n",
251-
"\n",
252-
"ℹ️ \u001b[1;32mCalling Hello World (ACA Backend Pool) API\u001b[0m ⌚ 22:50:47.865951 \n",
253-
"👉🏽 \u001b[1;34mGET https://apim-w6pw4mtuew6ga.azure-api.net/aca-pool/\u001b[0m \n",
254-
"👉🏽 \u001b[1;34m▶️ Run 1/3:\u001b[0m \n",
255-
"👉🏽 \u001b[1;34m⌚ 0.39 seconds\u001b[0m \n",
256-
"👉🏽 \u001b[1;34mResponse status : \u001b[1;31m404 - Resource Not Found\u001b[0m\u001b[0m \n",
257-
"👉🏽 \u001b[1;34m▶️ Run 2/3:\u001b[0m \n",
258-
"👉🏽 \u001b[1;34m⌚ 0.07 seconds\u001b[0m \n",
259-
"👉🏽 \u001b[1;34mResponse status : \u001b[1;31m404 - Resource Not Found\u001b[0m\u001b[0m \n",
260-
"👉🏽 \u001b[1;34m▶️ Run 3/3:\u001b[0m \n",
261-
"👉🏽 \u001b[1;34m⌚ 0.07 seconds\u001b[0m \n",
262-
"👉🏽 \u001b[1;34mResponse status : \u001b[1;31m404 - Resource Not Found\u001b[0m\u001b[0m \n",
263-
"\n",
264-
"\u001b[1;32mAll done!\u001b[0m ⌚ 22:50:48.559041 \n"
265-
]
266-
}
267-
],
123+
"outputs": [],
268124
"source": [
269125
"import utils\n",
270126
"from apimrequests import ApimRequests\n",
@@ -287,9 +143,18 @@
287143
"reqs = ApimRequests(apim_gateway_url)\n",
288144
"\n",
289145
"reqs.singleGet('/', msg = 'Calling Hello World (Root) API')\n",
290-
"reqs.singleGet('/map/default/geocode?query=15127%20NE%2024th%20Street%20Redmond%20WA/', msg = 'Calling Default Route with AAD Auth API')\n",
291-
"reqs.singleGet('/aca-2/', msg = 'Calling Hello World (ACA Backend 2) API')\n",
292-
"reqs.multiGet('/aca-pool/', 3, msg = 'Calling Hello World (ACA Backend Pool) API')\n",
146+
"reqs.singleGet('/map/default/geocode?query=15127%20NE%2024th%20Street%20Redmond%20WA', msg = 'Calling Default Route API with AAD Auth')\n",
147+
"reqs.singleGet('/map/geocode?query=15127%20NE%2024th%20Street%20Redmond%20WA', msg = 'Calling Geocode v2 API with AAD Auth')\n",
148+
"reqs.singlePost('/map/geocode/batch/async', data={\n",
149+
" \"batchItems\": [\n",
150+
" {\"query\": \"?query=400 Broad St, Seattle, WA 98109&limit=3\"},\n",
151+
" {\"query\": \"?query=One, Microsoft Way, Redmond, WA 98052&limit=3\"},\n",
152+
" {\"query\": \"?query=350 5th Ave, New York, NY 10118&limit=1\"},\n",
153+
" {\"query\": \"?query=Pike Pl, Seattle, WA 98101&lat=47.610970&lon=-122.342469&radius=1000\"},\n",
154+
" {\"query\": \"?query=Champ de Mars, 5 Avenue Anatole France, 75007 Paris, France&limit=1\"}\n",
155+
" ]\n",
156+
"}, msg = 'Calling Async Geocode Batch v1 API with Key Auth')\n",
157+
"\n",
293158
"utils.print_ok('All done!')"
294159
]
295160
}

samples/azure-maps/main.bicep

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ param namedValues array = []
1414
param mapsName string = 'maps-${resourceSuffix}'
1515
param apimName string = 'apim-${resourceSuffix}'
1616
param appInsightsName string = 'appi-${resourceSuffix}'
17+
param userAssignedIdentityName string = 'uami-maps-${resourceSuffix}'
1718
param apis array = []
1819

1920
// [ADD RELEVANT PARAMETERS HERE]
@@ -35,6 +36,16 @@ resource apimService 'Microsoft.ApiManagement/service@2024-06-01-preview' existi
3536
name: apimName
3637
}
3738

39+
// Create user-assigned managed identity
40+
resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
41+
name: userAssignedIdentityName
42+
location: location
43+
tags: {
44+
Purpose: 'Azure Maps Access'
45+
Environment: 'Sample'
46+
}
47+
}
48+
3849
// Create Azure Maps account
3950
resource mapsAccount 'Microsoft.Maps/accounts@2024-07-01-preview' = {
4051
name: mapsName
@@ -44,7 +55,10 @@ resource mapsAccount 'Microsoft.Maps/accounts@2024-07-01-preview' = {
4455
}
4556
kind: 'Gen2'
4657
identity: {
47-
type: 'SystemAssigned'
58+
type: 'UserAssigned'
59+
userAssignedIdentities: {
60+
'${userAssignedIdentity.id}': {}
61+
}
4862
}
4963
}
5064

@@ -81,6 +95,49 @@ module mapsClientIdNamedValue '../../shared/bicep/modules/apim/v1/named-value.bi
8195
}
8296
}
8397

98+
// Deploy user-assigned managed identity client id named value
99+
module userAssignedIdentityObjectIdNamedValue '../../shared/bicep/modules/apim/v1/named-value.bicep' = {
100+
name: 'nv-user-assigned-identity-object-id'
101+
params: {
102+
apimName: apimName
103+
namedValueName: 'user-assigned-identity-object-id'
104+
namedValueValue: userAssignedIdentity.properties.principalId
105+
namedValueIsSecret: false
106+
}
107+
}
108+
109+
// Deploy Azure subscription id named value
110+
module subscriptionIdNamedValue '../../shared/bicep/modules/apim/v1/named-value.bicep' = {
111+
name: 'nv-subscription-id'
112+
params: {
113+
apimName: apimName
114+
namedValueName: 'subscription-id'
115+
namedValueValue: subscription().subscriptionId
116+
namedValueIsSecret: true
117+
}
118+
}
119+
120+
// Deploy resource group name named value
121+
module resourceGroupNamedValue '../../shared/bicep/modules/apim/v1/named-value.bicep' = {
122+
name: 'nv-resource-group-name'
123+
params: {
124+
apimName: apimName
125+
namedValueName: 'resource-group-name'
126+
namedValueValue: resourceGroup().name
127+
namedValueIsSecret: false
128+
}
129+
}
130+
// Deploy resource group name named value
131+
module azureMapsResourceNamedValue '../../shared/bicep/modules/apim/v1/named-value.bicep' = {
132+
name: 'nv-azure-maps-resource-name'
133+
params: {
134+
apimName: apimName
135+
namedValueName: 'azure-maps-resource-name'
136+
namedValueValue: mapsName
137+
namedValueIsSecret: false
138+
}
139+
}
140+
84141
// APIM APIs
85142
module apisModule '../../shared/bicep/modules/apim/v1/api.bicep' = [for api in apis: if(!empty(apis)) {
86143
name: '${api.name}-${resourceSuffix}'
@@ -92,8 +149,7 @@ module apisModule '../../shared/bicep/modules/apim/v1/api.bicep' = [for api in a
92149
}
93150
}]
94151

95-
96-
// Grant managed identity access to Azure Maps, here are the RBAC roles you might need: https://learn.microsoft.com/en-us/azure/azure-maps/azure-maps-authentication#picking-a-role-definition
152+
// Grant APIM managed identity access to Azure Maps, here are the RBAC roles you might need: https://learn.microsoft.com/en-us/azure/azure-maps/azure-maps-authentication#picking-a-role-definition
97153
resource mapsDataReaderRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
98154
name: guid(mapsAccount.id, apimService.id, '6be48352-4f82-47c9-ad5e-0acacefdb005')
99155
scope: mapsAccount
@@ -104,6 +160,29 @@ resource mapsDataReaderRoleAssignment 'Microsoft.Authorization/roleAssignments@2
104160
}
105161
}
106162

163+
// Grant APIM managed identity 'Auzre Maps Contributor' role to Azure Maps, this allows the creation of SAS tokens
164+
resource mapsContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
165+
name: guid(mapsAccount.id, apimService.id, 'dba33070-676a-4fb0-87fa-064dc56ff7fb')
166+
scope: mapsAccount
167+
properties: {
168+
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'dba33070-676a-4fb0-87fa-064dc56ff7fb')
169+
principalId: apimService.identity.principalId
170+
principalType: 'ServicePrincipal'
171+
}
172+
}
173+
174+
175+
// Grant user-assigned managed identity Azure Maps Data Reader role
176+
resource userAssignedIdentityMapsDataReaderRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
177+
name: guid(mapsAccount.id, userAssignedIdentity.id, '6be48352-4f82-47c9-ad5e-0acacefdb005')
178+
scope: mapsAccount
179+
properties: {
180+
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '6be48352-4f82-47c9-ad5e-0acacefdb005')
181+
principalId: userAssignedIdentity.properties.principalId
182+
principalType: 'ServicePrincipal'
183+
}
184+
}
185+
107186
// ------------------
108187
// MARK: OUTPUTS
109188
// ------------------

0 commit comments

Comments
 (0)