EonaCat.SecretVault/README.md

6.7 KiB

EonaCat.SecretVault


Generate a secure vault for storing secrets like API keys, passwords, and other sensitive information.

Overview

EonaCat.SecretVault is a secure multi-tenant offline vault for storing secrets using AES encryption and SQLite, protected with API key authentication and per-tenant encryption keys. It consists of a server that manages the storage and retrieval of secrets, and a client library that allows you to interact with the server. The server provides a RESTful API for storing and retrieving secrets, while the client library simplifies the interaction with the server.

Features

  • Secure storage of secrets
  • RESTful API for storing and retrieving secrets
  • Client library for easy interaction with the server
  • Multi-tenancy support
  • Supports HMAC authentication for secure access
  • Supports SQLite for data storage
  • Audit logging
  • Cross-platform compatibility
  • Easy to use
  • Open-source and free to use

Extra security notices:

  • Rotate your API keys regularly.
  • Use strong, unique API keys for each tenant.
  • Ensure your ROOT_PROTECTOR_KEY is kept secret and secure.
  • Rotate the ROOT_PROTECTOR_KEY if you suspect it has been compromised.
  • Use HTTPS to encrypt data in transit.

API Keys

  • API keys are generated as secure random 256-bit strings.
  • Only SHA256 hashes of API keys are stored in the database.
  • Incoming API keys are hashed and compared against stored hashes for authentication.

Per-Tenant Encryption Keys

  • Each tenant has a unique 256-bit AES key, encrypted at rest with a master root key (ROOT_PROTECTOR_KEY).
  • Tenant keys are decrypted only in-memory during operations.

🧩 Multi-Tenant Support

Each API key is tied to a unique tenant. Secrets stored by one tenant cannot be retrieved by another.

Tenant mapping is defined in the TenantApiKey table:

ApiKey TenantId abc123-myapikey tenant-001 def456-otherkey tenant-002

🔍 Audit Logs

Every secret operation is logged in the AuditLogs table:

Id TenantId Action Key PerformedBy Timestamp 1 tenant-001 Store db-password apikey-user 2025-07-06 17:23:54 2 tenant-001 Retrieve db-password apikey-user 2025-07-06 17:25:04

HMAC Integrity (Optional) The server computes HMAC-SHA256 for internal verification.

You can expand this to validate incoming payloads by signing client-side with:

var hmac = new HMACSHA256(Convert.FromBase64String(masterKey));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(content));

Environment Variables

  • ROOT_PROTECTOR_KEY: Base64-encoded 256-bit key to encrypt tenant keys.

Example of ROOT_PROTECTOR_KEY:

export ROOT_PROTECTOR_KEY="your-base64-encoded-256-bit-key"

e.g.:

export ROOT_PROTECTOR_KEY="c2VjcmV0cGFzc3dvcmQtbm90LXN0b3JlLWluLWRpc3BsYXktb3V0c2lkZS1lbnZpcm9ubWVudC1rZXktdG8tYmUtdXNlZC1mb3ItZW5jcnlwdGlvbi1rZXktZm9yLXNlY3JldC1zdG9yYWdl"

EonaCat.SecretVault.Client library

How to use the client:

using EonaCat.SecretVault.Client;

var vault = new SecretVaultClient("https://your-vault-url.com/", "your-api-key");

// Store a secret
await vault.StoreAsync("db-password", "S3cr3t!");

// Retrieve a secret
string? secret = await vault.RetrieveAsync("db-password");
Console.WriteLine($"Secret: {secret}");

// Delete a secret
await vault.DeleteAsync("db-password");

How to use the server:

Generating a new tenant:


string GenerateApiKey()
{
    return Convert.ToBase64String(RandomNumberGenerator.GetBytes(32));
}

string HashApiKey(string apiKey)
{
    using var sha256 = SHA256.Create();
    var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(apiKey));
    return Convert.ToBase64String(hash);
}

var rootKey = Environment.GetEnvironmentVariable("ROOT_PROTECTOR_KEY")!;
var tenantKey = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32));

var encryptedTenantKey = TenantKeyProtector.EncryptTenantKey(tenantKey, rootKey);
var apiKey = GenerateApiKey();
var apiKeyHash = HashApiKey(apiKey);

_db.ApiKeys.Add(new TenantApiKey
{
    ApiKey = newApiKey,
    TenantId = "tenant-003",
    EncryptionKey = newTenantKey,
    EncryptedTenantKey = encryptedTenantKey,
    ApiKeyHash = apiKeyHash
});
await _db.SaveChangesAsync();

Edit appsettings.json or use environment variables if needed (default uses SQLite):

{
  "ConnectionStrings": {
    "Default": "Data Source=EonaCatSecretVault.db"
  }
}

Api usage:

Base URL: http://localhost:5000/api/secrets All requests must include:

  • X-Api-Key header with your API key

Generate a new tenant:

POST /api/admin/generate-key?role=ReadWrite

Headers:
  X-Api-Key: abc123-myapikey
  ### Response:
  HTTP/1.1 200
  Body:
  {
      "apiKey": "qY6LZ5i1dNpsQFF2kLE7uUd8Md5FeKfMY5r6KfzH7i8=",
      "role": "ReadWrite"
  }

Revoke an API key:

DELETE /api/admin/revoke-key?key=abc123-myapikey

Headers:
  X-Api-Key: abc123-myapikey

Response:

HTTP/1.1 200 OK
Body:
  "Key revoked"

List all API keys:

GET /api/admin/keys

Headers:
  X-Api-Key: abc123-myapikey

Response:

HTTP/1.1 200 OK
Body:
[
  {
    "apiKey": "qY6LZ5i1dNpsQFF2kLE7uUd8Md5FeKfMY5r6KfzH7i8=",
    "role": "ReadWrite",
    "isActive": true,
    "createdAt": "2025-07-06T17:00:00Z"
  },
  ...
]

Store a secret:

POST /api/secrets/store?key=my-service-token

Headers:
  Content-Type: application/json
  X-Api-Key: abc123-myapikey

Response:

HTTP/1.1 200 OK

Retrieve a secret:

GET /api/secrets/retrieve?key=my-service-token

Headers:
  X-Api-Key: abc123-myapikey

Response:

HTTP/1.1 200 OK
Body:
  "super-secret-value"
using EonaCat.SecretVault.Data;
using EonaCat.SecretVault.Middleware;
using EonaCat.SecretVault.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;

var builder = WebApplication.CreateBuilder(args);

var config = builder.Configuration;

builder.Services.AddDbContext<SecretDbContext>(x => x.UseSqlite("Data Source=EonaCatSecretVault.db"));
builder.Services.AddScoped<SecretService>();
builder.Services.AddScoped<TenantKeyService>();
builder.Services.AddControllers();

var app = builder.Build();

app.UseHsts();
app.UseHttpsRedirection();
app.UseMiddleware<ApiKeyMiddleware>();
app.MapControllers();

using (var scope = app.Services.CreateScope())
{
    var db = scope.ServiceProvider.GetRequiredService<SecretDbContext>();
    db.Database.EnsureCreated();
}

app.Run();

Curl Example:

curl -X POST http://localhost:5000/api/secrets/store?key=MYKEY \
  -H "X-Api-Key: abc123-myapikey" \
  -H "Content-Type: application/json" \
  -d "\"sensitive-data\""

curl http://localhost:5000/api/secrets/retrieve?key=MYKEY \
  -H "X-Api-Key: abc123-myapikey"