36 KiB
SecretumVault - Complete Post-Quantum Secrets Management System
Version: 3.0
Date: 2025-12-21
Author: Jesús Pérez (Kit Digital / Rust Las Palmas)
Project Name: secretumvault or svault
Executive Summary
SecretumVault es un sistema completo de gestión de secretos con:
- ✅ Post-Quantum Crypto desde el diseño (no retrofitted)
- ✅ Secrets Management completo (KV, dynamic, transit)
- ✅ Cedar Policies (modern authorization, no ACL legacy)
- ✅ Multi-Backend Crypto (aws-lc-rs, RustCrypto, Tongsuo)
- ✅ Encryption as a Service (EaaS)
- ✅ Storage Flexible (SurrealDB, filesystem, etcd)
- ✅ API Compatible con HashiCorp Vault (migration path)
- ✅ Rust Native (memory safe, high performance)
NO es:
- ❌ NO tiene auth complejo (OIDC, LDAP) - solo lo esencial
- ❌ NO tiene ACL legacy - usa Cedar policies
- ❌ NO compite con Enterprise Vault features
Es ideal para:
- ✅ Kit Digital projects (PYMES españolas)
- ✅ Aplicaciones que necesitan PQC ahora
- ✅ Infraestructura moderna (Kubernetes, SurrealDB)
- ✅ Compliance NIS2 + post-quantum readiness
Table of Contents
- Core Concepts
- Architecture
- Secrets Engines
- Authorization with Cedar
- Crypto Backends
- Storage Backends
- API Design
- Deployment Modes
- Integration Examples
- Implementation Roadmap
Core Concepts
What is SecretumVault?
SecretumVault = Secrets Manager + Encryption Service + Key Management
+ Cedar Policies + Post-Quantum Crypto
Key Features
-
Secrets Management
- Key/Value secrets (static)
- Dynamic secrets (database, cloud)
- Transit encryption (EaaS)
- SSH/TLS certificate generation
-
Post-Quantum Ready
- ML-KEM for key encapsulation
- ML-DSA for signatures
- Hybrid modes (classical + PQC)
- Future-proof key rotation
-
Modern Authorization
- Cedar policy language (AWS open-source)
- Attribute-based access control (ABAC)
- No legacy ACL complexity
- Policy-as-Code
-
High Performance
- Rust native (no GC pauses)
- Async/await throughout
- Optimized crypto backends
- Efficient storage
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ CLIENT LAYER │
├─────────────────────────────────────────────────────────────────┤
│ REST API │ CLI │ Rust SDK │ Language SDKs (future) │
└──────┬──────────────────────────────────────────────────────────┘
│
┌──────▼──────────────────────────────────────────────────────────┐
│ QUANTUMVAULT CORE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Request │ │ Auth/Policy │ │ Secrets Router │ │
│ │ Handler │→ │ Engine │→ │ (Path-based) │ │
│ └─────────────┘ └──────────────┘ └──────────────────────┘ │
│ │ │ │
│ ↓ ↓ │
│ ┌──────────────────┐ ┌────────────────────┐ │
│ │ Cedar Policies │ │ Secrets Engines │ │
│ │ (Authorization) │ │ (KV, Transit, │ │
│ └──────────────────┘ │ Dynamic, PKI) │ │
│ └────────────────────┘ │
│ │ │
└──────────────────────────────────────────────┼──────────────────┘
│
┌───────────────────────────────────────┼───────────────┐
│ │ │
┌──────▼────────┐ ┌──────────────▼──────┐ ┌▼──────────────┐ │
│ Crypto Layer │ │ Storage Layer │ │ Seal/Unseal │ │
├───────────────┤ ├─────────────────────┤ ├───────────────┤ │
│ • aws-lc-rs │ │ • SurrealDB │ │ • Master Key │ │
│ • RustCrypto │ │ • Filesystem │ │ • Shamir SSS │ │
│ • Tongsuo │ │ • etcd/Consul │ │ • Auto-unseal │ │
│ • OpenSSL │ │ • PostgreSQL │ │ (KMS) │ │
└───────────────┘ └─────────────────────┘ └───────────────┘ │
│
└────────────────────────────────────────────────────────────────┘
Secrets Engines
SecretumVault soporta múltiples "engines" montados en paths:
1. KV Engine (Key-Value Secrets)
Path: secret/
// API
POST /v1/secret/data/myapp/database # Write secret
GET /v1/secret/data/myapp/database # Read secret
DELETE /v1/secret/data/myapp/database # Delete secret
GET /v1/secret/metadata/myapp/database # Get metadata
Implementación:
pub struct KVEngine {
storage: Arc<dyn StorageBackend>,
versioned: bool, // KV v1 o v2
}
#[derive(Serialize, Deserialize)]
pub struct SecretData {
pub data: HashMap<String, serde_json::Value>,
pub metadata: SecretMetadata,
}
#[derive(Serialize, Deserialize)]
pub struct SecretMetadata {
pub version: u64,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub deleted: bool,
pub destroyed: bool,
}
impl SecretsEngine for KVEngine {
async fn write(
&self,
path: &str,
data: HashMap<String, serde_json::Value>,
) -> Result<SecretMetadata> {
let mut metadata = SecretMetadata {
version: self.get_next_version(path).await?,
created_at: Utc::now(),
updated_at: Utc::now(),
deleted: false,
destroyed: false,
};
let secret = SecretData { data, metadata: metadata.clone() };
// Encrypt data before storing
let encrypted = self.encrypt_secret(&secret).await?;
self.storage.store_secret(path, &encrypted).await?;
Ok(metadata)
}
async fn read(&self, path: &str) -> Result<SecretData> {
let encrypted = self.storage.get_secret(path).await?;
let secret = self.decrypt_secret(&encrypted).await?;
if secret.metadata.deleted {
return Err(Error::SecretDeleted);
}
Ok(secret)
}
}
2. Transit Engine (Encryption as a Service)
Path: transit/
Permite a aplicaciones cifrar/descifrar datos sin manejar claves directamente.
// API
POST /v1/transit/encrypt/my-key # Encrypt data
POST /v1/transit/decrypt/my-key # Decrypt data
POST /v1/transit/sign/my-key # Sign data (PQC)
POST /v1/transit/verify/my-key # Verify signature
POST /v1/transit/rewrap/my-key # Re-encrypt with new key version
Implementación:
pub struct TransitEngine {
crypto: Arc<dyn CryptoBackend>,
key_manager: Arc<KeyManager>,
}
#[derive(Deserialize)]
pub struct EncryptRequest {
pub plaintext: String, // Base64
pub context: Option<String>, // Additional authenticated data
}
#[derive(Serialize)]
pub struct EncryptResponse {
pub ciphertext: String, // vault:v1:base64ciphertext
}
impl SecretsEngine for TransitEngine {
async fn handle_encrypt(
&self,
key_name: &str,
req: EncryptRequest,
) -> Result<EncryptResponse> {
// 1. Decode plaintext
let plaintext = base64::decode(&req.plaintext)?;
// 2. Get encryption key (latest version)
let key = self.key_manager.get_key(key_name).await?;
// 3. Encrypt with crypto backend
let ciphertext = match key.algorithm {
KeyAlgorithm::Aes256Gcm => {
self.crypto.encrypt_symmetric(&key.key_data, &plaintext, SymmetricAlgorithm::Aes256Gcm)?
}
KeyAlgorithm::ChaCha20Poly1305 => {
self.crypto.encrypt_symmetric(&key.key_data, &plaintext, SymmetricAlgorithm::ChaCha20Poly1305)?
}
_ => return Err(Error::InvalidAlgorithm),
};
// 4. Format response (Vault-compatible)
let encoded = format!(
"vault:v{}:{}",
key.version,
base64::encode(&ciphertext)
);
Ok(EncryptResponse {
ciphertext: encoded,
})
}
async fn handle_sign(
&self,
key_name: &str,
data: &[u8],
) -> Result<String> {
let key = self.key_manager.get_key(key_name).await?;
// Firmar con PQC (ML-DSA)
let signature = self.crypto.sign(&key.private_key, data)?;
Ok(format!(
"vault:v{}:{}",
key.version,
base64::encode(&signature)
))
}
}
3. PKI Engine (Certificate Authority)
Path: pki/
Genera certificados X.509 (RSA/ECDSA tradicional + PQC experimental).
pub struct PKIEngine {
crypto: Arc<dyn CryptoBackend>,
ca_bundle: Option<CABundle>,
}
#[derive(Deserialize)]
pub struct IssueCertRequest {
pub common_name: String,
pub alt_names: Vec<String>,
pub ttl: String, // e.g., "720h"
pub algorithm: CertAlgorithm, // RSA2048, ECDSA256, ML-DSA-65
}
impl SecretsEngine for PKIEngine {
async fn issue_cert(&self, req: IssueCertRequest) -> Result<Certificate> {
// 1. Generate keypair
let keypair = self.crypto.generate_keypair(
req.algorithm.to_key_algorithm()
)?;
// 2. Create CSR
let csr = create_csr(&keypair, &req.common_name, &req.alt_names)?;
// 3. Sign with CA
let cert = self.ca_bundle
.as_ref()
.ok_or(Error::NoCAConfigured)?
.sign_csr(&csr, parse_duration(&req.ttl)?)?;
Ok(cert)
}
}
4. Dynamic Secrets Engine
Path: database/, aws/, gcp/
Genera credenciales dinámicas on-demand.
pub struct DatabaseEngine {
connections: HashMap<String, DatabaseConnection>,
}
#[derive(Deserialize)]
pub struct GetCredsRequest {
pub role: String, // e.g., "readonly", "admin"
}
#[derive(Serialize)]
pub struct DatabaseCreds {
pub username: String,
pub password: String,
pub lease_duration: u64, // seconds
}
impl SecretsEngine for DatabaseEngine {
async fn get_credentials(
&self,
role: &str,
) -> Result<DatabaseCreds> {
let role_config = self.get_role_config(role).await?;
// Generate random username/password
let username = format!("v-{}-{}", role, generate_id());
let password = generate_secure_password(32);
// Create user in database
let db = self.connections.get(&role_config.connection)
.ok_or(Error::ConnectionNotFound)?;
db.execute(&format!(
"CREATE USER '{}' IDENTIFIED BY '{}';",
username, password
)).await?;
// Grant permissions
for stmt in &role_config.creation_statements {
db.execute(stmt).await?;
}
// Schedule revocation
self.schedule_revocation(&username, role_config.ttl).await?;
Ok(DatabaseCreds {
username,
password,
lease_duration: role_config.ttl.as_secs(),
})
}
}
Authorization with Cedar
En lugar de ACL tradicional, usamos Cedar (AWS open-source policy language).
Cedar Overview
Cedar es:
- ✅ Expresivo y declarativo
- ✅ Verificable formalmente
- ✅ Policy-as-Code
- ✅ Usado por AWS (AVP)
Policy Example
Archivo: policies/allow-read-secrets.cedar
// Permitir a developers leer secrets de su proyecto
permit(
principal in Group::"developers",
action == Action::"read",
resource in Project::"kit-digital"
)
when {
context.environment == "development" ||
context.environment == "staging"
};
// Permitir a CI/CD leer solo secrets de deployment
permit(
principal == ServiceAccount::"github-actions",
action in [Action::"read", Action::"encrypt"],
resource in Path::"secret/deployments/*"
)
when {
context.ip_address in [
"192.30.252.0/22", // GitHub Actions IPs
"185.199.108.0/22"
]
};
// Permitir a admins todo
permit(
principal in Group::"admins",
action,
resource
);
// Denegar escritura a production desde dev environments
forbid(
principal,
action in [Action::"write", Action::"delete"],
resource in Path::"secret/production/*"
)
when {
context.environment != "production"
};
Cedar Integration
Archivo: src/auth/cedar.rs
use cedar_policy::{Authorizer, Context, Entities, PolicySet, Request};
pub struct CedarAuthz {
authorizer: Authorizer,
policies: PolicySet,
entities: Entities,
}
impl CedarAuthz {
pub fn from_files(policy_dir: &str) -> Result<Self> {
let policies = load_policies_from_dir(policy_dir)?;
let entities = load_entities()?; // Groups, users, etc.
Ok(Self {
authorizer: Authorizer::new(),
policies,
entities,
})
}
pub fn is_authorized(
&self,
principal: &str,
action: &str,
resource: &str,
context: RequestContext,
) -> Result<bool> {
let request = Request::new(
Some(principal.parse()?),
Some(action.parse()?),
Some(resource.parse()?),
Context::from_json_value(serde_json::to_value(context)?)?,
)?;
let response = self.authorizer.is_authorized(
&request,
&self.policies,
&self.entities,
);
Ok(response.decision() == cedar_policy::Decision::Allow)
}
}
#[derive(Serialize)]
pub struct RequestContext {
pub environment: String,
pub ip_address: String,
pub timestamp: i64,
pub mfa_verified: bool,
}
// Integración con Vault
impl Vault {
async fn handle_request(
&self,
req: VaultRequest,
auth_token: &str,
) -> Result<VaultResponse> {
// 1. Validate token
let principal = self.auth.validate_token(auth_token).await?;
// 2. Build context
let context = RequestContext {
environment: req.headers.get("X-Vault-Environment")
.unwrap_or("unknown").to_string(),
ip_address: req.remote_addr.to_string(),
timestamp: Utc::now().timestamp(),
mfa_verified: self.auth.is_mfa_verified(&principal).await?,
};
// 3. Authorize with Cedar
let authorized = self.cedar.is_authorized(
&principal.id,
&req.operation, // "read", "write", "delete"
&req.path, // "secret/production/db"
context,
)?;
if !authorized {
return Err(Error::PermissionDenied);
}
// 4. Route to appropriate engine
self.route_request(req).await
}
}
Entities Definition
Archivo: entities.json
[
{
"uid": {"type": "User", "id": "alice"},
"attrs": {
"email": "alice@example.com",
"groups": ["developers", "admins"]
},
"parents": [
{"type": "Group", "id": "developers"},
{"type": "Group", "id": "admins"}
]
},
{
"uid": {"type": "ServiceAccount", "id": "github-actions"},
"attrs": {
"type": "ci-cd",
"trusted": true
}
},
{
"uid": {"type": "Group", "id": "developers"},
"attrs": {},
"parents": []
}
]
Crypto Backends
Backend Interface (ya definido anteriormente)
pub trait CryptoBackend: Debug + Send + Sync {
fn backend_type(&self) -> BackendType;
// Asymmetric
fn generate_keypair(&self, algorithm: KeyAlgorithm) -> Result<KeyPair>;
fn sign(&self, key: &PrivateKey, data: &[u8]) -> Result<Vec<u8>>;
fn verify(&self, key: &PublicKey, data: &[u8], sig: &[u8]) -> Result<bool>;
// Symmetric
fn encrypt_symmetric(&self, key: &[u8], data: &[u8], alg: SymmetricAlgorithm) -> Result<Vec<u8>>;
fn decrypt_symmetric(&self, key: &[u8], data: &[u8], alg: SymmetricAlgorithm) -> Result<Vec<u8>>;
// KEM (for PQC)
fn kem_encapsulate(&self, key: &PublicKey) -> Result<(Vec<u8>, Vec<u8>)>;
fn kem_decapsulate(&self, key: &PrivateKey, ct: &[u8]) -> Result<Vec<u8>>;
// Random
fn random_bytes(&self, len: usize) -> Result<Vec<u8>>;
}
Hybrid Crypto Mode
Para defense-in-depth, soportar hybrid:
pub struct HybridBackend {
classical: Arc<dyn CryptoBackend>, // OpenSSL/aws-lc (RSA/ECDSA)
pqc: Arc<dyn CryptoBackend>, // aws-lc-rs (ML-KEM/ML-DSA)
}
impl CryptoBackend for HybridBackend {
fn kem_encapsulate(&self, key: &PublicKey) -> Result<(Vec<u8>, Vec<u8>)> {
// Encapsulate with both
let (ct_classical, ss_classical) = self.classical.kem_encapsulate(key)?;
let (ct_pqc, ss_pqc) = self.pqc.kem_encapsulate(key)?;
// Combine ciphertexts
let combined_ct = [ct_classical, ct_pqc].concat();
// Derive shared secret from both using KDF
let combined_ss = kdf_combine(&ss_classical, &ss_pqc)?;
Ok((combined_ct, combined_ss))
}
}
Storage Backends
Storage Trait
#[async_trait]
pub trait StorageBackend: Debug + Send + Sync {
// Secrets
async fn store_secret(&self, path: &str, data: &EncryptedData) -> Result<()>;
async fn get_secret(&self, path: &str) -> Result<EncryptedData>;
async fn delete_secret(&self, path: &str) -> Result<()>;
async fn list_secrets(&self, prefix: &str) -> Result<Vec<String>>;
// Keys
async fn store_key(&self, key: &StoredKey) -> Result<()>;
async fn get_key(&self, key_id: &str) -> Result<StoredKey>;
// Policies
async fn store_policy(&self, name: &str, policy: &str) -> Result<()>;
async fn get_policy(&self, name: &str) -> Result<String>;
async fn list_policies(&self) -> Result<Vec<String>>;
// Leases (for dynamic secrets)
async fn store_lease(&self, lease: &Lease) -> Result<()>;
async fn get_lease(&self, lease_id: &str) -> Result<Lease>;
async fn delete_lease(&self, lease_id: &str) -> Result<()>;
async fn list_expiring_leases(&self, before: DateTime<Utc>) -> Result<Vec<Lease>>;
}
SurrealDB Implementation
pub struct SurrealDBBackend {
db: Surreal<Client>,
}
#[async_trait]
impl StorageBackend for SurrealDBBackend {
async fn store_secret(&self, path: &str, data: &EncryptedData) -> Result<()> {
let record = SecretRecord {
path: path.to_string(),
data: data.clone(),
created_at: Utc::now(),
updated_at: Utc::now(),
};
let _: Option<SecretRecord> = self.db
.create(("secrets", path))
.content(record)
.await?;
Ok(())
}
async fn list_expiring_leases(&self, before: DateTime<Utc>) -> Result<Vec<Lease>> {
let leases: Vec<Lease> = self.db
.query("SELECT * FROM leases WHERE expires_at <= $before")
.bind(("before", before))
.await?
.take(0)?;
Ok(leases)
}
}
API Design
Vault-Compatible REST API
// Core paths
GET /v1/sys/health // Health check
POST /v1/sys/init // Initialize vault
POST /v1/sys/unseal // Unseal vault
POST /v1/sys/seal // Seal vault
GET /v1/sys/seal-status // Get seal status
// Auth
POST /v1/auth/token/create // Create token
POST /v1/auth/token/revoke // Revoke token
POST /v1/auth/token/lookup // Lookup token
// Secrets (KV)
GET /v1/secret/data/:path // Read secret
POST /v1/secret/data/:path // Write secret
DELETE /v1/secret/data/:path // Delete secret
GET /v1/secret/metadata/:path // Get metadata
LIST /v1/secret/metadata/:path // List secrets
// Transit (EaaS)
POST /v1/transit/encrypt/:key // Encrypt data
POST /v1/transit/decrypt/:key // Decrypt data
POST /v1/transit/sign/:key // Sign data (PQC)
POST /v1/transit/verify/:key // Verify signature
POST /v1/transit/keys/:key // Create key
POST /v1/transit/keys/:key/rotate // Rotate key
// PKI
POST /v1/pki/root/generate // Generate root CA
POST /v1/pki/issue/:role // Issue certificate
POST /v1/pki/revoke // Revoke certificate
// Database (Dynamic)
GET /v1/database/creds/:role // Get dynamic creds
POST /v1/database/config/:name // Configure connection
// Policies (Cedar)
GET /v1/sys/policies // List policies
GET /v1/sys/policy/:name // Read policy
PUT /v1/sys/policy/:name // Write policy
DELETE /v1/sys/policy/:name // Delete policy
Request/Response Format
#[derive(Serialize, Deserialize)]
pub struct VaultRequest {
pub path: String,
pub operation: Operation, // Read, Write, Delete, List
pub data: Option<serde_json::Value>,
pub headers: HashMap<String, String>,
}
#[derive(Serialize, Deserialize)]
pub struct VaultResponse {
pub request_id: String,
pub lease_id: Option<String>,
pub renewable: bool,
pub lease_duration: u64,
pub data: Option<serde_json::Value>,
pub warnings: Vec<String>,
pub auth: Option<AuthInfo>,
}
Deployment Modes
1. Standalone Server
# Start server
svault server --config svault.toml
# Initialize
svault operator init
# Unseal
svault operator unseal <key>
2. Kubernetes Deployment
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: secretumvault
spec:
serviceName: secretumvault
replicas: 3
selector:
matchLabels:
app: secretumvault
template:
metadata:
labels:
app: secretumvault
spec:
containers:
- name: vault
image: secretumvault:latest
ports:
- containerPort: 8200
name: api
env:
- name: QVAULT_BACKEND
value: "aws-lc"
- name: QVAULT_STORAGE
value: "surrealdb"
volumeMounts:
- name: config
mountPath: /etc/secretumvault
- name: data
mountPath: /var/secretumvault
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
3. Library Mode (Embedded)
// En tu aplicación
use secretumvault::{Vault, VaultConfig};
#[tokio::main]
async fn main() -> Result<()> {
let config = VaultConfig::builder()
.crypto_backend(BackendType::AwsLc)
.storage_backend(StorageType::SurrealDB)
.build()?;
let vault = Vault::new(config).await?;
// Usar vault embebido
vault.write("secret/myapp/db", json!({
"username": "admin",
"password": "secret123"
})).await?;
Ok(())
}
Integration Examples
1. Con RustyVault (como Backend)
SecretumVault puede servir como crypto backend de RustyVault:
// RustyVault usa SecretumVault para crypto
use secretumvault::CryptoService;
pub struct RustyVaultWithPQC {
secrets: RustyVaultCore,
crypto: Arc<CryptoService>, // SecretumVault crypto
}
impl RustyVaultWithPQC {
async fn sign_certificate(&self, csr: &[u8]) -> Result<Vec<u8>> {
// Usar SecretumVault para firmar con ML-DSA
self.crypto.sign("ca-key", csr).await
}
}
2. Con provctl (Orchestration)
use secretumvault::Vault;
async fn provision_secure_machine(machine_id: &str) -> Result<()> {
let vault = Vault::connect("https://vault.internal:8200").await?;
// Get dynamic SSH credentials
let ssh_creds = vault.read(&format!("ssh/creds/{}", machine_id)).await?;
// Encrypt machine config
let encrypted_config = vault.transit_encrypt(
"machine-config-key",
&machine_config.as_bytes()
).await?;
// Deploy
deploy_machine(machine_id, &ssh_creds, &encrypted_config).await?;
Ok(())
}
3. Con Nushell Scripts
# vault-ops.nu
export def get-db-creds [role: string] {
let response = (
http get http://vault:8200/v1/database/creds/$role
-H [X-Vault-Token $env.VAULT_TOKEN]
)
{
username: $response.data.username,
password: $response.data.password,
expires: ($response.lease_duration | into datetime)
}
}
export def encrypt-file [file: string, key: string] {
let plaintext = (open $file | encode base64)
let response = (
http post http://vault:8200/v1/transit/encrypt/$key {
plaintext: $plaintext
} -H [X-Vault-Token $env.VAULT_TOKEN]
)
$response.data.ciphertext | save $"($file).encrypted"
}
# Uso
get-db-creds "readonly" | save db-creds.json
encrypt-file "sensitive.txt" "app-key"
4. Con Nickel Config
# vault-bootstrap.ncl
let Vault = {
server = "https://vault.internal:8200",
policies = {
developers = m%"
permit(
principal in Group::"developers",
action == Action::"read",
resource in Path::"secret/dev/*"
);
"%,
ci_cd = m%"
permit(
principal == ServiceAccount::"github-actions",
action in [Action::"read", Action::"encrypt"],
resource in Path::"secret/deployments/*"
);
"%,
},
secrets = {
database = {
connection = "postgresql://vault-db:5432/app",
roles = {
readonly = {
creation_statements = [
"CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';",
"GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";"
],
ttl = "1h"
},
admin = {
creation_statements = [
"CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}' SUPERUSER;"
],
ttl = "15m"
}
}
}
}
}
{
vault = Vault
}
Implementation Roadmap
Phase 1: Foundation (Week 1-3)
Week 1: Core Architecture
- Project structure
- Storage trait + filesystem implementation
- Basic crypto backend (OpenSSL)
- Seal/Unseal mechanism with Shamir
Week 2: KV Engine
- KV secrets engine (v2 with versioning)
- Encryption at rest
- API handlers for KV
- CLI: read/write/delete
Week 3: Auth & Cedar
- Token-based authentication
- Cedar policy integration
- Policy evaluation
- Basic ACL tests
Phase 2: Crypto Engines (Week 4-6)
Week 4: Transit Engine
- Encrypt/decrypt endpoints
- Key rotation
- Multiple algorithm support
- Rewrap functionality
Week 5: PQC Integration
- aws-lc-rs backend
- ML-KEM implementation
- ML-DSA signatures
- Hybrid mode (classical + PQC)
Week 6: PKI Engine
- Root CA generation
- Certificate issuance (RSA/ECDSA)
- CRL/OCSP support
- PQC certificates (experimental)
Phase 3: Dynamic Secrets (Week 7-8)
Week 7: Database Engine
- PostgreSQL support
- MySQL support
- Lease management
- Automatic revocation
Week 8: Storage Backends
- SurrealDB implementation
- etcd/Consul support
- Migration tools
Phase 4: Production Ready (Week 9-10)
Week 9: High Availability
- Leader election
- Replication
- Auto-unseal with KMS
- Performance optimizations
Week 10: Polish & Docs
- API documentation (OpenAPI)
- Deployment guides
- Security audit
- Benchmarks
Configuration Example
Archivo: svault.toml
[vault]
# Crypto backend
crypto_backend = "aws-lc" # aws-lc | rustcrypto | tongsuo | openssl
# Storage backend
storage_backend = "surrealdb" # surrealdb | filesystem | etcd | consul
[server]
address = "0.0.0.0:8200"
tls_cert = "/etc/svault/tls/cert.pem"
tls_key = "/etc/svault/tls/key.pem"
# Mutual TLS (opcional)
tls_client_ca = "/etc/svault/tls/ca.pem"
[storage.surrealdb]
endpoint = "ws://localhost:8000"
namespace = "vault"
database = "production"
username = "vault"
password = "${SURREAL_PASSWORD}"
[storage.filesystem]
path = "/var/svault/data"
[seal]
type = "shamir" # shamir | auto | transit
shares = 5
threshold = 3
# Auto-unseal con KMS (opcional)
[seal.auto]
type = "aws-kms" # aws-kms | gcp-kms | azure-kv
key_id = "arn:aws:kms:..."
[auth.cedar]
policies_dir = "/etc/svault/policies"
entities_file = "/etc/svault/entities.json"
[engines]
# Secrets engines montados
kv = { path = "secret/", versioned = true }
transit = { path = "transit/" }
pki = { path = "pki/" }
database = { path = "database/" }
[logging]
level = "info"
format = "json"
output = "/var/log/svault/vault.log"
[telemetry]
prometheus_port = 9090
Project Structure
secretumvault/
├── Cargo.toml
├── README.md
├── svault.toml.example
│
├── src/
│ ├── lib.rs
│ ├── main.rs # Server binary
│ │
│ ├── core/
│ │ ├── mod.rs
│ │ ├── vault.rs # Main Vault struct
│ │ ├── seal.rs # Seal/Unseal logic
│ │ └── router.rs # Request routing
│ │
│ ├── engines/
│ │ ├── mod.rs
│ │ ├── kv.rs # KV engine
│ │ ├── transit.rs # Transit engine
│ │ ├── pki.rs # PKI engine
│ │ └── database.rs # Dynamic secrets
│ │
│ ├── auth/
│ │ ├── mod.rs
│ │ ├── token.rs # Token auth
│ │ └── cedar.rs # Cedar integration
│ │
│ ├── crypto/
│ │ ├── mod.rs
│ │ ├── backend.rs # Trait
│ │ ├── aws_lc.rs
│ │ ├── rustcrypto.rs
│ │ └── tongsuo.rs
│ │
│ ├── storage/
│ │ ├── mod.rs
│ │ ├── filesystem.rs
│ │ ├── surrealdb.rs
│ │ ├── etcd.rs
│ │ └── consul.rs
│ │
│ ├── api/
│ │ ├── mod.rs
│ │ ├── server.rs
│ │ └── handlers/
│ │ ├── sys.rs
│ │ ├── secret.rs
│ │ ├── transit.rs
│ │ └── pki.rs
│ │
│ ├── shamir/
│ │ └── mod.rs
│ │
│ └── error.rs
│
├── bin/
│ └── svault.rs # CLI tool
│
├── policies/
│ ├── default.cedar
│ ├── developers.cedar
│ └── ci-cd.cedar
│
├── scripts/
│ ├── vault-ops.nu # Nushell helpers
│ └── init-vault.sh
│
├── configs/
│ ├── vault-config.ncl
│ └── bootstrap.ncl
│
└── tests/
├── integration/
├── engines/
└── auth/
Cargo.toml
[package]
name = "secretumvault"
version = "0.1.0"
edition = "2021"
authors = ["Jesús Pérez <contact@jesusperez.pro>"]
description = "Post-quantum ready secrets management system"
license = "Apache-2.0"
[features]
default = ["aws-lc", "filesystem", "server"]
# Crypto backends
aws-lc = ["aws-lc-rs"]
rustcrypto = ["ml-kem", "ml-dsa", "slh-dsa"]
tongsuo = ["dep:tongsuo"]
openssl = ["dep:openssl"]
# Storage backends
filesystem = []
surrealdb-storage = ["surrealdb"]
etcd-storage = ["etcd-client"]
consul-storage = ["consulrs"]
# Components
server = ["axum", "tower-http"]
cli = ["clap"]
cedar = ["cedar-policy"]
[dependencies]
# Core
tokio = { version = "1.35", features = ["full"] }
async-trait = "0.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
toml = "0.8"
thiserror = "1.0"
anyhow = "1.0"
chrono = { version = "0.4", features = ["serde"] }
tracing = "0.1"
tracing-subscriber = "0.3"
# Crypto
aws-lc-rs = { version = "1.9", features = ["unstable"], optional = true }
ml-kem = { version = "0.2", optional = true }
ml-dsa = { version = "0.2", optional = true }
slh-dsa = { version = "0.2", optional = true }
openssl = { version = "0.10", optional = true }
tongsuo = { version = "0.3", optional = true }
# Shamir Secret Sharing
sharks = "0.5"
# Cedar policies
cedar-policy = { version = "3.0", optional = true }
# Storage
surrealdb = { version = "1.5", optional = true }
etcd-client = { version = "0.13", optional = true }
consulrs = { version = "0.1", optional = true }
# Server
axum = { version = "0.7", optional = true, features = ["macros"] }
tower-http = { version = "0.5", optional = true, features = ["cors", "trace"] }
# CLI
clap = { version = "4.5", optional = true, features = ["derive"] }
# Utilities
uuid = { version = "1.6", features = ["v4", "serde"] }
base64 = "0.21"
hex = "0.4"
[dev-dependencies]
tempfile = "3.8"
wiremock = "0.5"
[[bin]]
name = "svault"
path = "src/main.rs"
[[bin]]
name = "svault-cli"
path = "bin/svault.rs"
required-features = ["cli"]
¿Por qué SecretumVault vs. Solo Crypto Service?
| Feature | Crypto Service Solo | SecretumVault Completo |
|---|---|---|
| Secrets Management | ❌ | ✅ KV + Dynamic |
| Encryption as a Service | ⚠️ Básico | ✅ Transit engine completo |
| Authorization | ❌ | ✅ Cedar policies |
| PKI/Certificates | ❌ | ✅ Full CA |
| Dynamic Secrets | ❌ | ✅ Database, cloud |
| Lease Management | ❌ | ✅ Auto-revocation |
| Audit Logging | ❌ | ✅ Completo |
| High Availability | ❌ | ✅ Replication |
| Vault API Compatible | ❌ | ✅ Migration path |
Next Steps
- Validar arquitectura - ¿Tiene sentido este scope?
- Naming - ¿
secretumvault,svault, otro? - Prioridades - ¿Qué implementar primero?
- Cedar policies - ¿Es la mejor opción o prefieres ACL simple?
- Integración RustyVault - ¿Backend? ¿Fork? ¿Proyecto separado?
¿Empezamos con Phase 1 en Claude Code?