How to get connection string out of Azure KeyVault

2019-03-12 12:30发布

问题:

A hypothetical web-site currently connects using:

public SqlConnection CreateConnection()
{
   DbConnection connection = new SqlConnection();
   connection.ConnectionString = GetConnectionString();
   connection.Open();

   return connection;
}

Where the magical connection string is stored in web.config:

String GetConnectionString()
{
   //Get the connection string info from web.config
   ConnectionStringSettings cs = ConfigurationManager.ConnectionStrings["db"];

   if (cs == null)
      throw new Exception("Could not locate DB connection string");

   return cs.ConnectionString;
}

Now i'd like to move the connection string out of the web.config file into Azure KeyVault. How do you retrieve anything out of the Azure key vault?

String GetConnectionString()
{
   //Get the connection string info from Azure KeyVault
   String connectionString = GetAzureSecret("dbConnectionString");

   if (String.IsNullOrWhitespace(connectionString)
      throw new Exception.Create("Could not connection string of Azure Key Vault");

   return connectionString;
}

Except i just made up the easy-to-use Azure API. What is the actual api?

Untested attempt

string GetAzureSecret(string key)
{
    KeyVaultClient vault = new KeyVaultClient();
    vault.OnAuthenticate += VaultClientAuthenticate;

    var sec = await vault.GetSecretAsync(Key);
    return sec.Value;
}

public static async Task<string> VaultClientAuthenticate(string authority, string resource, string scope)
{
   String clientID = "8675209";
   String clientSecret = "correct battery horse pencil";

   var authContext = new AuthenticationContext(authority);
   ClientCredential clientCred = new ClientCredential(clientID, clientSecret);
   AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCred);

   if (result == null)
      throw new Exception("Could not acquire token");

   return result.AccessToken;
}

Bonus Reading

  • MSDN Forums: Storing sql connection string passwords in Key Vault for my Cloud Services
  • How to properly store connection strings in Azure?
  • Easily switching ConnectionStrings on publish to Azure

回答1:

What is the actual api?

We could use the GetSecret API to get value.

Preparation:

Registry Azure Active Directory application and assign Role

Steps:

1.Create KeyVault and add secret from Azure portal

2.Config Access policy

3.Get Access token

 var context = new AuthenticationContext("https://login.windows.net/" + tenantId);
            ClientCredential clientCredential = new ClientCredential(appId, secretKey);
            var tokenResponse =await context.AcquireTokenAsync("https://vault.azure.net", clientCredential);
            var accessToken = tokenResponse.AccessToken;
            return accessToken;

Note: The resource for Keyvault is https://vault.azure.net

4.Test with Fiddler

We also can do that easily with SDK:

1.Create a console project and a Utils.cs file

public static string EncryptSecret { get; set; }
        static string appId = "Application ID";
        static string secretKey = "Secert key";
        static string tenantId = "TenantId";

        public static async Task<string> GetAccessToken(string azureTenantId,string azureAppId,string azureSecretKey)
        {

            var context = new AuthenticationContext("https://login.windows.net/" + tenantId);
            ClientCredential clientCredential = new ClientCredential(appId, secretKey);
            var tokenResponse =await context.AcquireTokenAsync("https://vault.azure.net", clientCredential);
            var accessToken = tokenResponse.AccessToken;
            return accessToken;
        }

2.Add the follow code in the main function and test it.

packages.config file

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Hyak.Common" version="1.0.2" targetFramework="net452" />
  <package id="Microsoft.Azure.Common" version="2.0.4" targetFramework="net452" />
  <package id="Microsoft.Azure.Common.Dependencies" version="1.0.0" targetFramework="net452" />
  <package id="Microsoft.Azure.KeyVault" version="1.0.0" targetFramework="net452" />
  <package id="Microsoft.Bcl" version="1.1.9" targetFramework="net452" />
  <package id="Microsoft.Bcl.Async" version="1.0.168" targetFramework="net452" />
  <package id="Microsoft.Bcl.Build" version="1.0.14" targetFramework="net452" />
  <package id="Microsoft.IdentityModel.Clients.ActiveDirectory" version="3.13.9" targetFramework="net452" />
  <package id="Microsoft.Net.Http" version="2.2.22" targetFramework="net452" />
  <package id="Newtonsoft.Json" version="6.0.4" targetFramework="net452" />
</packages>

We also can get more information from CtrlDot mentioned document.



回答2:

I have nothing against Key Vault (i think it's a great product!), however i can't help myself but think you are overengineering this.

I would simply use the built-in Application Settings functionality in Azure App Service:

Connection strings

For .NET apps, these connection strings are injected into your .NET configuration connectionStrings settings at runtime, overriding existing entries where the key equals the linked database name.

Web App → Application Settings → Connection Strings → Add a Connection String and name it db.

String GetConnectionString()
{
   // Get the Connection String from Application Settings (App Service) 
   // with graceful fallback to web.config
   string cs = WebConfigurationManager.ConnectionStrings["db"].ConnectionString;

   if (cs == null)
      throw new Exception("Could not locate DB connection string");

   return cs;
}

What's the difference between the WebConfigurationManager and the ConfigurationManager?

May 2018 update:

Since Managed Service Identity became a thing, acquiring an access token no longer demands secrets (service principal credentials) being stored in your service to access Key Vault, which is a much better proposition. Here's a Node.js sample just to spice up this answer a little bit:

// Get an access token from Managed Service Identity
// on an Azure IaaS VM
async function getAccessTokenWithMSI() {
  let msi = await axios.get('http://169.254.169.254/metadata/identity/oauth2/token',
    {
      params: {
        'api-version': '2018-02-01',
        'resource': 'https://vault.azure.net'
      },
      headers: {
        'Metadata': 'true'
      },
      timeout: 2000
    });

  return msi.data.access_token;
}

and then:

// Get a secret from Key Vault
async function getSecret(accessToken, secretUrl) {
  let response;
  try {
    response = await axios.get(secretUrl,
      {
         params: { 'api-version': '2016-10-01' },
         headers: { 'Authorization': `Bearer ${accessToken}` },
         timeout: 3000
      });
  }
  catch (e) {
    console.log('\nError calling Key Vault:,
        e.response.status, e.response.statusText, e.response.data);
  }
  console.log('\nGet Secret response from Key Vault: ',
      JSON.stringify(response.data, null, 4));

  return response.data;
}