Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,31 @@ DYNAMODB_TABLE_IDENTITY_CACHE=slack-knowledge-bot-staging-identity-cache
SQS_AUDIT_QUEUE_URL=https://sqs.us-west-2.amazonaws.com/000000000000/slack-knowledge-bot-staging-audit
SQS_AUDIT_DLQ_URL=https://sqs.us-west-2.amazonaws.com/000000000000/slack-knowledge-bot-audit-dlq-staging
# Retrieval backend. Empty → null backend (retriever returns empty).
# postgres:// or postgresql:// → pgvector. CDK-deployed tasks can leave
# this blank and inject PG* individually; the app composes the URL.
# postgres:// or postgresql:// → pgvector. Pods can leave this blank and
# inject PG* individually; the app composes the URL.
RETRIEVAL_BACKEND_URL=
# PG* — only needed if RETRIEVAL_BACKEND_URL is blank and you want
# pgvector. CDK injects PGHOST/PGPORT as env and PGUSER/PGPASSWORD
# from Secrets Manager at deploy time.
# pgvector. The chart injects PGHOST/PGPORT as env and PGUSER/PGPASSWORD
# from the ExternalSecret-synced DB credentials.
PGHOST=
PGPORT=5432
PGUSER=
PGPASSWORD=
PGDATABASE=slack_knowledge_bot
# Postgres TLS. Default verifies the RDS server cert against the bundled
# global CA (certs/rds-global-bundle.pem). Set PG_SSL_REJECT_UNAUTHORIZED=false
# for a local/dev Postgres with no trusted chain.
PG_SSL_REJECT_UNAUTHORIZED=true
PG_SSL_CA_PATH=certs/rds-global-bundle.pem
KMS_KEY_ID=arn:aws:kms:us-west-2:000000000000:key/00000000-0000-0000-0000-000000000000
REDIS_URL=rediss://your-elasticache-primary:6379

# ── Bedrock ──────────────────────────────────────────────
BEDROCK_REGION=us-west-2
BEDROCK_LLM_MODEL_ID=anthropic.claude-sonnet-4-6
# Claude Sonnet 4.6 is reachable only via the cross-region inference profile
# (us.anthropic.…); the bare model ID returns "on-demand throughput isn't
# supported". Must match the Zod default in src/config/index.ts.
BEDROCK_LLM_MODEL_ID=us.anthropic.claude-sonnet-4-6
BEDROCK_EMBEDDING_MODEL_ID=amazon.titan-embed-text-v2:0

# ── WorkOS (workforce identity — Directory Sync) ─────────
Expand All @@ -53,7 +61,6 @@ APP_BASE_URL=https://slack-knowledge-bot-staging.nanocorp.internal
RATE_LIMIT_USER_PER_HOUR=20
RATE_LIMIT_WORKSPACE_PER_HOUR=500
STALE_DOC_THRESHOLD_DAYS=90
CRAWL_INTERVAL_MINUTES=30
TOKEN_STORE_ENCRYPTION_CONTEXT=slack-knowledge-bot-token-store

# ── OAuth delegation (slack-knowledge-bot-oauth) ─────────────────────
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ All config via env vars, validated by Zod in `src/config/index.ts`. Copy `.env.e
- **OAuth delegation**: `STATE_SIGNING_SECRET` (≥ 32 bytes — HMACs both the module's state cookie and SlackKnowledgeBot's signed `/start` URL tokens)
- **App**: `APP_BASE_URL`

Defaults: `AWS_REGION=us-west-2`, `BEDROCK_REGION=us-west-2`, `BEDROCK_LLM_MODEL_ID=anthropic.claude-sonnet-4-6`, `BEDROCK_EMBEDDING_MODEL_ID=amazon.titan-embed-text-v2:0`, `RATE_LIMIT_USER_PER_HOUR=20`, `RATE_LIMIT_WORKSPACE_PER_HOUR=500`, `STALE_DOC_THRESHOLD_DAYS=90`, `TOKEN_STORE_ENCRYPTION_CONTEXT=slack-knowledge-bot-token-store`, `NODE_ENV=development`.
Defaults: `AWS_REGION=us-west-2`, `BEDROCK_REGION=us-west-2`, `BEDROCK_LLM_MODEL_ID=us.anthropic.claude-sonnet-4-6` (cross-region inference profile — the bare `anthropic.…` ID is not invocable on-demand), `BEDROCK_EMBEDDING_MODEL_ID=amazon.titan-embed-text-v2:0`, `RATE_LIMIT_USER_PER_HOUR=20`, `RATE_LIMIT_WORKSPACE_PER_HOUR=500`, `STALE_DOC_THRESHOLD_DAYS=90`, `TOKEN_STORE_ENCRYPTION_CONTEXT=slack-knowledge-bot-token-store`, `PG_SSL_REJECT_UNAUTHORIZED=true` (verifies the Aurora cert against the bundled RDS global CA at `certs/rds-global-bundle.pem`; set `false` for a chain-less local Postgres), `PG_SSL_CA_PATH=certs/rds-global-bundle.pem`, `NODE_ENV=development`.

App-level secrets in deployment live in AWS Secrets Manager at `slack-knowledge-bot/{env}/app-secrets`. Per-user OAuth tokens live in DynamoDB with KMS envelope encryption — NOT in Secrets Manager (per-user secrets would cost ~$4k/month at 10k users vs ~$10/month for DDB+KMS).

Expand Down
5 changes: 5 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ RUN npm ci --omit=dev && npm cache clean --force

COPY --from=builder /app/dist ./dist

# Amazon RDS global CA bundle — used to verify the Aurora server cert for
# Postgres TLS (src/config PG_SSL_CA_PATH default). Resolved relative to the
# /app WORKDIR at runtime.
COPY certs ./certs

USER slack-knowledge-bot

EXPOSE 3001
Expand Down
19 changes: 19 additions & 0 deletions certs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# certs/

## `rds-global-bundle.pem`

Amazon RDS / Aurora global certificate-authority bundle. Used to verify the
Aurora Serverless v2 server certificate when the app opens its Postgres
(pgvector) connection, so the DB link is authenticated-encrypted rather than
merely encrypted (`src/config/index.ts` → `PG_SSL_CA_PATH`, consumed in
`src/index.ts` / `src/scripts/seed-demo.ts`).

- **Source:** <https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem>
- **Committed** (not fetched at build time) for reproducible builds; the bundle
is published by AWS for redistribution.
- **Refresh:** re-download from the URL above when AWS rotates the CA (the
bundle aggregates all regional RDS CAs). No code change needed — the path is
stable.

To opt out of verification on a local/dev Postgres with no trusted chain, set
`PG_SSL_REJECT_UNAUTHORIZED=false`.
Loading
Loading