Robin
Robin

Reputation: 737

Is it possible to protect Azure connection strings that are referenced with CloudConfigurationManager?

I've read the MSDN blog posts on protecting sensitive data in web.config by encrypting the contents and setting up a certificate on Azure so they can be read back.

However, there is top-secret data in my 'service configuration' .cscfg files in the Visual Studio Azure Deployment project. We store connection strings and other sensitive data here so that the test system, also on Azure, can be directed to equivalent test back-end services.

This data is accessed with CloudConfigurationManager (e.g. .GetSetting("AwsSecretKey")) rather than WebConfigurationManager as discussed in the blog post.

Is it possible to protect this data in a similar way? It's important that we have different AWS and SQL connection strings in test and production, and that the production keys are hidden from me and the rest of the dev staff.

Upvotes: 0

Views: 373

Answers (1)

viperguynaz
viperguynaz

Reputation: 12174

YES, we do this with a x509 cert uploaded in the deployment configuration. However, the settings are only as secure as your policy/procedures for protecting the private key! Here is the code we use in an Azure Role to decrypt a value in the ServiceConfiguration:

/// <summary>Wrapper that will wrap all of our config based settings.</summary>
public static class GetSettings
{
    private static object _locker = new object();

    /// <summary>locked dictionary that caches our settings as we look them up.  Read access is ok but write access should be limited to only within a lock</summary>
    private static Dictionary<string, string> _settingValues = new Dictionary<string, string>();

    /// <summary>look up a given setting, first from the locally cached values, then from the environment settings, then from app settings.  This handles caching those values in a static dictionary.</summary>
    /// <param name="settingsKey"></param>
    /// <returns></returns>
    public static string Lookup(string settingsKey, bool decrypt = false)
    {
        // have we loaded the setting value?
        if (!_settingValues.ContainsKey(settingsKey))
        {
            // lock our locker, no one else can get a lock on this now
            lock (_locker)
            {
                // now that we're alone, check again to see if someone else loaded the setting after we initially checked it
                //  if no one has loaded it yet, still, we know we're the only one thats goin to load it because we have a lock
                //  and they will check again before they load the value
                if (!_settingValues.ContainsKey(settingsKey))
                {
                    var lookedUpValue = "";
                    // lookedUpValue = RoleEnvironment.IsAvailable ? RoleEnvironment.GetConfigurationSettingValue(settingsKey) : ConfigurationManager.AppSettings[settingsKey];
                    // CloudConfigurationManager.GetSetting added in 1.7 - if in Role, get from ServiceConfig else get from web config.
                    lookedUpValue = CloudConfigurationManager.GetSetting(settingsKey);
                    if (decrypt)
                        lookedUpValue = Decrypt(lookedUpValue);
                    _settingValues[settingsKey] = lookedUpValue;
                }
            }

        }

        return _settingValues[settingsKey];
    }

    private static string Decrypt(string setting)
    {
        var thumb = Lookup("DTSettings.CertificateThumbprint");
        X509Store store = null;

        try
        {
            store = new X509Store(StoreName.My, StoreLocation.LocalMachine);

            store.Open(OpenFlags.ReadOnly);
            var cert = store.Certificates.Cast<X509Certificate2>().Single(xc => xc.Thumbprint == thumb);

            var rsaProvider = (RSACryptoServiceProvider)cert.PrivateKey;
            return Encoding.ASCII.GetString(rsaProvider.Decrypt(Convert.FromBase64String(setting), false));
        }
        finally
        {
            if (store != null)
                store.Close();
        }
    }
}

You then can leverage RoleEnvironment.IsAvailable to only decrypt values in the emulator or deployed environment, thereby running the web role in local IIS using an unencrypted App setting with key="MyConnectionString" for local debugging (without the emulator):

ContextConnectionString = GetSettings.Lookup("MyConnectionString", decrypt: RoleEnvironment.IsAvailable);

Then, to complete the example, we created a simple WinForsm App with the following code to encrypt/decrypt the value with the given cert. Our production team maintains access to the production cert and encrypts the necessary values using the WinForms App. They then provide the DEV team with the encrypted value. You can find a full working copy of the solution here. Here's the main code for the WinForms App:

    private void btnEncrypt_Click(object sender, EventArgs e)
    {
        var thumb = tbThumbprint.Text.Trim();
        var valueToEncrypt = Encoding.ASCII.GetBytes(tbValue.Text.Trim());

        var store = new X509Store(StoreName.My, rbLocalmachine.Checked ? StoreLocation.LocalMachine : StoreLocation.CurrentUser);
        store.Open(OpenFlags.ReadOnly);
        var cert = store.Certificates.Cast<X509Certificate2>().Single(xc => xc.Thumbprint == thumb);

        var rsaProvider = (RSACryptoServiceProvider)cert.PublicKey.Key;
        var cypher = rsaProvider.Encrypt(valueToEncrypt, false);
        tbEncryptedValue.Text = Convert.ToBase64String(cypher);
        store.Close();
        btnCopy.Enabled = true;
    }

    private void btnDecrypt_Click(object sender, EventArgs e)
    {
        var thumb = tbThumbprint.Text.Trim();
        var valueToDecrypt = tbEncryptedValue.Text.Trim();

        var store = new X509Store(StoreName.My, rbLocalmachine.Checked ? StoreLocation.LocalMachine : StoreLocation.CurrentUser);
        store.Open(OpenFlags.ReadOnly);
        var cert = store.Certificates.Cast<X509Certificate2>().Single(xc => xc.Thumbprint == thumb);

        var rsaProvider = (RSACryptoServiceProvider)cert.PrivateKey;
        tbDecryptedValue.Text = Encoding.ASCII.GetString(rsaProvider.Decrypt(Convert.FromBase64String(valueToDecrypt), false));
    }

    private void btnCopy_Click(object sender, EventArgs e)
    {
        Clipboard.SetText(tbEncryptedValue.Text);
    }

Upvotes: 4

Related Questions