API Management and Azure Functions secured without secret keys leveraging managed identities (RSS of TV series' new episodes)
Stefano d'Antonio
Posted on November 20, 2021
I wanted to keep up with new episodes of my favourite TV shows as they aired; being a big RSS feed fan, I wanted to centralise all the news in one place and I built a simple API that checks for new episodes and turns them into RSS XML for my feed reader and hosted it on cheap Azure Functions on consumption plan.
Link to the GitHub repository: TvShowRss
This is the architecture:
Securing with function keys
Making the Azure Function public, I would have opened my solution to abuse attacks.
E.G.
Someone knowing the URL, could run a massive invocation of my functions; given that Azure Functions scales seamlessly with no virtual limit, I could end up with a colossal bill on my Azure subscription.
So I decided to add an API Management instance in consumption plan in front of the functions API and set quotas, so only a certain amount of executions would be allowed per minute.
I wanted also to restrict access to my functions only from the outbound IP ranges of my APIM instance, but the consumption plan does not have a specific outbound range, so I had to secure it only with function authorisation (for now).
To secure the back-end I initially restricted the functions with function keys; so only people in possession of that key could invoke my functions.
This well known pattern, carries a big burden: to make sure your solution is secure, you need to have a key rotation system in place, in case someone manages to steal that secret value.
Authentication policies
Sometimes in 2021, a new feature was introduced in API Management, the authentication policies: https://docs.microsoft.com/en-us/azure/api-management/api-management-authentication-policies
With this feature, I could leverage the managed identity of the APIM instance and get rid of the keys completely.
Easy Auth
There is another amazing feature of App Services/Functions required, this is Easy Auth; this name is not well known, and the feature in the portal is just called Authentication/Authorization, which is a quite generic term and makes it harder to find in search engines. This is the link: https://docs.microsoft.com/en-us/azure/app-service/configure-authentication-provider-aad
This feature takes the burden of auth away from your application code and gets implemented at PaaS level.
You just configure your app to block non-authenticated traffic and it does the rest for your (create AAD App Registration, configure it, block anonymous access, check authentication token on calls):
Now you can set up your Functions as back-end of API Management and add the authentication-managed-identity resource
policy inbound, which will automatically add the Authorization
header to the requests and authenticate seamlessly, without stored secrets to your functions.
Unexpected problem
I was confident that it was going to take a few minutes to enable this and remove all the references to the old function key, but when I ran my test I had the following error:
IDX10214: Audience validation failed. Audiences: ‘[PII is hidden]’. Did not match: validationParameters.ValidAudience: ‘[PII is hidden]’ or validationParameters.ValidAudiences: ‘[PII is hidden]’.
Helpful, isn't it?
I kept looking at my audience in the JWT token from the APIM trace and at the allowed audiences in Easy Auth and they were exactly the same!
APIM trace
JWT.MS inspection
Easy Auth config
OK, the data is obfuscated, but you can trust me on that one, the rest of the 27c65941-
GUID, matched everywhere.
The PII hidden did not help as I was not able to see the difference in the error message. If you try and find how to allow PII to be shown in debug environments, that can be done by setting a property in code, but the Easy Auth code is not something we can change.
Then I stumbled across this article from a fellow CSA and I figured it out!
Despite the error message talking about audience, the problem was with the issuer! If you look at the Easy Auth configuration, it is requesting v2.0
, whereas the auto-generated AAD application issues v1.0
by default.
So I changed that in the app manifest et voilà... authentication worked and I was able to get rid of the secrets.
Infrastructure as code
OK, that's sorted, but it was time to script the changes and make sure my Pulumi deployment created the app registration with the correct configuration.
I started typing in Visual Studio in C#, under the Application definition: AccessTokenAccep<ctrl+space><ctrl+space><ctrl+space><ctrl+space>
but no joy from IntelliSense...
This property wasn't in the Pulumi class! Which is odd, considering Pulumi auto generates code from the REST API (I presume also for the Azure AD module now).
So I looked up in the Azure AD application REST API docs and... nothing. Turns out (see here) it was only available on the beta API.
So I had to include the REST API call in Pulumi after the creation of the app registration, using a workaround to force the call that is my code but I will not discuss in this article (see code here).
Posted on November 20, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.