A Practical Guide to Azure Durable Functions — Part 3: Configurations
Everything you need to know to create a production-ready function app via Azure Durable Functions.
You may want to read the previous story of the series first if you are not familiar with Azure, or you want to explore DI options of Azure Durable Functions.
In this story, we will improve our app step by step to demonstrate various approaches to set Azure Function App configurations.
In the end, we aim to configure our function app to retrieve app settings from a fully managed configuration store and have all sensitive information protected.
Configuration Manager
Firstly, I’d like to add the configuration manager to the project so that it no longer reads configs from the environment variables directly. The idea is that config consumer classes don’t have to know where the config values come from.
In the previous story, we have added our own Startup.cs file to the Azure Function project to register dependency injection. Now we can set up configurations in the Startup class, too.
The important line is AddEnvironmentVariables(). It adds an environment variable config provider. We can now inject an instance of IConfiguration to any class and get config items easily.
This also provides us a solid foundation to build on. We probably want to introduce other config providers later as the app evolves.
Strongly Typed Configurations
The other little problem I really want to fix is the hardcoded base URL. Let’s move that to the config, too. But three lines of “GitHubXXX” just look awful.
That’s fine. We just need to group GitHub API related configs together. And it should be easy because C# supports strongly typed configuration objects.
Configuration Files
In a .NET Core web application, there are appsettings.{environment}.json files. And it’s quite easy to load a strongly typed configuration section from a json object. Can I do the same in the local.settings.json file?
We don’t even have to try it — you can if you want. We know it won’t work because the “Values” section in the local.settings.json file simulates environment variables. It really should just contain key-value pairs.
I don’t really want to bring appsettings.{environment}.json files to the function app (it’s possible, though). Because local.settings.json covers local development. And it’s the pipeline’s job to set correct app settings for different environments. I don’t want to manage it in config files.
What other options do I have?
Azure App Configuration Service
Microsoft made a cloud fully managed configuration store App Configuration service generally available in early 2020.
With this service, it’s easy to configure and share config items between all your cloud services. And it enables the use of strongly typed configuration objects in a function app.
One stone, two birds.
Open the Azure portal on a browser tab and create a new App Configuration resource. Follow the steps on the UI. The only thing to notice is that you probably want to choose the free price tier.
Head to the service once it’s created. Click on Access Keys and save the Connection string value somewhere. Then click on Configuration explorer and create a few key-value config items. You want to enter your real values for YourUsername and YourPersonalAccessToken.
- “GitHub:BaseUrl”: “https://api.github.com”
- “GitHub:Username”: “YourUsername”
- “GitHub:Password”: “YourPersonalAccessToken”
This is the flattened json format. The settings above in a complex json object format look like this:
{
"GitHub": {
"BaseUrl": "https://api.github.com",
"Username": "YourUsername",
"Password": "YourPersonalAccessToken"
}
}
Update the local.settings.json file. The only config we need here now is the app configuration service connection string. Use the real value you saved earlier if you want to run the app locally.
Add Microsoft.Extensions.Configuration.AzureAppConfiguration nuget package to the project. We will add app configuration service as a config provider in the Startup class.
The extra handling of existing config providers in the Startup class is not ideal. But it lets you run the function app locally. You don’t need to do it in a regular .NET Core project.
Any change to ConfigurationBuilder is causing a runtime problem on the deployed function app at the moment (5 June 2020). But Microsoft is working on a fix (track the poll request here). I’ll revisit this problem once the fix is released.
Update the constructor of the service class to use the strongly typed configuration via options pattern.
With the correct App Configuration service connection string, when I run this function app locally, the GitHub API related config values are populated with the values I set earlier on the Azure portal.
Now we can update the pipeline to pass only the AppConfigurationConnectionString variable to the Azure Function App. Don’t forget to update the AzureFunctionApp task.
Note: in a real app, you want to make sure an appropriate connection string value is set in the pipeline for different environments: dev, test, prod, etc. But it beyonds the scope of this story.
Protect Sensitive Configurations
The config solution for the function app improved a lot. But there is still a problem. It is a good security practice to store all sensitive data — including all passwords, certificates, and database connection strings — in secret storage to protect them.
Azure Key Vault service is a fully managed cloud-based secret storage service.
I know that .NET Core has a key vault config provider. In fact, if you don’t want to use the managed app configuration service because it’s not free. It’s a good option to add a key vault config provider directly in your project.
But when we create configs on the App Configuration services earlier, you might have noticed that there’s an option to add a key vault reference.
Let’s try it out.
First, create a Key Vault service on the Azure portal. The standard price tier is sufficient.
Go to the resource once it’s created and click on Access policies. There should be only one access policy showing that your Azure user has full access to all secrets.
Click on Secrets and generate a new secret GitPassword. You can enter your real value here. Your personal access token is safe in the key vault.
Navigate to the app configuration service and click on Configuration explorer. Delete the previously created GitHub:Password config item and recreate it as a key vault reference. Link it to the secret created in the key vault earlier.
We also need to make code changes to connect to the key vault. Install Azure.Identity nuget package to the project. At the time of this writing, 1.2.0-preview.3 is a good option as it supports authentication via Visual Studio logged in user. And add a few lines of code in the Startup class when we configure App Configuration service.
If we run the code locally, it should still work. And the password now comes from the Azure key vault.
But wait, how is that secure? What’s different from just setting the password in the App Configuration service?
Don’t worry. The App Configuration service does not have access to your key vault. It worked locally because DefaultAzureCredential used your Visual Studio’s Azure Service Authentication account to authenticate to the key vault service (You can view it by going Tools => Azure Service Authentication => Account Selection in Visual Studio). And that account does have permission to get secrets if it’s the same as your Azure logged in user account.
To verify that, remove List and Get permissions from your user’s access policy of the key vault and try again.
Or use the pipeline to deploy the code changes to Azure. The deployed function app won’t work because we haven’t configured its access policy, yet. Let’s do it now.
Find the function app on the Azure portal. Click on Identify and turn on the System assigned identity then save the change.
After the identity is created, find the key vault service and add an access policy for the system assigned identity of the function app. You can find it by the function app’s name, or the object id. Give it List and Get permissions on secrets.
Now try the deployed function app again. It should work now.
Summary
At the beginning of this story, our function app can only read configs from the environment variables. And we have to set all of them as key-value pairs in the local.settings.json file and the pipeline with clear text.
It now evolved to having only a single config item — the app configuration service connection string — in the function app itself. All other configurations are stored in the fully managed config store, with sensitive config protected by the Azure key vault.
On top of all these, it doesn’t require storing any credentials to access the key vault, neither locally nor deployed. The complexity is hidden by a nuget package.
This is excellent. Hat off to Microsoft.
The code for this story can be found in my GitHub repo.
In the next story, I will talk about three different retry mechanisms of Azure Durable Functions. Two of them are exclusive to durable functions. Stay tuned!
Part 4 is published here.