A configuration management platform with versioning, encryption, rollback support, role-based access control, and full audit logging. Includes a React frontend dashboard and a .NET API backend.
- 🔐 User Authentication — Identity API with registration, login, JWT tokens, 2FA, and password management
- 👥 Role-Based Access Control — Global roles (Admin, User, CreateApplications, etc.) plus granular per-application and per-environment permissions (Read, Write, Delete, Rollback, Tag, RegenerateKey, Decrypt)
- 🔒 Encryption — Encrypt sensitive configuration values using the Data Protection API
- 📜 Version History — Every change creates a new version, maintaining full history
- ⏪ Rollback — Rollback to any previous version, point in time, or tagged version (single key or entire environment)
- 🏷️ Tagging — Tag individual versions or entire environments as "stable", "last-known-good", etc.
- 📊 Audit Logging — Complete audit trail of all changes with user, IP, and user agent tracking
- 🌍 Multi-Environment — Support for Development, Staging, Production, and custom environments
- 📱 Multi-Application — Manage configs for multiple applications under one instance
- 🔌 Client API — Simple API for applications to fetch configs using API keys
- 📤 Import/Export — Bulk import and export configurations per environment
- 📋 Environment Copy — Copy configurations between environments
- 🖥️ Frontend Dashboard — React + TypeScript + Material UI management interface
ConfigVault/
├── src/
│ ├── ConfigVault.Api/ # ASP.NET Core API (.NET 10)
│ ├── ConfigVault.Core/ # Domain entities, services, interfaces
│ └── ConfigVault.Client/ # .NET IConfiguration provider (NuGet package)
│ └── ConfigVault.FrontEnd/ # React + Vite + TypeScript + MUI dashboard
├── docker-compose.yml
└── Dockerfile
Backend: ASP.NET Core (.NET 10), Entity Framework Core, PostgreSQL, ASP.NET Identity, Data Protection API, Swagger/OpenAPI 3.1
Frontend: React 19, TypeScript, Vite, Material UI 7, React Router 7, TanStack React Query, Axios (auto-generated API client from Swagger), Topiray Auth
docker-compose up -dThe API will be available at http://localhost:8080. Swagger UI is available in development mode at the root URL.
cd src/ConfigVault.Api
dotnet runcd frontend
cp _env .env # Configure your environment variables
npm install
npm run devTo regenerate the typed API client from the backend's Swagger spec:
npm run generate-apiThe backend uses the following environment variables for initial setup:
| Variable | Description | Default |
|---|---|---|
ADMIN_EMAIL |
Email for the auto-created admin user | admin@localhost |
ADMIN_PASSWORD |
Password for the admin user (required to create) | — |
ADMIN_USERNAME |
Display name for the admin user | Administrator |
ConnectionStrings__DefaultConnection |
PostgreSQL connection string | — |
CorsHosts |
Comma-separated allowed CORS origins | — |
FrontEndHost |
Base URL of the frontend app, used to rewrite email links to frontend routes (/confirm-email, /confirm-password-reset) |
http://localhost:5173 |
EmailSettings__Host |
SMTP server hostname | — |
EmailSettings__Port |
SMTP server port | — |
EmailSettings__EnableSsl |
Whether to use SSL for SMTP | — |
EmailSettings__Username |
SMTP username (also used as the sender address) | — |
EmailSettings__Password |
SMTP password | — |
DataProtection__StorageType |
Where to persist Data Protection keys: Database, AzureBlobStorage, or AwsSystemsManager |
Database |
DataProtection__ProtectionType |
How to encrypt keys at rest: None, AzureKeyVault, AwsKms, or Certificate |
None |
DataProtection__AzureBlobStorage__ConnectionString |
Azure Storage connection string | — |
DataProtection__AzureBlobStorage__ContainerName |
Blob container name | dataprotection |
DataProtection__AzureBlobStorage__BlobName |
Blob name for the key file | keys.xml |
DataProtection__AwsSystemsManager__KeyPrefix |
SSM parameter path prefix | /DataProtection/ConfigVault/ |
DataProtection__AwsSystemsManager__Region |
AWS region override (optional) | — |
DataProtection__AzureKeyVault__KeyIdentifier |
Full Key Vault key URI (e.g. https://myvault.vault.azure.net/keys/dataprotection) |
— |
DataProtection__AzureKeyVault__TenantId |
Azure AD tenant ID for DefaultAzureCredential (optional) |
— |
DataProtection__AwsKms__KeyId |
KMS key ID, ARN, or alias (e.g. alias/configvault-dp) |
— |
DataProtection__Certificate__Thumbprint |
Certificate thumbprint (looked up in CurrentUser/My store) | — |
DataProtection__Certificate__Path |
Path to a .pfx file (alternative to Thumbprint) |
— |
DataProtection__Certificate__Password |
Password for the .pfx file |
— |
These can also be set via appsettings.json:
{
"FrontEndHost": "http://localhost:5173",
"EmailSettings": {
"Host": "smtp.example.com",
"Port": 587,
"EnableSsl": true,
"Username": "no-reply@example.com",
"Password": "your-smtp-password"
},
"DataProtection": {
"StorageType": "Database",
"ProtectionType": "None"
}
}ConfigVault uses a layered permission system.
Global Roles control platform-level actions: Admin, User, CreateApplications, EditApplications, DeleteApplications.
Application Roles are flag-based and can be assigned at two levels:
| Role | Description |
|---|---|
Read |
View configs and history |
Write |
Create and update configs |
Delete |
Delete configs |
Rollback |
Rollback configs or environments |
Tag |
Add/remove tags on versions |
RegenerateKey |
Regenerate the application's API key |
Decrypt |
View decrypted values of encrypted configs |
Permissions can be granted globally for an application (all environments) or scoped to specific environments. Effective permissions are the union of application-level and environment-level grants. Admins bypass all permission checks.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/register |
Register a new user |
| POST | /api/auth/login |
Login and get access + refresh tokens |
| POST | /api/auth/refresh |
Refresh access token |
| GET | /api/auth/confirmEmail |
Confirm email address |
| POST | /api/auth/forgotPassword |
Request password reset |
| POST | /api/auth/resetPassword |
Reset password |
| POST | /api/auth/manage/2fa |
Manage two-factor authentication |
| GET | /api/auth/manage/info |
Get user info |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/applications |
List accessible applications |
| GET | /api/applications/{name} |
Get application details |
| POST | /api/applications |
Create new application (returns API key) |
| PUT | /api/applications/{name} |
Update application |
| DELETE | /api/applications/{name} |
Delete application and all configs |
| POST | /api/applications/{name}/regenerate-key |
Regenerate API key |
| Method | Endpoint | Description |
|---|---|---|
| GET | /configs |
List all configs (optional ?keyPrefix=) |
| GET | /configs/key/{key} |
Get config value |
| GET | /configs/key/{key}/decrypt |
Get decrypted value |
| PUT | /configs/key/{key} |
Create or update config |
| DELETE | /configs/key/{key} |
Delete config |
| GET | /configs/key/{key}/history |
Get version history |
| GET | /configs/key/{key}/versions/{version} |
Get specific version |
| GET | /configs/key/{key}/at?pointInTime=... |
Get config at point in time |
| GET | /configs/key/{key}/diff?fromVersion=1&toVersion=2 |
Compare two versions |
| POST | /configs/key/{key}/rollback |
Rollback config |
| POST | /configs/key/{key}/versions/{v}/tag |
Add tag to version |
| DELETE | /configs/key/{key}/versions/{v}/tag |
Remove tag |
| Method | Endpoint | Description |
|---|---|---|
| GET | /export |
Export all configs as JSON |
| POST | /import |
Import configs from JSON |
| POST | /rollback |
Rollback entire environment to a point in time |
| POST | /rollback/tag/{tag} |
Rollback environment to a tagged snapshot |
| POST | /copy-to/{targetEnv} |
Copy configs to another environment |
| POST | /tag |
Tag all current configs in environment |
| DELETE | /tag/{tag} |
Remove a tag from all configs |
| GET | /tags |
List all tags used in environment |
| Method | Endpoint | Description |
|---|---|---|
| GET | /me |
Get current user info, roles, and accesses |
| GET | /me/applications |
Get current user's application accesses |
| GET | /me/environments |
Get current user's environment accesses |
| GET | /me/permissions/{appName}/{env} |
Get effective permissions |
| GET | / |
List all users (Admin) |
| GET | /{userId} |
Get user by ID (Admin) |
| POST | / |
Create user (Admin) |
| PUT | /{userId} |
Update user (Admin) |
| DELETE | /{userId} |
Delete user (Admin) |
| PATCH | /{userId}/active?isActive= |
Activate/deactivate user (Admin) |
| GET | /{userId}/roles |
Get user's global roles (Admin) |
| POST | /{userId}/roles/{role} |
Add global role (Admin) |
| DELETE | /{userId}/roles/{role} |
Remove global role (Admin) |
| POST | /{userId}/applications/{appName} |
Grant app access (Admin) |
| PUT | /{userId}/applications/{appName} |
Update app access (Admin) |
| DELETE | /{userId}/applications/{appName} |
Revoke app access (Admin) |
| POST | /{userId}/applications/{appName}/environments/{env} |
Grant env access (Admin) |
| PUT | /{userId}/applications/{appName}/environments/{env} |
Update env access (Admin) |
| DELETE | /{userId}/applications/{appName}/environments/{env} |
Revoke env access (Admin) |
| GET | /by-application/{appName} |
Get users with app access (Admin) |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/client/{appName}/{env} |
Get all configs (requires X-Api-Key header) |
| GET | /api/client/{appName}/{env}/key/{key} |
Get specific config |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/audit |
Query audit logs with filtering |
| Method | Endpoint | Description |
|---|---|---|
| GET | /health |
Health check endpoint |
ConfigVault uses the standard .NET configuration key format — colon-separated segments — which maps naturally to structured JSON when consumed by the .NET client. Understanding this convention lets you model objects, nested objects, and arrays as flat key-value pairs.
Use : to separate property levels. Each segment becomes a level of nesting in JSON.
| Key | Value |
|---|---|
Smtp:Host |
smtp.example.com |
Smtp:Port |
587 |
Smtp:EnableSsl |
true |
This is equivalent to:
{
"Smtp": {
"Host": "smtp.example.com",
"Port": "587",
"EnableSsl": "true"
}
}Additional : segments create deeper nesting.
| Key | Value |
|---|---|
Logging:LogLevel:Default |
Information |
Logging:LogLevel:Microsoft |
Warning |
Resolves to:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning"
}
}
}Use numeric indices (starting from 0) as the final key segment to represent array elements.
| Key | Value |
|---|---|
AllowedHosts:0 |
example.com |
AllowedHosts:1 |
api.example.com |
AllowedHosts:2 |
cdn.example.com |
Resolves to:
{
"AllowedHosts": [
"example.com",
"api.example.com",
"cdn.example.com"
]
}Combine numeric indices with named properties to create arrays of complex objects.
| Key | Value |
|---|---|
DatabaseCluster:Nodes:0:Host |
db-primary.internal |
DatabaseCluster:Nodes:0:Port |
5432 |
DatabaseCluster:Nodes:0:Role |
primary |
DatabaseCluster:Nodes:1:Host |
db-replica.internal |
DatabaseCluster:Nodes:1:Port |
5432 |
DatabaseCluster:Nodes:1:Role |
replica |
Resolves to:
{
"DatabaseCluster": {
"Nodes": [
{ "Host": "db-primary.internal", "Port": "5432", "Role": "primary" },
{ "Host": "db-replica.internal", "Port": "5432", "Role": "replica" }
]
}
}Because ConfigVault's .NET client feeds directly into IConfiguration, you can bind these structured keys to strongly-typed options classes as usual:
public class SmtpOptions
{
public string Host { get; set; }
public int Port { get; set; }
public bool EnableSsl { get; set; }
}
// In Program.cs
builder.Services.Configure<SmtpOptions>(builder.Configuration.GetSection("Smtp"));Tip: When using the
?keyPrefix=query parameter on the list endpoint, you can fetch just a subtree of your config — for example?keyPrefix=Smtpreturns only keys starting withSmtp:.
# Register
curl -X POST http://localhost:8080/api/auth/register \
-H "Content-Type: application/json" \
-d '{"email": "admin@example.com", "password": "SecurePass123"}'
# Login
curl -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "admin@example.com", "password": "SecurePass123"}'
# Returns: {"accessToken": "...", "refreshToken": "...", ...}curl -X POST http://localhost:8080/api/applications \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "my-web-app",
"description": "My Web Application",
"allowedEnvironments": ["Development", "Staging", "Production"]
}'
# Returns: {"applicationName": "my-web-app", "apiKey": "cv_...", ...}# Plain text config
curl -X PUT http://localhost:8080/api/apps/my-web-app/envs/Production/configs/key/Smtp:Host \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"value": "smtp.example.com", "changeReason": "Initial configuration"}'
# Encrypted config
curl -X PUT http://localhost:8080/api/apps/my-web-app/envs/Production/configs/key/Smtp:Password \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"value": "super-secret-password", "encrypt": true, "changeReason": "Setting SMTP password"}'# Rollback to specific version
curl -X POST http://localhost:8080/api/apps/my-web-app/envs/Production/configs/key/Smtp:Host/rollback \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"toVersion": 1, "reason": "Reverting due to issue"}'
# Rollback to point in time
curl -X POST ... \
-d '{"toPointInTime": "2024-01-15T10:00:00Z", "reason": "Reverting to last known good state"}'
# Rollback to tagged version
curl -X POST ... \
-d '{"toTag": "stable", "reason": "Reverting to stable version"}'curl -X POST http://localhost:8080/api/apps/my-web-app/envs/Production/configs/key/Smtp:Host/versions/3/tag \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"tag": "stable"}'curl -X POST http://localhost:8080/api/apps/my-web-app/envs/Production/tag \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"tag": "release-v2.1", "reason": "Pre-deployment snapshot"}'# Get all configs
curl http://localhost:8080/api/client/my-web-app/Production \
-H "X-Api-Key: cv_YOUR_API_KEY"
# Returns: {"Smtp:Host": "smtp.example.com", "Smtp:Password": "super-secret-password", ...}
# Get specific config
curl http://localhost:8080/api/client/my-web-app/Production/key/Smtp:Host \
-H "X-Api-Key: cv_YOUR_API_KEY"
# Returns: {"key": "Smtp:Host", "value": "smtp.example.com"}# Export
curl http://localhost:8080/api/apps/my-web-app/envs/Development/export \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# Import
curl -X POST http://localhost:8080/api/apps/my-web-app/envs/Staging/import \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"Smtp:Host": "smtp.staging.example.com", "Smtp:Port": "587"}'
# Copy environment
curl -X POST "http://localhost:8080/api/apps/my-web-app/envs/Development/copy-to/Staging?overwrite=true" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"# Create a user
curl -X POST http://localhost:8080/api/users \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"email": "dev@example.com", "password": "DevPass123!", "roles": ["User"]}'
# Grant app-level Read + Write access
curl -X POST http://localhost:8080/api/users/{userId}/applications/my-web-app \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"roles": 3}'
# Grant environment-specific access (e.g. Read only on Production)
curl -X POST http://localhost:8080/api/users/{userId}/applications/my-web-app/environments/Production \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"roles": 1}'ConfigVault ships a .NET IConfiguration provider for seamless integration. It supports automatic refresh and reads configs directly into your app's configuration pipeline.
// Basic usage
builder.Configuration.AddConfigVault(
baseUrl: "http://localhost:8080",
applicationName: "my-web-app",
environment: builder.Environment.EnvironmentName,
apiKey: Environment.GetEnvironmentVariable("CONFIGVAULT_API_KEY")!
);
// With options
builder.Configuration.AddConfigVault(
baseUrl: "http://localhost:8080",
applicationName: "my-web-app",
environment: "Production",
apiKey: "cv_...",
configure: options =>
{
options.RefreshInterval = TimeSpan.FromMinutes(5);
options.ThrowOnLoadFailure = true;
options.HttpTimeout = TimeSpan.FromSeconds(30);
}
);
// From existing configuration (reads ConfigVault:* keys)
builder.Configuration.AddConfigVault(builder.Configuration);ConfigVault uses ASP.NET Core Data Protection to encrypt sensitive configuration values. By default, the encryption keys are stored in the database alongside your data. For production deployments, you can configure both where keys are stored and how they are protected at rest.
Storage and protection are independent — you can mix and match any storage provider with any protection provider (with the exception that AWS KMS protection requires AWS Systems Manager storage, since KMS encryption is applied through the SSM persistence layer).
StorageType |
Description | Required Config |
|---|---|---|
Database |
Persisted to PostgreSQL via EF Core (default) | None |
AzureBlobStorage |
Stored in an Azure Blob Storage container | AzureBlobStorage section |
AwsSystemsManager |
Stored as AWS Systems Manager Parameter Store entries | AwsSystemsManager section |
ProtectionType |
Description | Required Config |
|---|---|---|
None |
Keys stored unencrypted (default, suitable for development) | None |
AzureKeyVault |
Keys encrypted with an Azure Key Vault key | AzureKeyVault section |
AwsKms |
Keys encrypted with AWS KMS (requires AwsSystemsManager storage) |
AwsKms section |
Certificate |
Keys encrypted with an X.509 certificate | Certificate section |
Database + no protection (default / development):
{
"DataProtection": {
"StorageType": "Database",
"ProtectionType": "None"
}
}Azure Blob Storage + Azure Key Vault:
{
"DataProtection": {
"StorageType": "AzureBlobStorage",
"ProtectionType": "AzureKeyVault",
"AzureBlobStorage": {
"ConnectionString": "DefaultEndpointsProtocol=https;AccountName=...",
"ContainerName": "dataprotection",
"BlobName": "keys.xml"
},
"AzureKeyVault": {
"KeyIdentifier": "https://myvault.vault.azure.net/keys/dataprotection"
}
}
}Azure Key Vault authentication uses DefaultAzureCredential, which supports Managed Identity, Azure CLI, environment variables, and more. Optionally set TenantId to pin a specific tenant.
AWS Systems Manager + KMS:
{
"DataProtection": {
"StorageType": "AwsSystemsManager",
"ProtectionType": "AwsKms",
"AwsSystemsManager": {
"KeyPrefix": "/DataProtection/ConfigVault/"
},
"AwsKms": {
"KeyId": "alias/configvault-dataprotection"
}
}
}AWS credentials are resolved through the default SDK credential chain (IAM role, environment variables, ~/.aws/credentials, etc.).
Database + Certificate protection:
{
"DataProtection": {
"StorageType": "Database",
"ProtectionType": "Certificate",
"Certificate": {
"Path": "/etc/certs/dataprotection.pfx",
"Password": "cert-password"
}
}
}Alternatively, reference a certificate already installed in the CurrentUser/My store by thumbprint:
{
"DataProtection": {
"StorageType": "Database",
"ProtectionType": "Certificate",
"Certificate": {
"Thumbprint": "A1B2C3D4..."
}
}
}PostgreSQL is the supported database, used with Entity Framework Core and automatic migrations on startup.
Connection string format:
Host=localhost;Database=configvault;Username=postgres;Password=yourpassword
- API Keys — Store application API keys securely (environment variables, secrets manager). Keys are hashed before storage.
- Data Protection Keys — By default, encryption keys are stored unprotected in the database. For production, configure a protection provider (Azure Key Vault, AWS KMS, or a certificate) via the
DataProtectionconfig section. See Data Protection Key Management above. - HTTPS — Always use HTTPS in production.
- CORS — Configure
CorsHoststo restrict allowed origins. - Admin Seeding — The first admin user is created from environment variables on startup only if no users exist.
- Password Policy — Requires 8+ characters with uppercase, lowercase, and digit. Account lockout after 5 failed attempts.
JayArrowz
MIT License