In my original article here where I stored the database connection string in an Environment Variable which is no longer an optimal approach (it was only for example). I will now update the Azure Function to access an Azure Key Vault secret which has the database connection string and use it to make the database connection. See these articles to get more information about the steps I took to get this far.
- Create an Azure Key Vault secret
- Using Managed Service Identity MSI with and Azure App Service or an Azure Function
- How to connect to a database from an Azure Function
- Azure Function 400 Bad Request
There are 3 steps which need to happen to make this happen:
- Get a authentication token
- Get the secret
- Use the value of the secret as the database connection string
Recognize that this code is not running on my workstation so it does not have access to my Identity, nor will I provide my identity to the Azure Function App to get the token. Instead, in previous steps, I have created an MSI for the Azure Function App and granted that principle read access to the Azure Key Vault. Therefore, the token verifies the identity of the Azure Function App and not of any individual.
Here is the code snippet I used to get the token on behalf of the Azure Function App.
public static async Task<string> GetToken(string resource, string apiversion)
{
string msiEndpoint = Environment.GetEnvironmentVariable("MSI_ENDPOINT");
string endpoint = $"{msiEndpoint}/?resource={resource}&api-version={apiversion}";
string msiSecret = Environment.GetEnvironmentVariable("MSI_SECRET");
tokenClient.DefaultRequestHeaders.Add("Secret", msiSecret);
JObject tokenServiceResponse = JsonConvert
.DeserializeObject<JObject>(await tokenClient.GetStringAsync(endpoint));
return tokenServiceResponse["access_token"].ToString();
}
The first challenge I had was figuring out the MSI_ENDPOINT and MSI_SECRET. After some searching I found them in the Environment variables list via KUDU/SCM, which I discuss more here. See Figure 1 for an example.
Figure 1, where are the values for MSI_ENDPOINT and MSI_SECRET
MSI_ENDPOINT and MSI_SECRET get created when you turn on MSI for the Azure Resource, as I discussed here.
The second code snippet uses the token and gets the value of the Azure Key Vault secret.
public static async Task<string> GetSecret(string secretName, string token, string apiversion)
{
string kvURL = "https://??**??.vault.azure.net/";
string endpoint = $"{kvURL}secrets/{secretName}/?api-version={apiversion}";
keyVaultClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
JObject keyVaulteResponse = JsonConvert
.DeserializeObject<JObject>(await keyVaultClient.GetStringAsync(endpoint));
return keyVaulteResponse["value"].ToString();
}
The secretName was again a little challenging to find and get correct, but I found it by clicking on the Azure Key Vault secret details and looked at the content of the Secret Identifier, as seen in Figure 2.
igure 2, what is the Azure Key Vault endpoint URL, example Azure Key Vault endpoint URL
And the final code snippet that calls both the previous methods and uses the secret as the connection string is shown here.
var token = await GetToken("https://vault.azure.net", "2017-09-01");
var secret = await GetSecret("<secretName>", token, "2016-10-01");
connection.ConnectionString = secret;
await connection.OpenAsync();
And that is it, this is a good very secure way to protect passwords and secrets leaking into your source code for all having access to see.