Month: December 2021

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.