In my previous post (secret management part 1: using azurekey vault and azure managed identity) I showed an example of storing secrets (keys, passwords or certificates) in an Azure Key Vault and how to retrieve them securely. Now, this approach has one downside and that is this indirection via the Key Vault.
In the previous implementation, service X creates an key to access it and we store it in the Key Vault. After that, service Y that needs the key, authenticates to the Azure Active Directory to access the Key Vault, retrieve the secret and use it to access service X. Why can’t we just access service X, after authenticating to the Azure Active Directory, as shown below?
In this approach we completely removed the need for Azure Key Vault, reducing the amount of hassle. Another benefit is that we are no longer creating extra secrets, which means we can also not loose them. Just another security benefit. Now let’s build an example and see how this works.
Infrastructure
Again we start by creating an ARM Template to deploy our infrastructure. This time we are using a feature of the Azure SQL DB Server to have an AAD identity be appointed as an administrator on that server, in the following snippet.
{ "type": "administrators", "name": "activeDirectory", "apiVersion": "2014-04-01-preview", "location": "[resourceGroup().location]", "properties": { "login": "MandatorySettingValueHasNoFunctionalImpact", "administratorType": "ActiveDirectory", "sid": "[reference(concat(resourceId('Microsoft.Web/sites', parameters('appServiceName')),'/providers/Microsoft.ManagedIdentity/Identities/default'), '2015-08-31-preview').principalId]", "tenantId": "[subscription().tenantid]" }, "dependsOn": [ "[concat('Microsoft.Sql/servers/', parameters('sqlServerName'))]" ] }
We are using the same approach as earlier, but now to set the objectId for the AAD admin of the Azure SQL DB Server. One thing that is also important is that the property for ‘login’ is just a placeholder of the principals name. Since we do not know it, we can set it to anything we want. If we would ever change the user through the portal (which we shouldn’t), this property will reflect the actual username.
Again, the full template can be found on GitHub.
Code
With the infrastructure in place, let’s write some passwordless code to have our App Service access the created Azure SQL DB:
if (isManagedIdentity) { var azureServiceTokenProvider = new AzureServiceTokenProvider(); var accessToken = await azureServiceTokenProvider.GetAccessTokenAsync("https://database.windows.net/"); var builder = new SqlConnectionStringBuilder { DataSource = ConfigurationManager.AppSettings["databaseServerName"] + ".database.windows.net", InitialCatalog = ConfigurationManager.AppSettings["databaseName"], ConnectTimeout = 30 }; if (accessToken == null) { ViewBag.Secret = "Failed to acuire the token to the database."; } else { using (var connection = new SqlConnection(builder.ConnectionString)) { connection.AccessToken = accessToken; connection.Open(); ViewBag.Secret = "Connected to the database!"; } } }
First we request a token and specify a specific resource “https://database.windows.net/” as the type of resource we want to use the token for. Next we start building a connection string, just as we would do normally. However, we leave out anything related to authentication. Next (and this is only available in .NET Framework 4.6.1 or higher), just before opening the SQL Connection we set the acquired token on the connection object. From there on, we can again work normally as ever before.
Again, it’s that simple! The code is, yet again available on GitHub.
Supported services
Unfortunately, you can not use this approach for every service you will want to call and are dependent on the service supporting this approach. A full list of services that support token based application authentication are listed on MSDN. Also, you can support this way of authentication on your own services. Especially when you are moving to a microservices architecture, this can save you a lot of work and management of secrets.