Now that we’ve been through the authentication and authorization side of security, it’s the time for us to talk about deploying apps securely in Azure. One of the benefits of hosting your applications in the cloud is the security capabilities that come with the platform.
In this chapter, we’re going to start by talking about securing your application secrets with Azure Key Vault. This will also cover recommendations for providing access to your vaults and secrets. We’re going to then look at how to authenticate your application with Azure resources using managed identities, exploring the different types of managed identities available within Azure.
We’ll finish the chapter with a look at the Azure App Configuration service for centrally and securely storing your application configuration settings, including how to use the feature management capabilities of the service to centrally manage enabling and disabling features, among other things.
By the end of this chapter, you’ll know how to secure and provide access to your application secrets, how to authenticate your application with other Azure resources, and how to centrally manage settings and feature flags for your applications, all of which will include exercises in code for demonstration purposes.
In this chapter, we’ll cover the following main topics:
To follow along with the exercises in this chapter, you will need the following:
Code in Action videos for this chapter: https://bit.ly/3UAB3Mj
I’m sure it won’t come as a surprise to hear that storing credentials, connection strings, and other sensitive secrets in application code isn’t a great idea. We hear about these secrets being leaked all the time, and there are many tools integrated into code repositories nowadays that warn you if potential secrets are detected within your source code.
Azure Key Vault provides a way to store your application secrets, create and control encryption keys, and manage both public and private certificates centrally and securely. With the Standard tier, your keys, secrets, and certificates are software-protected and safeguarded by Azure. With the Premium tier, you have the option to import or generate keys in hardware security modules (HSMs) that never leave the HSM boundary.
A key vault is a logical group of secrets, and as such, the recommendation is to use one vault per application per environment (dev/prod, for example). This helps prevent the sharing of secrets across different environments and applications and reduces the threat of secrets being exposed.
You can monitor a key vault’s usage by enabling and configuring logging, with the ability to restrict access to and delete the logs as needed. You can send the logs to a Log Analytics workspace, archive them to a storage account, stream them to an event hub, or send them to a partner solution. Previously, you were able to send the logs to Azure Monitor logs, but at the time of writing, a preview feature of Azure Monitor called Key Vault insights can be used instead. A link to further details on this preview feature can be found in the Further reading section of this chapter. As far as the exam is concerned (again, at the time of writing), you can still send the logs to Azure Monitor logs.
Key Vault secrets are encrypted at rest transparently, meaning that the encryption happens without any user interaction required, and the decryption happens automatically when you request to read those secrets (providing you have the permissions to do so). They’re also encrypted in transport using Transport Layer Security (TLS). The combination of Perfect Forward Secrecy (PFS) – which protects connections between client systems and Microsoft cloud services – and connections using RSA-based 2048-bit encryption makes intercepting and accessing this data in transit difficult.
Before a user or application can get access to any secrets or keys stored within a key vault, that caller needs to first be authenticated with AAD. Once they are authenticated, authorization determines what operations they are allowed to perform (if any). Let’s first look at authorization because authentication will lead us nicely into the subsequent section.
Key Vault supports two permission models (only one of which can be used for accessing secrets at any one time):
Before we move into authentication, let’s create a new vault and explore the permission models. We’ll do the initial setup using the Azure CLI before moving into the portal:
az group create -n "<name>" -l "<location>"
az keyvault create -n "<unique vault name>" -g "<resource group name>" -l "<location>"
This may take a few minutes. Notice that the vault name needs to be unique because it will create a globally unique URI in the format https://<vault name>.vault.azure.net/.
az keyvault secret set --vault-name "<vault name>" --name "<secret name>" --value "<secret value>"
Your secret is now encrypted in your new key vault.
az keyvault secret show --vault-name "<vault name>" --name "<secret name>"
From the terminal output, you can see that there are multiple properties, including activation and expiration dates.
Notice that we have a version with a status. Every secret is versioned and can be disabled at any time if needed. When we ran the CLI command to view the secret, we could have specified the version. When a version isn’t specified, the latest version is used.
Notice the options to allow access to different types of Azure resources as well as the Permissions model option previously discussed.
Changing the Permissions Model
When changing the Permissions Model, it’s strongly recommended that you don’t do this during production hours in a production environment. If users or services were previously able to access the secrets as part of the vault access policy, they may no longer have access unless they’ve been granted access via RBAC as well.
To give yourself access, you would need to assign yourself to an appropriate role within the Access control (IAM) blade, although we won’t do that right now.
Don’t delete the resources just created because we’ll use them in the exercises that follow.
There is a lot more to Key Vault than we can cover in this chapter. For example, soft-delete is a feature that allows deleted secrets to be recovered if they were deleted in error. There is a lot of useful information and multiple resources in the Azure Key Vault developer’s guide, a link to which can be found in the Further reading section of this chapter.
We have just completed a very basic demonstration of the authorization side of accessing a key vault. This was all done using your own user account, but as the exam is focused on development, we should talk about how your apps can get access. Let’s talk about how to authenticate your apps with Key Vault.
In addition to authenticating with a user account like we just did, you can authenticate with a service principal (either using a certificate or secret), which we discussed in the previous chapter. You can also authenticate with Key Vault using managed identities, which is the recommended approach.
With service principals, you are responsible for storing and rotating the secrets and certificates. We hear all the time about these credentials being leaked or stolen, sometimes being accidentally committed to source control, or things ceasing to work because someone forgot to rotate a secret and the previous one has now expired.
The answer to this problem is managed identities, which remove the need for you to manage these credentials yourself.
With managed identities (previously called managed service identities), the secret and secret rotation is automatically handled by Azure. You don’t even have access to the credentials. This is the recommended way to authenticate your application with Key Vault and can be used to authenticate with any resource that supports AAD authentication, even your own applications.
If you’re building an application using popular Azure resources such as App Service which access anything that supports AAD authentication, using managed identities is generally the recommended best practice. You can provide all the permissions you need without having to manage any of the credentials yourself. A link to a list of services that can use managed identities can be found in the Further reading section of this chapter.
Internally, managed identities are a special type of service principal (not an app registration) that are only usable with Azure resources. If a managed identity is deleted, the corresponding service principal is also deleted automatically. It’s important to understand the two types of managed identity, so let’s look at each one individually.
A user-assigned managed identity is a standalone resource tied to an AAD identity that’s trusted by the subscription in which it’s created. Once you’ve created a user-assigned managed identity, you can assign it to one or more applications, which can also have one or more user-assigned managed identities for authentication (we’ll demonstrate this shortly, don’t worry). With this type of managed identity being standalone and assignable to multiple resources, its life cycle is managed separately to any of those resources.
Let’s explore this by using Azure Functions to query our key vault using a user-assigned managed identity to authenticate:
az identity create -n "<name>" -g "<resource group name>"
If you wanted to confirm that a service principal is indeed behind this, feel free to check Enterprise applications within AAD, but make sure the Application type filter is changed to Managed identities.
That’s our user-assigned managed identity created and permissions to get secrets from the vault assigned to it. We’ll eventually assign the identity to a function app. Let’s go ahead and create said function app:
Figure 8.1 – VS Code view command palette menu
dotnet add package Azure.Identity
dotnet add package Azure.Security.KeyVault.Secrets
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
The Azure.Identity package is what we’ll use to authenticate with Key Vault (it’s not just for Key Vault) and the Azure.Security.KeyVault.Secrets package is what we’ll use to get the secret we created earlier.
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string userAssignedClientId = "Client ID of the user-assigned managed identity";
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions { ManagedIdentityClientId = userAssignedClientId });
var client = new SecretClient(new Uri("Key Vault URI (in the format of https://<vault name>.vault.azure.net/)"), credential);
KeyVaultSecret secret = await client.GetSecretAsync("Name of secret you want to get");
string responseMessage = $"Secret value: { secret.Value }";
return new OkObjectResult(responseMessage);
}
The key information to notice from this code is that we’re using DefaultAzureCredential, which will try to get a token using the following credential types in the following order:
The list should be somewhat self-explanatory. DefaultAzureCredential is a popular choice for developers because when we’re developing locally, it can use our VS Code credentials via the Azure Account extension, and when the code is running in Azure, it will use the managed identity provided in DefaultAzureCredentialOptions.
The code finishes off by using the GetSecretAsync() method to get the secret from the key vault using whichever credential was able to acquire a token from DefaultAzureCredential, before returning the secret value in plain text. Outputting the unencrypted value of a secret isn’t something you’d usually want to do, but this is purely for demonstration purposes.
Let’s test this locally to confirm it all works before deploying it to Azure:
Figure 8.2 – GET URL shown in the VS Code terminal output
It will take a few moments, but eventually the secret value should be displayed in your browser. This works fine locally using our credentials. Now we’ll try in Azure.
To recap, we now have an identity with credentials we don’t have to store, manage, or rotate. This identity can be assigned to one or more resources, such as a function app, and permissions can be provided for that identity that the relevant resources can use to authenticate with. We’ve also demonstrated how to access Key Vault secrets in code using DefaultAzureCredential to use whatever credentials are available in the current environment.
One thing that isn’t especially obvious is that certain scenarios will default to attempting to use a system-assigned managed identity for authentication even if the resource doesn’t have one and has a user-assigned managed identity assigned. I’ve been caught out by that in the past!
One example of this is if you try setting an App Service application setting (which we covered in Chapter 3, Creating Azure App Service Web Apps) value to reference a Key Vault secret, it will fail unless you either change the default behavior programmatically or configure a system-assigned managed identity. We’ll come across this scenario shortly.
That brings us nicely onto the topic of system-assigned managed identities.
While a user-assigned managed identity is a standalone resource that can be assigned to multiple resources, with its own separate life cycle, a system-assigned managed identity is enabled within a resource and shares the life cycle of that resource.
Let’s get straight into an example to demonstrate this. We’re going to create a web app that reads an application setting, like we did in Chapter 3, Creating Azure App Service Web Apps, but the application setting value will be read from Key Vault:
az appservice plan create -n "< App Service plan name>" -g "<resource group name>" --sku "<SKU code. S1 for example>"
This is assuming you’re using the same resource group as before, or another existing one. By now, you should be able to create a new one if you need to.
az webapp create -n "<web app name>" -g "<resource group name>" -p "<App Service plan name>"
We could have also used @Microsoft.KeyVault(SecretUri=https://<vault URI>/<secret name>/). The forward slash (/) at the end allows this to pick up newer versions of the secret, whereas if you don’t add the slash, it doesn’t.
Notice that Source for this new setting displays Key vault Reference:
Figure 8.3 – Application setting source showing as a Key Vault reference
Figure 8.4 – Key vault reference error on an application setting
Figure 8.5 – Key vault reference application setting error status
Notice that Identity is specifically saying System assigned managed identity. If we were to now assign our user-assigned managed identity to the App Service, this message would remain because the default behavior is for App Service to attempt authentication with a system-assigned managed identity. This can be changed, but we don’t need to worry about that for this exercise.
We could have also used the following CLI command to enable the system-assigned managed identity if we didn’t want to use the portal:
az webapp identity assign -n "<app name>" -g "<resource group name>"
This has once again created a new service principal, but this time, it didn’t create a standalone resource, unlike with a user-assigned managed identity. The life cycle of this system-assigned managed identity is intrinsically linked to that of the App Service.
dotnet new webapp
<h3>Secret value: @(Environment.GetEnvironmentVariable("KV_SECRET"))</h3>
If you run this locally, it will just display Secret value: with no value because that variable doesn’t exist in this context.
dotnet publish -c Release -o DeployToAppService
If you’re already familiar with .NET, you’ll know there are various ways to do this and create a .zip if you need to. Feel free to use your preferred method instead of the provided steps.
To recap, we now have an identity linked to the app itself, which can be used to provide access to resources that support AAD authentication. If the application gets deleted, so does the identity.
We’ve been using Key Vault secrets during this chapter to explain concepts, and we have demonstrated how having a centrally managed secret store can be useful. What about configuration settings that aren’t secrets? With cloud applications often being made up of many distributed components, wouldn’t it be useful to have a central store of configuration settings that can be shared across these components? Enter Azure App Configuration.
Azure App Configuration enables you to centrally manage your configuration settings, so you don’t have to save all your settings in each individual component. In addition to configuration settings, you can also manage feature flags, which allow you to decouple your feature releases from code deployment, all managed centrally.
With App Configuration, you can create key-value pairs, and each setting can also be tagged with a label, so you can have the same key name multiple times but with different labels – perhaps a Development label and a Production label for the same key. This allows your code to reference a single key and have the value selected based on the environment (development or production, for example).
If you need security isolation between these environments, then you should create a new App Configuration store for each environment, rather than just using labels, because access control is at the per-store level.
As well as labels, you can organize your keys by adopting a hierarchical namespace approach to key names. For example, you could have all settings for an app in the MyApp namespace, and maybe add another level for service names, for example, MyApp:MyFirstService:MyAPIEndpoint and MyApp:MySecondService:MyAPIEndpoint. Ultimately, it’s down to you how you want to manage this kind of thing. Keys are case sensitive as well, so bear that in mind.
All settings within App Configuration are encrypted at rest and in transit. This doesn’t make App Configuration a replacement for Key Vault, however. Key Vault is still the best place to store secrets because of the hardware-level encryption, access policy model, and features such as certificate rotation, which is unique to Key Vault. You can create an App Configuration setting that pulls a value from a Key Vault secret, so your application can reference the App Configuration key and the value will come from Key Vault.
You can view and restore from historical revisions of each key, as well as compare two sets of configurations based on the times and labels you define. Unlike the app settings we’ve been changing so far, you don’t always need to restart the service when you make changes to App Configuration keys. In fact, you can make your app handle dynamic configuration, so it will be updated with the latest key changes without needing a restart. The final exercise in this chapter will demonstrate this.
As with Key Vault, there’s native integration with several popular frameworks for connecting to an App Configuration store, and we’ll demonstrate this by using the App Configuration provider for .NET Core in an ASP.NET Core app shortly.
Also like Key Vault, you can use private endpoints to allow clients on a vNet to access data securely over a private link, and on-premises networks to connect to the VNet using VPN or ExpressRoutes with private peering, which allows you to configure the firewall to block all connections on the public endpoint.
Let’s get started with creating a new App Configuration instance and have a look around. This exercise assumes we’re using the same resource group as the previous exercises in this chapter, but feel free to create a new one or use a different one:
az appconfig create -n "<unique name>" -g "<resource group name>" -l "<location>"
az appconfig kv set -n "<App Config name>" --key "Chapter8:DemoApp:Greeting" --value "Hello, World!" --yes
If we didn’t add --yes, we’d have been prompted for confirmation before the key is created.
az appconfig kv set -n "<App Config name>" --key "Chapter8:DemoApp:Greeting" --value "Hello from Development!" --label "Development" --yes
az appconfig kv set -n "<App Config name>" --key "Chapter8:DemoApp:Greeting" --value "Hello from Production!" --label "Production" --yes
Notice there’s a Manage deleted stores option. Soft delete is enabled by default on your App Configuration stores (just like in Key Vault), so if you delete one, you can recover it again if it’s still within the retention period. Alternatively, you can purge the deleted stores unless purge protection is enabled. We didn’t customize the retention period or enable purge protection, but you can.
Figure 8.6 – New key-value pairs listed in App Configuration with both labels
If you haven’t already noticed, you can click on the Values button with the eye icon at the top of the screen to display the values.
Feel free to also check out the Compare blade. There you’ll be able to compare the state of the current store with itself or another store at a specific date and time and filtered by label. Also notice there are many familiar blades, including the Identity blade we explored earlier in this chapter.
Now that we’ve seen how to create, edit, and restore a configuration setting, let’s reference it within code.
Follow these steps to make use of App Configuration settings within an application’s code:
dotnet new webapp
dotnet add package Microsoft.Azure.AppConfiguration.AspNetCore
dotnet user-secrets init
dotnet user-secrets set ConnectionStrings:AppConfig "<connection string>"
dotnet user-secrets list
var connectionString = builder.Configuration.GetConnectionString("AppConfig");
builder.Host.ConfigureAppConfiguration(builder =>
{
builder.AddAzureAppConfiguration(connectionString);
});
Because we didn’t add any options to AddAzureAppConfiguration(), we’ll be getting the value that didn’t have a label assigned, because, by default, it will load configuration values with no labels/label values of null.
@page
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<h1>Message: @Configuration["Chapter8:DemoApp:Greeting"]</h1>
We’re using the .NET Core Configuration API to access the store, and we’re using Razor syntax to display the message in the final line.
dotnet build
dotnet run
You should be presented with the message we created in App Configuration.
Figure 8.7 – Website showing message from App Configuration
Great! That proves everything is working as expected. We could now edit that App Configuration value and run the app again, but we can be confident of the outcome. Let’s instead make use of labels now. We’re going to use HostingEnvironment.EnvironmentName to determine within which environment the app is running, which will correlate to our labels:
dotnet add package Microsoft.Extensions.Configuration.AzureAppConfiguration
using Microsoft.Extensions.Configuration.AzureAppConfiguration;
builder.Configuration.AddUserSecrets<Program>();
As you may already know, user secrets are only intended for development, so AddUserSecrets<>() is included in the default options for the Development environment but not others. We’re changing that behavior for the purposes of this exercise.
builder.Host.ConfigureAppConfiguration((hostingContext, builder) =>
{
builder.AddAzureAppConfiguration(options =>
{
options.Connect(connectionString)
.Select(KeyFilter.Any, hostingContext.HostingEnvironment.EnvironmentName);
});
});
dotnet run --environment Production
We’ve just seen how we can change the behavior of our app, without needing to make changes to our code, by using labels. We could have also made changes to the values in App Configuration. Consider the value this service can bring when you have multiple services using the same keys – change the value in one central location and all relevant services will be able to pick up the changes.
Another common requirement in modern development is to decouple feature releases from code deployment, so you can push code continuously into production without the risk of impacting production because the code is hidden behind a feature flag that is yet to be enabled.
The concept of a feature flag is very straightforward – it’s a Boolean variable which has some code that executes based on the value of the flag. For example, you may have a new UI in development and although you’re still deploying the code, the code that changes the UI sits behind a feature flag that defaults to false. You may have the option for users to opt into the new experience, so the flag gets changed to true and the new UI code runs.
You can also use a filter to evaluate the state of a feature flag. For example, user group memberships, device types, locations, and specific time windows can be evaluated and can ultimately determine the state of a feature flag. Feature flags can also act as a safety net – if something undesired happens as a result of enabling a feature, just toggle the feature back off.
App Configuration provides a centralized repository for your feature flags, so you can externalize feature flags and change their state quickly from Azure without having to modify or redeploy the application. Let’s check out feature flags and how to use them in code. We’ll also add the ability to automatically update when changes are made without needing to restart the app:
Your list should now look like this:
Figure 8.8 – Two feature flags with the same name and different labels
dotnet add package Microsoft.FeatureManagement.AspNetCore
using Microsoft.FeatureManagement;
builder.AddAzureAppConfiguration(options =>
{
options.Connect(connectionString)
.UseFeatureFlags(option =>
{
option.Select("demofeature", hostingContext.HostingEnvironment.EnvironmentName);
})
.ConfigureRefresh(refreshOptions =>
{
refreshOptions.Register("demofeature");
})
.Select(KeyFilter.Any, hostingContext.HostingEnvironment.EnvironmentName);
});
Here, we’re allowing the use of feature flags and returning only those features with the name of demofeature and with labels that match our hosting environment, just like we did with the keys in the previous exercise. We’re also using ConfigureRefresh to monitor demofeature for changes. Once changes occur, the app will update after the refresh interval, which is 30 seconds by default.
builder.Services.AddAzureAppConfiguration()
.AddFeatureManagement();
app.UseAzureAppConfiguration();
@addTagHelper *, Microsoft.FeatureManagement.AspNetCore
<feature name="demofeature">
<h1>Demo feature enabled!</h1>
</feature>
dotnet build
dotnet run
Feel free to experiment further and add the --environment Production switch to the dotnet run command to see the difference if you wish.
Conceptually, we now have a way to deploy new code into production that will only be executed under certain circumstances, including the feature flag being enabled, without having to restart the app or make any additional code changes. Once a feature is rolled out to production, enabled, and fully tested, you probably want to clean up your code and remove the feature flag logic, so it becomes a normal part of the code.
That was a very basic example, but it has hopefully given you enough insight to grasp the concept and be able to answer any exam questions related to feature flags. Feel free to delete any resources created during this chapter if you want.
In this chapter, we explored some of the key services and features available for implementing secure solutions on Azure. We started off with a look into centralized secret management with Azure Key Vault, including how authorization and authentication work with it.
This led us onto the topic of managed identities, where we discussed user-assigned and system-assigned managed identities and used Azure.Identity to authenticate either with local credentials or – when the app is running in Azure – the managed identity via DefaultAzureCredential.
We finished off this chapter by exploring Azure App Configuration for centralized configuration management, discussing various ways to organize your settings with namespaces and labels, and how to reference them in code.
Finally, we ended the topic by discussing the feature management capabilities of App Configuration and some of the useful features, including making use of the feature in code and automatically refreshing without needing to restart the app.
In the next chapter, we’re going to introduce the caching solutions available within Azure. We’ll go into common caching patterns, Azure Cache for Redis, and how to use content delivery networks for web applications.
3.17.110.58