Deploying the Front Door using Bicep

Bicep is a very nice language that rests on top of ARM. I have always preferred ARM to scripting as “wanted state” is a much better way of looking at anything relating to infrastructure.

But this reliance on ARM also means that whatever template definition is output by the product, that is what you must use. Case in point: Azure Front Door.

Circular reference

A circular reference in an age old problem in coding. You cannot use circual references because, logically, it does not work. It is a series of references where the last object references the first, resulting in a closed loop.

Within Azure Front Door (or AFD) we have this problem, a lot. Within AFD it refers to itself, and if you add a Rules Engine (RE), that refers to the AFD, that refers to the RE.

If you do like I do: Setup something the first time using the portal, and the extract the ARM using the Export Template, or Insert Resource in VS Code to get the Bicep Code directly, you will find that the generated ARM contains a lot of errors due to circular references.

We need to overcome this, and solve some other issues.

The first problem

The first problem comes about 5 rows in, when we need to define the (first) routing rule. You need to assign it an ID. This ID consist of the resource ID for the AFD that we are just about to create!

This is the first of many circular references.

The solution

I solved this, and all other circular references, by using variables and string interpolation. Simply put: Create the resource ID yourself. You know what the ID will be, because you are just about to create it.

Create two useful variables at the top of the bicep file.

var frontDoorName = 'contoso-afd-test'
var frontDoorResourceId = '{resourceGroup().id}/providers/Microsoft.Network/frontDoors/{frontDoorName}'

You must deploy the AFD within the context of a resource group, even if the AFD is a global service. You get the start of the resource ID by reffering to this, and the add the rest. The resulting frontDoorResourceId would be:

/subscriptions/GUIDHERE/resourcegroups/contoso-TEST-rg/providers/Microsoft.Network/frontdoors/contoso-afd-test

Now, we can use the frontDoorResourceId variable, every time a sub-resource needs to be defined, such as a routing rule or backend pool, as defined below.

routingRules: [
  {
    id: '{frontDoorResourceId}/RoutingRules/APIm'
    name: 'APIm'
    properties: {
      routeConfiguration: {
        forwardingProtocol: 'HttpsOnly'
        backendPool: {
          id: '{frontDoorResourceId}/BackendPools/ApimEurope${Env}'
        }
        '@odata.type': '#Microsoft.Azure.FrontDoor.Models.FrontdoorForwardingConfiguration'
      }
      ...
    }
  }
]

The second problem

The next problem is the rules engines. In the template definition it looks like you need to add another Resource ID, but there is a problem. The Rules Engines is a totally different resource type and it needs to be created separately. This means you simply cannot refer to it in the same way as with the frontDoorResourceId above.

The solution

It is very easy: Do not add the property to the AFD Definition. This property is not read only, but it will be assigned at deploy time by the Rules Engines Definition you add to the same Bicep File.

resource frontDoorRulesEngine 'Microsoft.Network/frontDoors/rulesEngines@2020-05-01' = {
  name: rulesEngineName
  parent: frontDoor
  properties: {
    rules: [
      {
        name: 'RuleNumberOne'
        priority: 0
        action: {
          routeConfigurationOverride: {
            forwardingProtocol: 'HttpsOnly'
            backendPool: {
              id: '${frontDoorResourceId}/BackendPools/FightClub'
            }
            '@odata.type': '#Microsoft.Azure.FrontDoor.Models.FrontdoorForwardingConfiguration'
          }
          requestHeaderActions: []
          responseHeaderActions: []
        }
  ...
  // Rule settings go here
  ...

      }
    ]
  }
}

You assign the AFD you are about to create as the rules engine’s parent. This will connect the AFD and the RE at deploytime.

Note the use of the frontDoorResourceId to refer to the Backend Pool.

The third (last) problem

The this is only a problem if you are using the Web Application Firewall (WAF) and you should. If you need to enable the WAF you must refer to a policy, or rather a FrontDoorWebApplicationFirewallPolicies resource. You do this when you create your frontendEndpoints.

A solution

This is not the solution, but I know it works for me. I define the WAF policies in a separate Bicep file as these tend to be separate from the Front Door. Remember, you can reuse the same policy for different WAFs.

Create a separate file with the resource definition. If you want to send a customized error message you need to run the base64 string function on it before deployment. This is much more useful than converting it to Base64 and pasting that sting into the Bicep-file.

Name the policy using a parameter.

param PolicyName string

