5.5 KiB
5.5 KiB
Patterns
Secret Rotation
Zero-downtime rotation with versioned naming (api_key_v1, api_key_v2):
interface Env {
PRIMARY_KEY: { get(): Promise<string> };
FALLBACK_KEY?: { get(): Promise<string> };
}
async function fetchWithAuth(url: string, key: string) {
return fetch(url, { headers: { "Authorization": `Bearer ${key}` } });
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
let resp = await fetchWithAuth("https://api.example.com", await env.PRIMARY_KEY.get());
// Fallback during rotation
if (!resp.ok && env.FALLBACK_KEY) {
resp = await fetchWithAuth("https://api.example.com", await env.FALLBACK_KEY.get());
}
return resp;
}
}
Workflow: Create api_key_v2 → add fallback binding → deploy → swap primary → deploy → remove v1
Encryption with KV
interface Env {
CACHE: KVNamespace;
ENCRYPTION_KEY: { get(): Promise<string> };
}
async function encryptValue(value: string, key: string): Promise<string> {
const enc = new TextEncoder();
const keyMaterial = await crypto.subtle.importKey(
"raw", enc.encode(key), { name: "AES-GCM" }, false, ["encrypt"]
);
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv }, keyMaterial, enc.encode(value)
);
const combined = new Uint8Array(iv.length + encrypted.byteLength);
combined.set(iv);
combined.set(new Uint8Array(encrypted), iv.length);
return btoa(String.fromCharCode(...combined));
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const key = await env.ENCRYPTION_KEY.get();
const encrypted = await encryptValue("sensitive-data", key);
await env.CACHE.put("user:123:data", encrypted);
return Response.json({ ok: true });
}
}
HMAC Signing
interface Env {
HMAC_SECRET: { get(): Promise<string> };
}
async function signRequest(data: string, secret: string): Promise<string> {
const enc = new TextEncoder();
const key = await crypto.subtle.importKey(
"raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]
);
const sig = await crypto.subtle.sign("HMAC", key, enc.encode(data));
return btoa(String.fromCharCode(...new Uint8Array(sig)));
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const secret = await env.HMAC_SECRET.get();
const payload = await request.text();
const signature = await signRequest(payload, secret);
return Response.json({ signature });
}
}
Audit & Monitoring
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
const startTime = Date.now();
try {
const apiKey = await env.API_KEY.get();
const resp = await fetch("https://api.example.com", {
headers: { "Authorization": `Bearer ${apiKey}` }
});
ctx.waitUntil(
fetch("https://log.example.com/log", {
method: "POST",
body: JSON.stringify({
event: "secret_used",
secret_name: "API_KEY",
timestamp: new Date().toISOString(),
duration_ms: Date.now() - startTime,
success: resp.ok
})
})
);
return resp;
} catch (error) {
ctx.waitUntil(
fetch("https://log.example.com/log", {
method: "POST",
body: JSON.stringify({
event: "secret_access_failed",
secret_name: "API_KEY",
error: error instanceof Error ? error.message : "Unknown"
})
})
);
return new Response("Error", { status: 500 });
}
}
}
Migration from Worker Secrets
Change env.SECRET (direct) to await env.SECRET.get() (async).
Steps:
- Create in Secrets Store:
wrangler secrets-store secret create <store-id> --name API_KEY --scopes workers --remote - Add binding to
wrangler.jsonc:{"binding": "API_KEY", "store_id": "abc123", "secret_name": "api_key"} - Update code:
const key = await env.API_KEY.get(); - Test staging, deploy
- Remove old:
wrangler secret delete API_KEY
Sharing Across Workers
Same secret, different binding names:
// worker-1: binding="SHARED_DB", secret_name="postgres_url"
// worker-2: binding="DB_CONN", secret_name="postgres_url"
JSON Secret Parsing
Store structured config as JSON secrets:
interface Env {
DB_CONFIG: { get(): Promise<string> };
}
interface DbConfig {
host: string;
port: number;
username: string;
password: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
try {
const configStr = await env.DB_CONFIG.get();
const config: DbConfig = JSON.parse(configStr);
// Use parsed config
const dbUrl = `postgres://${config.username}:${config.password}@${config.host}:${config.port}`;
return Response.json({ connected: true });
} catch (error) {
if (error instanceof SyntaxError) {
return new Response("Invalid config JSON", { status: 500 });
}
throw error;
}
}
}
Store JSON secret:
echo '{"host":"db.example.com","port":5432,"username":"app","password":"secret"}' | \
wrangler secrets-store secret create <store-id> \
--name DB_CONFIG --scopes workers --remote
Integration
Service Bindings
Auth Worker signs JWT with Secrets Store; API Worker verifies via service binding.
See: workers for service binding patterns.
See: api.md, gotchas.md