Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ labs-in-progress/
.coverage
tests/python/htmlcov/

shared/bicep/modules/**/*.json
shared/bicep/modules/**/*.json
main.json
6 changes: 3 additions & 3 deletions infrastructure/afd-apim/create.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@
"if use_ACA:\n",
" utils.print_info('ACA APIs will be created.')\n",
"\n",
" aca_backend_1_policy_xml = utils.read_policy_xml(ACA_BACKEND_1_XML_POLICY_PATH)\n",
" aca_backend_2_policy_xml = utils.read_policy_xml(ACA_BACKEND_2_XML_POLICY_PATH)\n",
" aca_backend_pool_policy_xml = utils.read_policy_xml(ACA_BACKEND_POOL_XML_POLICY_PATH)\n",
" aca_backend_1_policy_xml = utils.read_policy_xml(BACKEND_XML_POLICY_PATH).format(backend_id = 'aca-backend-1')\n",
" aca_backend_2_policy_xml = utils.read_policy_xml(BACKEND_XML_POLICY_PATH).format(backend_id = 'aca-backend-2')\n",
" aca_backend_pool_policy_xml = utils.read_policy_xml(BACKEND_XML_POLICY_PATH).format(backend_id = 'aca-backend-pool')\n",
"\n",
" # Hello World (ACA Backend 1)\n",
" api_hwaca_1_get = GET_APIOperation('This is a GET for Hello World on ACA Backend 1')\n",
Expand Down
2 changes: 1 addition & 1 deletion infrastructure/afd-apim/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ module backendPoolModule '../../shared/bicep/modules/apim/v1/backend-pool.bicep'

