具有 Entity Framework "DefaultConnection" 应用程序设置的 Azure Key Vault
Azure Key Vault with Entity Framework "DefaultConnection" app setting
我正在尝试使用 Azure Key Vault 为 Entity Framework 存储我的网络 api 连接字符串。理想情况下,我想避免将密钥保管库 nuget 包与我的数据访问代码耦合。我的 dbContext class 有两个构造函数:
public MyDbContext() : base("DefaultConnection")
{ . . . }
public MyDbContext(string connectionString) : base(connectionString)
{ . . . }
我的代码使用无参数构造函数,它从 Web 配置中获取连接字符串。有些地方我实例化了一个新的MyDbContext对象,禁止使用注入的解决方案。
我采用的方法是在我的 dbcontext 上使用连接字符串定位器设置静态 属性:
public interface IConnectionStringLocator
{ string Get(); }
public class DefaultConnectionStringLocator : IConnectionStringLocator
{
public string Get()
{
return "DefaultConnection";
}
}
public static IConnectionStringLocator ConnectionStringLocator { get; set; } =
new DefaultConnectionStringLocator();
我的网络 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;
}
}
}
我发布了这个并且它有效,但是 "feel" 不对。
另一种选择是遵循这篇文章 https://blog.falafel.com/keeping-secrets-with-azure-key-vault/,但这需要我将 KeyVault API 包与我的数据访问结合起来。
我正在寻找反馈和方向。我应该补充一点,我想使用密钥保管库的原因是因为它允许我拥有 azure 管理员,他们可以在线查看应用程序设置,而无需通过连接字符串访问 sql 数据库。
具有新 MSI 实现的 KeyVault 资源:https://github.com/Azure-Samples/app-service-msi-keyvault-dotnet/
这是我解决这个问题的方法,以防其他人无意中发现它。
创建了一个 ConfigurationManager class,它首先尝试从 Key Vault 获取值,但失败时它使用 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 class 中调用 Configurationmanager.GetConnectionStringValue("DefaultConnection").
public MyDbContext()
: base(ConfigurationManager.GetConnectionStringValue("DefaultConnection"))
{...}
对于 .NET 4.7.1 或更高版本,您可以使用 Configuration Builders for .NET to connect to key vault with just a small amount of XML in appsettings or web.config files. I have used the library along with XML transformations and azure devops to connect to different key vaults based on the environment. I talked about it in my blog post。
我正在尝试使用 Azure Key Vault 为 Entity Framework 存储我的网络 api 连接字符串。理想情况下,我想避免将密钥保管库 nuget 包与我的数据访问代码耦合。我的 dbContext class 有两个构造函数:
public MyDbContext() : base("DefaultConnection")
{ . . . }
public MyDbContext(string connectionString) : base(connectionString)
{ . . . }
我的代码使用无参数构造函数,它从 Web 配置中获取连接字符串。有些地方我实例化了一个新的MyDbContext对象,禁止使用注入的解决方案。
我采用的方法是在我的 dbcontext 上使用连接字符串定位器设置静态 属性:
public interface IConnectionStringLocator
{ string Get(); }
public class DefaultConnectionStringLocator : IConnectionStringLocator
{
public string Get()
{
return "DefaultConnection";
}
}
public static IConnectionStringLocator ConnectionStringLocator { get; set; } =
new DefaultConnectionStringLocator();
我的网络 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;
}
}
}
我发布了这个并且它有效,但是 "feel" 不对。
另一种选择是遵循这篇文章 https://blog.falafel.com/keeping-secrets-with-azure-key-vault/,但这需要我将 KeyVault API 包与我的数据访问结合起来。
我正在寻找反馈和方向。我应该补充一点,我想使用密钥保管库的原因是因为它允许我拥有 azure 管理员,他们可以在线查看应用程序设置,而无需通过连接字符串访问 sql 数据库。
具有新 MSI 实现的 KeyVault 资源:https://github.com/Azure-Samples/app-service-msi-keyvault-dotnet/
这是我解决这个问题的方法,以防其他人无意中发现它。
创建了一个 ConfigurationManager class,它首先尝试从 Key Vault 获取值,但失败时它使用 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 class 中调用 Configurationmanager.GetConnectionStringValue("DefaultConnection").
public MyDbContext()
: base(ConfigurationManager.GetConnectionStringValue("DefaultConnection"))
{...}
对于 .NET 4.7.1 或更高版本,您可以使用 Configuration Builders for .NET to connect to key vault with just a small amount of XML in appsettings or web.config files. I have used the library along with XML transformations and azure devops to connect to different key vaults based on the environment. I talked about it in my blog post。