resource defaultPolicy 'Microsoft.Network/FrontDoorWebApplicationFirewallPolicies@2020-11-01' = {
  name: PolicyName
   policySettings: {
     enabledState: 'Enabled'
     mode: 'Prevention'
     customBlockResponseStatusCode: 406
     customBlockResponseBody: base64('<html><head><title>You are blocked</title></head><body bgcolor="#FFB299"><p><h1>WAF custom response</h1>You are being blocked. If you need access, contact support with this reference{{azure-ref}}</p></body></html>')
   }
}

Send the policy name parameter to your AFD bicep and construct the reference using string interpolation. My naming below assumes that the policy is deployed in the same resource group as the AFD. If that is not your case, create a reference to that resource group in the bicep file, or update your bicep file for the policy to use an output parameter and send that to the AFD bicep deployment.

About the error message and the use of azure-ref, consult this documentation.

output policyResourceID string = myPolicy.id

This is how I constructed the resource reference in my AFD Bicep file. Note the liberal use of the frontDoorResourceId and frontDoorName variables.

frontendEndpoints: [
  {
    id: '{frontDoorResourceId}/FrontendEndpoints/{frontDoorName}-azurefd-net'
    name: '{frontDoorName}-azurefd-net'
    properties: {
      hostName: '{frontDoorName}.azurefd.net'
      sessionAffinityEnabledState: 'Enabled'
      sessionAffinityTtlSeconds: 0
      webApplicationFirewallPolicyLink: {
        id: '{resourceGroup().id}/providers/Microsoft.Network/frontdoorwebapplicationfirewallpolicies/{WAFPolicyName}'
      }
    }
  }
]

Conclusion

Deploying AFD is possible but also a bit of a pain using Bicep. This is notdue to the language, but the ARM template. AFD is a very old service that was in use by the XBOX network for years before being made available in Azure. I think this shows, and I hope for a revisional update at some point.

Still: This should NOT deter you from using Bicep (or ARM) and infrastructure as code for your AFD installations. Please avoid PowerShell and Azure Cli.

How to deploy NamedValues from KeyVault in APIm using Bicep

Introduction

I recently had a bit of a … run in … with deploying Named Values in APIm. These values are connected to a keyvault. This is a very good strategy for keeping keys and passwords centralized and secret. I have done this before, but was trying to use Bicep this time, but in doing so I got an error.

The error

This is the error: Can not find Managed Identity with provided IdentityClientId : <Principal ID goes here>

What I was trying to do

I was trying to add the Named Value and KeyVault connection using this bicep:

resource ApimNamedValue 'Microsoft.ApiManagement/service/namedValues@2021-04-01-preview' =  {
  parent: ApimInstance
  name: 'BackEndAPIKey'
  properties: {
    displayName: 'BackEndAPIKey'
    secret: true
    keyVault:{
      secretIdentifier: '${MyKeyVault.properties.vaultUri}secrets/BackEndAPIKey'
      identityClientId: APIminstance.identity.principalId
    }
  }
}

resource ApimInstance 'Microsoft.ApiManagement/service@2021-04-01-preview' existing ={
  name: 'ApimInstanceName'
}

resource MyKeyVault 'Microsoft.KeyVault/vaults@2019-09-01'  existing = {
  scope: resourceGroup(KeyVaultRGName)
  name: keyVaultName
}

I am using “references” to existing instances of a KeyVault (row 19) and an APIm (row 15).
There is just one thing wrong with it: You cannot suppliy the ClientID by submitting the PrincipalID (sometimes also called ObjectID). That is what triggers the error.

The solution

I got the solution directly from Adrian Hall (thank you).
It is down to a misunderstanding of the documentation. You are supposed to supply a ClientID if you want to use another identity than the APIm identity. If you want to use the identity of the APIm you are trying to deploy a named value for: Simply omit the property.

TLDR; remove row 9 from the Bicep code above and everything will work.

Just a short thing about access

To get the APIm instance to read values from the KeyVault, you need to add it to your Access Policies for the KeyVault. You can achieve this by adding the following Bicep code to your deployment.

// APIm reference
resource APIminstance 'Microsoft.ApiManagement/service@2021-04-01-preview' existing = {
  name: 'APImInstanceName'
  scope: resourceGroup('APImRGName')
}

// Keyvault config
resource MyKeyvault 'Microsoft.KeyVault/vaults@2021-06-01-preview' existing = {
  name: 'KeyVaultName'
}

resource KeyVaultSetting 'Microsoft.KeyVault/vaults/accessPolicies@2021-06-01-preview' = {
  name: 'add'
  parent: MyKeyvault
  properties: {
    accessPolicies: [
      {
        objectId: APIminstance.identity.principalId
        tenantId: APIminstance.identity.tenantId
        permissions: {
          'keys': []
          'secrets': [
            'list'
            'get'
          ]
          'certificates': []
        }
      }
    ]
  }
}

