mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-03-21 06:11:27 -07:00
update skills
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
# Patterns
|
||||
|
||||
## Secret Rotation
|
||||
|
||||
Zero-downtime rotation with versioned naming (`api_key_v1`, `api_key_v2`):
|
||||
|
||||
```typescript
|
||||
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
|
||||
|
||||
```typescript
|
||||
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
|
||||
|
||||
```typescript
|
||||
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
|
||||
|
||||
```typescript
|
||||
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:
|
||||
1. Create in Secrets Store: `wrangler secrets-store secret create <store-id> --name API_KEY --scopes workers --remote`
|
||||
2. Add binding to `wrangler.jsonc`: `{"binding": "API_KEY", "store_id": "abc123", "secret_name": "api_key"}`
|
||||
3. Update code: `const key = await env.API_KEY.get();`
|
||||
4. Test staging, deploy
|
||||
5. Remove old: `wrangler secret delete API_KEY`
|
||||
|
||||
## Sharing Across Workers
|
||||
|
||||
Same secret, different binding names:
|
||||
|
||||
```jsonc
|
||||
// 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:
|
||||
|
||||
```typescript
|
||||
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:
|
||||
|
||||
```bash
|
||||
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](../workers/) for service binding patterns.
|
||||
|
||||
See: [api.md](./api.md), [gotchas.md](./gotchas.md)
|
||||
Reference in New Issue
Block a user