Skip to content

Commit b191db1

Browse files
Add Azure Maps integration with APIM, including policies and Bicep configurations
1 parent 7a0797f commit b191db1

File tree

9 files changed

+606
-2
lines changed

9 files changed

+606
-2
lines changed

samples/azure-maps/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Samples: Api Management proxing calls to Azure Maps
2+
3+
This is a sample demonstrating how to use Azure API Management (APIM) to proxy calls to the Azure Maps service. This setup allows you to manage, secure, and monitor access to Azure Maps through APIM.
4+
5+
⚙️ **Supported infrastructures**: All infrastructures
6+
7+
👟 **Expected *Run All* runtime (excl. infrastructure prerequisite): ~[NOTEBOOK RUNTIME] minute**
8+
9+
## 🎯 Objectives
10+
11+
1. Learn how to set up APIM to proxy requests to Azure Maps on a path to operation based mapping.
12+
1. Learn how to set up APIM to proxy requests to Azure Maps on a generic path.
13+
1. See how to secure access to Azure Maps using APIM policies for all 3 authentication methods (subscription key, Azure Entra AD, and SAS Tokens).
14+
1. Show how to connect to the v1 enpoint of Azure Maps using APIM.
15+
16+
## 📝 Scenario
17+
18+
This sample demonstrates how to use APIM to proxy requests to the Azure Maps service. By doing so, you can leverage APIM's capabilities to manage, secure, and monitor access to Azure Maps. This particular setup will show you how to map specific paths to Azure Maps APIs, as well as how to handle generic paths. Additionally, the sample will illustrate how to secure access to Azure Maps using different authentication methods supported by APIM policies.
19+
20+
## 🛩️ Lab Components
21+
22+
This lab sets up:
23+
24+
- An Azure Maps resource in Azure
25+
- APIM managed identity with Storage Blob Data Reader permissions
26+
- An API that demonstrates proxying requests to Azure Maps specific to APIs (geocode, search, etc.)
27+
- Also in that api there will be an operation that demonstrates a generic path to Azure Maps
28+
29+
## ⚙️ Configuration
30+
31+
1. Decide which of the [Infrastructure Architectures](../../README.md#infrastructure-architectures) you wish to use.
32+
1. If the infrastructure _does not_ yet exist, navigate to the desired [infrastructure](../../infrastructure/) folder and follow its README.md.
33+
1. If the infrastructure _does_ exist, adjust the `user-defined parameters` in the _Initialize notebook variables_ below. Please ensure that all parameters match your infrastructure.

samples/azure-maps/create.ipynb

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"### 🛠️ 1. Initialize notebook variables\n",
8+
"\n",
9+
"Configures everything that's needed for deployment. \n",
10+
"\n",
11+
"[ADD ANY SPECIAL INSTRUCTIONS]\n",
12+
"\n",
13+
"**Modify entries under _1) User-defined parameters_ and _3) Define the APIs and their operations and policies_**."
14+
]
15+
},
16+
{
17+
"cell_type": "code",
18+
"execution_count": 25,
19+
"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+
],
31+
"source": [
32+
"import subprocess\n",
33+
"import utils\n",
34+
"from apimtypes import *\n",
35+
"\n",
36+
"# 1) User-defined parameters (change these as needed)\n",
37+
"rg_location = 'eastus2'\n",
38+
"index = 1\n",
39+
"deployment = INFRASTRUCTURE.SIMPLE_APIM\n",
40+
"tags = ['azure-maps'] # ENTER DESCRIPTIVE TAG(S)\n",
41+
"api_prefix = 'am-' # OPTIONAL: ENTER A PREFIX FOR THE APIS TO REDUCE COLLISION POTENTIAL WITH OTHER SAMPLES\n",
42+
"azure_maps_url = 'https://atlas.microsoft.com' # OPTIONAL: ENTER THE AZURE MAPS URL IF DIFFERENT FROM DEFAULT\n",
43+
"\n",
44+
"# 2) Service-defined parameters (please do not change these)\n",
45+
"rg_name = utils.get_infra_rg_name(deployment, index)\n",
46+
"supported_infrastructures = [INFRASTRUCTURE.SIMPLE_APIM, INFRASTRUCTURE.AFD_APIM_PE, INFRASTRUCTURE.APIM_ACA] # ENTER SUPPORTED INFRASTRUCTURES HERE, e.g., [INFRASTRUCTURE.AFD_APIM_PE, INFRASTRUCTURE.AFD_APIM_FE]\n",
47+
"utils.validate_infrastructure(deployment, supported_infrastructures)\n",
48+
"\n",
49+
"# 3) Define the APIs and their operations and policies\n",
50+
"\n",
51+
"# Policies\n",
52+
"# Named values must be set up a bit differently as they need to have two surrounding curly braces\n",
53+
"map_async_geocode_batch_v1_keyauth_post_xml = utils.read_policy_xml('./map_async_geocode_batch_v1_keyauth_post.xml')\n",
54+
"map_default_route_v2_aad_get_xml = utils.read_policy_xml('./map_default_route_v2_aad_get.xml')\n",
55+
"map_geocode_v2_aad_get_xml = utils.read_policy_xml('./map_geocode_v2_aad_get.xml')\n",
56+
"\n",
57+
"# 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",
59+
"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",
60+
"mapApi_v2_geocode_get = GET_APIOperation2('get-geocode','Get Geocode','/geocode','Get geocode endpoint',map_geocode_v2_aad_get_xml)\n",
61+
"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",
62+
"\n",
63+
"# API n\n",
64+
"# ...\n",
65+
"\n",
66+
"# APIs Array\n",
67+
"# apis: List[API] = [api1, apin]\n",
68+
"apis: List[API] = [api1]\n",
69+
"\n",
70+
"# 4) Set up the named values\n",
71+
"nvs: List[NamedValue] = [\n",
72+
"]\n",
73+
"\n",
74+
"utils.print_ok('Notebook initialized')"
75+
]
76+
},
77+
{
78+
"cell_type": "markdown",
79+
"metadata": {},
80+
"source": [
81+
"### 🚀 2. Create deployment using Bicep\n",
82+
"\n",
83+
"Creates the bicep deployment into the previously-specified resource group. A bicep parameters file will be created prior to execution."
84+
]
85+
},
86+
{
87+
"cell_type": "code",
88+
"execution_count": 27,
89+
"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+
],
105+
"source": [
106+
"import utils\n",
107+
"\n",
108+
"# 1) Define the Bicep parameters with serialized APIs\n",
109+
"bicep_parameters = {\n",
110+
" 'apis': {'value': [api.to_dict() for api in apis]},\n",
111+
" 'namedValues': {'value': [nv.to_dict() for nv in nvs]}\n",
112+
"}\n",
113+
"\n",
114+
"# 2) Infrastructure must be in place before samples can be layered on top\n",
115+
"if not utils.does_resource_group_exist(rg_name):\n",
116+
" utils.print_error(f'The specified infrastructure resource group and its resources must exist first. Please check that the user-defined parameters above are correctly referencing an existing infrastructure. If it does not yet exist, run the desired infrastructure in the /infra/ folder first.')\n",
117+
" raise SystemExit(1)\n",
118+
"\n",
119+
"# 3) Run the deployment\n",
120+
"output = utils.create_bicep_deployment_group(rg_name, rg_location, deployment, bicep_parameters)\n",
121+
"\n",
122+
"# 4) Print a deployment summary, if successful; otherwise, exit with an error\n",
123+
"if not output.success:\n",
124+
" raise SystemExit('Deployment failed')\n",
125+
"\n",
126+
"if output.success and output.json_data:\n",
127+
" apim_gateway_url = output.get('apimResourceGatewayURL', 'APIM API Gateway URL')\n",
128+
"\n",
129+
"utils.print_ok('Deployment completed')"
130+
]
131+
},
132+
{
133+
"cell_type": "markdown",
134+
"metadata": {},
135+
"source": [
136+
"### ✅ 3. Verify API Request Success\n",
137+
"\n",
138+
"Assert that the deployment was successful by making simple calls to APIM. \n",
139+
"\n",
140+
"❗️ If the infrastructure shields APIM and requires a different ingress (e.g. Azure Front Door), the request to the APIM gateway URl will fail by design. Obtain the Front Door endpoint hostname and try that instead."
141+
]
142+
},
143+
{
144+
"cell_type": "code",
145+
"execution_count": 28,
146+
"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+
],
268+
"source": [
269+
"import utils\n",
270+
"from apimrequests import ApimRequests\n",
271+
"\n",
272+
"# [ADD RELEVANT TESTS HERE]\n",
273+
"\n",
274+
"# 1) Issue a direct request to API Management\n",
275+
"# reqsApim = ApimRequests(apim_gateway_url)\n",
276+
"# reqsApim.singleGet('/request-headers', msg = 'Calling Request Headers API via API Management Gateway URL. Response codes 200 and 403 are both valid depending on the infrastructure used.')\n",
277+
"\n",
278+
"# # 2) Issue requests against Front Door.\n",
279+
"# # Check if the infrastructure architecture deployment uses Azure Front Door.\n",
280+
"# utils.print_message('Checking if the infrastructure architecture deployment uses Azure Front Door.', blank_above = True)\n",
281+
"# afd_endpoint_url = utils.get_frontdoor_url(deployment, rg_name)\n",
282+
"\n",
283+
"# if afd_endpoint_url:\n",
284+
"# reqsAfd = ApimRequests(afd_endpoint_url)\n",
285+
"# reqsAfd.singleGet('/request-headers', msg = 'Calling Request Headers API via via Azure Front Door. Expect 200.')\n",
286+
"\n",
287+
"reqs = ApimRequests(apim_gateway_url)\n",
288+
"\n",
289+
"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",
293+
"utils.print_ok('All done!')"
294+
]
295+
}
296+
],
297+
"metadata": {
298+
"kernelspec": {
299+
"display_name": ".venv",
300+
"language": "python",
301+
"name": "python3"
302+
},
303+
"language_info": {
304+
"codemirror_mode": {
305+
"name": "ipython",
306+
"version": 3
307+
},
308+
"file_extension": ".py",
309+
"mimetype": "text/x-python",
310+
"name": "python",
311+
"nbconvert_exporter": "python",
312+
"pygments_lexer": "ipython3",
313+
"version": "3.12.11"
314+
}
315+
},
316+
"nbformat": 4,
317+
"nbformat_minor": 2
318+
}

0 commit comments

Comments
 (0)