BICEP-Automate deployment of API Management and its components

Vinnarason James
5 min readJul 27, 2021

Bicep reference for API management and its components.

Deploy API Management using BICEP

Azure API Management one of the pivotal components offered by Microsoft Azure and an important member of the Azure Integration Services. API Management provides capabilities not only to host and manage your APIs, but help securing them, help transforming the legacy web services to modern rest APIs, improve API discoverability via a fully customizable Developer Portal, protect API resources from throttling and other issues using policies.

API management can now run anywhere on a Kubernetes cluster and still be managed from Azure using ARC Enabled cluster

Automating the deployment of API Management and its configuration can be cumbersome. I have tried exploring the automation of the service and its configuration using BICEP. Following sections will explain how it can be done using BICEP.

API management configurations

Create API Management:

To create API management with system assigned identity enabled.

resource apiManagement 'Microsoft.ApiManagement/service@2020-12-01' = {
name: '${apimServiceName}-${env}'
location: resourceGroup().location
tags:tagValues
sku: {
name: sku
capacity: skuCount
}
properties: {
publisherName: publisherName
publisherEmail: publisherEmail
}
identity: {
type: 'SystemAssigned'
}
}

If you are deploying consumption tier, remember to keep skuCount to 0

Next step would be to create logger for APIM. You can use an Application Insights instance to create a logger.

Create Application Insights:

resource workspace 'Microsoft.OperationalInsights/workspaces@2020-10-01' = {
name: '${workspaceName}-${env}'
location: location
tags:tagValues
properties: {
sku: {
name: 'Free'
}
}
}
resource applicationInsights 'Microsoft.Insights/components@2020-02-02-preview' = {
name: '${appInsightsName}-${env}'
location: location
tags:tagValues
kind: 'web'
properties: {
Application_Type: 'web'
WorkspaceResourceId: workspace.id
}
}

If you already have an Application Insights available, you can refer them using existing keyword:

resource appInsights 'Microsoft.Insights/components@2020-02-02-preview' existing = {
name: '${appInsightsName}-${env}'
}

Create Logger:

Now its time to create the logger for API management. These configurations can be added to the API Management service as a nested resource.

resource apiManagementLogger 'Microsoft.ApiManagement/service/loggers@2020-12-01' = {
name: '${appInsightsName}-${env}'
parent: apiManagement
properties: {
loggerType: 'applicationInsights'
description: 'Logger resources to APIM'
credentials: {
instrumentationKey: appInsights.properties.InstrumentationKey
}
}
}

resource declaration if logger is created as a separate resource:

resource apiManagementLogger ‘Microsoft.ApiManagement/service/loggers@2020–12–01’

If created as a nested resource it can be changed to

resource apiManagementLogger ‘loggers@2020–12–01’

To enable API Management instance diagnostics

resource apimInstanceDiagnostics 'diagnostics@2020-12-01' = {
name: 'applicationinsights'
parent: apiManagement
properties: {
loggerId: apiManagementLogger.id
alwaysLog: 'allErrors'
logClientIp: true
sampling: {
percentage: 100
samplingType: 'fixed'
}
}
}

Product

To create a product and add APIs to it

resource product 'products@2021-01-01-preview' = {
name: 'horse'
properties: {
displayName: 'Sample'
description: 'for a collection of API request needed for enterprise management'
subscriptionRequired: true
approvalRequired: false
state: 'notPublished'
}
resource api1 'apis@2021-01-01-preview' = {
name: 'api-v1'
}
resource api2 'apis@2021-01-01-preview' = {
name: 'search-v1'
}
resource product_group_administrators 'groups@2021-01-01-preview' = {
name: 'administrators'
}
}

Custom policy on a product

resource apimProductPolicy 'policies@2019-12-01' = {
name: '${apimProduct.name}/policy'
properties: {
format: 'rawxml'
value: '<policies><inbound><base /></inbound><backend><base /></backend><outbound><set-header name="Server" exists-action="delete" /><set-header name="X-Powered-By" exists-action="delete" /><set-header name="X-AspNet-Version" exists-action="delete" /><base /></outbound><on-error><base /></on-error></policies>'
}
}

Subscription:

resource apimSubscription 'subscriptions@2021-01-01-preview' = {
name: 'Management'
properties: {
displayName: 'Management'
primaryKey: 'custom-primary-key-${uniqueString(resourceGroup().id)}'
secondaryKey: 'custom-secondary-key-${uniqueString(resourceGroup().id)}'
state: 'active'
scope: '/products/${product_horse.id}'
}
}

Add a user

resource apimUser 'users@2019-12-01' = {
name: 'custom-user'
properties: {
firstName: 'Custom'
lastName: 'User'
state: 'active'
email: 'custom-user-email@address.com'
}
}

Add a group

resource exampleGroup 'groups@2020-06-01-preview' = {
name: 'exampleGroup'
properties: {
displayName: 'Example Group Name'
description: 'Example group description'
}
}

Add a certificate