Disallowing use of API key as a query in APIm

Introduction

I asked on Twitter if, using API Management, there is any way of not allowing the user to send the API-key as a query parameter. The feedback and discussion made me create a blog post.

As ever, if you are looking for the solution, just scroll to the end.

The problem

TLS or HTTPs encryption

You are using an API-key to protect your API. That key is what security people calls a “shared private secret”. This means that anyone getting their hands on the key can use it, regardless of who you are and where. For most scenarios this is really good, but you do not want the key to fall into the wrong hands, you need to protect it.

You can protect it using TLS (called HTTPs) here is a good and detailed overview of the communication process. In HTTPs almost everything is protected using encryption. A very good and clear explaination of this can be found here.

Unencrypted

Encrypted

So what is the problem with sending an API-key as a query? It is encrypted, right?

Logging

The issue is not that anyone can intercept a message and look at the querystring, it is down to logging. When hosting your API (in Azure API manager or not) you might be behind a firewall, an application gateway or similar. The people running that log usually hosts the TLS endpoint, meaning they can decrypt the message. These logging tools usually surface some metadata about the message, including the querystring. The Azure Application Gateway is a great example of this.

This is an example of a call to an API. See how the subscription-key is logged?

Sending the API-key as a header is much more secure, from the logging standpoint. You should also consider sending other sensitive data such as PII as headers and not query for the same reason.

Best practise

Since the person hosting the private key (the pfx-cert) theoretically can decrypt and look at the whole message, they could access the data anyway. However this is not a built in functionallity in any Azure logging product (that I am aware of). I would consider not sending the API-key as a query, being a best practise; a should and not a must.

The solution

I added a policy to the Global policy in APIm. The Global policy is always executed before any call and I think you should consider putting more stuff in there.

<choose>
    <when condition="@(context.Request.OriginalUrl.Query.GetValueOrDefault("subscription-key","") != "")">
        <return-response>
            <set-status code="400" reason="Bad request" />
            <set-header name="content-type" exists-action="override">
                <value>application/json</value>
            </set-header>
            <set-body>{"Message":"Use the header Auth option."}</set-body>
        </return-response>
    </when>
</choose>

A flaw

This means that the call was sent all the way to APIm and was logged. However, it can create better habits in your callers from now on.

Another flaw

This solution has one obvious flaw: If someone renames the API-key query from subscription-key to something-else, then the policy will accept the incoming call, but remember this is a recommendation. A should.

Logging in to Azure as yourself, but from Postman

A simple self-reminding how to

First of all, thank you to @ToonVanhoutte for writing the original post on how to get the bearer token using Fiddler (super sneaky!) and also to @splaxi (Mötz Jensen) who provided the PowerShell solution.

What are you trying to do?

Hopefully you found this post while googling “logging into azure as a user from postman” or “postman azure as user”. Most posts (including my own) are about how to access Azure APIs from Postman using an App Registration.

Sometimes that is not an option, and your account might actually have enough access-rights to get the information you want. The problem is that sometimes, the portal is not up to the task.

Using Postman to log into Azure as yourself

Rumor has it that it is possible to do this, using just postman but I was not able to, but a simple PowerShell script does the trick.

If you run the following script, you will log in and copy the bearer token to the clipboard. Then you paste it into the token textbox in Postman and you are good for about an hour untill your token has expired.

The powershell script

# Install-Module Az.Accounts

# Login as a user
Connect-AzAccount

# Get the token, and store it in the clipboard.
Get-AzAccessToken | Select-Object -ExpandProperty Token | Set-Clipboard

Note that you need to install the Az.Accounts module for this to work.

When you run this script you will get the old Login Window(tm), you login and when the script is done, the bearer is in the clipboard.

Postman

Return to Postman and find the Authorization tab. Select Bearer Token and paste the copied token into the textbox. That is it.

Failing APIm export and breaking in thru the backdoor

A strange experience and a bug

You can read this post in two ways, either get the full story or scroll down to the solution. My guess is that you found this post because you searched for this error message:

"Definitions/body parameters are not exported since this API references multiple schemas with OpenAPI components as opposed to one. Schemas referenced: [number],[another number]"

And you have spent some time being frustrated because removing the whole API and starting over did not work.

Backstory

This is not only filler, but it also describes how the error occured. Perhaps you recognize yourself in this.

The error from my point of view

I had developed an API for a colleague. The API had a demand for using Definitions of both requests and responses. Honestly, we as API developers, do not use that as much as we should. The colleague supplied me with examples and I got to working using the portal interface to enter definitions.

