Skip to content

Commit c4811c7

Browse files
Add App Gateway to API Management via VNet Infrastructure (#109)
1 parent c8ba82d commit c4811c7

File tree

11 files changed

+1172
-14
lines changed

11 files changed

+1172
-14
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ _Try it out, learn from it, apply it in your setups._
2525
| [API Management & Container Apps][infra-apim-aca] | APIs are often implemented in containers running in Azure Container Apps. This architecture accesses the container apps publicly. It's beneficial to test both APIM and container app URLs to contrast and compare experiences of API calls through and bypassing APIM. It is not intended to be a security baseline. |
2626
| [Front Door & API Management & Container Apps][infra-afd-apim-pe] | A secure implementation of Azure Front Door connecting to APIM via the new private link integration. This traffic, once it traverses through Front Door, rides entirely on Microsoft-owned and operated networks. The connection from APIM to Container Apps is secured but through a VNet configuration (it is also entirely possible to do this via private link). APIM Standard V2 is used here to accept a private link from Front Door. |
2727
| [Application Gateway (Private Endpoint) & API Management & Container Apps][infra-appgw-apim-pe] | A secure implementation of Azure Application Gateway connecting to APIM via the new private link integration. This traffic, once it traverses through App Gateway, uses a private endpoint set up in the VNet's private endpoint subnet. The connection from APIM to Container Apps is secured but through a VNet configuration (it is also entirely possible to do this via private link). APIM Standard V2 is used here to accept a private link from App Gateway. |
28-
| Application Gateway (VNet) & API Management & Container Apps | *ETA TBD - Stay tuned!* |
28+
| [Application Gateway (VNet) & API Management & Container Apps][infra-appgw-apim] | Full VNet injection of APIM and ACA! APIM is shielded from any type of traffic unless it comes through App Gateway. This offers maximum isolation for instances in which customers seek VNet injection. |
2929

3030
## 📁 List of Samples
3131

@@ -66,6 +66,7 @@ These prerequisites apply broadly across all infrastructure and samples. If ther
6666
- Python 3.13 and 3.14 should work as well, but have not been verified extensively
6767
- [VS Code][vscode] installed with the [Jupyter notebook extension][vscode-jupyter] enabled
6868
- [Azure CLI][azure-cli-install] installed
69+
- [Azure Bicep][azure-bicep-install] installed
6970
- [An Azure Subscription][azure-free] with Owner or Contributor+UserAccessAdministrator permissions. Execute [Verify Azure Account][verify-az-account-notebook] to verify.
7071
- **Azure Authentication**: Sign in to Azure with Azure CLI using the specific tenant and subscription you want to work with:
7172
- To log in to a specific tenant: `az login --tenant <your-tenant-id-or-domain>`
@@ -384,6 +385,7 @@ The original author of this project is [Simon Kurtz][simon-kurtz].
384385
[andrew-redman]: https://github.com/anotherRedbeard
385386
[apim-lza]: https://learn.microsoft.com/azure/cloud-adoption-framework/scenarios/app-platform/api-management/landing-zone-accelerator
386387
[apim-snippets-repo]: https://github.com/Azure/api-management-policy-snippets
388+
[azure-bicep-install]: https://learn.microsoft.com/azure/azure-resource-manager/bicep/install#azure-cli
387389
[azure-cli-auth]: https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively
388390
[azure-cli-install]: https://learn.microsoft.com/cli/azure/install-azure-cli
389391
[azure-free]: https://azure.microsoft.com/free/
@@ -394,6 +396,7 @@ The original author of this project is [Simon Kurtz][simon-kurtz].
394396
[import-troubleshooting]: .devcontainer/IMPORT-TROUBLESHOOTING.md
395397
[infra-afd-apim-pe]: ./infrastructure/afd-apim-pe
396398
[infra-apim-aca]: ./infrastructure/apim-aca
399+
[infra-appgw-apim]: ./infrastructure/appgw-apim/
397400
[infra-appgw-apim-pe]: ./infrastructure/appgw-apim-pe/
398401
[infra-simple-apim]: ./infrastructure/simple-apim
399402
[pytest-docs]: https://docs.pytest.org/
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Application Gateway & API Management (VNet Internal) Infrastructure
2+
3+
This architecture provides secure ingress through Azure Application Gateway (WAF_v2) to Azure API Management (APIM) deployed in a Virtual Network using Internal mode (private IP only). No Private Endpoints are used; traffic stays on private networking after the gateway, and APIM cannot be accessed aside from traversing Application Gateway.
4+
5+
<!-- TODO: Generate diagram -->
6+
<!-- <img src="./Azure Application Gateway + APIM (Internal).svg" alt="Diagram showing Application Gateway routing to APIM in VNet Internal mode. Optional Container Apps shown behind APIM. Telemetry to Azure Monitor." title="Application Gateway + APIM (Internal)" width="1000" /> -->
7+
8+
## 🎯 Objectives
9+
10+
1. Expose APIs publicly via Application Gateway while keeping APIM private (VNet Internal)
11+
2. Maintain private networking end-to-end without Private Endpoints
12+
3. Enable optional backends with Azure Container Apps (ACA)
13+
4. Provide observability via Log Analytics and Application Insights
14+
15+
## 💡 Why Developer SKU?
16+
17+
- Significant cost savings for learning, demos, and dev/test:
18+
- Developer is a fraction of Premium costs (often >90% cheaper)
19+
- No SLA and single-instance only, which is acceptable for the purpose of this repo
20+
- Trade-offs:
21+
- Longer deployment times compared to v2/Premium SKUs (APIM creation can be slow)
22+
23+
We choose the Developer SKU here to dramatically lower costs for experimentation. If you need SLAs, scaling, or production-grade features, use Premium/Premiumv2 as those are the only SKUs that support VNet *injection*.
24+
25+
## ⚙️ Configuration
26+
27+
Adjust the user-defined parameters in this lab's Jupyter Notebook's Initialize notebook variables section.
28+
29+
Key parameters:
30+
- `apimSku`: Defaults to `Developer`
31+
- `useACA`: Enable to provision a private ACA environment and sample apps
32+
33+
## ▶️ Execution
34+
35+
1. Execute this lab's Jupyter Notebook step-by-step or via Run All.
36+
37+
👟 Expected Run All runtime: longer than v2 SKUs for APIM creation. We will measure and update this value after testing.
38+
39+
- TODO: Measure actual runtime on Developer SKU and update this section accordingly.
40+
41+
## 🧪 Testing
42+
43+
Because we use a self-signed certificate for Application Gateway TLS termination (for convenience in this sample), testing can be done with curl by ignoring certificate warnings and sending the Host header:
44+
45+
```
46+
curl -v -k -H "Host: api.apim-samples.contoso.com" https://<APPGW_PUBLIC_IP>/status-0123456789abcdef
47+
```
48+
49+
A 200 status from the health endpoint indicates success through App Gateway to APIM.
50+
51+
## 🔐 Security Notes
52+
53+
- Self-signed certificates are for demos only. Use managed or trusted certs for production.
54+
- This sample uses RBAC-enabled Key Vault with minimal role assignments for App Gateway certificate retrieval.
55+
- **Azure limitation**: APIM must be created with public access enabled initially. You can disable public access in a subsequent update if desired.
56+
- Review and harden NSGs/WAF policy as needed for your environment.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"### 🗑️ Clean up resources\n",
8+
"\n",
9+
"When you're finished with the lab, you should remove all your deployed resources from Azure to avoid extra charges and keep your Azure subscription uncluttered."
10+
]
11+
},
12+
{
13+
"cell_type": "code",
14+
"execution_count": null,
15+
"metadata": {},
16+
"outputs": [],
17+
"source": [
18+
"from apimtypes import INFRASTRUCTURE\n",
19+
"from infrastructures import cleanup_infra_deployments\n",
20+
"\n",
21+
"indexes = [1]\n",
22+
"\n",
23+
"cleanup_infra_deployments(INFRASTRUCTURE.APPGW_APIM, indexes)"
24+
]
25+
}
26+
],
27+
"metadata": {
28+
"kernelspec": {
29+
"display_name": ".venv (3.14.0)",
30+
"language": "python",
31+
"name": "python3"
32+
},
33+
"language_info": {
34+
"codemirror_mode": {
35+
"name": "ipython",
36+
"version": 3
37+
},
38+
"file_extension": ".py",
39+
"mimetype": "text/x-python",
40+
"name": "python",
41+
"nbconvert_exporter": "python",
42+
"pygments_lexer": "ipython3",
43+
"version": "3.14.0"
44+
}
45+
},
46+
"nbformat": 4,
47+
"nbformat_minor": 2
48+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"### 🛠️ Configure Infrastructure Parameters & Create the Infrastructure\n",
8+
"\n",
9+
"Set your desired parameters for the APPGW-APIM infrastructure deployment.\n",
10+
"\n",
11+
"❗️ **Modify entries under _User-defined parameters_**.\n",
12+
"\n",
13+
"**Note:** This infrastructure includes Application Gateway with API Management using VNet injection."
14+
]
15+
},
16+
{
17+
"cell_type": "code",
18+
"execution_count": null,
19+
"metadata": {},
20+
"outputs": [],
21+
"source": [
22+
"from apimtypes import APIM_SKU, INFRASTRUCTURE\n",
23+
"from utils import InfrastructureNotebookHelper\n",
24+
"from console import print_ok\n",
25+
"\n",
26+
"# ------------------------------\n",
27+
"# USER CONFIGURATION\n",
28+
"# ------------------------------\n",
29+
"\n",
30+
"rg_location = 'eastus2' # Azure region for deployment\n",
31+
"index = 1 # Infrastructure index (use different numbers for multiple environments)\n",
32+
"apim_sku = APIM_SKU.DEVELOPER # Options: 'DEVELOPER'\n",
33+
"\n",
34+
"\n",
35+
"\n",
36+
"# ------------------------------\n",
37+
"# SYSTEM CONFIGURATION\n",
38+
"# ------------------------------\n",
39+
"\n",
40+
"inb_helper = InfrastructureNotebookHelper(rg_location, INFRASTRUCTURE.APPGW_APIM, index, apim_sku)\n",
41+
"inb_helper.create_infrastructure()\n",
42+
"\n",
43+
"print_ok('All done!')"
44+
]
45+
},
46+
{
47+
"cell_type": "markdown",
48+
"metadata": {},
49+
"source": [
50+
"### 🗑️ Clean up resources\n",
51+
"\n",
52+
"When you're finished experimenting, it's advisable to remove all associated resources from Azure to avoid unnecessary cost.\n",
53+
"Use the [clean-up notebook](clean-up.ipynb) for that."
54+
]
55+
}
56+
],
57+
"metadata": {
58+
"kernelspec": {
59+
"display_name": ".venv (3.14.0)",
60+
"language": "python",
61+
"name": "python3"
62+
},
63+
"language_info": {
64+
"codemirror_mode": {
65+
"name": "ipython",
66+
"version": 3
67+
},
68+
"file_extension": ".py",
69+
"mimetype": "text/x-python",
70+
"name": "python",
71+
"nbconvert_exporter": "python",
72+
"pygments_lexer": "ipython3",
73+
"version": "3.14.0"
74+
}
75+
},
76+
"nbformat": 4,
77+
"nbformat_minor": 2
78+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"""
2+
Reusable script to create Application Gateway + APIM (Internal) infrastructure.
3+
"""
4+
5+
import sys
6+
import argparse
7+
8+
# APIM Samples imports
9+
import azure_resources as az
10+
from apimtypes import APIM_SKU, API, GET_APIOperation, BACKEND_XML_POLICY_PATH, INFRASTRUCTURE
11+
from infrastructures import AppGwApimInfrastructure
12+
import utils
13+
14+
15+
def create_infrastructure(location: str, index: int, apim_sku: APIM_SKU, no_aca: bool = False) -> None:
16+
# Check if infrastructure already exists to determine messaging
17+
infrastructure_exists = az.does_resource_group_exist(az.get_infra_rg_name(INFRASTRUCTURE.APPGW_APIM, index))
18+
19+
# Create custom APIs for APPGW-APIM with optional Container Apps backends
20+
custom_apis = _create_appgw_specific_apis(not no_aca)
21+
22+
infra = AppGwApimInfrastructure(location, index, apim_sku, infra_apis = custom_apis)
23+
result = infra.deploy_infrastructure(infrastructure_exists)
24+
25+
raise SystemExit(0 if result.success else 1)
26+
27+
def _create_appgw_specific_apis(use_aca: bool = True) -> list[API]:
28+
"""
29+
Create APPGW-APIM specific APIs with optional Container Apps backends.
30+
31+
Args:
32+
use_aca (bool): Whether to include Azure Container Apps backends. Defaults to true.
33+
34+
Returns:
35+
list[API]: List of AppGw-specific APIs.
36+
"""
37+
38+
# If Container Apps is enabled, create the ACA APIs in APIM
39+
if use_aca:
40+
pol_backend = utils.read_policy_xml(BACKEND_XML_POLICY_PATH)
41+
pol_aca_backend_1 = pol_backend.format(backend_id = 'aca-backend-1')
42+
pol_aca_backend_2 = pol_backend.format(backend_id = 'aca-backend-2')
43+
pol_aca_backend_pool = pol_backend.format(backend_id = 'aca-backend-pool')
44+
45+
# API 1: Hello World (ACA Backend 1)
46+
api_hwaca_1_get = GET_APIOperation('This is a GET for Hello World on ACA Backend 1')
47+
api_hwaca_1 = API('hello-world-aca-1', 'Hello World (ACA 1)', '/aca-1', 'This is the ACA API for Backend 1', pol_aca_backend_1, [api_hwaca_1_get])
48+
49+
# API 2: Hello World (ACA Backend 2)
50+
api_hwaca_2_get = GET_APIOperation('This is a GET for Hello World on ACA Backend 2')
51+
api_hwaca_2 = API('hello-world-aca-2', 'Hello World (ACA 2)', '/aca-2', 'This is the ACA API for Backend 2', pol_aca_backend_2, [api_hwaca_2_get])
52+
53+
# API 3: Hello World (ACA Backend Pool)
54+
api_hwaca_pool_get = GET_APIOperation('This is a GET for Hello World on ACA Backend Pool')
55+
api_hwaca_pool = API('hello-world-aca-pool', 'Hello World (ACA Pool)', '/aca-pool', 'This is the ACA API for Backend Pool', pol_aca_backend_pool, [api_hwaca_pool_get])
56+
57+
return [api_hwaca_1, api_hwaca_2, api_hwaca_pool]
58+
59+
return []
60+
61+
def main():
62+
"""
63+
Main entry point for command-line usage.
64+
"""
65+
66+
parser = argparse.ArgumentParser(description = 'Create APPGW-APIM infrastructure (VNet Internal)')
67+
parser.add_argument('--location', default = 'eastus2', help = 'Azure region (default: eastus2)')
68+
parser.add_argument('--index', type = int, help = 'Infrastructure index')
69+
parser.add_argument('--sku', choices = ['Developer', 'Basicv2', 'Standardv2'], default = 'Developer', help = 'APIM SKU (default: Developer)')
70+
parser.add_argument('--no-aca', action = 'store_true', help = 'Disable Azure Container Apps')
71+
args = parser.parse_args()
72+
73+
# Convert SKU string to enum using the enum's built-in functionality
74+
try:
75+
apim_sku = APIM_SKU(args.sku)
76+
except ValueError:
77+
print(f"Error: Invalid SKU '{args.sku}'. Valid options are: {', '.join([sku.value for sku in APIM_SKU])}")
78+
sys.exit(1)
79+
80+
create_infrastructure(args.location, args.index, apim_sku, args.no_aca)
81+
82+
if __name__ == '__main__':
83+
main()

0 commit comments

Comments
 (0)