resource certificate 'certificates@2020-06-01-preview' = {
name: 'exampleCertificate'
properties: {
data: mutualAuthenticationCertificate
password: certificatePassword
}
}

OpenID connect provider

resource openIdConnectProvider 'openidConnectProviders@2020-06-01-preview' = {
name: 'exampleOpenIdConnectProvider'
properties: {
displayName: 'exampleOpenIdConnectProviderName'
description: 'Description for example OpenId Connect provider'
metadataEndpoint: 'https://example-openIdConnect-url.net'
clientId: 'exampleClientId'
clientSecret: openIdConnectClientSecret
}
}

Google as an Identity provider

resource identityProvider 'identityProviders@2020-06-01-preview' = {
name: any('${apiManagementService.name}/google')
properties: {
clientId: 'googleClientId'
clientSecret: googleClientSecret
}
dependsOn: [
apiManagementService
]
}

Create named values:

To retrieve the secret value from key vault (recommended)

resource namedvalue_apikey 'namedValues@2021-01-01-preview' = {
name: 'api-key'
properties: {
displayName: 'ApiKey'
keyVault: {
secretIdentifier: 'https://${keyvaultName}-${env}.vault.azure.net/secrets/${ApiKeySecretName}'
identityClientId: '1e17c875-1b51-4758-9af9-64ac58befd86'
}
secret: true
}
}

API managements identity should be passed as the value for “identityClientId”. Reference using “apimanagement.identity.principalId” doesn’t work as it retrieves the object ID but this property requires the “ApplicationID”. Best to retrieve the ApplicationID using az cli script and pass it to “identityClientId” parameter

To pass security value directly from the parameter file as a secure value

resource namedvalue_apiKey 'namedValues@2021-01-01-preview' = {
name: 'api-key
properties: {
displayName: 'apiKey'
value: '<secureValue>'
secret: true
}
}

API version set

To deploy APIs with support to versioning, version set should be created and referred at the API.

resource apiVersionset 'apiVersionSets@2019-01-01' = {
name: 'api'
properties: {
displayName: 'APIVersionSet'
versioningScheme: 'Segment'
}
}

Versioning schemes Query and header can be set if they are being used

API

API and its components can be deployed as a single resource with the nested resource definition.

resource api 'apis@2021-01-01-preview' = {
name: 'api-v1'
properties: {
displayName: 'Sample API'
subscriptionRequired: true
path: 'api'
protocols: [
'https'
]
isCurrent: true
apiVersion: 'v1'
apiVersionSetId: apiVersionset.id
}

Logger for API:

resource logger_api 'diagnostics@2021-01-01-preview' = {
name: 'applicationinsights'
properties: {
alwaysLog: 'allErrors'
httpCorrelationProtocol: 'Legacy'
verbosity: 'information'
logClientIp: true
loggerId: apiManagementLogger.id
sampling: {
samplingType: 'fixed'
percentage: 100
}
frontend: {
request: {
headers: []
body: {
bytes: 0
}
}
response: {
headers: []
body: {
bytes: 0
}
}
}
backend: {
request: {
headers: []
body: {
bytes: 0
}
}
response: {
headers: []
body: {
bytes: 0
}
}
}
}
resource applicationinsights 'loggers@2018-01-01' = {
name: '${appInsightsName}-${env}'
}
}

API Operation with policy:

resource operation 'operations@2021-01-01-preview' = {
name: 'search'
properties: {
displayName: 'Search'
method: 'GET'
urlTemplate: '/something'
templateParameters: []
request: {
queryParameters: [
{
name: 'id'
type: 'String'
values: [
'1052'
]
}
]
headers: []
representations: []
}
responses: []
}
resource policy 'policies@2021-01-01-preview' = {
name: 'policy'
properties: {
value: '<policies>\r\n <inbound>\r\n <rewrite-uri template="/NZBSearch" copy-unmatched-params="true" />\r\n <base />\r\n </inbound>\r\n <backend>\r\n <base />\r\n </backend>\r\n <outbound>\r\n <base />\r\n </outbound>\r\n <on-error>\r\n <base />\r\n </on-error>\r\n</policies>'
format: 'xml'
}
}
}

Policy for all operations:

resource policy_all_operations 'policies@2021-01-01-preview' = {
name: 'policy'
properties: {
value: concat('<policies>\r\n <inbound>\r\n <base />\r\n <set-backend-service base-url="', BaseUrl, '" />\r\n <set-header name="Subscription-Key" exists-action="override">\r\n <value>{{ApiKey}}</value>\r\n </set-header>\r\n </inbound>\r\n <backend>\r\n <base />\r\n </backend>\r\n <outbound>\r\n <base />\r\n </outbound>\r\n <on-error>\r\n <base />\r\n </on-error>\r\n</policies>')
format: 'xml'
}
}

There are scenarios where API management will be deployed in a VNet and will also be configured with custom domain configurations. I will explore the Scripts to achieve them in a separate article.

--

--

Vinnarason James

An Azure Enthusiast, looking to share my learning about Azure Services I use as part of my job