Хранилище ключей Azure с настройкой приложения "DefaultConnection" в Entity Framework

Я пытаюсь использовать хранилище ключей Azure для хранения строк подключения веб-API для Entity Framework. В идеале я бы хотел не связывать пакеты nuget ключей хранилища с моим кодом доступа к данным. Мой класс dbContext имеет два конструктора:

public MyDbContext() : base("DefaultConnection")
{ . . . }

public MyDbContext(string connectionString) : base(connectionString)
{ . . . }

Мой код использует конструктор без параметров, который получает строку подключения из веб-конфигурации. В некоторых местах я создаю новый объект MyDbContext, который запрещает решение, использующее инъекцию.

Путь, который я выбрал, - установить статическое свойство в моем dbcontext с помощью локатора строки соединения:

public interface IConnectionStringLocator
{ string Get(); }

public class DefaultConnectionStringLocator : IConnectionStringLocator
{
    public string Get()
    {
        return "DefaultConnection";
    }
}

public static IConnectionStringLocator ConnectionStringLocator { get; set; } =
    new DefaultConnectionStringLocator();

В моем проекте web api есть пакеты nuget для получения секретов хранилища ключей. Итак, в моем файле Global.asax у меня есть это:

protected void Application_Start()
{
    MyDbContext.ConnectionStringLocator = new ConnectionStringLocator("DefaultConnection");
}

public class ConnectionStringLocator : IConnectionStringLocator
{
    private  readonly string _connectionStringName;

    public ConnectionStringLocator(string connectionStringName)
    {
        this._connectionStringName = connectionStringName;
    }
    public string Get()
    {
        var keyVaultName = WebConfigurationManager.AppSettings.Get("KeyVaultName");
        if (keyVaultName == "develop")
            return _connectionStringName;
        else
        {
            AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider();
            var keyVaultClient =
                new KeyVaultClient(
                    new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
            var defaultConnectionSecret =
                keyVaultClient.GetSecretAsync($"https://{keyVaultName}.vault.azure.net/secrets/{this._connectionStringName}");

            return defaultConnectionSecret.Result.Value;
        }
    }
}

Я опубликовал это, и это работает, но это не "чувствует" правильно.

Другим вариантом будет следовать этой статье https://blog.falafel.com/keeping-secrets-with-azure-key-vault/ но для этого потребуется соединить пакеты API Key Vault с моим доступом к данным.

Я ищу обратную связь и направление. Я должен добавить, что причина, по которой я хочу использовать хранилище ключей, заключается в том, что он позволит мне иметь администраторов Azure, которые могут просматривать настройки приложения в Интернете, не имея доступа к базе данных sql через строку подключения.

Ресурс Key Vault с новой реализацией MSI: https://github.com/Azure-Samples/app-service-msi-keyvault-dotnet/

1 ответ

Решение

Вот как я решил это, если кто-то еще наткнется на это.

Создан класс ConfigurationManager, который сначала пытается получить значение из хранилища ключей, но при сбое он использует WebConfigurationManager для чтения параметров приложения.

    public static class ConfigurationManager
{
    public static string KeyVaultName => WebConfigurationManager.AppSettings.Get("KeyVaultName");
    private static readonly Dictionary<string, string> ConfigurationCache = new Dictionary<string, string>();
    private static SecretBundle GetSecret(string secretName, string vaultName = null)
    {
        AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider();
        var keyVaultClient =
            new KeyVaultClient(
                new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
        var secretUri = $"https://{vaultName ?? KeyVaultName}.vault.azure.net/secrets/{secretName}";
        var secret = keyVaultClient.GetSecretAsync(secretUri);
        return secret.Result;
    }

    public static string GetAppSettingValue(string secretName, string vaultName = null)
    {
        vaultName = vaultName ?? KeyVaultName;
        string key = $"{vaultName}:{secretName}";
        string value;

        if (ConfigurationCache.TryGetValue(key, out value))
            return value;

        if (string.IsNullOrEmpty(vaultName) || vaultName == "develop")
        {
            value = WebConfigurationManager.AppSettings.Get(secretName);
            ConfigurationCache.Add(key, value);
            return value;
        }

        var secret = GetSecret(secretName);
        value = secret.Value;
        ConfigurationCache.Add(key, value);
        return value;
    }

    public static void SetAppSettingValue(string secretName, string value, string vaultName = null)
    {
        vaultName = vaultName ?? KeyVaultName;
        string key = $"{vaultName}:{secretName}";

        if (ConfigurationCache.ContainsKey(key))
            ConfigurationCache[key] = value;
        else
        {
            WebConfigurationManager.AppSettings[key] = value;
            ConfigurationCache.Add(key, value);
        }


    }
    public static string GetConnectionStringValue(string secretName, string vaultName = null)
    {
        vaultName = vaultName ?? KeyVaultName;
        string key = $"{vaultName}:{secretName}";
        string value;

        if (ConfigurationCache.TryGetValue(key, out value))
            return value;

        if (string.IsNullOrEmpty(vaultName) || vaultName == "develop")
        {
            value = WebConfigurationManager.ConnectionStrings[secretName].ConnectionString;
            ConfigurationCache.Add(key, value);
            return value;
        }

        var secret = GetSecret(secretName);
        value = secret.Value;
        ConfigurationCache.Add(key, value);
        return value;
    }
}

Затем в моем классе dbcontext я вызываю Configurationmanager.GetConnectionStringValue("DefaultConnection").

    public MyDbContext()
        : base(ConfigurationManager.GetConnectionStringValue("DefaultConnection"))
    {...}

Для.NET 4.7.1 или более поздних версий вы можете использовать Configuration Builders для.NET для подключения к хранилищу ключей с небольшим количеством XML в файлах appsettings или web.config. Я использовал библиотеку вместе с преобразованиями XML и azure DevOps для подключения к различным хранилищам ключей в зависимости от среды. Я говорил об этом в своем блоге пост.

Другие вопросы по тегам