diff --git a/CLAUDE.md b/CLAUDE.md index 7700b81..ae09049 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -80,6 +80,7 @@ Examples: `getAutotaskClient()`, `getMsgraphClient()`, `getDattoRmmClient()`, | Veeam VSPC | `VEEAM_VSPC_*` | | Auvik / Addigy / IT Glue / Mimecast / S1 / Duo / Zoom / QBO / Zabbix / Salesbldr | `_*` | | PAX8 | `PAX8_*` (OAuth2 client-credentials, read-only partner/reseller API) | +| AWS Route 53 | `AWS_*` (literal `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_REGION` — intentional exception to the per-service prefix convention; the AWS SDK's default credential chain hardcodes these names. Injected by BWS at the container entrypoint, never in `.env`) | | Anthropic | `ANTHROPIC_API_KEY` (analyzer pipeline + `ai-triage-service.ts`) | | OpenRouter | `OPENROUTER_API_KEY` (alternate analyzer provider, opt-in per request) | | Backblaze B2 | `B2_*` (LogLift evidence storage) | diff --git a/lib/services/route53-factory.ts b/lib/services/route53-factory.ts new file mode 100644 index 0000000..4b3c1bd --- /dev/null +++ b/lib/services/route53-factory.ts @@ -0,0 +1,47 @@ +import { Route53Client } from '@aws-sdk/client-route-53'; + +let route53ClientInstance: Route53Client | null = null; + +/** + * Check if AWS Route 53 credentials are configured. + */ +export function isRoute53Configured(): boolean { + return !!(process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY); +} + +/** + * Get or create the Route 53 client singleton instance. + * + * Intentionally does NOT pass an explicit `credentials` option — omitting it + * lets @aws-sdk/credential-provider-node's default credential chain read + * AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN from + * process.env automatically (fromEnv() is first in the chain). This is + * exactly how Bitwarden Secrets Manager (`bws run`) injects credentials at + * the docker-entrypoint.sh layer. Do NOT "fix" this by adding an explicit + * credentials object — see 24-RESEARCH.md Pitfall 1. + */ +export function getRoute53Client(): Route53Client { + if (!route53ClientInstance) { + if (!isRoute53Configured()) { + throw new Error( + 'AWS credentials missing. Please set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY (and AWS_REGION) environment variables.' + ); + } + + // Route 53 is a global service but the SDK still requires a signing + // region; us-east-1 is the conventional default AWS's own CLI/console use. + route53ClientInstance = new Route53Client({ + region: process.env.AWS_REGION || 'us-east-1', + }); + console.log('Route 53 client initialized'); + } + + return route53ClientInstance; +} + +/** + * Reset the singleton instance (useful for testing). + */ +export function resetRoute53Client(): void { + route53ClientInstance = null; +}