Security is the name of the game with cloud applications these days. Gone are the days of willy-nilly handling of passwords and secrets, finding your production database password in web.config files and storing keys on servers in the earnest hope they are never discovered by an attacker. These days, great care must be given to not only controlling access to sensitive values but, ensuring proper isolation in the event of breach to limit the blast radius. For this reason, teams often turn to products like Azure Key Vault as a way to securely keep data off services and machines so would be attackers must go through yet another hoop to get the data they want.
As I work with teams I still find a lot of confusion around this aspect, in particular since teams can often find themselves in the Circle of Life as I call it – where they are constantly trying to hide their values but then must secure the way they are hiding values, and so on and so forth. I want to talk about the principles of isolation and impressing upon teams that, rather than try to prevent a hack, prevent the hacker from getting everything.
Footnote: Security is a huge topic, especially when it comes to Cloud products. I wont be going into everything, or even 25% of everything. When it comes to building applications the best advice I can give is, try to think like an attacker
For most software development teams, Key Vault is the most common way to achieve the pattern of keeping sensitive values off services and in a secure location. Key Vault fully encrypts everything and has three offerings, each with different use cases:
- Keys – The keys feature is used mostly for encryption and decryption. It operates on the principle “you cannot reveal what you do not know”. These are managed keys which means, you, as the developer and accessor, do not know the actual raw value. This is very useful for asymmetric encryption, such as what is used with JWT, where the secrecy of the private key is paramount
- Secrets – For most developers, this will be the feature you interact with the most. This lets you store known values and retrieve them via an HTTP call to the Key Vault. The values are versioned and are soft-deleted when removed. This gives you an ideal place to keep passwords and other sensitive values without revealing them in source code
- Certificates – This feature lets you store certificates that can be used later. I honestly have not used this feature too much
A word of advice with Secrets, do not store everything here. Not everything is sensitive, nor does it require an extra lookup from a separate machine. Pick and choose carefully what is secret and what is not.
As an extra boon, you can “link” a Key Vault to a Variable Group in Azure DevOps Library. This lets you use sensitive values in your pipeline. Its a great way to inject certain values into services. But, always remember, what you send to a service has to get stored somewhere
Role Based Access Control (RBAC)
If you have spent any large amount of time around Cloud in the last 4-5 years you have likely heard the term RBAC thrown around. Modern cloud architectures are built around the notion of roles and we create specific roles for specific use cases. This enable very tight control over what a user/application can do and, more importantly, what they cannot do. Using roles also lessens the possibility of an application breaking when a login is removed or a password change is forced.
In Microsoft Azure, RBAC is mainly used (from the context of application development) with Managed Service Identity (MSI). In Azure we can either create this identity manually or ask Azure to create it for us (you will often see an Identity option for most Azure resources, this indicates to Azure that is should created a “System Managed identity”. Alternatively, you can create “User Managed Identity”. Each of these would be referred to, by Azure, as a Service Principal. Its important though to understand the difference between these two, especially how they aid code running locally and code running in the cloud.
User Managed Identity vs System Assigned Identity
Without getting too deep, Identities in Azure are held in Azure Active Directory service, which every tenant receives. User Managed identities are created using App Registrations, by this the creator can pick information like the name and, most importantly, the secret or certificate that acts as the password.
For System Assigned Identities, these are found under Enterprise Applications and most of its data is, as you would expect, managed by Azure. This means the Secret is unknown to you. This is ideal for a production scenario where, the less you know the less you can expose. In contrast, the App Registration is ideal for development and cases where we want the user to specify their identity verbosely.
When to use?
Let’s return to something I said earlier – “we cannot prevent ourselves from being hacked, we can minimize the blast radius of a breach”, this often refereed to as “defense in depth (or layers)”. Simply put, dont give the attacker something that will work everywhere.
- If they get a login to a development environment, ensure that login does not work in Dev.
- If your staging database password gets posted on reedit, make sure its not the same one you use for production
- If the attacker compromises a role, ensure that role has access to only what it needs for the environment that it needs
- Segregate your environments so they dont talk to each other. If an attacker breaches the virtual machine holding the dev version of your website, they should not be able to access the production servers
There are many more, but hopefully you get the idea. Taking this approach you can understand that, exposing sensitive data in an environment like development is acceptable as long as the information does not grant would-be attackers access to critical data.
Let’s take this approach and develop a simple application that uses Key Vault to return a sensitive value.
Create the Identities for Key Vault Access
Assuming you know how to create a Key Vault in Azure you need to define Access Policies. Key Vault’s do not support the notion of “granular access” which means when you define a permission that permission applies to everything in that category.
If you enable GET access to Secrets in your Key Vault, the authorized party can retrieve ALL secrets from that Key Vault. Consider this when defining your infrastructure, it is not uncommon for teams segregate data for apps across multiple key vaults to control access
Before you can grant access you need to define the parties requesting access. Create an Azure App Service. Once created, find the Identity option, switch it On and hit Save. After a time Azure will come back with an ObjectId – this Id represents the new Identity created (and managed) by Azure – the display name will be the same as the name of the service (the Azure App Service in this case).
Next, access the Azure Active Directory for your tenant, select App Registrations. Create a new Registration with any name you like (this will be the username of the Service Principal/Identity). Once created, select Certificates & Secrets from the left hand menu. Create a Secret and copy the value somewhere (you wont be able to see it again once you leave) – also take note of the Application (client id).
Go back to the top level of Azure Active Directory and select Enterprise Registrations – you should be able to search for an find your Managed Identity by its name (remember the name of the Azure App Service). Select it and note its Application (client id).
Ok, we have our identities, now we just need to use them.
Code the app
I created a simple WebApi with the following command:
dotnet new webapi -o KeyVaultTest
Install the following Nuget packages:
- Azure.Security.KeyVault.Secrets (v4.0.3)
- Azure.Identity (v1.1.1)
Here is the code for my controller
Thanks to Azure.Identity there are a number of ways to get credentials for accessing Azure services (here). Here I am using two, but the usage depends on the context.
- If the environment is Local (my local machine) I use the ClientSecretCredential where I expose the values from the App Registration earlier. As I have said, I do not care if these values leak as they will only ever get a person into my Dev Key Vault to read the secrets there. I will NOT use this anywhere else and other environments will not support this type of access
- If the environment is Development, I am running on Azure and can use ManagedIdentityCredential where Azure will interrogate the service (or VM) for the assigned identity. This has the advantage of exposing NOTHING in the environment to a would-be attacker. They can only do what the machine can already do so, the damage would be so confined.
By taking this approach we enable tighter security in the cloud while still allowing developers to easily do their jobs locally.
I tend to favor the environment injection approach since it still enables the code to be built one time. Using something like Precompiler directives works but requires the code to be special built for each environment
Security is vitally important for applications. It is important to do what we can to remove sensitive values from code bases and configuration files. We can use tools like Key Vault to ensure these values are kept safe and are not exposed in the event of a breach. We can further remove values by using RBAC to give identities access to only what they need. This enables us to have fewer values in code and makes our application easier to manage overall