1// Secrets Management — Azure Key Vault + Managed Identity
2import { SecretClient } from '@azure/keyvault-secrets';
3import { DefaultAzureCredential } from '@azure/identity';
4import { EventGridDeserializer } from '@azure/eventgrid';
5
6// ─── NEVER DO THIS ────────────────────────────────
7// const connString = "Server=prod-db;Password=Sup3rS3cr3t!"; // ❌
8// const apiKey = process.env.API_KEY; // ❌ still in shell env
9// require('dotenv').config(); // ❌ if .env is committed
10
11// ─── CORRECT: Managed Identity + Key Vault ────────
12const credential = new DefaultAzureCredential();
13// In Azure: uses Managed Identity automatically (no env vars needed)
14// Locally: uses Azure CLI / VS Code credentials
15
16const kvClient = new SecretClient(
17 'https://myapp-kv.vault.azure.net',
18 credential
19);
20
21// Cache secrets in memory — avoid repeated KV round-trips
22const secretCache = new Map<string, { value: string; expiresAt: number }>();
23const CACHE_TTL_MS = 5 * 60 * 1000; // 5-minute cache
24
25async function getSecret(name: string): Promise<string> {
26 const cached = secretCache.get(name);
27 if (cached && cached.expiresAt > Date.now()) {
28 return cached.value; // Return cached — no KV call
29 }
30
31 const secret = await kvClient.getSecret(name);
32 secretCache.set(name, {
33 value: secret.value!,
34 expiresAt: Date.now() + CACHE_TTL_MS,
35 });
36 return secret.value!;
37}
38
39// ─── ROTATION via Event Grid ──────────────────────
40// Azure Function triggered by Key Vault "NearExpiry" event
41export async function rotateSecret(event: KeyVaultEvent) {
42 const secretName = event.subject.split('/').pop()!;
43
44 // Generate new secret (e.g., new DB password)
45 const newValue = crypto.randomBytes(32).toString('hex');
46
47 // Update the service (e.g., Azure SQL password)
48 await updateServiceCredential(secretName, newValue);
49
50 // Update Key Vault with new version (old version retained for rollback)
51 await kvClient.setSecret(secretName, newValue);
52
53 console.log(`✅ Rotated: ${secretName} — old version preserved for 24h`);
54}
55
56// ─── EMERGENCY REVOCATION ────────────────────────
57async function revokeCompromisedSecret(name: string) {
58 // Disable current version immediately
59 await kvClient.updateSecretProperties(name, '', {
60 enabled: false,
61 });
62
63 // Clear all caches
64 secretCache.clear();
65
66 // Alert security team
67 await sendSecurityAlert(`CRITICAL: Secret '${name}' disabled — possible compromise`);
68}
69