UNPKG

@iota-big3/sdk-security

Version:

Advanced security features including zero trust, quantum-safe crypto, and ML threat detection

804 lines 28.6 kB
"use strict"; /** * HashiCorp Vault Provider * Enterprise-grade secrets management with dynamic secrets and encryption */ Object.defineProperty(exports, "__esModule", { value: true }); exports.VaultProvider = void 0; const tslib_1 = require("tslib"); const crypto = tslib_1.__importStar(require("crypto")); const events_1 = require("events"); const types_1 = require("../types"); class VaultProvider extends events_1.EventEmitter { constructor(config) { super(); this.provider = types_1.SecretsProvider.HASHICORP_VAULT; this.cache = new Map(); this.leases = new Map(); this.isInitialized = false; this.config = config; this.validateConfig(); } /** * Initialize Vault connection */ async initialize() { if (this.isInitialized) return; try { await this.authenticate(); this.isInitialized = true; this.emit('initialized', { provider: this.provider }); // Start token renewal if needed if (this.config.authentication.renewable) { this.startTokenRenewal(); } } catch (error) { this.emit('error', { error, operation: 'initialize' }); throw error; } } /** * Authenticate with Vault */ async authenticate() { const { method, credentials } = this.config.authentication; switch (method) { case 'TOKEN': this.token = credentials?.token; break; case 'APP_ROLE': this.token = await this.authenticateAppRole(credentials?.roleId, credentials?.secretId); break; case 'AWS_IAM': this.token = await this.authenticateAWS(credentials?.awsRole); break; case 'KUBERNETES': this.token = await this.authenticateKubernetes(); break; default: throw new Error(`Unsupported auth method: ${method}`); } // Set token expiry if (this.config.authentication.ttl) { this.tokenExpiry = new Date(Date.now() + this.config.authentication.ttl * 1000); } } /** * Get secret from Vault */ async getSecret(name, version) { await this.ensureInitialized(); // Check cache if (this.config.caching?.enabled) { const cached = this.getFromCache(name); if (cached) return cached; } try { // Simulate Vault API call const path = this.buildPath(name, version); const response = await this.vaultRequest('GET', path); const secret = { name, value: response.data, type: this.inferSecretType(name, response.data), createdAt: new Date(response.metadata.created_time), updatedAt: new Date(response.metadata.updated_time), version: response.metadata.version, tags: response.metadata.custom_metadata }; // Cache if enabled if (this.config.caching?.enabled) { this.addToCache(name, secret); } // Audit this.auditLog({ timestamp: new Date(), action: types_1.SecretAction.READ, secretName: name, actor: { id: 'system', type: 'SYSTEM' }, result: 'SUCCESS' }); return secret; } catch (error) { this.auditLog({ timestamp: new Date(), action: types_1.SecretAction.READ, secretName: name, actor: { id: 'system', type: 'SYSTEM' }, result: 'FAILURE', error: error.message }); throw error; } } /** * Set secret in Vault */ async setSecret(name, value, metadata) { await this.ensureInitialized(); try { const path = this.buildPath(name); const data = { data: typeof value === 'object' ? value : { value }, metadata: { custom_metadata: metadata?.tags, max_versions: 10, cas_required: false, delete_version_after: metadata?.compliance?.retentionPeriod ? `${metadata.compliance.retentionPeriod}d` : '0s' } }; await this.vaultRequest('POST', path, data); // Set rotation if specified if (metadata?.rotationPolicy) { await this.scheduleRotation(name, metadata.rotationPolicy); } // Clear cache this.cache.delete(name); // Audit this.auditLog({ timestamp: new Date(), action: types_1.SecretAction.CREATE, secretName: name, actor: { id: 'system', type: 'SYSTEM' }, result: 'SUCCESS', metadata: { type: metadata?.type } }); this.emit('secret:created', { name, metadata }); } catch (error) { this.auditLog({ timestamp: new Date(), action: types_1.SecretAction.CREATE, secretName: name, actor: { id: 'system', type: 'SYSTEM' }, result: 'FAILURE', error: error.message }); throw error; } } /** * Delete secret from Vault */ async deleteSecret(name, permanent) { await this.ensureInitialized(); try { const path = this.buildPath(name); if (permanent) { // Permanent deletion await this.vaultRequest('DELETE', `${path}/metadata`); } else { // Soft delete (latest version) await this.vaultRequest('DELETE', path); } // Clear cache this.cache.delete(name); // Audit this.auditLog({ timestamp: new Date(), action: types_1.SecretAction.DELETE, secretName: name, actor: { id: 'system', type: 'SYSTEM' }, result: 'SUCCESS', metadata: { permanent } }); this.emit('secret:deleted', { name, permanent }); } catch (error) { this.auditLog({ timestamp: new Date(), action: types_1.SecretAction.DELETE, secretName: name, actor: { id: 'system', type: 'SYSTEM' }, result: 'FAILURE', error: error.message }); throw error; } } /** * List secrets with optional filter */ async listSecrets(filter) { await this.ensureInitialized(); try { // List from KV engine const response = await this.vaultRequest('LIST', this.config.namespace || 'secret'); const keys = response.data.keys || []; // Get metadata for each key const secrets = []; for (const key of keys) { if (filter?.namePattern && !key.match(new RegExp(filter.namePattern))) { continue; } try { const metadata = await this.getSecretMetadata(key); // Apply filters if (filter) { if (filter.type && metadata.type !== filter.type) continue; if (filter.environment && metadata.environment !== filter.environment) continue; if (filter.owner && metadata.owner !== filter.owner) continue; if (filter.tags && !this.matchTags(metadata.tags, filter.tags)) continue; if (filter.modifiedSince && metadata.updatedAt < filter.modifiedSince) continue; if (filter.expiringWithin && metadata.expiresAt) { const expiryDays = (metadata.expiresAt.getTime() - Date.now()) / (1000 * 60 * 60 * 24); if (expiryDays > filter.expiringWithin) continue; } } secrets.push(metadata); } catch (error) { // Skip secrets we can't read continue; } } return secrets; } catch (error) { throw new Error(`Failed to list secrets: ${error.message}`); } } /** * Rotate a secret */ async rotateSecret(name) { await this.ensureInitialized(); try { // Get current secret const current = await this.getSecret(name); // Generate new value based on type const newValue = await this.generateNewSecretValue(current); // Store new version await this.setSecret(name, newValue, { ...current, rotationPolicy: { ...current.rotationPolicy, lastRotated: new Date() } }); // Audit this.auditLog({ timestamp: new Date(), action: types_1.SecretAction.ROTATE, secretName: name, actor: { id: 'system', type: 'SYSTEM' }, result: 'SUCCESS' }); this.emit('secret:rotated', { name, oldVersion: current.version }); } catch (error) { this.auditLog({ timestamp: new Date(), action: types_1.SecretAction.ROTATE, secretName: name, actor: { id: 'system', type: 'SYSTEM' }, result: 'FAILURE', error: error.message }); throw error; } } /** * Schedule secret rotation */ async scheduleRotation(name, policy) { // In production, would integrate with Vault's rotation schedules // or external schedulers like AWS EventBridge if (policy.autoRotate && policy.interval) { const nextRotation = new Date(); nextRotation.setDate(nextRotation.getDate() + policy.interval); // Store rotation schedule this.emit('rotation:scheduled', { name, nextRotation, interval: policy.interval }); } } /** * Get secret versions */ async getSecretVersions(name) { await this.ensureInitialized(); const path = `${this.buildPath(name)}/metadata`; const response = await this.vaultRequest('GET', path); return Object.keys(response.data.versions || {}); } /** * Rollback to previous version */ async rollbackSecret(name, version) { await this.ensureInitialized(); try { // Get the specified version const secret = await this.getSecret(name, version); // Set it as the current version await this.setSecret(name, secret.value, secret); // Audit this.auditLog({ timestamp: new Date(), action: types_1.SecretAction.ROLLBACK, secretName: name, actor: { id: 'system', type: 'SYSTEM' }, result: 'SUCCESS', metadata: { targetVersion: version } }); this.emit('secret:rollback', { name, version }); } catch (error) { this.auditLog({ timestamp: new Date(), action: types_1.SecretAction.ROLLBACK, secretName: name, actor: { id: 'system', type: 'SYSTEM' }, result: 'FAILURE', error: error.message }); throw error; } } /** * Get multiple secrets */ async getBulkSecrets(names) { const secrets = await Promise.all(names.map(name => this.getSecret(name).catch(() => null))); return secrets.filter(Boolean); } /** * Import secrets */ async importSecrets(secrets) { for (const secret of secrets) { await this.setSecret(secret.name, secret.value, secret); } this.emit('secrets:imported', { count: secrets.length }); } /** * Export secrets */ async exportSecrets(filter) { const metadata = await this.listSecrets(filter); const secrets = []; for (const meta of metadata) { try { const secret = await this.getSecret(meta.name); secrets.push(secret); } catch (error) { // Skip secrets we can't export continue; } } this.auditLog({ timestamp: new Date(), action: types_1.SecretAction.EXPORT, secretName: '*', actor: { id: 'system', type: 'SYSTEM' }, result: 'SUCCESS', metadata: { count: secrets.length } }); return secrets; } /** * Health check */ async healthCheck() { const startTime = Date.now(); try { // Check token validity if (this.tokenExpiry && this.tokenExpiry < new Date()) { await this.authenticate(); } // Try to access sys/health await this.vaultRequest('GET', 'sys/health'); return { healthy: true, provider: this.provider, connectivity: true, authentication: true, lastCheck: new Date(), latency: Date.now() - startTime }; } catch (error) { return { healthy: false, provider: this.provider, connectivity: false, authentication: false, lastCheck: new Date(), latency: Date.now() - startTime, errors: [error.message] }; } } /** * Create dynamic database credentials */ async createDynamicSecret(config) { await this.ensureInitialized(); const path = `${config.backend}/creds/${config.role}`; const response = await this.vaultRequest('GET', path); const lease = { leaseId: response.lease_id, secretName: `${config.backend}/${config.role}`, credentials: response.data, leaseDuration: response.lease_duration, renewable: response.renewable, createdAt: new Date(), expiresAt: new Date(Date.now() + response.lease_duration * 1000) }; // Track lease this.leases.set(lease.leaseId, lease); // Schedule renewal if needed if (lease.renewable) { this.scheduleLeaseRenewal(lease); } this.emit('dynamic-secret:created', lease); return lease; } /** * Renew dynamic secret lease */ async renewLease(leaseId) { await this.ensureInitialized(); const lease = this.leases.get(leaseId); if (!lease) { throw new Error(`Lease not found: ${leaseId}`); } await this.vaultRequest('PUT', 'sys/leases/renew', { lease_id: leaseId }); // Update expiry lease.expiresAt = new Date(Date.now() + lease.leaseDuration * 1000); this.emit('lease:renewed', { leaseId }); } /** * Revoke dynamic secret lease */ async revokeLease(leaseId) { await this.ensureInitialized(); await this.vaultRequest('PUT', 'sys/leases/revoke', { lease_id: leaseId }); this.leases.delete(leaseId); this.emit('lease:revoked', { leaseId }); } /** * Encryption service: Encrypt data */ async encrypt(plaintext, context) { await this.ensureInitialized(); const data = { plaintext: Buffer.isBuffer(plaintext) ? plaintext.toString('base64') : Buffer.from(plaintext).toString('base64'), context: context ? Buffer.from(JSON.stringify(context)).toString('base64') : undefined }; const response = await this.vaultRequest('POST', 'transit/encrypt/app-key', data); return response.data.ciphertext; } /** * Encryption service: Decrypt data */ async decrypt(ciphertext, context) { await this.ensureInitialized(); const data = { ciphertext, context: context ? Buffer.from(JSON.stringify(context)).toString('base64') : undefined }; const response = await this.vaultRequest('POST', 'transit/decrypt/app-key', data); const plaintext = Buffer.from(response.data.plaintext, 'base64'); return plaintext.toString('utf8'); } /** * Generate data encryption key */ async generateDataKey(bits = 256) { await this.ensureInitialized(); const response = await this.vaultRequest('POST', 'transit/datakey/plaintext/app-key', { bits: bits / 8 }); return { plaintext: response.data.plaintext, ciphertext: response.data.ciphertext }; } /** * Rewrap ciphertext with new key version */ async rewrapKey(ciphertext, newKeyVersion) { await this.ensureInitialized(); const response = await this.vaultRequest('POST', 'transit/rewrap/app-key', { ciphertext, key_version: parseInt(newKeyVersion) }); return response.data.ciphertext; } /** * PKI: Generate certificate */ async generateCertificate(config) { await this.ensureInitialized(); const response = await this.vaultRequest('POST', 'pki/issue/app-cert', { common_name: config.commonName, alt_names: config.altNames?.join(','), ip_sans: config.ipSans?.join(','), ttl: config.ttl ? `${config.ttl}s` : '8760h', // 1 year default key_type: config.keyType || 'rsa', key_bits: config.keyBits || 2048, exclude_cn_from_sans: config.excludeCnFromSans || false }); return { certificate: response.data.certificate, privateKey: response.data.private_key, certificateChain: response.data.ca_chain?.join('\n'), serialNumber: response.data.serial_number, notBefore: new Date(response.data.not_before), notAfter: new Date(response.data.not_after), subject: this.parseCertificateSubject(response.data.certificate) }; } /** * PKI: Sign CSR */ async signCSR(csr, config) { await this.ensureInitialized(); const response = await this.vaultRequest('POST', 'pki/sign/app-cert', { csr, ttl: config.ttl ? `${config.ttl}s` : '8760h', format: config.format || 'pem' }); return { certificate: response.data.certificate, certificateChain: response.data.ca_chain?.join('\n'), serialNumber: response.data.serial_number, notBefore: new Date(response.data.not_before), notAfter: new Date(response.data.not_after), subject: this.parseCertificateSubject(response.data.certificate) }; } /** * PKI: Revoke certificate */ async revokeCertificate(serial, reason) { await this.ensureInitialized(); await this.vaultRequest('POST', 'pki/revoke', { serial_number: serial, revocation_reason: reason }); this.emit('certificate:revoked', { serial, reason }); } /** * PKI: Get CRL */ async getCRL() { await this.ensureInitialized(); const response = await this.vaultRequest('GET', 'pki/crl'); return response.data.crl; } /** * PKI: Get certificate */ async getCertificate(serial) { await this.ensureInitialized(); const response = await this.vaultRequest('GET', `pki/cert/${serial}`); return { certificate: response.data.certificate, serialNumber: serial, notBefore: new Date(response.data.not_before), notAfter: new Date(response.data.not_after), subject: this.parseCertificateSubject(response.data.certificate) }; } /** * Helper methods */ validateConfig() { if (!this.config.endpoint) { throw new Error('Vault endpoint is required'); } if (!this.config.authentication) { throw new Error('Authentication configuration is required'); } } async ensureInitialized() { if (!this.isInitialized) { await this.initialize(); } } buildPath(name, version) { const base = `${this.config.namespace || 'secret'}/data/${name}`; return version ? `${base}?version=${version}` : base; } getFromCache(name) { if (!this.config.caching?.enabled) return null; const cached = this.cache.get(name); if (!cached) return null; const age = Date.now() - cached.cachedAt.getTime(); if (age > (this.config.caching.ttl * 1000)) { this.cache.delete(name); return null; } return cached.secret; } addToCache(name, secret) { if (!this.config.caching?.enabled) return; // Check cache size if (this.cache.size >= (this.config.caching.maxSize || 100)) { // Remove oldest entry const oldest = Array.from(this.cache.entries()) .sort((a, b) => a[1].cachedAt.getTime() - b[1].cachedAt.getTime())[0]; this.cache.delete(oldest[0]); } this.cache.set(name, { secret, cachedAt: new Date() }); } inferSecretType(name, value) { if (name.includes('api') || name.includes('key')) return types_1.SecretType.API_KEY; if (name.includes('db') || name.includes('database')) return types_1.SecretType.DATABASE_CREDENTIAL; if (name.includes('cert')) return types_1.SecretType.CERTIFICATE; if (name.includes('ssh')) return types_1.SecretType.SSH_KEY; if (name.includes('token')) return types_1.SecretType.TOKEN; return types_1.SecretType.CUSTOM; } matchTags(secretTags, filterTags) { if (!filterTags) return true; if (!secretTags) return false; return Object.entries(filterTags).every(([key, value]) => secretTags[key] === value); } async getSecretMetadata(name) { const path = `${this.buildPath(name)}/metadata`; const response = await this.vaultRequest('GET', path); return { name, type: this.inferSecretType(name, {}), createdAt: new Date(response.data.created_time), updatedAt: new Date(response.data.updated_time), version: response.data.current_version, tags: response.data.custom_metadata }; } async generateNewSecretValue(secret) { switch (secret.type) { case types_1.SecretType.API_KEY: case types_1.SecretType.TOKEN: return crypto.randomBytes(32).toString('hex'); case types_1.SecretType.PASSWORD: return this.generatePassword(); case types_1.SecretType.DATABASE_CREDENTIAL: return { username: secret.value.username, password: this.generatePassword() }; default: throw new Error(`Cannot auto-rotate secret type: ${secret.type}`); } } generatePassword() { const length = 24; const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*'; let password = ''; for (let i = 0; i < length; i++) { password += charset[Math.floor(Math.random() * charset.length)]; } return password; } async vaultRequest(method, path, data) { // In production, would use node-vault or axios to make actual API calls // For now, simulate responses await new Promise(resolve => setTimeout(resolve, 50)); // Simulate network delay // Mock responses based on path if (path === 'sys/health') { return { initialized: true, sealed: false }; } if (path.includes('/data/')) { return { data: data?.data || { value: 'mock-secret-value' }, metadata: { created_time: new Date().toISOString(), updated_time: new Date().toISOString(), version: '1', custom_metadata: {} } }; } if (path.includes('transit/encrypt')) { return { data: { ciphertext: `vault:v1:${Buffer.from(JSON.stringify(data)).toString('base64')}` } }; } if (path.includes('transit/decrypt')) { const encoded = data.ciphertext.replace('vault:v1:', ''); return { data: { plaintext: encoded } }; } if (path.includes('pki/issue')) { return { data: { certificate: '-----BEGIN CERTIFICATE-----\nMOCK CERTIFICATE\n-----END CERTIFICATE-----', private_key: '-----BEGIN RSA PRIVATE KEY-----\nMOCK KEY\n-----END RSA PRIVATE KEY-----', ca_chain: ['-----BEGIN CERTIFICATE-----\nMOCK CA\n-----END CERTIFICATE-----'], serial_number: crypto.randomBytes(8).toString('hex'), not_before: new Date().toISOString(), not_after: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString() } }; } return { data: {}, metadata: {} }; } async authenticateAppRole(roleId, secretId) { // Mock AppRole authentication return `mock-token-${roleId}-${secretId}`; } async authenticateAWS(role) { // Mock AWS authentication return `mock-aws-token-${role || 'default'}`; } async authenticateKubernetes() { // Mock Kubernetes authentication return 'mock-k8s-token'; } startTokenRenewal() { // In production, would implement token renewal logic setInterval(() => { if (this.tokenExpiry && this.tokenExpiry < new Date(Date.now() + 60000)) { this.authenticate().catch(err => { this.emit('error', { error: err, operation: 'token-renewal' }); }); } }, 30000); // Check every 30 seconds } scheduleLeaseRenewal(lease) { // Renew at 80% of lease duration const renewAt = lease.leaseDuration * 0.8 * 1000; setTimeout(() => { this.renewLease(lease.leaseId).catch(err => { this.emit('error', { error: err, operation: 'lease-renewal', leaseId: lease.leaseId }); }); }, renewAt); } parseCertificateSubject(cert) { // In production, would parse actual certificate return { CN: 'app.example.com', O: 'Example Corp', OU: 'Engineering', C: 'US', ST: 'CA', L: 'San Francisco' }; } auditLog(event) { this.emit('audit', event); // In production, would send to audit log if (this.config.namespace?.includes('audit')) { // Store audit event } } } exports.VaultProvider = VaultProvider; //# sourceMappingURL=vault-provider.js.map