// 8. APIM APIs
module apisModule '../../shared/bicep/modules/apim/v1/api.bicep' = [for api in apis: if(length(apis) > 0) {
name: '${api.name}-${resourceSuffix}'
name: 'api-${api.name}'
params: {
apimName: apimName
appInsightsInstrumentationKey: appInsightsInstrumentationKey
Expand Down
6 changes: 3 additions & 3 deletions infrastructure/apim-aca/create.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@
"\n",
"# Policies\n",
"hello_world_policy_xml = utils.read_policy_xml(HELLO_WORLD_XML_POLICY_PATH)\n",
"aca_backend_1_policy_xml = utils.read_policy_xml(ACA_BACKEND_1_XML_POLICY_PATH)\n",
"aca_backend_2_policy_xml = utils.read_policy_xml(ACA_BACKEND_2_XML_POLICY_PATH)\n",
"aca_backend_pool_policy_xml = utils.read_policy_xml(ACA_BACKEND_POOL_XML_POLICY_PATH)\n",
"aca_backend_1_policy_xml = utils.read_policy_xml(BACKEND_XML_POLICY_PATH).format(backend_id = 'aca-backend-1')\n",
"aca_backend_2_policy_xml = utils.read_policy_xml(BACKEND_XML_POLICY_PATH).format(backend_id = 'aca-backend-2')\n",
"aca_backend_pool_policy_xml = utils.read_policy_xml(BACKEND_XML_POLICY_PATH).format(backend_id = 'aca-backend-pool')\n",
"\n",
"# Hello World (Root)\n",
"api_hwroot_get = GET_APIOperation('This is a GET for Hello World in the root', hello_world_policy_xml)\n",
Expand Down
2 changes: 1 addition & 1 deletion infrastructure/apim-aca/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ module backendPoolModule '../../shared/bicep/modules/apim/v1/backend-pool.bicep'
// 7. APIM APIs
module apisModule '../../shared/bicep/modules/apim/v1/api.bicep' = [
for api in apis: if (length(apis) > 0) {
name: '${api.name}-${resourceSuffix}'
name: 'api-${api.name}'
params: {
apimName: apimName
appInsightsInstrumentationKey: appInsightsInstrumentationKey
Expand Down
2 changes: 1 addition & 1 deletion infrastructure/simple-apim/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ module apimModule '../../shared/bicep/modules/apim/v1/apim.bicep' = {

// 4. APIM APIs
module apisModule '../../shared/bicep/modules/apim/v1/api.bicep' = [for api in apis: if(length(apis) > 0) {
name: '${api.name}-${resourceSuffix}'
name: 'api-${api.name}'
params: {
apimName: apimName
appInsightsInstrumentationKey: appInsightsInstrumentationKey
Expand Down
10 changes: 6 additions & 4 deletions samples/authX-pro/create.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,9 @@
" hr_member_role_id = '{{HRMemberRoleId}}'\n",
")\n",
"\n",
"hr_product_name = 'hr'\n",
"products: List[Product] = [\n",
" Product('hr', 'Human Resources', 'Product for Human Resources APIs providing access to employee data, organizational structure, benefits information, and HR management services. Includes JWT-based authentication for HR members.', 'published', False, False, hr_product_xml)\n",
" Product(hr_product_name, 'Human Resources', 'Product for Human Resources APIs providing access to employee data, organizational structure, benefits information, and HR management services. Includes JWT-based authentication for HR members.', 'published', False, False, hr_product_xml)\n",
"]\n",
"\n",
"# 6) Define the APIs and their operations and policies\n",
Expand All @@ -136,13 +137,13 @@
"hremployees_api_path = f'/{api_prefix}employees'\n",
"hremployees_get = GET_APIOperation('Gets the employees', utils.read_policy_xml('./hr_get.xml'))\n",
"hremployees_post = POST_APIOperation('Creates a new employee', utils.read_policy_xml('./hr_post.xml'))\n",
"hremployees = API(f'{api_prefix}Employees', 'Employees Pro', hremployees_api_path, 'This is a Human Resources API for employee information', utils.read_policy_xml(DEFAULT_XML_POLICY_PATH), operations = [hremployees_get, hremployees_post], tags = tags, productNames = ['hr'])\n",
"hremployees = API(f'{api_prefix}Employees', 'Employees Pro', hremployees_api_path, 'This is a Human Resources API for employee information', utils.read_policy_xml(DEFAULT_XML_POLICY_PATH), operations = [hremployees_get, hremployees_post], tags = tags, productNames = [hr_product_name])\n",
"\n",
"# Benefits (HR)\n",
"hrbenefits_api_path = f'/{api_prefix}benefits'\n",
"hrbenefits_get = GET_APIOperation('Gets employee benefits', utils.read_policy_xml('./hr_get.xml'))\n",
"hrbenefits_post = POST_APIOperation('Creates employee benefits', utils.read_policy_xml('./hr_post.xml'))\n",
"hrbenefits = API(f'{api_prefix}Benefits', 'Benefits Pro', hrbenefits_api_path, 'This is a Human Resources API for employee benefits', utils.read_policy_xml(DEFAULT_XML_POLICY_PATH), operations = [hrbenefits_get, hrbenefits_post], tags = tags, productNames = ['hr'])\n",
"hrbenefits = API(f'{api_prefix}Benefits', 'Benefits Pro', hrbenefits_api_path, 'This is a Human Resources API for employee benefits', utils.read_policy_xml(DEFAULT_XML_POLICY_PATH), operations = [hrbenefits_get, hrbenefits_post], tags = tags, productNames = [hr_product_name])\n",
"\n",
"# APIs Array\n",
"apis: List[API] = [hremployees, hrbenefits]\n",
Expand Down Expand Up @@ -171,7 +172,8 @@
"bicep_parameters = {\n",
" 'apis': {'value': [api.to_dict() for api in apis]},\n",
" 'namedValues': {'value': [nv.to_dict() for nv in nvs]},\n",
" 'policyFragments': {'value': [pf.to_dict() for pf in pfs]}\n",
" 'policyFragments': {'value': [pf.to_dict() for pf in pfs]},\n",
" 'products': {'value': [product.to_dict() for product in products]}\n",
"}\n",
"\n",
"# 2) Infrastructure must be in place before samples can be layered on top\n",
Expand Down
6 changes: 4 additions & 2 deletions samples/authX-pro/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ module productHr '../../shared/bicep/modules/apim/v1/product.bicep' = [for produ
]
}]

// APIM APIs
// APIM APIs (deployed after products are ready to avoid race conditions)
@batchSize(1) // Deploy APIs sequentially to avoid race conditions
module apisModule '../../shared/bicep/modules/apim/v1/api.bicep' = [for api in apis: {
name: 'api-${api.name}'
params:{
Expand All @@ -90,7 +91,8 @@ module apisModule '../../shared/bicep/modules/apim/v1/api.bicep' = [for api in a
}
dependsOn: [
namedValue // ensure all named values are created before APIs
productHr // ensure products are created before APIs that reference them
policyFragment // ensure all policy fragments are created before APIs
productHr // ensure all products are fully created before APIs
]
}]

Expand Down
31 changes: 0 additions & 31 deletions samples/authX/hr_product.xml

This file was deleted.

4 changes: 2 additions & 2 deletions samples/authX/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ resource apimService 'Microsoft.ApiManagement/service@2024-06-01-preview' existi

// APIM Named Values
module namedValue '../../shared/bicep/modules/apim/v1/named-value.bicep' = [for nv in namedValues: if (!empty(namedValues)) {
name: nv.name
name: 'nv-${nv.name}'
params: {
apimName: apimName
namedValueName: nv.name
Expand All @@ -45,7 +45,7 @@ module namedValue '../../shared/bicep/modules/apim/v1/named-value.bicep' = [for

// APIM APIs
module apisModule '../../shared/bicep/modules/apim/v1/api.bicep' = [for api in apis: if(!empty(apis)) {
name: '${api.name}-${resourceSuffix}'
name: 'api-${api.name}'
params: {
apimName: apimName
appInsightsInstrumentationKey: appInsightsInstrumentationKey
Expand Down
2 changes: 1 addition & 1 deletion samples/general/create.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"# 1) User-defined parameters (change these as needed)\n",
"rg_location = 'eastus2'\n",
"index = 1\n",
"deployment = INFRASTRUCTURE.AFD_APIM_PE\n",
"deployment = INFRASTRUCTURE.SIMPLE_APIM\n",
"tags = ['general']\n",
"\n",
"# 2) Service-defined parameters (please do not change these)\n",
Expand Down
2 changes: 1 addition & 1 deletion samples/general/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ resource apimService 'Microsoft.ApiManagement/service@2024-06-01-preview' existi

// APIM APIs
module apisModule '../../shared/bicep/modules/apim/v1/api.bicep' = [for api in apis: if(!empty(apis)) {
name: '${api.name}-${resourceSuffix}'
name: 'api-${api.name}'
params: {
apimName: apimName
appInsightsInstrumentationKey: appInsightsInstrumentationKey
Expand Down
2 changes: 1 addition & 1 deletion samples/load-balancing/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ module backendPoolModule4 '../../shared/bicep/modules/apim/v1/backend-pool.bicep
// APIM APIs
module apisModule '../../shared/bicep/modules/apim/v1/api.bicep' = [
for api in apis: if (!empty(apis)) {
name: '${api.name}-${resourceSuffix}'
name: 'api-${api.name}'
params: {
apimName: apimName
appInsightsInstrumentationKey: appInsightsInstrumentationKey
Expand Down
18 changes: 0 additions & 18 deletions shared/apim-policies/aca-backend-2.xml

This file was deleted.

18 changes: 0 additions & 18 deletions shared/apim-policies/aca-backend-pool.xml

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<policies>
<inbound>
<base />
<set-backend-service backend-id="aca-backend-1" />
<set-backend-service backend-id="{backend_id}" />
</inbound>
<backend>
<base />
Expand Down
26 changes: 26 additions & 0 deletions shared/apim-policies/fragments/pf-authz-match-all.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!--
- Expected context variables:
- "jwt": The parsed JWT token which should already be set by the pf-authx fragment.
- "authz_roles": A csv of required role GUIDs to check against. All roles must be present.

- This fragment blocks access (returns 403) when not ALL required roles are present. If all roles are present, processing continues normally.
-->
<fragment>
<choose>
<!-- Check if ALL required role GUIDs are present in the JWT -->
<when condition="@{
var jwt = (Jwt)context.Variables["jwt"];
var requiredRoleGuids = context.Variables.GetValueOrDefault<string>("authz_roles", "").ToString().Split(',');
var jwtRoles = jwt.Claims.GetValueOrDefault("roles", new string[0]);

// Check if ALL required role GUIDs are present in the JWT
return !requiredRoleGuids.All(requiredRole => jwtRoles.Contains(requiredRole.Trim()));
}">
<return-response>
<set-status code="403" reason="Forbidden" />
<set-body>Access denied - not all required roles found</set-body>
</return-response>
</when>
<!-- If all roles are present, continue processing (no action needed) -->
</choose>
</fragment>
3 changes: 1 addition & 2 deletions shared/apim-policies/fragments/pf-authz-match-any.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
- "jwt": The parsed JWT token which should already be set by the pf-authx fragment.
- "authz_roles": A csv of allowed role GUIDs to check against. Any match will grant access.

- This fragment only blocks access (returns 403) when no roles match. If roles match, processing
continues normally.
- This fragment only blocks access (returns 403) when no roles match. If roles match, processing continues normally.
-->
<fragment>
<choose>
Expand Down
27 changes: 20 additions & 7 deletions shared/bicep/modules/apim/v1/api.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,24 @@ resource apiDiagnostics 'Microsoft.ApiManagement/service/apis/diagnostics@2024-0
} }
}

// Product associations are handled directly in this module with proper dependency management // to prevent race conditions while keeping the architecture simple
// Reference existing products for association (with explicit dependency timing)
resource apimProducts 'Microsoft.ApiManagement/service/products@2024-06-01-preview' existing = [for productName in productNames: {
name: productName
parent: apimService
}]

// Create product-API associations with proper dependency management
// https://learn.microsoft.com/azure/templates/microsoft.apimanagement/service/products/apis
resource apiProductAssociation 'Microsoft.ApiManagement/service/products/apis@2024-05-01' = [for productName in productNames: {
name: '${apimName}/${productName}/${api.name}'
resource apiProductAssociation 'Microsoft.ApiManagement/service/products/apis@2024-06-01-preview' = [for (productName, index) in productNames: {
name: apimApi.name
parent: apimProducts[index]
dependsOn: [
apimApi
apimProducts[index] // Ensure the specific product exists and is ready
apiPolicy // Ensure API policy is applied if present
apiOperation // Ensure all operations are created
apiOperationPolicy // Ensure all operation policies are applied
apiDiagnostics // Ensure diagnostics are configured if present
]
}]

Expand All @@ -174,10 +187,10 @@ output apiDisplayName string = apimApi.properties.displayName
@description('The path of the created API.')
output apiPath string = apimApi.properties.path

// @description('Array of product names this API is associated with.')
// output associatedProducts string[] = productNames
@description('Array of product names this API is associated with.')
output associatedProducts array = productNames

// @description('Number of products this API is associated with.')
// output productAssociationCount int = length(productNames)
@description('Number of products this API is associated with.')
output productAssociationCount int = length(productNames)

//output subscriptionPrimaryKey string = apimSubscription.listSecrets().primaryKey
4 changes: 1 addition & 3 deletions shared/python/apimtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@
DEFAULT_XML_POLICY_PATH = f'{SHARED_XML_POLICY_BASE_PATH}/default.xml'
HELLO_WORLD_XML_POLICY_PATH = f'{SHARED_XML_POLICY_BASE_PATH}/hello-world.xml'
REQUEST_HEADERS_XML_POLICY_PATH = f'{SHARED_XML_POLICY_BASE_PATH}/request-headers.xml'
ACA_BACKEND_1_XML_POLICY_PATH = f'{SHARED_XML_POLICY_BASE_PATH}/aca-backend-1.xml'
ACA_BACKEND_2_XML_POLICY_PATH = f'{SHARED_XML_POLICY_BASE_PATH}/aca-backend-2.xml'
ACA_BACKEND_POOL_XML_POLICY_PATH = f'{SHARED_XML_POLICY_BASE_PATH}/aca-backend-pool.xml'
BACKEND_XML_POLICY_PATH = f'{SHARED_XML_POLICY_BASE_PATH}/backend.xml'

SUBSCRIPTION_KEY_PARAMETER_NAME = 'api_key'
SLEEP_TIME_BETWEEN_REQUESTS_MS = 50
Expand Down
Loading