Skip to content

Commit ec252d3

Browse files
Feature/dynamic cors sample (#187)
1 parent 7000074 commit ec252d3

19 files changed

Lines changed: 1729 additions & 43 deletions

.github/copilot-instructions.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ Match the heading emojis, heading levels, and section ordering exactly. If a sec
366366

367367
- Use the `ApimRequests` and `ApimTesting` classes from `apimrequests.py` and `apimtesting.py` for all API testing and traffic generation in notebooks.
368368
- Do not use the `requests` library directly for calling APIM endpoints.
369+
- **Favour HTTP connection reuse.** When a notebook makes multiple HTTP calls to the same APIM gateway (e.g. a test matrix), create a single `requests.Session()` early and route all calls through it. This avoids repeated TCP+TLS handshakes, which can add 200-500 ms per request. Configure `session.verify` and `session.headers` once from `utils.get_endpoint()` and pass the session (or use it in helper functions) for OPTIONS, GET, and POST calls alike.
369370
- Use `utils.get_endpoint(deployment, rg_name, apim_gateway_url)` to determine the correct endpoint URL, headers, and TLS verification flag based on the infrastructure type. `allow_insecure_tls` is returned as `True` only for Application Gateway infrastructures because they use a self-signed certificate; it defaults to `False` everywhere else.
370371
- Example:
371372
```python
@@ -396,6 +397,7 @@ Match the heading emojis, heading levels, and section ordering exactly. If a sec
396397
- Only use apostrophe (U+0027) and quotes (U+0022), not left or right single or double quotation marks.
397398
- Do not localize URLs (e.g. no "en-us" in links).
398399
- Never use emoji variation selectors in Markdown. They are sneaky little things that can cause rendering and Markdown anchor link issues.
400+
- **Markdown tables must be column-aligned.** Pad cell values with spaces so that every `|` delimiter in a column lines up vertically. Use the separator row (`---`, `:---:`, etc.) to establish column widths and align all subsequent rows to match. This applies to every Markdown file in the repository (READMEs, skills, instructions, etc.).
399401

400402
## Testing and Edge Cases
401403

@@ -485,6 +487,17 @@ Check `docs/README.md` for local preview instructions and styling notes. The pag
485487
```
486488
- When executing KQL via `az rest` or `az monitor log-analytics query`, write the query body to a temporary JSON file and pass it with `--body @tempfile.json` to avoid shell pipe-character interpretation issues on Windows.
487489

490+
### Admin APIs (`/admin/`) Convention
491+
492+
Samples that require administrative or operational endpoints (cache loading, configuration reloads, health checks, etc.) must place them under an **`/admin/`** API path. This establishes a consistent, recognisable pattern across all APIM Samples.
493+
494+
- **API path**: `{api_prefix}admin` (e.g. `cors-admin`, `lb-admin`). The sample's `api_prefix` keeps admin APIs namespaced per sample.
495+
- **Subscription required**: Always `True`. Admin APIs must never be publicly accessible without a subscription key.
496+
- **Production security**: Subscription keys are a baseline gate but are shared secrets, not identity-based auth. Production deployments should layer JWT validation (`validate-azure-ad-token` or `validate-jwt`) on top of subscription keys. See the `authX` and `authX-pro` samples for implementation patterns.
497+
- **Naming**: Use kebab-case operation paths that describe the action (e.g. `/load-cache`, `/clear-cache`, `/refresh-config`).
498+
- **Tags**: Include the sample's tags so the admin API is grouped with its sibling APIs in the APIM portal.
499+
- **Documentation**: The admin API's display name should start with the phase or sample context (e.g. `Phase 3 Admin`) so its purpose is clear in the APIM portal.
500+
488501
### API Management Policy XML Instructions
489502

490503
- Policies should use camelCase for all variable names.

AGENTS.md

Lines changed: 37 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ This repository provides resources to deploy Azure API Management infrastructure
1212

1313
## Repository Structure
1414

15-
```
15+
```text
1616
/
1717
├── infrastructure/ # Azure infrastructure deployments
1818
│ ├── afd-apim-pe/ # Azure Front Door + APIM with Private Endpoint
@@ -52,31 +52,31 @@ This repository provides resources to deploy Azure API Management infrastructure
5252

5353
## Key Files in Each Sample/Infrastructure
5454

55-
| File | Purpose |
56-
|------|---------|
57-
| `README.md` | Documentation, objectives, and configuration instructions |
58-
| `create.ipynb` | Jupyter notebook for deploying the sample |
59-
| `main.bicep` | Bicep template defining Azure resources |
60-
| `clean-up.ipynb` | (Infrastructure only) Teardown notebook |
61-
| `*.xml` | APIM policy files |
55+
| File | Purpose |
56+
| ----------------- | -------------------------------------------------------------- |
57+
| `README.md` | Documentation, objectives, and configuration instructions |
58+
| `create.ipynb` | Jupyter notebook for deploying the sample |
59+
| `main.bicep` | Bicep template defining Azure resources |
60+
| `clean-up.ipynb` | (Infrastructure only) Teardown notebook |
61+
| `*.xml` | APIM policy files |
6262

6363
## Available Skills
6464

6565
Use these skills for specialized tasks. Skills are located in `.github/skills/`.
6666

67-
| Skill | When to Use |
68-
|-------|-------------|
69-
| **sample-creator** | Creating new samples under `samples/` following the `_TEMPLATE` structure |
70-
| **apim-bicep** | Writing Bicep templates for APIM resources (APIs, backends, policies, products) |
71-
| **apim-policies** | Creating or modifying APIM XML policies (inbound/outbound, authentication, rate limiting) |
67+
| Skill | When to Use |
68+
| ------------------- | ------------------------------------------------------------------------------------------------ |
69+
| **sample-creator** | Creating new samples under `samples/` following the `_TEMPLATE` structure |
70+
| **apim-bicep** | Writing Bicep templates for APIM resources (APIs, backends, policies, products) |
71+
| **apim-policies** | Creating or modifying APIM XML policies (inbound/outbound, authentication, rate limiting) |
7272

7373
## Available Agents
7474

7575
Use these custom agents for focused repository workflows. Agents are located in `.github/agents/`.
7676

77-
| Agent | When to Use |
78-
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
79-
| **APIM Sample Creator** | Adding a new sample, gathering missing sample metadata, scaffolding from `_TEMPLATE`, and updating README, website, slide deck, and compatibility artifacts |
77+
| Agent | When to Use |
78+
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
79+
| **APIM Sample Creator** | Adding a new sample, gathering missing sample metadata, scaffolding from `_TEMPLATE`, and updating README, website, slide deck, and compatibility artifacts |
8080

8181
### How to Use Skills
8282

@@ -120,18 +120,19 @@ Skills provide templates, patterns, and step-by-step workflows.
120120
- **Folder**: kebab-case (e.g., `oauth-validation`, `rate-limiting`)
121121
- **API prefix**: Short, unique, with trailing hyphen (e.g., `oauth-`, `rl-`)
122122
- **Policy files**: Descriptive, kebab-case (e.g., `token-validation.xml`)
123+
- **Admin APIs**: Samples needing admin/operational endpoints use path `{api_prefix}admin` with `subscriptionRequired=True` and kebab-case operation paths (e.g., `/load-cache`)
123124

124125
### Infrastructure Constants
125126

126127
Available in Python via `from apimtypes import INFRASTRUCTURE`:
127128

128-
| Constant | Description |
129-
|----------|-------------|
130-
| `INFRASTRUCTURE.AFD_APIM_PE` | Azure Front Door + APIM with Private Endpoint |
131-
| `INFRASTRUCTURE.APIM_ACA` | APIM with Azure Container Apps |
132-
| `INFRASTRUCTURE.APPGW_APIM` | Application Gateway + APIM (VNet injection) |
133-
| `INFRASTRUCTURE.APPGW_APIM_PE` | Application Gateway + APIM with Private Endpoint |
134-
| `INFRASTRUCTURE.SIMPLE_APIM` | Basic APIM setup |
129+
| Constant | Description |
130+
| ----------------------------------- | ----------------------------------------------------- |
131+
| `INFRASTRUCTURE.AFD_APIM_PE` | Azure Front Door + APIM with Private Endpoint |
132+
| `INFRASTRUCTURE.APIM_ACA` | APIM with Azure Container Apps |
133+
| `INFRASTRUCTURE.APPGW_APIM` | Application Gateway + APIM (VNet injection) |
134+
| `INFRASTRUCTURE.APPGW_APIM_PE` | Application Gateway + APIM with Private Endpoint |
135+
| `INFRASTRUCTURE.SIMPLE_APIM` | Basic APIM setup |
135136

136137
## Working with Existing Samples
137138

@@ -177,14 +178,14 @@ apis = [api]
177178

178179
Key modules in `shared/python/`:
179180

180-
| Module | Purpose |
181-
|--------|---------|
182-
| `utils.py` | NotebookHelper, policy loading, endpoint helpers |
183-
| `apimtypes.py` | Type definitions (API, INFRASTRUCTURE, APIM_SKU) |
184-
| `azure_resources.py` | Azure CLI wrappers for resource management |
185-
| `console.py` | Formatted console output (print_ok, print_error) |
186-
| `apimrequests.py` | HTTP request helpers for testing APIs |
187-
| `apimtesting.py` | Test framework for sample verification |
181+
| Module | Purpose |
182+
| -------------------- | ----------------------------------------------------------- |
183+
| `utils.py` | NotebookHelper, policy loading, endpoint helpers |
184+
| `apimtypes.py` | Type definitions (API, INFRASTRUCTURE, APIM_SKU) |
185+
| `azure_resources.py` | Azure CLI wrappers for resource management |
186+
| `console.py` | Formatted console output (print_ok, print_error) |
187+
| `apimrequests.py` | HTTP request helpers for testing APIs |
188+
| `apimtesting.py` | Test framework for sample verification |
188189

189190
## Bicep Modules
190191

@@ -240,11 +241,11 @@ ruff check shared/python/
240241

241242
## Common Tasks Reference
242243

243-
| Task | Skill to Use | Key Files |
244-
|------|--------------|-----------|
245-
| Create a new sample | sample-creator | `samples/_TEMPLATE/*` |
246-
| Write APIM policies | apim-policies | `shared/apim-policies/*.xml` |
247-
| Create Bicep templates | apim-bicep | `shared/bicep/modules/` |
244+
| Task | Skill to Use | Key Files |
245+
| ---------------------- | -------------- | ---------------------------- |
246+
| Create a new sample | sample-creator | `samples/_TEMPLATE/*` |
247+
| Write APIM policies | apim-policies | `shared/apim-policies/*.xml` |
248+
| Create Bicep templates | apim-bicep | `shared/bicep/modules/` |
248249

249250
## Additional Resources
250251

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ It's quick and easy to get started!
6868
| [AuthX Pro][sample-authx-pro] | Authentication and role-based authorization in a mock product with multiple APIs and policy fragments. | All infrastructures |
6969
| [Azure Maps][sample-azure-maps] | Proxying calls to Azure Maps with APIM policies. | All infrastructures |
7070
| [Costing][sample-costing] | Track and allocate API costs per business unit using APIM subscriptions, Entra ID application tracking, and AI Gateway token/PTU tracking via Log Analytics and Cost Management. | All infrastructures |
71+
| [Dynamic CORS][sample-dynamic-cors] | Dynamic per-API CORS origin validation using custom policy fragments and a maintainable origin mapping. | All infrastructures |
7172
| [Egress Control][sample-egress-control] | Control APIM outbound internet traffic by routing it through a Network Virtual Appliance (NVA) in a hub/spoke topology. | appgw-apim, appgw-apim-pe |
7273
| [General][sample-general] | Basic demo of APIM sample setup and policy usage. | All infrastructures |
7374
| [Load Balancing][sample-load-balancing] | Priority and weighted load balancing across backends. | apim-aca, afd-apim-pe |
@@ -380,6 +381,7 @@ _For much more API Management content, please also check out [APIM Love](https:/
380381
[sample-authx-pro]: ./samples/authX-pro/README.md
381382
[sample-azure-maps]: ./samples/azure-maps/README.md
382383
[sample-costing]: ./samples/costing/README.md
384+
[sample-dynamic-cors]: ./samples/dynamic-cors/README.md
383385
[sample-general]: ./samples/general/README.md
384386
[sample-load-balancing]: ./samples/load-balancing/README.md
385387
[sample-egress-control]: ./samples/egress-control/README.md

assets/APIM-Samples-Slide-Deck.html

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,7 +1015,7 @@ <h3>&#128218; Samples</h3>
10151015
<ul style="list-style: none; padding: 0;">
10161016
<li style="padding: 6px 0; font-size: 15px; color: #444; display: flex; gap: 10px; align-items: flex-start;">
10171017
<div class="bullet" style="flex-shrink:0; width:8px; height:8px; border-radius:50%; background:#0078D4; margin-top:7px;"></div>
1018-
<div>9 real-world scenarios covering auth, networking, costing, and more</div>
1018+
<div>10 real-world scenarios covering auth, networking, costing, and more</div>
10191019
</li>
10201020
<li style="padding: 6px 0; font-size: 15px; color: #444; display: flex; gap: 10px; align-items: flex-start;">
10211021
<div class="bullet" style="flex-shrink:0; width:8px; height:8px; border-radius:50%; background:#0078D4; margin-top:7px;"></div>
@@ -1095,7 +1095,7 @@ <h4>App Gateway & APIM (VNet Injection)</h4>
10951095
<div class="slide slide-light">
10961096
<div class="header-bar">
10971097
<div class="accent"></div>
1098-
<h2>9 Real-World Policy Samples</h2>
1098+
<h2>10 Real-World Policy Samples</h2>
10991099
</div>
11001100
<p class="subtitle">From authentication &amp; authorization to network routing, cost tracking, and secure access patterns</p>
11011101

@@ -1120,6 +1120,10 @@ <h4>Azure Maps</h4>
11201120
<h4>Costing &amp; Showback</h4>
11211121
<p>Track API costs per business unit via subscriptions, Entra ID apps &amp; AI Gateway tokens.</p>
11221122
</div>
1123+
<div class="arch-card">
1124+
<h4>Dynamic CORS</h4>
1125+
<p>Per-API CORS origin validation with custom policy fragments &amp; maintainable origin mappings.</p>
1126+
</div>
11231127
<div class="arch-card">
11241128
<h4>Credential Manager</h4>
11251129
<p>APIM Credential Manager with Spotify's REST API.</p>

docs/index.html

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,12 @@
136136
{ "@type": "ListItem", "position": 7, "name": "AuthX Pro sample", "url": "https://github.com/Azure-Samples/Apim-Samples/tree/main/samples/authX-pro" },
137137
{ "@type": "ListItem", "position": 8, "name": "Azure Maps sample", "url": "https://github.com/Azure-Samples/Apim-Samples/tree/main/samples/azure-maps" },
138138
{ "@type": "ListItem", "position": 9, "name": "Costing sample", "url": "https://github.com/Azure-Samples/Apim-Samples/tree/main/samples/costing" },
139-
{ "@type": "ListItem", "position": 10, "name": "Egress Control sample", "url": "https://github.com/Azure-Samples/Apim-Samples/tree/main/samples/egress-control" },
140-
{ "@type": "ListItem", "position": 11, "name": "General sample", "url": "https://github.com/Azure-Samples/Apim-Samples/tree/main/samples/general" },
141-
{ "@type": "ListItem", "position": 12, "name": "Load Balancing sample", "url": "https://github.com/Azure-Samples/Apim-Samples/tree/main/samples/load-balancing" },
142-
{ "@type": "ListItem", "position": 13, "name": "OAuth 3rd-Party sample", "url": "https://github.com/Azure-Samples/Apim-Samples/tree/main/samples/oauth-3rd-party" },
143-
{ "@type": "ListItem", "position": 14, "name": "Secure Blob Access sample", "url": "https://github.com/Azure-Samples/Apim-Samples/tree/main/samples/secure-blob-access" }
139+
{ "@type": "ListItem", "position": 10, "name": "Dynamic CORS sample", "url": "https://github.com/Azure-Samples/Apim-Samples/tree/main/samples/dynamic-cors" },
140+
{ "@type": "ListItem", "position": 11, "name": "Egress Control sample", "url": "https://github.com/Azure-Samples/Apim-Samples/tree/main/samples/egress-control" },
141+
{ "@type": "ListItem", "position": 12, "name": "General sample", "url": "https://github.com/Azure-Samples/Apim-Samples/tree/main/samples/general" },
142+
{ "@type": "ListItem", "position": 13, "name": "Load Balancing sample", "url": "https://github.com/Azure-Samples/Apim-Samples/tree/main/samples/load-balancing" },
143+
{ "@type": "ListItem", "position": 14, "name": "OAuth 3rd-Party sample", "url": "https://github.com/Azure-Samples/Apim-Samples/tree/main/samples/oauth-3rd-party" },
144+
{ "@type": "ListItem", "position": 15, "name": "Secure Blob Access sample", "url": "https://github.com/Azure-Samples/Apim-Samples/tree/main/samples/secure-blob-access" }
144145
]
145146
}
146147
]
@@ -450,6 +451,12 @@ <h3>Costing</h3>
450451
<span class="infra-tag">All infrastructures</span>
451452
</a>
452453

454+
<a class="sample-card" href="https://github.com/Azure-Samples/Apim-Samples/tree/main/samples/dynamic-cors" target="_blank" rel="noopener">
455+
<h3>Dynamic CORS</h3>
456+
<p>Dynamic per-API CORS origin validation using custom policy fragments and a maintainable origin mapping.</p>
457+
<span class="infra-tag">All infrastructures</span>
458+
</a>
459+
453460
<a class="sample-card" href="https://github.com/Azure-Samples/Apim-Samples/tree/main/samples/egress-control" target="_blank" rel="noopener">
454461
<h3>Egress Control</h3>
455462
<p>Control APIM outbound internet traffic by routing it through an Azure Firewall NVA in a hub/spoke topology with user-defined routes.</p>

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ license = { file = "LICENSE" }
1111
dependencies = [
1212
"pillow>=12.1.1", # Resolves CVE 2021-25289: https://pillow.readthedocs.io/en/stable/releasenotes/12.1.1.html#security
1313
"ipykernel",
14+
"jinja2",
1415
"matplotlib",
1516
"pandas",
1617
"pyjwt>=2.12.0",

0 commit comments

Comments
 (0)