Secure Configuration in .NET Applications
This guide covers best practices for managing secrets and configuration in .NET applications using environment variables and various secret management solutions.
1. Introduction to Configuration Security
Sensitive information such as connection strings, API keys, and credentials should never be stored in:
- Source code repositories
- Configuration files committed to source control
- Plaintext on servers
2. Using Environment Variables
Environment variables provide a simple way to manage configuration without hardcoding sensitive data.
Setting environment variables in Ubuntu
# Setting temporarily
export ConnectionString="Server=myserver;Database=mydb;User Id=myuser;Password=mypassword;"
# Setting permanently in .profile or .bashrc
echo 'export ApiKey="your-secret-api-key"' >> ~/.bashrc
source ~/.bashrcReading environment variables in .NET
// Program.cs
var connectionString = Environment.GetEnvironmentVariable("ConnectionString");
var apiKey = Environment.GetEnvironmentVariable("ApiKey");In ASP.NET Core
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add environment variables as a configuration source
builder.Configuration.AddEnvironmentVariables();3. Azure Key Vault Strategy
Azure Key Vault provides a secure service for storing keys, secrets, and certificates.
Setup
- Install required NuGet packages:
dotnet add package Azure.Identity
dotnet add package Azure.Security.KeyVault.Secrets
dotnet add package Microsoft.Extensions.Configuration.AzureKeyVault
# Add reference project
dotnet add reference ../MyProject
# remove reference project
dotnet remove reference ../MyProject
APP_USER=myusername APP_PASSWORD=mypassword docker-compose up- Create an Azure Key Vault and add secrets
Implementation
// Program.cs in ASP.NET Core application
var builder = WebApplication.CreateBuilder(args);
// Add Azure Key Vault configuration
string keyVaultUrl = builder.Configuration["KeyVaultSettings:VaultUri"];
string tenantId = builder.Configuration["KeyVaultSettings:TenantId"];
string clientId = builder.Configuration["KeyVaultSettings:ClientId"];
string clientSecret = builder.Configuration["KeyVaultSettings:ClientSecret"];
builder.Configuration.AddAzureKeyVault(
new Uri(keyVaultUrl),
new ClientSecretCredential(tenantId, clientId, clientSecret));
// Now you can access secrets as if they were configuration settings
// var mySecret = builder.Configuration["MySecretName"];
var app = builder.Build();
// ...rest of application setupUsage Example
// WeatherForecastController.cs
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly IConfiguration _configuration;
public WeatherForecastController(IConfiguration configuration)
{
_configuration = configuration;
}
[HttpGet]
public IActionResult Get()
{
// Access the secret from Key Vault
var apiKey = _configuration["ExternalApiKey"];
// Use the apiKey to make external API calls
// ...
return Ok(new { message = "Data retrieved successfully" });
}
}4. HashiCorp Vault Strategy
HashiCorp Vault is a powerful secrets management tool that works well in multi-platform environments.
Setup
- Install required packages:
dotnet add package VaultSharpImplementation
// VaultService.cs
public class VaultService
{
private readonly IVaultClient _vaultClient;
public VaultService(string vaultAddress, string vaultToken)
{
var vaultClientSettings = new VaultClientSettings(
vaultAddress,
new TokenAuthMethodInfo(vaultToken)
);
_vaultClient = new VaultClient(vaultClientSettings);
}
public async Task<string> GetSecretAsync(string path, string key)
{
var secret = await _vaultClient.V1.Secrets.KeyValue.V2.ReadSecretAsync(path);
return secret.Data.Data[key].ToString();
}
}Registering as a Service
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Get Vault configuration from environment variables
string vaultAddress = builder.Configuration["Vault:Address"];
string vaultToken = builder.Configuration["Vault:Token"];
// Register VaultService
builder.Services.AddSingleton<VaultService>(sp =>
new VaultService(vaultAddress, vaultToken));
var app = builder.Build();
// ...rest of application setupUsage Example
// PaymentController.cs
[ApiController]
[Route("[controller]")]
public class PaymentController : ControllerBase
{
private readonly VaultService _vaultService;
public PaymentController(VaultService vaultService)
{
_vaultService = vaultService;
}
[HttpPost]
public async Task<IActionResult> ProcessPayment()
{
// Get payment gateway API key from Vault
var apiKey = await _vaultService.GetSecretAsync("payment/api-keys", "stripe");
// Use the API key for payment processing
// ...
return Ok(new { message = "Payment processed" });
}
}5. Docker Secrets Strategy
Docker Secrets is ideal for containerized applications, especially in Swarm mode.
Creating Docker Secrets
# Create a secret
echo "my-database-password" | docker secret create db_password -
# Creating from a file
docker secret create cert_file ./server.certDocker Compose Configuration
# docker-compose.yml
version: '3.8'
services:
api:
image: my-dotnet-api
secrets:
- db_password
- api_key
environment:
- DB_PASSWORD_FILE=/run/secrets/db_password
- API_KEY_FILE=/run/secrets/api_key
secrets:
db_password:
external: true
api_key:
external: trueImplementation in .NET
// SecretReader.cs
public static class SecretReader
{
public static string GetSecret(string secretFile)
{
try
{
return File.ReadAllText(secretFile).Trim();
}
catch (Exception ex)
{
Console.Error.WriteLine($"Error reading secret file: {ex.Message}");
return null;
}
}
}Usage Example
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Get paths to secret files from environment variables
string dbPasswordFile = Environment.GetEnvironmentVariable("DB_PASSWORD_FILE");
string apiKeyFile = Environment.GetEnvironmentVariable("API_KEY_FILE");
// Read secrets
string dbPassword = SecretReader.GetSecret(dbPasswordFile);
string apiKey = SecretReader.GetSecret(apiKeyFile);
// Configure services using the secrets
builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
var connectionString = $"Server=db;Database=myapp;User=admin;Password={dbPassword};";
options.UseSqlServer(connectionString);
});
// Register API client with key
builder.Services.AddHttpClient("externalApi", client =>
{
client.DefaultRequestHeaders.Add("X-API-Key", apiKey);
});
var app = builder.Build();
// ...rest of application setup6. Hybrid Approach for Different Environments
A common strategy is to use different methods based on the environment:
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Base configuration from appsettings.json
var configuration = builder.Configuration;
// Add environment variables
configuration.AddEnvironmentVariables();
// Add appropriate secret manager based on environment
if (builder.Environment.IsProduction())
{
// In production: Use Azure Key Vault or HashiCorp Vault
string keyVaultUrl = configuration["KeyVault:Url"];
// Add Key Vault configuration
// ...
}
else if (builder.Environment.IsStaging())
{
// In staging: Docker Secrets or HashiCorp Vault
// ...
}
else
{
// In development: Use user secrets
configuration.AddUserSecrets<Program>();
}
var app = builder.Build();
// ...rest of application setup7. Best Practices
Rotate secrets regularly - Implement a process to rotate credentials
Use managed identities when possible - Especially with Azure services
Implement secret caching - Avoid unnecessary calls to the vault service:
// CachingVaultService.cs
public class CachingVaultService
{
private readonly VaultService _vaultService;
private readonly IMemoryCache _cache;
private readonly TimeSpan _cacheExpiration = TimeSpan.FromMinutes(15);
public CachingVaultService(VaultService vaultService, IMemoryCache cache)
{
_vaultService = vaultService;
_cache = cache;
}
public async Task<string> GetSecretAsync(string path, string key)
{
string cacheKey = $"{path}:{key}";
if (!_cache.TryGetValue(cacheKey, out string cachedSecret))
{
cachedSecret = await _vaultService.GetSecretAsync(path, key);
_cache.Set(cacheKey, cachedSecret, _cacheExpiration);
}
return cachedSecret;
}
}Implement fallback mechanisms - Have contingency plans for when vault services are unavailable
Audit secret access - Implement logging and monitoring for secret access patterns
8. Comparing Strategies
| Strategy | Pros | Cons | Best For |
|---|---|---|---|
| Environment Variables | Simple, universal | No encryption at rest, no versioning | Development, small applications |
| Azure Key Vault | Managed service, high availability, integrates with Azure services | Azure-specific, potential cost | Azure-hosted applications |
| HashiCorp Vault | Platform-agnostic, advanced features | Complex setup, self-managed | Multi-cloud, enterprise applications |
| Docker Secrets | Simple for containerized apps, native Docker integration | Limited to Docker environment | Docker Swarm deployments |