As developers, we are all guilty of leaking sensitive information of our applications and systems more than we would perhaps like to admit. Don't get me wrong, I'm not talking about breaking the
"The Cloud" is all beautiful and powerful but with power comes responsibility. Among all people, we developers should be well aware and feel responsible & accountable for handling the sensitive information of our applications. We must ensure that it doesn't leak under any circumstances which can jeopardize our systems (and also our positions). And believe it or not cloud has made these things easier and we have all the tools we need to make it happen. It is not complicated anymore, you just need to be a bit thoughtful and set up the habit to do this when you are starting your project (or even better put these things if you end up in a project where this is lacking).
Ok, enough talking let's get to the meat of this post. In this blog post, I will create a Web Application (hosted in Microsoft Azure) which lists URIs of blobs from a container in Storage Account (Microsoft Azure). For the sake of simplicity, I'm creating the container and a dummy blob at the runtime. The Web app is a standard ASP.Net Core web app. I will present two implementations of this scenario.
The first implementation would be with the connection string of a Storage account stored in Application Settings of the web application directly. In the second part, I will move that connection string from the web application and keep it somewhere safe and will fetch information from the Storage account as earlier. Both the applications are deployed using ARM Templates and completely automated (You can create these resources manually if you so wish).
Part I
Here is the source code for this part. I will refer to some bits and pieces of this below:
Here is how the setup looks like:
If you look at the source code (here), in my WebApWithSecrets solution, I have two projects:
deployment project contains the ARM template for provisioning the resources. The ARM Template does the following
- provisions a Storage Account
- provisions the web application
- adds 'StorageAccountConnectionString' as a Connection String in the Web Application
You have instructions to deploy this template in GitHub repository at the above URL (if you want to try it out yourself). Once the resources are provisioned, this is how my resource group looks like when I see it in Azure Portal.
If I navigate to my Web Application and look at the Application Settings, this is what I see: a connection string with the name 'StorageAccountConnectionString'
Once the resources are provisioned and in place, we can push the code to our web application. You can push this in a variety of ways but for now, I will just right-click and publish (WebAppsWithSecrets project (don't do this in production). Once the code is pushed successfully, the web application will open up in my browser (otherwise navigate to the web application). This is how it looks for me (the red box is my custom code that lists the URIs):
At this point, the app is fully functional and there is nothing wrong here. If I navigate to
I have this details not only to me but this is now checked-in to my source control.
We can all argue that it is not that bad after all since this is hosted in GitHub (or Azure DevOps or whatever the version control system you are using). These are secure systems and only our team members have access to them - so why bother?
This all holds true until someone gets hold of your GitHub account or you haven't configured the permissions correctly and someone who shouldn't have the detail might get access. Put it simply, we are increasing the attack surface and we might have to deal with some unwanted situations later on. The good news is we don't necessary have to do it this way, there is a better alternative which I will demonstrate in next part.
Part II
Here is the source code for this part. I will refer to some bits and pieces of this below:
The alternative approach could be that we keep such sensitive information in some sort of secured place or vault which is encrypted and secure which we can rely on and our application simply asks for this information from that vault without ever knowing the details itself. We sort of want to delegate the responsibility of handling sensitive information of our application to some other party without us worrying about the implications. By doing this we not only simplify our lives but also gain much more benefits like it would be better to govern these secrets, we would be able to update the keys and connection strings and certificates without changing anything in our application. The good news is Microsoft Azure has exactly such
This is how the redesign of our application looks like after introducing the Azure KeyVault:
The Web Application has information about the KeyVault only and delegates the responsibility of retrieving ConnectionString to Storage Account to the KeyVault.
If you look at the source code (here). In WebApWithourSecrets solution, I have two projects, same as before.
I have updated my ARM template for this. The template now does the following:
- provisions a Storage Account
- provisions the KeyVault
- provisions the web application
- adds 'StorageAccountConnectionString' as a Secret in the KeyVault
- sets the name of the KeyVault in application settings
There is one catch
If you navigate to Program.cs file in the solution, here is code that hooks up the KeyVault into our application (BuildWebHost method):
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
//this is where KeyVault magic happens - we are setting up configurations from Azure KeyVault using Managed Service Identity
//without specifiying any details of the Azure KeyVault itself (except the Url of the vault)
.ConfigureAppConfiguration((context, config) =>
{
var builtConfig = config.Build();
var keyVaultUrl = $"https://{builtConfig["KeyVaultName"]}.vault.azure.net";
//this comes with .net core 2.1
config.AddAzureKeyVault(keyVaultUrl);
//if using 2.0, you should use this apporach
//AzureServiceTokenProvider - this is the magic piece that makes it seamless to work with MSI
//var azureServiceTokenProvider = new AzureServiceTokenProvider();
//var keyVaultClient = new KeyVaultClient(
//new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
//config.AddAzureKeyVault(keyVaultUrl, keyVaultClient, new DefaultKeyVaultSecretManager());
})
.UseStartup<Startup>()
.Build();
if you look closely, all information we have about the Azure KeyVault in our web application is the name (or URL if you will) of the KeyVault (KeyVaultName) stored in our appsettings.json (or Application Settings in the published web app). Everything else is managed by MSI for us.
In ARM template, we enable MSI for the web application with this property:
"identity": {
"type": "SystemAssigned"
}
This enables this setting for the web application
So, as we did it Part I, let's deploy the template to provision new set of resources for us (You will find instructions in
If i navigate to the application settings of my web application, this is what I see:
Notice, there is no connection string as we had earlier, instead there is an App Settings 'KeyVaultName' which contains the name of the vault. That's the only information this web application has. Let's deploy the code for the web application (like earlier, right-click and publish). After
This looks exactly same as previous web application since we haven't changed the logic. The only thing that was changed in this web app was to read connection string from KeyVault.
Now let's uncover some more details. If you navigate to the Azure KeyVault resource and then head over to Secrets, you will see this:
If you pay attention, you don't see anything, instead, it tells you that "You are unauthorized to view these contents." But the web application lists the URIs just fine! Let's go to Access Policies tab, you will see that there is one policy created for a web application:
This was created by the ARM template after web application was provisioned. This is the section in ARM Template which defines this policy for the web application
"accessPolicies": [
{
"tenantId": "[reference(concat(resourceId('Microsoft.Web/sites', variables('webAppName')), '/providers/Microsoft.ManagedIdentity/Identities/default'), '2015-08-31-PREVIEW').tenantId]",
"objectId": "[reference(concat(resourceId('Microsoft.Web/sites', variables('webAppName')), '/providers/Microsoft.ManagedIdentity/Identities/default'), '2015-08-31-PREVIEW').principalId]",
"permissions": {
"keys": [ "all" ],
"secrets": [ "all" ]
}
}
],
"tenantId": null,
"tenantId": "[reference(concat(resourceId('Microsoft.Web/sites', variables('webAppName')), '/providers/Microsoft.ManagedIdentity/Identities/default'), '2015-08-31-PREVIEW').tenantId]",
"sku": {
"name": "Standard",
"family": "A"
}
Since at the moment, the only allowed access is for the web application you are not able to see any secrets when you go to Secrets tab. You can give access to your account if you want to see these secrets. Remember to click Save button after adding the policy, otherwise settings will not be saved
now, when I navigate the Secrets again, i'm able to see the Secrets.
Remember: If you want to use secrets from the KeyVault while development, you will have to assign access policy for your user in Azure KeyVault. Otherwise, you will get access denied exception.
Conclusion:
So as we saw, it's fairly straightforward to keep sensitive information in Azure KeyVault and configure our Web Application to read these secrets from the Vault. Moreover, Managed Service Identity feature further facilitates us to keep the information about KeyVault out of our web application. This can be applied to any scenarios - this is not necessarily bound to the web application only. You can keep connection strings, keys, certificates and all sorts of information you don't want to keep in your consumer application.
So, how do you handle your secrets? What is your opinion on this topic? I would love to hear more from you, please leave your comments.
Cheers
comments powered by Disqus