Vault & Secrets Management
Built-in secrets management - store API keys, credentials, and sensitive configuration securely with automatic encryption and access control
The .do platform provides enterprise-grade secrets management out of the box. Store sensitive data securely, rotate credentials automatically, and audit all access - without managing encryption keys or infrastructure.
How It Works
Secrets are stored in the platform vault and accessed via simple APIs:
// Store a secret
await $.Vault.set('stripe-api-key', process.env.STRIPE_API_KEY, {
description: 'Stripe API key for payment processing',
rotationPolicy: '90d'
})
// Retrieve a secret
const apiKey = await $.Vault.get('stripe-api-key')
// Use in your code
const stripe = new Stripe(apiKey)The platform automatically:
- Encrypts secrets at rest using AES-256
- Controls access via role-based permissions
- Logs access for audit trails
- Rotates credentials based on policy
- Versions secrets for rollback capability
Secret Types
Store different types of sensitive data:
API Keys
// External service credentials
await $.Vault.set('openai-api-key', key, {
type: 'api-key',
service: 'openai',
rotationPolicy: '90d'
})
await $.Vault.set('aws-access-key', accessKey, {
type: 'api-key',
service: 'aws',
metadata: {
accountId: '123456789',
region: 'us-east-1'
}
})
// Access with type safety
const key = await $.Vault.get<APIKey>('openai-api-key')Database Credentials
// Database connection strings
await $.Vault.set('postgres-connection', {
host: 'db.example.com',
port: 5432,
database: 'production',
username: 'app',
password: 'secure-password',
ssl: true
}, {
type: 'database',
rotationPolicy: '30d'
})
// Platform handles connection pooling and rotation
const db = await $.Database.connect(
await $.Vault.get('postgres-connection')
)Certificates
// TLS/SSL certificates
await $.Vault.set('api-tls-cert', {
certificate: certPEM,
privateKey: keyPEM,
chain: chainPEM
}, {
type: 'certificate',
expiresAt: new Date('2025-12-31'),
autoRenew: true
})
// Platform monitors expiration and alertsSigning Keys
// JWT signing keys, webhook signatures, etc.
await $.Vault.set('jwt-signing-key', privateKey, {
type: 'signing-key',
algorithm: 'RS256',
rotationPolicy: '180d'
})
// Use for signing
const token = await $.JWT.sign(payload, {
key: await $.Vault.get('jwt-signing-key')
})OAuth Tokens
// OAuth access and refresh tokens
await $.Vault.set('github-oauth-token', {
accessToken: 'gho_...',
refreshToken: 'ghr_...',
expiresAt: new Date('2025-02-01')
}, {
type: 'oauth-token',
service: 'github',
userId: user.id,
autoRefresh: true
})
// Platform automatically refreshes expired tokens
const token = await $.Vault.get('github-oauth-token')
// Returns fresh token, auto-refreshed if neededEnvironment Configuration
// Sensitive environment variables
await $.Vault.set('app-config', {
STRIPE_WEBHOOK_SECRET: 'whsec_...',
SENDGRID_API_KEY: 'SG...',
ENCRYPTION_KEY: 'base64-key',
SESSION_SECRET: 'random-secret'
}, {
type: 'config',
environment: 'production'
})
// Access all at once
const config = await $.Vault.get('app-config')Access Control
Secrets have granular access control:
// Set secret with permissions
await $.Vault.set('production-db-password', password, {
permissions: {
read: ['role:backend-engineer', 'role:sre'],
write: ['role:sre'],
delete: ['role:security-admin']
}
})
// Platform enforces automatically
const password = await $.Vault.get('production-db-password')
// Throws UnauthorizedException if user lacks 'read' permission
// Organization-scoped secrets
await $.Vault.set('org-api-key', apiKey, {
scope: 'organization',
organizationId: org.id
})
// Only accessible within that organizationSecret Rotation
Automatic credential rotation:
// Define rotation policy
await $.Vault.set('database-password', password, {
rotationPolicy: {
frequency: '30d',
handler: async (secret) => {
// Generate new password
const newPassword = generateSecurePassword()
// Update in database
await updateDatabasePassword(secret.metadata.username, newPassword)
// Return new secret
return newPassword
}
}
})
// Platform rotates automatically every 30 days
// Old version kept for 7 days for rollbackManual Rotation
Rotate secrets on demand:
// Rotate immediately
await $.Vault.rotate('api-key')
// Rotate with new value
await $.Vault.rotate('api-key', newValue)
// Rotate and notify
await $.Vault.rotate('api-key', {
value: newValue,
notify: ['security-team']
})Versioning
Secrets are versioned automatically:
// Update secret (creates new version)
await $.Vault.set('api-key', newKey)
// Get latest version
const latest = await $.Vault.get('api-key')
// Get specific version
const v1 = await $.Vault.get('api-key', { version: 1 })
const v2 = await $.Vault.get('api-key', { version: 2 })
// Rollback to previous version
await $.Vault.rollback('api-key', { version: 1 })
// List versions
const versions = await $.Vault.versions('api-key')
// Returns: [
// { version: 2, createdAt: '2025-01-15', createdBy: userId },
// { version: 1, createdAt: '2025-01-01', createdBy: userId }
// ]Encryption
All secrets are encrypted:
At Rest
- AES-256-GCM encryption
- Key Encryption Keys (KEK) rotated annually
- Data Encryption Keys (DEK) unique per secret
- Hardware Security Modules (HSM) for key storage
In Transit
- TLS 1.3 for all API communication
- Certificate pinning for platform services
- Encrypted backups with separate keys
In Memory
- Secrets cleared after use
- No logging of secret values
- Encrypted swap on platform infrastructure
Dynamic Secrets
Generate short-lived credentials on demand:
// Define dynamic secret generator
await $.Vault.defineDynamic('temp-database-user', {
type: 'database',
generator: async (userId) => {
// Create temporary database user
const username = `temp_${userId}_${Date.now()}`
const password = generateSecurePassword()
await db.query(`
CREATE USER ${username} WITH PASSWORD '${password}';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO ${username};
`)
return {
username,
password,
ttl: 3600 // 1 hour
}
},
cleanup: async (credentials) => {
// Revoke user when TTL expires
await db.query(`DROP USER ${credentials.username}`)
}
})
// Request dynamic secret
const tempCreds = await $.Vault.generate('temp-database-user')
// Use for 1 hour, then automatically cleaned upIntegration Patterns
Environment Variables
// Load secrets as environment variables
on($.Worker.starting, async () => {
const secrets = await $.Vault.get('app-config')
// Inject into environment
for (const [key, value] of Object.entries(secrets)) {
process.env[key] = value
}
})Configuration Files
// Generate config file from secrets
on($.Deployment.starting, async () => {
const config = await $.Vault.get('app-config')
// Write to secure location
await $.File.writeSecure('.env.production',
Object.entries(config)
.map(([k, v]) => `${k}=${v}`)
.join('\n')
)
})Third-Party Services
// Sync secrets to external vault
on($.Vault.secret.updated, async ({ secret }) => {
if (secret.metadata.sync) {
await syncToHashiCorpVault(secret)
}
})Compliance
Vault meets compliance requirements:
SOC 2
- Encryption at rest and in transit
- Access logging and monitoring
- Regular security audits
- Incident response procedures
HIPAA
- PHI encryption requirements
- Access controls and audit trails
- Breach notification procedures
- Business associate agreements
PCI DSS
- Encryption of cardholder data
- Key management procedures
- Access control mechanisms
- Security logging and monitoring
GDPR
- Data protection by design
- Encryption of personal data
- Access controls and consent
- Data breach procedures
Audit & Monitoring
All secret access is logged:
// Every access creates audit log
const secret = await $.Vault.get('api-key')
// Logs: {
// userId,
// action: 'vault.secret.read',
// secretId: 'api-key',
// timestamp,
// ipAddress,
// userAgent
// }
// Query audit logs
const logs = await $.AuditLog.list({
type: 'vault.secret',
secretId: 'api-key',
period: 'last_30_days'
})
// Set up alerts
on($.Vault.secret.accessed, async ({ secret, user }) => {
if (secret.metadata.sensitive) {
await $.Alert.send({
severity: 'medium',
message: `Sensitive secret accessed: ${secret.id}`,
user: user.email,
recipient: 'security-team'
})
}
})Backup & Recovery
Secrets are backed up securely:
// Automatic encrypted backups
// - Hourly incremental backups
// - Daily full backups
// - 30-day retention
// - Separate encryption keys
// Manual backup
const backup = await $.Vault.backup({
secrets: ['api-key', 'database-password'],
encrypt: true
})
// Restore from backup
await $.Vault.restore(backup, {
verify: true,
dryRun: false
})
// Disaster recovery
// - Multi-region replication
// - Cross-datacenter backups
// - Recovery time objective: `<1` hour
// - Recovery point objective: `<5` minutesPerformance
Vault operations are optimized:
<10msread latency - Secrets cached in memory<50mswrite latency - Asynchronous encryption- 1000+ requests/sec - Per-worker throughput
- Regional caching - Edge-cached for global access
// Batch secret retrieval
const secrets = await $.Vault.getMany([
'api-key-1',
'api-key-2',
'api-key-3'
])
// Single request, parallel decryption
// Cache frequently accessed secrets
const cached = await $.Vault.get('api-key', {
cache: true,
ttl: 300 // Cache for 5 minutes
})Best Practices
1. Principle of Least Privilege
Grant minimum access necessary:
// Good: Role-based access
await $.Vault.set('production-key', key, {
permissions: {
read: ['role:backend-service']
}
})
// Avoid: Overly permissive
await $.Vault.set('production-key', key, {
permissions: {
read: ['*'] // Too broad
}
})2. Rotation Policies
Rotate secrets regularly:
// Good: Automatic rotation
await $.Vault.set('database-password', password, {
rotationPolicy: '30d'
})
// Avoid: Static secrets
await $.Vault.set('database-password', password)
// No rotation, security risk over time3. Never Log Secrets
Prevent accidental exposure:
// Good: Log without sensitive data
console.log('API request successful')
// Avoid: Logging secrets
console.log('Using API key:', apiKey) // Secret in logs!
// Platform prevents this automatically
await $.Log.info('API request', {
apiKey // Platform redacts automatically
})
// Logged as: { apiKey: '[REDACTED]' }4. Use Dynamic Secrets
Prefer short-lived credentials:
// Good: Dynamic secrets
const creds = await $.Vault.generate('temp-db-user')
// Expires after 1 hour
// Avoid: Long-lived static credentials
const password = await $.Vault.get('static-db-password')
// Never expires, higher riskMigration
Moving from existing secret management:
From Environment Variables
// Before: Plain environment variables
const apiKey = process.env.STRIPE_API_KEY
// After: Vault
const apiKey = await $.Vault.get('stripe-api-key')From AWS Secrets Manager
// Import from AWS
import { SecretsManager } from '@aws-sdk/client-secrets-manager'
const aws = new SecretsManager()
const secrets = await aws.listSecrets()
for (const secret of secrets.SecretList) {
const value = await aws.getSecretValue({ SecretId: secret.ARN })
await $.Vault.set(secret.Name, value.SecretString, {
source: 'aws-secrets-manager',
imported: true
})
}From HashiCorp Vault
// Import from HashiCorp Vault
import vault from 'node-vault'
const client = vault({ endpoint: 'http://vault:8200' })
const secrets = await client.list('secret/data')
for (const path of secrets.data.keys) {
const data = await client.read(`secret/data/${path}`)
await $.Vault.set(path, data.data.data, {
source: 'hashicorp-vault',
imported: true
})
}CLI Access
Access vault from command line:
# Set secret
do vault set api-key "sk-..."
# Get secret
do vault get api-key
# List secrets
do vault list
# Rotate secret
do vault rotate api-key
# Delete secret
do vault delete api-keyNext Steps
- RBAC & Authorization - Access control
- Compliance - Audit logging
- Multi-Tenancy - Organization isolation
- Enterprise Authentication - SSO integration
Secrets management is critical for security. The .do platform makes it simple with built-in encryption, automatic rotation, and comprehensive audit trails.
RBAC & Fine-Grained Authorization
Built-in role-based access control and fine-grained authorization - define permissions once, enforce everywhere automatically
Automatic User Provisioning
Real-time user and group synchronization from enterprise directories via SCIM - no manual onboarding or offboarding required