2025-12-22 21:34:01 +00:00
|
|
|
# SecretumVault - Complete Post-Quantum Secrets Management System
|
|
|
|
|
|
|
|
|
|
**Version:** 3.0
|
|
|
|
|
**Date:** 2025-12-21
|
2025-12-26 15:48:32 +00:00
|
|
|
**Author:** Jesús Pérez
|
2025-12-22 21:34:01 +00:00
|
|
|
**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**:
|
|
|
|
|
- ✅ Aplicaciones que necesitan PQC ahora
|
|
|
|
|
- ✅ Infraestructura moderna (Kubernetes, SurrealDB)
|
|
|
|
|
- ✅ Compliance NIS2 + post-quantum readiness
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Table of Contents
|
|
|
|
|
|
|
|
|
|
1. [Core Concepts](#core-concepts)
|
|
|
|
|
2. [Architecture](#architecture)
|
|
|
|
|
3. [Secrets Engines](#secrets-engines)
|
|
|
|
|
4. [Authorization with Cedar](#authorization-with-cedar)
|
|
|
|
|
5. [Crypto Backends](#crypto-backends)
|
|
|
|
|
6. [Storage Backends](#storage-backends)
|
|
|
|
|
7. [API Design](#api-design)
|
|
|
|
|
8. [Deployment Modes](#deployment-modes)
|
|
|
|
|
9. [Integration Examples](#integration-examples)
|
|
|
|
|
10. [Implementation Roadmap](#implementation-roadmap)
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Core Concepts
|
|
|
|
|
|
|
|
|
|
### What is SecretumVault?
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
SecretumVault = Secrets Manager + Encryption Service + Key Management
|
|
|
|
|
+ Cedar Policies + Post-Quantum Crypto
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Key Features
|
|
|
|
|
|
|
|
|
|
1. **Secrets Management**
|
|
|
|
|
- Key/Value secrets (static)
|
|
|
|
|
- Dynamic secrets (database, cloud)
|
|
|
|
|
- Transit encryption (EaaS)
|
|
|
|
|
- SSH/TLS certificate generation
|
|
|
|
|
|
|
|
|
|
2. **Post-Quantum Ready**
|
|
|
|
|
- ML-KEM for key encapsulation
|
|
|
|
|
- ML-DSA for signatures
|
|
|
|
|
- Hybrid modes (classical + PQC)
|
|
|
|
|
- Future-proof key rotation
|
|
|
|
|
|
|
|
|
|
3. **Modern Authorization**
|
|
|
|
|
- Cedar policy language (AWS open-source)
|
|
|
|
|
- Attribute-based access control (ABAC)
|
|
|
|
|
- No legacy ACL complexity
|
|
|
|
|
- Policy-as-Code
|
|
|
|
|
|
|
|
|
|
4. **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 │
|
|
|
|
|
├─────────────────────────────────────────────────────────────────┤
|
|
|
|
|
│ │
|
2025-12-26 15:48:32 +00:00
|
|
|
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
|
|
|
|
|
│ │ Request │ │ Auth/Policy │ │ Secrets Router │ │
|
|
|
|
|
│ │ Handler │→ │ Engine │→ │ (Path-based) │ │
|
|
|
|
|
│ └─────────────┘ └──────────────┘ └──────────────────────┘ │
|
2025-12-22 21:34:01 +00:00
|
|
|
│ │ │ │
|
|
|
|
|
│ ↓ ↓ │
|
2025-12-26 15:48:32 +00:00
|
|
|
│ ┌──────────────────┐ ┌────────────────────┐ │
|
|
|
|
|
│ │ Cedar Policies │ │ Secrets Engines │ │
|
|
|
|
|
│ │ (Authorization) │ │ (KV, Transit, │ │
|
|
|
|
|
│ └──────────────────┘ │ Dynamic, PKI) │ │
|
|
|
|
|
│ └────────────────────┘ │
|
2025-12-22 21:34:01 +00:00
|
|
|
│ │ │
|
|
|
|
|
└──────────────────────────────────────────────┼──────────────────┘
|
2025-12-26 15:48:32 +00:00
|
|
|
│ │ │
|
|
|
|
|
│ ┌──────────────────────────────────────┼──────┐ │
|
|
|
|
|
│ │ │ │ │
|
|
|
|
|
│ ▼ ▼ ─────────────┘ ▼ │
|
|
|
|
|
│ ┌───────────────┐ ┌─────────────────────┐ ┌───────────────┐ │
|
|
|
|
|
│ │ Crypto Layer │ │ Storage Layer │ │ Seal/Unseal │ │
|
|
|
|
|
│ ├───────────────┤ ├─────────────────────┤ ├───────────────┤ │
|
|
|
|
|
│ │ • aws-lc-rs │ │ • SurrealDB │ │ • Master Key │ │
|
|
|
|
|
│ │ • RustCrypto │ │ • Filesystem │ │ • Shamir SSS │ │
|
|
|
|
|
│ │ • Tongsuo │ │ • etcd/Consul │ │ • Auto-unseal │ │
|
|
|
|
|
│ │ • OpenSSL │ │ • PostgreSQL │ │ (KMS) │ │
|
|
|
|
|
│ └───────────────┘ └─────────────────────┘ └───────────────┘ │
|
|
|
|
|
└─────────────────────────────────────────────────────────────────┘
|
2025-12-22 21:34:01 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Secrets Engines
|
|
|
|
|
|
|
|
|
|
SecretumVault soporta múltiples "engines" montados en paths:
|
|
|
|
|
|
|
|
|
|
### 1. KV Engine (Key-Value Secrets)
|
|
|
|
|
|
|
|
|
|
**Path**: `secret/`
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
// 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**:
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
// 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**:
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
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).
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
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`
|
|
|
|
|
|
|
|
|
|
```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`
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
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`
|
|
|
|
|
|
|
|
|
|
```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)
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
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:
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
#[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
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
#[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
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# Start server
|
|
|
|
|
svault server --config svault.toml
|
|
|
|
|
|
|
|
|
|
# Initialize
|
|
|
|
|
svault operator init
|
|
|
|
|
|
|
|
|
|
# Unseal
|
|
|
|
|
svault operator unseal <key>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 2. Kubernetes Deployment
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
// 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:
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
// 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)
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
```nu
|
|
|
|
|
# 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
|
|
|
|
|
|
|
|
|
|
```nickel
|
|
|
|
|
# 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`
|
|
|
|
|
|
|
|
|
|
```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
|
2025-12-26 15:13:36 +00:00
|
|
|
├── config/
|
|
|
|
|
│ └── svault.toml.example
|
2025-12-22 21:34:01 +00:00
|
|
|
│
|
|
|
|
|
├── 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
|
|
|
|
|
|
|
|
|
|
```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
|
|
|
|
|
|
|
|
|
|
1. **Validar arquitectura** - ¿Tiene sentido este scope?
|
|
|
|
|
2. **Naming** - ¿`secretumvault`, `svault`, otro?
|
|
|
|
|
3. **Prioridades** - ¿Qué implementar primero?
|
|
|
|
|
4. **Cedar policies** - ¿Es la mejor opción o prefieres ACL simple?
|
|
|
|
|
5. **Integración RustyVault** - ¿Backend? ¿Fork? ¿Proyecto separado?
|
|
|
|
|
|
|
|
|
|
**¿Empezamos con Phase 1 en Claude Code?**
|