Things started going south when the portal stopped responding, but after a refresh everything seemed ok. I added all the definitions and then connected them to operations within the API. When I was done I exported the API as OpenAPI v2 (compatability reasons). This is when the error occured.

Looking at the OpenAPI file I could see that all my operations was missing their content properties, meaning that all definitions I had put in, had been omitted. Scrolling down in the OpenAPI file I found the error above.

Redeploy will not solve it

I deleted the entire API and started over, but the same thing happened again! How could APIm remeber “schemas” from an API that I had deleted? Time to get creative and provide the solution.

The solution – short version

You need to delete the “schemas” and you cannot do that from the portal, you need to access your APIm instance using the RestAPI. Find the offending schema using Api Schema – List By Api and the delete it using Api Schema – Delete

The solution – how to version

I will try to explain every aspect of this operation, to make you feel confident about deleting things from your APIm instance.

Schemas?

Looking back at the error message, it mentions schemas, which might seem strange as APIm never seem to use schemas but rather definitions. The reason is simple: OpenAPI uses schemas and a collection of definition is a schema. APIm usually puts all your defintions into one single schema. The problem was that something went wrong and my API had multiple schemas which messed up the export.

Since the schema property is never surfaced in APIm you cannot delete them. Even if I deleted the entire API, the schema was retained and since you cannot delete it (you can only delete all values/definitions within a schema) I was stuck.

The APIm Rest API?

One of the most confusing things is that there is an API to manage your Azure API management. A manager’s manager if you will. This is really well documented an you can find the basics on how to activate and access it here.

You need to activate the API via the portal and also generate an accesskey in order to execute any APIs. You can use Azure OAuth if you want to but I find it easy to just generate a key and supply it using an Authentication header. I use this in all my calls.

Using the APIm Rest API

This API can be used for all sorts of operations on your APIm instance and I encourage you to explore it further. For now you need to execute two calls: one to find the extra schema(s), and one to delete.

There is one important exception to the documentation, and that is the API base-address. If you want to use the easy, key-based, authentication you need to update the address given in the documentation. Lets look at the first API, Api Schema – ListByApi. The documentation gives the following address:

https://management.azure.com/... etc

You must use the direct API path:

https://serviceinstancename.management.azure-api.net.management.azure-api.net/... etc

If you use the first one, you will get an authentication error.

Find the extra schemas

The schema identifiers are given in the error message, but you need to know which one is the wrong one. Therefore you need to list the schemas for your API. Issue this call:

GET https://serviceinstancename.management.azure-api.net/subscriptions/[subscriptionID]/resourceGroups/[resourcegroupname]/providers/Microsoft.ApiManagement/service/[serviceinstancename]/apis/[ApiID]?api-version=2020-06-01-preview

If you use Postman you can easily handle the path variables by using this:

GET https://{{api-servicename}}.management.azure-api.net/subscriptions/:subscriptionID/resourceGroups/:resourceGroupName/providers/Microsoft.ApiManagement/service/:serviceName/apis/:apiId/schemas?api-version=2020-06-01-preview?api-version=2020-06-01-preview

The API-ID can be found on the Settings-page for the API, but there it is called Name and is located below Display Name.

The response contains the schemas, in an array and a count property at the end. If this property is > 1, you have too many schemas.

Find the schema ID

The schema object is defined with four base properties, id, type, name and properties. Confusingly the ID you are looking for is located in name. The ID is a string of numbers and sometimes also letters, but it is not a GUID.

As to finding which schema you need to delete? I cannot tell you. It depends on your data model. In my case it was easy.

Deleting the schema

To be extra sure that you will be deleting the correct schema; find the schema first. Issue this call:

GET https://serviceinstancename.management.azure-api.net/subscriptions/[subscriptionID]/resourceGroups/[resourcegroupname]/providers/Microsoft.ApiManagement/service/[serviceinstancename]/apis/[ApiID]/schemas/[SchemaID]?api-version=2020-06-01-preview

The schema ID is the one you got from the call above.

Here is a Postman call for you:

GET https://{{api-servicename-neu}}.management.azure-api.net/subscriptions/:subscriptionId/resourceGroups/:resourceGroup/providers/Microsoft.ApiManagement/service/:serviceName/apis/:apiId/schemas/:schemaId?api-version=2020-06-01-preview

If you get back the schema you want to delete, you simply issue the same call again, just change the VERB from GET to DELETE.

Conclusion

I have NO IDEA how the problem happened to begin with, and I do not like that schemas cannot be deleted from the portal, but is is easy to do using the API Management REST.