-
Notifications
You must be signed in to change notification settings - Fork 2
Azure Key Vaults
This page will provide as an explanation/tutorial with Azure Key Vault and the implementation within the Will Clinic Application. This documentation will be following pretty closely to Microsoft tutorial but will be more pointed to our specific implementation. In order to get started, make sure that you have an azure account.
As of writing this, there are two ways to that you can implement the following and both are very similar.
- Azure Cloud Shell
- Azure CLI for Powershell
The steps are the same regardless of which interface you choose to use. If you want to follow exactly with this tutorial, you will need to download the the latest Azure CLI for Powershell.
All users should download the Azure CLI as this will make logging into the specific vault easier for each machine.
First, you will need to login to your azure account with the following command:
az login
If you don't already have a specific resource group created, you can also use the azure cli to do that for you with the following command:
az group create --name "NameOfResourceGroup" --location "Westus2"
If you don't know which location, you can leave it blank and it will give you the list of possible locations. Then you can press the up-arrow
key and fill in the blank. For this, we have chosen to use West US 2 since that is a server grouping close to us.
After logging in and creating a resource group (if you choose to create one), now you can create a vault to store your secrets. When creating a vault, you need to supply a name for the vault, resource group and location. So it's very similar to creating the resource group as shown above:
az keyvault create --name "NameOfVault" --resource-group "NameOfResourceGroup" --location "Westus2"
After this command, you should start to see some output in your Powershell. You will see a vault name, in this example our vault is currently named NameOfVault
as well as our vault uri. This uri is https://NameOfVault.vault.azure.net/
and is important as this is required for applications through REST to use this.
Now lets create a secret
You can store things like connection strings to databases or other sensitive information for your application but you need to still be able to access it.
Note: As of writing this, special characters tend to have issues so you should avoid using them within your secrets. You can create some workarounds if you must and we will show an example with this application's break down at the end of this setup
Setting up secrets follows a very similar command to the previous ones. We need to supply the vault we wish to use, set a secret name and then set the value of that secret:
az keyvault secret set --vault-name "NameOfVault" --name "NameOfSecret" --value "SecretValue"
So now our current vault [NameOfVault] has a secret [NameOfSecret] and a value [SecretValue]. You can verify this with the following command:
az keyvault secret show --name "NameOfSecret" --vault-name "NameOfVault"
This will show you a bunch of information regarding the secret if it was created successfully.
This assumes already that you have an application and we will be showing a code example with our implementation so we are skipping ahead a bit from the Microsoft tutorial. To incorporate the use of Azure Key Vault, you will need to install these two nuget packages:
- Microsoft.Azure.Services.AppAuthentication
- Microsoft.Azure.KeyVault
These provide us the functionality needed to get the requested information from the supplied endpoint. After it is implemented and deployed, your application is going to give a 502 error and that is because the identity rights of the application have not been set.
This is a nifty feature within Azure and allows your application to communicate to other Azure services and is secure as it is built in. The easiest way to approach this is to navigate to your azure portal and find your application. Click on it to get an overview and then navigate to the section labeled settings and then look for the selection of Managed Service Identity. If it is not, turn it on and then save your changes.
Now you will want to find your Azure Key Vault within your portal and select Access Policies. You'll want to Add New in the Secret permissions and then select Get and List. After, you'll want to Select a Principle and find your web application that you wish to grant that access too. Select okay and make sure you save your changes. Now when you go to your web application, your 500 error should no longer exist and you should be up and running.
It is a little mis-leading but in order to add other accounts to your specific vault, you first need to invite team members via the Access manager or IAM through the vault settings. Then you can add those accounts as access policies so that multiple local machines can have access to the vault. An invite exists within the Access Policies creation but as of writing this, it doesn't seem to incorporate that same functionality.
Now that we've shown how to create a vault and store secrets, let's go over how this is actually implemented within the WA Will Clinic Application. Again the implementation follows the previously linked documentation pretty closely with some adjustments to also utilize the power of user secrets and dealing with a secret that requires special characters.
For this application our variables are:
- Vault Name - WAWillClinicVault
- Resource group - WaWillClinicResourceGroup
- Secrets -
- SendGridAPIKey
- ProductionConnection
- DefaultConnection
We also take advantage of user secrets to ensure that any sensitive data that can't live within the vault is secured on our application and within the deployed app settings. We are not calling our user secrets within the Startup.cs file however, we build it as a configuration within the Program.cs. So your configuration constructor will be bare:
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
Since we do store our SQL connection strings within the vault we can put what would normally be a User Secret tag in the same spot, making our connection looking exactly like a normal connection:
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["ProductionConnection"]));
This changes a bit when it comes to our development db connection string as this has special characters. As noted in the tutorial, Azure Key Vaults currently doesn't support or at least, allow, special characters to directly be in a secret's value. It will be escaped within the vault which makes things like a development connection string difficult to store as it is build like so:
"Server=(localdb)\\MSSQLLocalDB;Database=WillClincTest;Trusted_Connection=True;MultipleActiveResultSets=true"
So in our application, we need to remove the special backslashes \
as if we store this within the vault, it will quickly become:
Server=(localdb)\\\\
Which effectively renders our string useless. We can use string interpolation to combat this:
options.UseSqlServer($"Server=(localdb)\\{Configuration["ProductionConnection"]}"));
Otherwise you could utilize and appsettings.json to store your localdb connection string. This is useful if for some reason you would like your local databases to follow a specific naming convention for a team and removes the possibility of spelling mistakes.
This is where a lot of the differences will show up. First will be the packages at the top of the program which make the following functionality possible:
using Microsoft.Azure.KeyVault;
using Microsoft.Azure.Services.AppAuthentication;
Below in our program file we have created a method that will be our webhost builder but also build all of our configurations within the application. We will break down each snippet bit by bit within our method.
In our string of method calls, we come across a .ConfigureAppConfigurations((ctx, builder) =>
. This is the start of our application configurations. We are giving it a context (ctx) and a builder as our input variables. We currently do not use our ctx but it is set up if we were to expand upon our configurations.
First, we need to bring in our secrets. Depending on how you choose to approach this, you could use either User Secrets or an appsettings.json file with some tweaking but the principle is still the same: Bring in the file that stores the secrets and then build that into our configurations so we can access these key/value pairs:
builder.AddUserSecrets<Startup>().Build();
var config = builder.Build();
This has successfully brought in the secrets.json file and added it's key/value pairs to our configuration. In this case, we are storing our Azure Key Vault URI so we can make a request to Azure through a RESTful means. We now are going to check if the key (for the application it will be config["AzureKeyVaultEndPoint"]) actually has value. This serves a few purposes but the immediate reason is while debugging, you can step through the application and see if the secrets.json is actually getting built into the application:
if (!string.IsNullOrEmpty(config["AzureKeyVaultEndPoint"]))
{
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(
azureServiceTokenProvider.KeyVaultTokenCallback));
builder.AddAzureKeyVault(
config["AzureKeyVaultEndPoint"], keyVaultClient, new DefaultKeyVaultSecretManager());
}
In the next line of code we are now going to grab our Azure Service Token Provider. This is opening up the gate for us to connect to all of the Azure Services. Below that, we are beginning our process to create our connection directly to our Azure Key Vault. This will use our azureServiceTokenProvider as a way to validate our account is actually tied to the azure system.
Now after all of our authentication checks, we are going to add the Azure Key Vault to our configurations by passing in our config["AzureKeyVaultEndPoint"], keyVaultClient, and then instantiating a new DefaultKeyVaultSecretManager. When you connect all of this together your setup looks like this:
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((ctx, builder) =>
{
builder.AddUserSecrets<Startup>().Build();
var config = builder.Build();
if (!string.IsNullOrEmpty(config["AzureKeyVaultEndPoint"]))
{
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(
azureServiceTokenProvider.KeyVaultTokenCallback));
builder.AddAzureKeyVault(
config["AzureKeyVaultEndPoint"], keyVaultClient, new DefaultKeyVaultSecretManager());
}
})
.UseStartup<Startup>();