Secrets Management in ConnectSoft Microservice Template¶
Purpose & Overview¶
Secrets Management is the secure handling of sensitive information such as connection strings, API keys, passwords, tokens, and certificates. In the ConnectSoft Microservice Template, secrets management follows security-first principles, ensuring that sensitive data is never stored in source control, properly encrypted, and accessed only by authorized services using secure patterns.
Secrets management provides:
- Zero Secrets in Source Control: Secrets never committed to Git repositories
- Centralized Management: Azure Key Vault for production secrets
- Environment-Specific Secrets: Different secrets for development, staging, and production
- Secure Injection: Managed Identity authentication for Key Vault access
- Automatic Rotation: Key Vault handles secret rotation and versioning
- Audit Logging: Track secret access and changes
- Local Development Support: User Secrets and environment variables for local development
Secrets Management Philosophy
Secrets are the crown jewels of any application. The template follows a zero-trust approach where secrets are never stored in source control, never logged, and accessed only through secure channels. Production secrets are managed through Azure Key Vault with managed identity authentication, while local development uses User Secrets or environment variables. This ensures that sensitive information is protected throughout the entire application lifecycle.
Architecture Overview¶
Secrets Management Hierarchy¶
Production (Azure)
├── Azure Key Vault
│ ├── Connection Strings
│ ├── API Keys
│ ├── Passwords
│ └── Certificates
├── Azure App Configuration
│ └── Key Vault References (@Microsoft.KeyVault)
└── Managed Identity Authentication
↓
Local Development
├── User Secrets (dotnet user-secrets)
├── Environment Variables
└── .env files (optional)
↓
Container/Kubernetes
├── Kubernetes Secrets
├── Environment Variables (from Secrets)
└── Volume Mounts (for certificates)
Secrets Flow¶
Secret Storage
├── Azure Key Vault (Production)
│ ├── Managed Identity Access
│ ├── RBAC Authorization
│ └── Audit Logging
├── Azure App Configuration
│ └── Key Vault References
└── Environment Variables (Local/Dev)
↓
Configuration Provider
├── Azure App Configuration Provider
│ └── Resolves Key Vault References
├── Environment Variables Provider
└── User Secrets Provider (Local)
↓
Application Configuration (IConfiguration)
└── Options Pattern (Strongly-Typed Access)
Secrets Storage Options¶
Azure Key Vault (Production)¶
Purpose: Centralized, secure secret storage for production environments.
Features: - Encryption: Hardware Security Modules (HSM) or software encryption - Access Control: Role-Based Access Control (RBAC) - Versioning: Automatic versioning of secrets - Rotation: Built-in secret rotation support - Audit Logging: Complete audit trail of secret access - Soft Delete: Secrets can be recovered after deletion - Managed Identity: No connection strings needed
Key Vault Reference Syntax:
{
"ConnectionStrings": {
"DefaultConnection": "@Microsoft.KeyVault(SecretUri=https://vault.vault.azure.net/secrets/DbConnectionString)"
}
}
Alternative Syntax:
{
"ConnectionStrings": {
"DefaultConnection": "@Microsoft.KeyVault(VaultName=my-vault;SecretName=DbConnectionString)"
}
}
Azure App Configuration with Key Vault References¶
Purpose: Centralized configuration management with secure secret references.
Configuration in Azure App Configuration:
Key: ConnectSoft.MicroserviceTemplate:Database:ConnectionString
Value: @Microsoft.KeyVault(SecretUri=https://vault.vault.azure.net/secrets/DbConnectionString)
Benefits: - Secrets never stored in App Configuration - Automatic secret resolution at runtime - Centralized configuration management - Support for secret rotation
Key Vault Configuration:
configurationBuilder.AddAzureAppConfiguration(options =>
{
options.Connect(connectionString)
.Select("ConnectSoft.MicroserviceTemplate:*", LabelFilter.Null)
.ConfigureKeyVault(kv =>
{
// Use Managed Identity
kv.SetCredential(new DefaultAzureCredential());
// Or User-Assigned Managed Identity
// kv.SetCredential(new ManagedIdentityCredential(clientId));
});
});
User Secrets (Local Development)¶
Purpose: Secure secret storage for local development without committing secrets to source control.
Initialization:
# Navigate to project directory
cd ConnectSoft.MicroserviceTemplate.Application
# Initialize user secrets
dotnet user-secrets init
Setting Secrets:
# Set connection string
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=localhost;Database=MyDb;..."
# Set API key
dotnet user-secrets set "ApiKeys:ExternalApi" "sk-1234567890abcdef"
# Set nested configuration
dotnet user-secrets set "Database:Password" "MyPassword123!"
Listing Secrets:
# List all secrets
dotnet user-secrets list
# List specific secret
dotnet user-secrets list "ConnectionStrings"
Removing Secrets:
# Remove specific secret
dotnet user-secrets remove "ConnectionStrings:DefaultConnection"
# Clear all secrets
dotnet user-secrets clear
Location: User Secrets are stored in:
- Windows: %APPDATA%\Microsoft\UserSecrets\{UserSecretsId}\secrets.json
- macOS/Linux: ~/.microsoft/usersecrets/{UserSecretsId}/secrets.json
Enable in Application:
// Program.cs (typically only in Development)
if (builder.Environment.IsDevelopment())
{
builder.Configuration.AddUserSecrets<Program>();
}
Environment Variables¶
Purpose: Inject secrets at runtime without modifying code or configuration files.
Format:
# Connection strings
export ConnectionStrings__DefaultConnection="Server=localhost;Database=MyDb;..."
# Nested configuration
export Database__Password="MyPassword123!"
# API keys
export ApiKeys__ExternalApi="sk-1234567890abcdef"
Priority: Environment variables override configuration files (highest priority in configuration hierarchy).
Use Cases: - Container deployments (Docker, Kubernetes) - CI/CD pipelines - Local development overrides - Production secret injection
Kubernetes Secrets¶
Purpose: Secure secret storage for Kubernetes deployments.
Creating Secrets:
# From literal values
kubectl create secret generic microservice-secrets \
--from-literal=ConnectionStrings__DefaultConnection="Server=...;Database=...;..." \
--from-literal=ApiKeys__ExternalApi="sk-1234567890abcdef"
# From file
kubectl create secret generic microservice-secrets \
--from-file=connection-string.txt
YAML Manifest:
apiVersion: v1
kind: Secret
metadata:
name: microservice-secrets
namespace: default
type: Opaque
stringData:
ConnectionStrings__DefaultConnection: "Server=...;Database=...;..."
ApiKeys__ExternalApi: "sk-1234567890abcdef"
Using Secrets in Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: microservice
spec:
template:
spec:
containers:
- name: microservice
image: microservice:latest
env:
- name: ConnectionStrings__DefaultConnection
valueFrom:
secretKeyRef:
name: microservice-secrets
key: ConnectionStrings__DefaultConnection
- name: ApiKeys__ExternalApi
valueFrom:
secretKeyRef:
name: microservice-secrets
key: ApiKeys__ExternalApi
Note: Kubernetes secrets are base64-encoded, not encrypted. For production, use External Secrets Operator with Azure Key Vault.
Authentication Methods¶
Managed Identity (Recommended for Production)¶
Purpose: Secure, credential-free authentication to Azure Key Vault.
System-Assigned Managed Identity:
configurationBuilder.AddAzureAppConfiguration(options =>
{
options.Connect(new Uri("https://{store-name}.azconfig.io"))
.ConfigureKeyVault(kv =>
{
kv.SetCredential(new DefaultAzureCredential());
});
});
User-Assigned Managed Identity:
configurationBuilder.AddAzureAppConfiguration(options =>
{
options.Connect(new Uri("https://{store-name}.azconfig.io"))
.ConfigureKeyVault(kv =>
{
kv.SetCredential(new ManagedIdentityCredential(clientId));
});
});
Benefits: - No connection strings or credentials needed - Automatic credential rotation - Better security posture - Azure AD integration
Connection String Authentication¶
Purpose: Alternative authentication method (less secure, not recommended for production).
Connection String Format:
Usage:
configurationBuilder.AddAzureAppConfiguration(options =>
{
string connectionString = configuration.GetConnectionString("AzureAppConfiguration");
options.Connect(connectionString);
});
Security Considerations: - Connection string must be stored securely - Use Key Vault reference for connection string itself - Rotate secrets regularly - Consider using Managed Identity instead
Configuration Patterns¶
Connection Strings¶
Storage: Connection strings are stored in ConnectionStrings section.
Example:
{
"ConnectionStrings": {
"DefaultConnection": "@Microsoft.KeyVault(SecretUri=https://vault.vault.azure.net/secrets/DbConnectionString)",
"AzureAppConfiguration": "@Microsoft.KeyVault(SecretUri=https://vault.vault.azure.net/secrets/AppConfigConnectionString)"
}
}
Access:
API Keys¶
Storage: API keys stored in configuration sections.
Example:
{
"ApiKeys": {
"ExternalApi": "@Microsoft.KeyVault(SecretUri=https://vault.vault.azure.net/secrets/ExternalApiKey)"
}
}
Options Class:
public sealed class ApiKeysOptions
{
public const string ApiKeysSectionName = "ApiKeys";
[Required]
required public string ExternalApi { get; set; }
}
Access:
public class ExternalApiClient
{
private readonly ApiKeysOptions apiKeys;
public ExternalApiClient(IOptions<ApiKeysOptions> apiKeys)
{
this.apiKeys = apiKeys.Value;
}
public async Task CallApiAsync()
{
var apiKey = this.apiKeys.ExternalApi;
// Use API key
}
}
Nested Secrets¶
Storage: Nested configuration structure.
Example:
{
"Database": {
"Server": "db.example.com",
"Password": "@Microsoft.KeyVault(SecretUri=https://vault.vault.azure.net/secrets/DbPassword)"
}
}
Options Class:
public sealed class DatabaseOptions
{
public const string DatabaseSectionName = "Database";
[Required]
required public string Server { get; set; }
[Required]
required public string Password { get; set; }
}
Secret Injection Strategies¶
Strategy 1: Azure App Configuration with Key Vault References¶
Best For: Production environments with Azure infrastructure.
Configuration:
configurationBuilder.AddAzureAppConfiguration(options =>
{
options.Connect(new Uri("https://{store-name}.azconfig.io"), new DefaultAzureCredential())
.Select("ConnectSoft.MicroserviceTemplate:*", LabelFilter.Null)
.ConfigureKeyVault(kv =>
{
kv.SetCredential(new DefaultAzureCredential());
});
});
Key Vault Reference in App Configuration:
Key: ConnectSoft.MicroserviceTemplate:Database:ConnectionString
Value: @Microsoft.KeyVault(SecretUri=https://vault.vault.azure.net/secrets/DbConnectionString)
Strategy 2: Direct Key Vault Provider¶
Best For: Direct Key Vault access without App Configuration.
Configuration:
configurationBuilder.AddAzureKeyVault(
new Uri("https://{vault-name}.vault.azure.net/"),
new DefaultAzureCredential());
Access:
Strategy 3: Environment Variables (Container/Kubernetes)¶
Best For: Containerized deployments and CI/CD pipelines.
Configuration:
# Kubernetes Secret
apiVersion: v1
kind: Secret
metadata:
name: microservice-secrets
stringData:
ConnectionStrings__DefaultConnection: "Server=...;Database=...;..."
# Deployment
env:
- name: ConnectionStrings__DefaultConnection
valueFrom:
secretKeyRef:
name: microservice-secrets
key: ConnectionStrings__DefaultConnection
Strategy 4: User Secrets (Local Development)¶
Best For: Local development without committing secrets.
Configuration:
Setting Secrets:
Secret Naming Conventions¶
Key Vault Secret Naming¶
Pattern: {Service}-{Environment}-{SecretType} or {Service}-{SecretType}
Examples:
- MicroserviceTemplate-DbConnectionString
- MicroserviceTemplate-Prod-ExternalApiKey
- MicroserviceTemplate-Dev-RedisConnectionString
- JwtSigningKey
- EncryptionKey
Configuration Key Naming¶
Pattern: {Service}:{Section}:{Property}
Examples:
- ConnectSoft.MicroserviceTemplate:Database:ConnectionString
- ConnectSoft.MicroserviceTemplate:ApiKeys:ExternalApi
- ConnectSoft.MicroserviceTemplate:Authentication:JwtSecret
Environment Variable Naming¶
Pattern: {Section}__{SubSection}__{Property} (double underscore for nesting)
Examples:
- ConnectionStrings__DefaultConnection
- Database__Password
- ApiKeys__ExternalApi
Secret Rotation¶
Azure Key Vault Rotation¶
Automatic Rotation:
// Key Vault automatically supports secret versions
// Access latest version
var secret = await secretClient.GetSecretAsync("DbConnectionString");
// Access specific version
var secret = await secretClient.GetSecretAsync("DbConnectionString", "v2");
Rotation Strategy:
- Create New Secret Version: Add new version in Key Vault
- Update Reference: Update App Configuration reference (if needed)
- Verify: Test application with new secret
- Rollback: Use previous version if issues occur
Application Handling¶
Graceful Rotation:
public class DatabaseConnectionManager
{
private readonly IConfiguration configuration;
private readonly ILogger<DatabaseConnectionManager> logger;
public async Task<string> GetConnectionStringAsync()
{
var connectionString = configuration.GetConnectionString("DefaultConnection");
// Test connection
if (!await TestConnectionAsync(connectionString))
{
logger.LogWarning("Primary connection failed, trying previous version");
// Fallback to previous version if supported
}
return connectionString;
}
}
Security Best Practices¶
Do's¶
-
Use Managed Identity for Production
-
Never Commit Secrets
-
Use User Secrets for Local Development
-
Separate Secrets from Configuration
-
Use Environment Variables for Containers
-
Enable Audit Logging
-
Rotate Secrets Regularly
Don'ts¶
-
Don't Store Secrets in Source Control
-
Don't Log Secrets
-
Don't Use Connection Strings for Key Vault Access in Production
-
Don't Hardcode Secrets
-
Don't Share Secrets Between Environments
-
Don't Store Secrets in Plain Text
Common Scenarios¶
Scenario 1: Database Connection String¶
Key Vault:
Secret Name: DbConnectionString
Secret Value: Server=db.example.com;Database=MyDb;User Id=appuser;Password=P@ssw0rd!;
Azure App Configuration:
Key: ConnectSoft.MicroserviceTemplate:Database:ConnectionString
Value: @Microsoft.KeyVault(SecretUri=https://vault.vault.azure.net/secrets/DbConnectionString)
Access:
var connectionString = configuration.GetConnectionString("DefaultConnection");
// Or
var connectionString = configuration["ConnectSoft.MicroserviceTemplate:Database:ConnectionString"];
Scenario 2: External API Key¶
Key Vault:
Configuration:
{
"ApiKeys": {
"ExternalApi": "@Microsoft.KeyVault(SecretUri=https://vault.vault.azure.net/secrets/ExternalApiKey)"
}
}
Options Class:
public sealed class ApiKeysOptions
{
public const string ApiKeysSectionName = "ApiKeys";
[Required]
required public string ExternalApi { get; set; }
}
Registration:
services.AddOptions<ApiKeysOptions>()
.BindConfiguration(ApiKeysOptions.ApiKeysSectionName)
.ValidateDataAnnotations()
.ValidateOnStart();
Usage:
public class ExternalApiClient
{
private readonly ApiKeysOptions apiKeys;
public ExternalApiClient(IOptions<ApiKeysOptions> apiKeys)
{
this.apiKeys = apiKeys.Value;
}
public async Task<Response> CallApiAsync()
{
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.example.com/data");
request.Headers.Add("X-API-Key", this.apiKeys.ExternalApi);
// Make request
}
}
Scenario 3: JWT Signing Key¶
Key Vault:
Configuration:
{
"Authentication": {
"Jwt": {
"SigningKey": "@Microsoft.KeyVault(SecretUri=https://vault.vault.azure.net/secrets/JwtSigningKey)"
}
}
}
Access:
Scenario 4: Local Development Setup¶
Initialize User Secrets:
Set Secrets:
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=localhost;Database=MyDb;User Id=sa;Password=Password@123;"
dotnet user-secrets set "ApiKeys:ExternalApi" "sk-dev-key-1234567890abcdef"
Enable in Application:
// Program.cs
if (builder.Environment.IsDevelopment())
{
builder.Configuration.AddUserSecrets<Program>();
}
Troubleshooting¶
Issue: Key Vault Reference Not Resolving¶
Symptoms: Configuration value shows @Microsoft.KeyVault(...) instead of actual secret.
Solutions: 1. Verify Managed Identity:
# Check if managed identity is assigned
az webapp identity show --name <app-name> --resource-group <rg-name>
-
Check Key Vault Access Policy:
-
Verify Secret URI:
-
Check Key Vault Configuration:
Issue: User Secrets Not Loading¶
Symptoms: User Secrets not available in application.
Solutions: 1. Verify User Secrets ID:
<!-- .csproj file -->
<PropertyGroup>
<UserSecretsId>your-user-secrets-id</UserSecretsId>
</PropertyGroup>
-
Check Registration:
-
Verify Secret Names:
Issue: Environment Variables Not Overriding¶
Symptoms: Environment variables not taking effect.
Solutions: 1. Check Naming Format:
# ✅ GOOD - Double underscore for nesting
export ConnectionStrings__DefaultConnection="..."
# ❌ BAD - Single underscore
export ConnectionStrings_DefaultConnection="..."
-
Verify Load Order:
-
Check Variable Names:
Issue: Secrets Visible in Logs¶
Symptoms: Secrets appearing in application logs.
Solutions: 1. Use Redaction:
-
Avoid Logging Secrets:
-
Use Structured Logging Carefully:
Related Documentation¶
- Configuration: Configuration management and options pattern
- Azure App Configuration: Azure App Configuration with Key Vault references
- Kubernetes: Kubernetes secrets management
- Security: Security best practices and patterns
Summary¶
Secrets management in the ConnectSoft Microservice Template provides:
- ✅ Zero Secrets in Source Control: Secrets never committed to Git
- ✅ Centralized Management: Azure Key Vault for production secrets
- ✅ Secure Authentication: Managed Identity for Key Vault access
- ✅ Local Development: User Secrets for secure local development
- ✅ Container Support: Environment variables and Kubernetes secrets
- ✅ Automatic Rotation: Key Vault handles secret rotation
- ✅ Audit Logging: Complete audit trail of secret access
- ✅ Best Practices: Security-first approach to secrets management
By following these patterns, teams can:
- Protect Sensitive Data: Secrets never exposed in source control or logs
- Enable Secure Access: Managed Identity eliminates credential management
- Support Multiple Environments: Different secrets for dev, staging, and production
- Simplify Development: User Secrets for easy local development
- Ensure Compliance: Audit logging and access control for regulatory compliance
Secrets management is essential for maintaining security and compliance, ensuring that sensitive information is protected throughout the application lifecycle while enabling secure access for authorized services and developers.