- Add badges, competitive comparison, and 30-sec demo to README - Add Production Status section showing OQS backend is production-ready - Mark PQC KEM/signing operations complete in roadmap - Fix GitHub URL - Create CHANGELOG.md documenting all recent changes Positions SecretumVault as first Rust vault with production PQC.
22 KiB
SecretumVault How-To Guide
Step-by-step instructions for common tasks with SecretumVault.
Table of Contents
- Quick Start (CLI + Filesystem)
- Getting Started
- Initialize Vault
- Unseal Vault
- Manage Secrets
- Configure Engines
- Setup Authorization
- Configure TLS
- Integrate with Kubernetes
- Backup & Restore
- Monitor & Troubleshoot
Quick Start (CLI + Filesystem)
Fastest way to get SecretumVault running locally with CLI and filesystem storage.
Prerequisites
# Rust toolchain installed
rustc --version # Should be 1.75+
# Build with server and CLI features
cd secretumvault
cargo build --features server,cli
Step 1: Create Configuration
# Create config file
cat > config/svault.toml <<'EOF'
[vault]
crypto_backend = "openssl"
[server]
address = "0.0.0.0:8200"
[storage]
backend = "filesystem"
[storage.filesystem]
path = "data"
[seal]
seal_type = "shamir"
[seal.shamir]
shares = 5
threshold = 3
[engines.kv]
path = "/secret"
versioned = true
[engines.transit]
path = "/transit"
[logging]
level = "info"
format = "json"
EOF
Step 2: Start Server (Terminal 1)
cargo run --features server,cli -- server -c config/svault.toml
Expected output:
{"level":"INFO","message":"Loading configuration from \"config/svault.toml\""}
{"level":"INFO","message":"Vault initialized successfully"}
{"level":"WARN","message":"Starting HTTP server on http://0.0.0.0:8200"}
{"level":"WARN","message":"TLS not configured. For production, configure tls_cert and tls_key"}
Leave this terminal running.
Step 3: Initialize Vault (Terminal 2)
# Open new terminal
cargo run --features server,cli -- operator init --shares 5 --threshold 3
Expected output:
Vault Initialization
====================
Unseal Key 1: YjVmN2E4ZDktMzQ1Ni03ODkwLWFiY2QtZWYxMjM0NTY3ODkw
Unseal Key 2: MmQ3ZjRhOGMtOTAxMi0zNDU2LTc4OTAtYWJjZGVmMTIzNDU2
Unseal Key 3: OGNhYjEyMzQtNTY3OC05MDEyLTM0NTYtNzg5MGFiY2RlZjEy
Unseal Key 4: ZjEyMzQ1NjctODkwMS0yMzQ1LTY3ODktMDEyMzQ1Njc4OTAx
Unseal Key 5: YWJjZGVmMTIzNC01Njc4LTkwMTItMzQ1Ni03ODkwYWJjZGVm
Initial Root Token: hvs.CAESIJ4k8n2jW8h3mK...
IMPORTANT: Store these keys securely!
- You need 3 keys to unseal the vault
- If lost, the vault cannot be unsealed
- Root token grants full access
⚠️ CRITICAL: Copy and save all keys immediately to a password manager!
Step 4: Verify Vault Status
# Check if sealed
curl -s http://localhost:8200/v1/sys/status | jq .
Expected output:
{
"status": "success",
"data": {
"sealed": true,
"initialized": true,
"engines": ["/secret", "/transit"]
}
}
Note: "sealed": true means vault is locked and cannot store secrets yet.
Step 5: Unseal Vault
# Use 3 of the 5 unseal keys from Step 3
cargo run --features server,cli -- operator unseal \
--shares "YjVmN2E4ZDktMzQ1Ni03ODkwLWFiY2QtZWYxMjM0NTY3ODkw" \
--shares "MmQ3ZjRhOGMtOTAxMi0zNDU2LTc4OTAtYWJjZGVmMTIzNDU2" \
--shares "OGNhYjEyMzQtNTY3OC05MDEyLTM0NTYtNzg5MGFiY2RlZjEy"
Expected output:
✓ Vault unsealed successfully!
Step 6: Verify Unsealed Status
curl -s http://localhost:8200/v1/sys/status | jq .data.sealed
Expected output: false
Now the vault is ready to store secrets!
Step 7: Store Your First Secret
curl -X POST http://localhost:8200/v1/secret/data/myapp/database \
-H "Content-Type: application/json" \
-d '{
"username": "admin",
"password": "supersecret123",
"host": "db.example.com"
}'
Expected output:
{
"status": "success",
"data": {"path": "data/myapp/database"},
"error": null
}
Step 8: Verify File Was Created
# List files in storage
find data/secrets -type f
Expected output:
data/secrets/secret/data/myapp/database
# View encrypted content (JSON format)
cat data/secrets/secret/data/myapp/database
Expected output (encrypted):
{
"ciphertext": [12,45,78,90,...],
"nonce": [34,56,78,90,...],
"algorithm": "AES-256-GCM"
}
Note: The data is encrypted at rest using the master key.
Step 9: Read Secret Back
curl -s http://localhost:8200/v1/secret/data/myapp/database | jq .
Expected output (decrypted):
{
"status": "success",
"data": {
"username": "admin",
"password": "supersecret123",
"host": "db.example.com"
}
}
Step 10: List All Secrets
curl -s http://localhost:8200/v1/secret/data/ | jq .
Common Issues
Q: Why are data/secrets/, data/keys/ folders empty?
A: The vault is sealed. Folders are created automatically but files only appear after:
- Vault is initialized (
operator init) - Vault is unsealed (
operator unsealwith 3+ keys) - Secrets are stored (
POST /v1/secret/data/...)
Q: Getting "sealed": true but I unsealed it?
A: Vault seals automatically on restart. Run operator unseal again after each server restart.
Q: Can't store secrets, getting errors?
A: Verify vault is unsealed:
curl -s http://localhost:8200/v1/sys/status | jq .data.sealed
# Must return: false
Q: Where are my encryption keys stored?
A: Keys are in memory only when unsealed. The master key is sealed using Shamir Secret Sharing and requires threshold unseal keys to reconstruct.
Next Steps
- Enable TLS: See Configure TLS section
- Create policies: See Setup Authorization section
- Use Transit Engine: See Configure Engines section
- Production deployment: See Deployment Guide
Getting Started
1. Start Vault Locally
Using Docker Compose (recommended for development):
# Navigate to project
cd secretumvault
# Build image
docker build -t secretumvault:latest .
# Start all services
docker-compose up -d
# Verify vault is running
curl http://localhost:8200/v1/sys/health
Using Cargo:
# Create configuration
cat > svault.toml <<'EOF'
[vault]
crypto_backend = "openssl"
[server]
address = "0.0.0.0"
port = 8200
[storage]
backend = "etcd"
[storage.etcd]
endpoints = ["http://localhost:2379"]
[seal]
seal_type = "shamir"
threshold = 2
shares = 3
[engines.kv]
path = "secret/"
versioned = true
[logging]
level = "info"
format = "json"
EOF
# Start vault (requires etcd running)
cargo run --release -- server --config svault.toml
2. Verify Health
curl http://localhost:8200/v1/sys/health
Response:
{
"initialized": false,
"sealed": true,
"standby": false,
"performance_standby": false,
"replication_performance_mode": "disabled",
"replication_dr_mode": "disabled",
"server_time_utc": 1703142600,
"version": "0.1.0"
}
Key fields:
initialized: false- Vault not initialized yetsealed: true- Master key is sealed (expected before initialization)
Initialize Vault
1. Generate Unseal Keys
Create a request to initialize vault with Shamir Secret Sharing:
curl -X POST http://localhost:8200/v1/sys/init \
-H "Content-Type: application/json" \
-d '{
"shares": 5,
"threshold": 3
}'
Parameters:
shares: 5- Total unseal keys generated (5 people get 1 key each)threshold: 3- Need 3 keys to unseal (quorum)
Response:
{
"keys": [
"key_1_base64_encoded",
"key_2_base64_encoded",
"key_3_base64_encoded",
"key_4_base64_encoded",
"key_5_base64_encoded"
],
"root_token": "root_token_abc123def456"
}
2. Store Keys Securely
CRITICAL: Store unseal keys immediately in a secure location!
Save in password manager (Bitwarden, 1Password, LastPass):
- Each unseal key separately (don't store all together)
- Distribute keys to different people/locations
- Test that stored keys are retrievable
Save root token separately:
- Store in same password manager
- Label clearly: "Root Token - SecretumVault"
- Keep temporary access only
3. Verify Initialization
curl http://localhost:8200/v1/sys/health
Response should now show initialized: true and sealed: true
Unseal Vault
Vault must be unsealed before it can serve requests.
1. Unseal with Keys
You need threshold keys (e.g., 3 of 5) to unseal.
Unseal with first key:
curl -X POST http://localhost:8200/v1/sys/unseal \
-H "Content-Type: application/json" \
-d '{
"key": "first_unseal_key_from_storage"
}'
Response:
{
"sealed": true,
"t": 3,
"n": 5,
"progress": 1
}
Progress shows 1/3 keys provided.
Unseal with second key:
curl -X POST http://localhost:8200/v1/sys/unseal \
-H "Content-Type: application/json" \
-d '{
"key": "second_unseal_key_from_storage"
}'
Response shows progress: 2/3
Unseal with third key (final):
curl -X POST http://localhost:8200/v1/sys/unseal \
-H "Content-Type: application/json" \
-d '{
"key": "third_unseal_key_from_storage"
}'
Response:
{
"sealed": false,
"t": 3,
"n": 5,
"progress": 0
}
sealed: false means vault is now unsealed!
2. Verify Unsealed State
curl http://localhost:8200/v1/sys/health
Should show sealed: false
3. Auto-Unseal (Future)
For production, configure auto-unseal via AWS KMS or GCP Cloud KMS (planned):
[seal]
seal_type = "aws-kms"
[seal.aws-kms]
key_id = "arn:aws:kms:us-east-1:account:key/id"
region = "us-east-1"
Manage Secrets
1. Store a Secret
HTTP Request:
curl -X POST http://localhost:8200/v1/secret/data/myapp \
-H "X-Vault-Token: $VAULT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"data": {
"username": "admin",
"password": "supersecret123",
"api_key": "sk_live_abc123"
}
}'
Environment variable setup:
# From initialization response
export VAULT_TOKEN="root_token_abc123"
Response:
{
"request_id": "req_123",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": null,
"wrap_info": null,
"warnings": null,
"auth": null
}
Status 201 Created indicates success.
2. Read a Secret
HTTP Request:
curl -X GET http://localhost:8200/v1/secret/data/myapp \
-H "X-Vault-Token: $VAULT_TOKEN"
Response:
{
"request_id": "req_124",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": {
"data": {
"username": "admin",
"password": "supersecret123",
"api_key": "sk_live_abc123"
},
"metadata": {
"created_time": "2025-12-21T10:30:00Z",
"deletion_time": "",
"destroyed": false,
"version": 1
}
}
}
Extract secret data:
# Get password field
curl -s http://localhost:8200/v1/secret/data/myapp \
-H "X-Vault-Token: $VAULT_TOKEN" | jq '.data.data.password'
Output: "supersecret123"
3. Update a Secret
curl -X POST http://localhost:8200/v1/secret/data/myapp \
-H "X-Vault-Token: $VAULT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"data": {
"username": "admin",
"password": "newsecret456",
"api_key": "sk_live_abc123"
}
}'
New version created (version 2). Previous versions retained.
4. Delete a Secret
curl -X DELETE http://localhost:8200/v1/secret/data/myapp \
-H "X-Vault-Token: $VAULT_TOKEN"
Soft delete: metadata retained, data destroyed.
5. List Secrets
curl -X LIST http://localhost:8200/v1/secret/metadata \
-H "X-Vault-Token: $VAULT_TOKEN"
Response:
{
"data": {
"keys": [
"myapp",
"database-prod",
"aws-credentials"
]
}
}
6. Restore from Version
View available versions:
curl -X GET http://localhost:8200/v1/secret/metadata/myapp \
-H "X-Vault-Token: $VAULT_TOKEN"
Response shows all versions with timestamps.
Get specific version:
curl -X GET http://localhost:8200/v1/secret/data/myapp?version=1 \
-H "X-Vault-Token: $VAULT_TOKEN"
Configure Engines
1. Enable Additional Engines
Edit svault.toml:
[engines.kv]
path = "secret/"
versioned = true
[engines.transit]
path = "transit/"
versioned = true
[engines.pki]
path = "pki/"
versioned = false
[engines.database]
path = "database/"
versioned = false
Restart vault:
# Docker Compose
docker-compose restart vault
# Or Cargo
# Kill running process (Ctrl+C) and restart
cargo run --release -- server --config svault.toml
2. Use Transit Engine (Encryption)
Create encryption key:
curl -X POST http://localhost:8200/v1/transit/keys/my-key \
-H "X-Vault-Token: $VAULT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"exportable": false,
"key_size": 256,
"type": "aes-gcm"
}'
Encrypt data:
# Plaintext must be base64 encoded
PLAINTEXT=$(echo -n "sensitive data" | base64)
curl -X POST http://localhost:8200/v1/transit/encrypt/my-key \
-H "X-Vault-Token: $VAULT_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"plaintext\": \"$PLAINTEXT\"}"
Response:
{
"data": {
"ciphertext": "vault:v1:abc123def456..."
}
}
Decrypt data:
CIPHERTEXT="vault:v1:abc123def456..."
curl -X POST http://localhost:8200/v1/transit/decrypt/my-key \
-H "X-Vault-Token: $VAULT_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"ciphertext\": \"$CIPHERTEXT\"}"
Response:
{
"data": {
"plaintext": "c2Vuc2l0aXZlIGRhdGE="
}
}
Decode plaintext:
echo "c2Vuc2l0aXZlIGRhdGE=" | base64 -d
# Output: sensitive data
3. Mount at Custom Path
Change mount path in config:
[engines.kv]
path = "app-secrets/" # Instead of "secret/"
versioned = true
Then access at:
curl http://localhost:8200/v1/app-secrets/data/myapp \
-H "X-Vault-Token: $VAULT_TOKEN"
Setup Authorization
1. Create Cedar Policies
Create policy directory:
mkdir -p /etc/secretumvault/policies
Create policy file:
cat > /etc/secretumvault/policies/default.cedar <<'EOF'
permit (
principal,
action,
resource
) when {
principal has policies &&
principal.policies.contains("admin")
};
deny (
principal,
action == Action::"write",
resource
) unless {
context.time_of_day < 20:00
};
EOF
Update config:
[auth]
cedar_policies_dir = "/etc/secretumvault/policies"
2. Create Auth Token
curl -X POST http://localhost:8200/v1/auth/token/create \
-H "X-Vault-Token: $VAULT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"policies": ["default", "app-reader"],
"ttl": "24h",
"renewable": true
}'
Response:
{
"auth": {
"client_token": "s.abc123def456",
"policies": ["default", "app-reader"],
"metadata": {
"created_time": "2025-12-21T10:30:00Z",
"ttl": "24h"
}
}
}
Use token:
export APP_TOKEN="s.abc123def456"
curl http://localhost:8200/v1/secret/data/myapp \
-H "X-Vault-Token: $APP_TOKEN"
3. Renew Token
curl -X POST http://localhost:8200/v1/auth/token/renew \
-H "X-Vault-Token: $APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"increment": "24h"
}'
4. Revoke Token
curl -X POST http://localhost:8200/v1/auth/token/revoke \
-H "X-Vault-Token: $VAULT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"token": "s.abc123def456"
}'
Configure TLS
1. Generate Self-Signed Certificate
For development:
openssl req -x509 -newkey rsa:4096 -keyout tls.key -out tls.crt \
-days 365 -nodes \
-subj "/CN=localhost/O=SecretumVault/C=US"
2. Configure Vault
Update svault.toml:
[server]
address = "0.0.0.0"
port = 8200
tls_cert = "/path/to/tls.crt"
tls_key = "/path/to/tls.key"
3. Access via HTTPS
# Allow self-signed certificate
curl --insecure https://localhost:8200/v1/sys/health \
-H "X-Vault-Token: $VAULT_TOKEN"
# Or with CA certificate
curl --cacert tls.crt https://localhost:8200/v1/sys/health \
-H "X-Vault-Token: $VAULT_TOKEN"
4. Production Certificate (Let's Encrypt)
For Kubernetes with cert-manager, use the Helm installation which handles automatic certificate renewal.
Integrate with Kubernetes
1. Deploy Vault
# Apply manifests
kubectl apply -f k8s/01-namespace.yaml
kubectl apply -f k8s/02-configmap.yaml
kubectl apply -f k8s/03-deployment.yaml
kubectl apply -f k8s/04-service.yaml
kubectl apply -f k8s/05-etcd.yaml
# Wait for pods
kubectl -n secretumvault wait --for=condition=ready pod -l app=vault --timeout=300s
2. Initialize and Unseal
Port-forward vault:
kubectl -n secretumvault port-forward svc/vault 8200:8200 &
Initialize (from earlier steps):
curl -X POST http://localhost:8200/v1/sys/init \
-H "Content-Type: application/json" \
-d '{"shares": 3, "threshold": 2}'
Save keys, then unseal (from earlier steps).
3. Create Kubernetes ServiceAccount
cat > /tmp/app-sa.yaml <<'EOF'
apiVersion: v1
kind: ServiceAccount
metadata:
name: myapp
namespace: default
EOF
kubectl apply -f /tmp/app-sa.yaml
4. Pod Secret Injection
Create ClusterRoleBinding to allow reading vault-config:
cat > /tmp/vault-reader.yaml <<'EOF'
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: vault-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: vault-reader
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: vault-reader
subjects:
- kind: ServiceAccount
name: myapp
namespace: default
EOF
kubectl apply -f /tmp/vault-reader.yaml
5. Deploy Application Pod
cat > /tmp/myapp-pod.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: myapp
namespace: default
spec:
serviceAccountName: myapp
containers:
- name: app
image: myapp:latest
env:
- name: VAULT_ADDR
value: "http://vault.secretumvault.svc.cluster.local:8200"
- name: VAULT_TOKEN
valueFrom:
secretKeyRef:
name: vault-token
key: token
volumeMounts:
- name: vault-config
mountPath: /etc/vault
readOnly: true
volumes:
- name: vault-config
configMap:
name: vault-config
namespace: secretumvault
EOF
kubectl apply -f /tmp/myapp-pod.yaml
Backup & Restore
1. Backup Secrets
Export all secrets:
# List all secrets
SECRETS=$(curl -s http://localhost:8200/v1/secret/metadata \
-H "X-Vault-Token: $VAULT_TOKEN" | jq -r '.data.keys[]')
# Backup each secret
for secret in $SECRETS; do
curl -s http://localhost:8200/v1/secret/data/$secret \
-H "X-Vault-Token: $VAULT_TOKEN" > $secret-backup.json
done
2. Export with Encryption
Encrypt backup before storing:
# Create transit key for backups
curl -X POST http://localhost:8200/v1/transit/keys/backup-key \
-H "X-Vault-Token: $VAULT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"type": "aes-gcm"}'
# Backup and encrypt
for secret in $SECRETS; do
CONTENT=$(curl -s http://localhost:8200/v1/secret/data/$secret \
-H "X-Vault-Token: $VAULT_TOKEN" | base64)
ENCRYPTED=$(curl -s -X POST http://localhost:8200/v1/transit/encrypt/backup-key \
-H "X-Vault-Token: $VAULT_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"plaintext\": \"$CONTENT\"}" | jq -r '.data.ciphertext')
echo "$ENCRYPTED" > $secret-backup.enc
done
3. Restore Secrets
# List backup files
for backup in *-backup.json; do
secret=${backup%-backup.json}
# Read backup
curl -X POST http://localhost:8200/v1/secret/data/$secret \
-H "X-Vault-Token: $VAULT_TOKEN" \
-H "Content-Type: application/json" \
-d @$backup
done
Monitor & Troubleshoot
1. Check Vault Health
curl http://localhost:8200/v1/sys/health | jq .
Key fields to check:
sealed: Should befalseinitialized: Should betruestandby: Should befalse(or expected leader state)
2. View Metrics
Prometheus metrics endpoint:
curl http://localhost:9090/metrics | grep vault
Common metrics:
vault_secrets_stored_total- Total secrets storedvault_secrets_read_total- Total secrets readvault_operations_encrypt- Encryption operationsvault_tokens_created- Tokens created
3. Check Logs
Docker Compose:
docker-compose logs -f vault
Kubernetes:
kubectl -n secretumvault logs -f deployment/vault
Look for:
ERRORentries with detailsWARNfor unexpected but recoverable conditionsINFOfor normal operations
4. Verify Storage Connectivity
Check etcd from vault pod:
kubectl -n secretumvault exec deployment/vault -- \
curl http://vault-etcd-client:2379/health
5. Test Token Access
Validate token is working:
curl -X GET http://localhost:8200/v1/auth/token/self \
-H "X-Vault-Token: $VAULT_TOKEN" | jq '.auth'
Response shows token metadata and policies.
6. Common Issues
Issue: "sealed: true" after restart
- Solution: Run unseal procedure with stored keys
Issue: "permission denied" on secret read
- Solution: Check Cedar policies, verify token has correct policies
Issue: Storage connection error
- Solution: Verify backend endpoint in config (etcd DNS/IP)
Issue: High memory usage
- Solution: Check number of active leases, revoke old tokens
Issue: Slow operations
- Solution: Check storage backend performance, review metrics
For more details, see: