diff --git a/AGENTS.md b/AGENTS.md index c5c338955..0506d62f2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -235,7 +235,7 @@ variables (`infra/test` enforces the list): a hash that differs between laptops | `SIGN_PUBLIC_URL` | api: origin of Sign's signing app (`apps/sign`, its own registrable domain); signing session links point there. Default `http://localhost:3004` outside production; unset in production → creating a session answers 503 | | `SIGN_DOCUMENTS_BUCKET` | api, worker, sign: Sign's documents bucket (S3, with `AWS_REGION`; Terraform output `sign_documents_bucket`). Unset: the fake store (Postgres) outside production, Sign off in production | | `SIGN_NOTIFICATIONS_FROM` | worker: coss.com's sender of Sign emails (" via coss.com") for environments without their own `email_from`, on a verified domain of `NOTIFICATIONS_ENVIRONMENT_ID`'s organization; default `NOTIFICATIONS_FROM`'s address. The worker also reads `SIGN_PUBLIC_URL` (emailed links) | -| `SIGN_SEALER`, `SIGN_SEAL_KMS_KEY_ID`, `SIGN_SEAL_CERTIFICATE_CHAIN`, `SIGN_TSA_URL` | worker: what seals Sign's completed documents (`local`, the default outside production: a self-signed key made at start; `kms`: the KMS key and its certificate chain, PEM leaf first, `\n` accepted; `csc`: the CA's cloud signing API, below) and an optional RFC 3161 TSA (B-T timestamps; production: DigiCert's). Unset in production: completions wait. Shape in `@coss/sign/env` | +| `SIGN_SEALER`, `SIGN_SEAL_KMS_KEY_ID`, `SIGN_SEAL_CERTIFICATE_CHAIN`, `SIGN_TSA_URL` | worker: what seals Sign's completed documents (`local`, the default outside production: a self-signed key made at start; `kms`: the KMS key and its certificate chain, PEM leaf first, `\n` accepted; `csc`: the CA's cloud signing API, below) and an optional RFC 3161 TSA (B-T timestamps; production: DigiCert's). Production: `csc` (SSL.com eSealing); unset there, completions wait. Shape in `@coss/sign/env` | | `SIGN_CSC_URL`, `SIGN_CSC_TOKEN_URL`, `SIGN_CSC_CLIENT_ID`, `SIGN_CSC_CLIENT_SECRET`, `SIGN_CSC_USERNAME`, `SIGN_CSC_PASSWORD`, `SIGN_CSC_CREDENTIAL_ID`, `SIGN_CSC_TOTP_SECRET` | worker with `SIGN_SEALER=csc`: a Cloud Signature Consortium API (SSL.com eSigner eSealing: `https://cs.ssl.com/csc/v1`, token `https://login.ssl.com/oauth2/token`; sandbox `cs-try.ssl.com`, `oauth-sandbox.ssl.com`), its OAuth password grant (client id, optional secret, account, password), the credential, and a base64 TOTP secret only for credentials that want a one-time password. Production: the secret ones in the stage secret (else `unset_secrets`) | | `FORMS_PUBLIC_URL` | api, worker, web: the origin forms are served on (`/f/`; a registrable domain of its own in production, routed to the api service, which then serves `/f/*` only for that host). Default `http://localhost:3001` outside production; unset in production → Forms is off (503 `forms_unavailable`) | | `FORMS_FILES_BUCKET` | api, worker, web: Forms' uploads (S3, with `AWS_REGION`). Unset: the fake store (Postgres) outside production; in production forms refuse files | @@ -812,8 +812,7 @@ and SMTP credentials, dedicated IP pools for sub-accounts, tracking and receivin endpoint filters by sub-account, budgets per sub-account, a portal for end users, MCP support, rollups of sub-account reputation counts (each evaluation counts its window live), and sub-account tenant pause webhooks, Sign's archival timestamps in seals (PAdES B-LTA: renewing a document timestamp before its TSA certificate expires) and a standalone certificate download, -a price for `sign.completed_requests`, Sign in the iOS app and the admin console, and Sign -in production (off until "Going live" in `packages/sign/AGENTS.md` is done), +a price for `sign.completed_requests`, and Sign in the iOS app and the admin console, Forms in the iOS app, malware scanning of form uploads, conditional or multi-page forms, a drag-and-drop form builder, per-form webhook URLs (endpoints filter by event type), custom domains for hosted forms, sending from inboxes at coss.com's managed inbound domain in live environments (they only receive), inbox diff --git a/infra/terraform/production.tfvars b/infra/terraform/production.tfvars index adbd3c8d7..18d418d47 100644 --- a/infra/terraform/production.tfvars +++ b/infra/terraform/production.tfvars @@ -28,8 +28,8 @@ deploy_actor_ids = ["2538462", "8019099", "25907159", "55134778", "343308169", " # Optional secrets not stored in coss/production: take a name out once put-secret.sh has set it. unset_secrets = [ "IPINFO_PRIVACY_TOKEN", - # Sign's CSC sealer (SSL.com eSigner): until its account exists. - "SIGN_CSC_CLIENT_SECRET", "SIGN_CSC_PASSWORD", "SIGN_CSC_TOTP_SECRET", + # Sign's CSC sealer: SSL.com's eSealing credential needs no one-time password. + "SIGN_CSC_TOTP_SECRET", ] initial_image_tag = "2a8195a759b0f11d430f3e9cebfcf535a99dbca7" log_retention_days = 30 @@ -69,6 +69,7 @@ plain_env = { TURNSTILE_SITE_KEY = "0x4AAAAAAFU8QY0DX4ZJGoSK" PORTAL_PUBLIC_URL = "https://portal.cosshooks.com" SIGN_PUBLIC_URL = "https://cosssign.com" + SIGN_DOCUMENTS_BUCKET = "coss-production-sign-documents-294417174674" SCHEDULING_PUBLIC_URL = "https://cossbook.com" # Forms (`/f/*` on its own domain, routed to the api) and its uploads bucket. FORMS_PUBLIC_URL = "https://cossforms.com" @@ -131,6 +132,7 @@ plain_env = { } sign = { DATABASE_POOL_MAX = "5" + SIGN_DOCUMENTS_BUCKET = "coss-production-sign-documents-294417174674" AWS_REGION = "us-east-2" VALKEY_CLUSTER = "true" RATE_LIMIT_KEY_PREFIX = "coss" @@ -146,6 +148,7 @@ plain_env = { TRACKING_PUBLIC_URL = "https://track.cosslinks.com" PORTAL_PUBLIC_URL = "https://portal.cosshooks.com" SIGN_PUBLIC_URL = "https://cosssign.com" + SIGN_DOCUMENTS_BUCKET = "coss-production-sign-documents-294417174674" SCHEDULING_PUBLIC_URL = "https://cossbook.com" GOOGLE_CALENDAR_CLIENT_ID = "776019028945-dnuug57hi1tsjfdbu93evm8ggp1ge00q.apps.googleusercontent.com" MICROSOFT_CALENDAR_CLIENT_ID = "e58a0c0d-e1bb-4aef-a861-43c755d96761" @@ -232,9 +235,17 @@ plain_env = { # Account and reputation emails go out through coss.com itself, from its own organization. NOTIFICATIONS_ENVIRONMENT_ID = "env_01m4s0af5zent8fzxwmtkergw5" NOTIFICATIONS_FROM = "coss.com " - # Sign's seal timestamps (PAdES B-T): DigiCert's public TSA, whose chain reaches an unrestricted - # AATL root. Unused until SIGN_SEALER is set. - SIGN_TSA_URL = "http://timestamp.digicert.com" + # Sign's seal: Cal.com, Inc.'s OV document signing certificate in SSL.com eSigner, converted to + # eSealing (the client secret and password are in the secret). Timestamps (PAdES B-T): DigiCert's + # public TSA, whose chain reaches an unrestricted AATL root. + SIGN_DOCUMENTS_BUCKET = "coss-production-sign-documents-294417174674" + SIGN_SEALER = "csc" + SIGN_CSC_URL = "https://cs.ssl.com/csc/v1" + SIGN_CSC_TOKEN_URL = "https://login.ssl.com/oauth2/token" + SIGN_CSC_CLIENT_ID = "2SMQFRewdWcDsf1Nv2Fjh69pFNrwNtYJ7TxtR8AS6pQ" + SIGN_CSC_USERNAME = "pumfleet" + SIGN_CSC_CREDENTIAL_ID = "ed56283f-eb6f-4f30-a958-f0e87783a900" + SIGN_TSA_URL = "http://timestamp.digicert.com" # Push notifications to the iOS app: the "coss.com push" key (production, topic com.coss.ios). APNS_KEY_ID = "WCQDCU4HUL" APNS_TEAM_ID = "ADWKXDJV75" diff --git a/packages/sign/AGENTS.md b/packages/sign/AGENTS.md index d7adb9ab7..0e99bbd98 100644 --- a/packages/sign/AGENTS.md +++ b/packages/sign/AGENTS.md @@ -175,22 +175,24 @@ templates and the embedded builder. See "Not built yet" in the root `AGENTS.md` ## Going live (production) -Sign is off in production until all of this is done, in this order: +Sign is on in production since these steps (in this order): 1. **Domain (done):** `cosssign.com` (self-serve Cloudflare account) → the `sign` service (README, "Deploying to AWS"); `SIGN_PUBLIC_URL=https://cosssign.com` (web, api, worker). 2. **Bucket (done):** `sign.tf` is applied: `coss-production-sign-documents-294417174674` and - both KMS keys (the seal's is `alias/coss-production-sign-seal`). -3. **Seal:** a document-signing certificate from a CA on Adobe's trust list (AATL), either for - the KMS key (its CSR is signed by KMS: get the public key, sign the request's SHA-256 with - `kms sign --message-type DIGEST`; asked of Sectigo, since AATL CAs want key attestation KMS - can't give) → `SIGN_SEALER=kms`, `SIGN_SEAL_KMS_KEY_ID`, `SIGN_SEAL_CERTIFICATE_CHAIN`; or an - OV document signing certificate in SSL.com eSigner converted to eSealing ("Remove 2FA") → - `SIGN_SEALER=csc` with `SIGN_CSC_*` (worker; the password and any client secret through - `put-secret.sh`, then out of `unset_secrets`). `SIGN_TSA_URL` is set (DigiCert). Before - switching on, seal a test PDF with the same values (`bun run --cwd packages/sign seal:test - --out `) and open it in Acrobat: signer trusted, timestamp trusted, "LTV enabled". -4. **Switch on:** set `SIGN_DOCUMENTS_BUCKET` (web, api, worker, sign), optionally - `SIGN_NOTIFICATIONS_FROM` (default `NOTIFICATIONS_FROM`'s address), `terraform apply`, deploy. -5. **Before announcing:** get legal review of `DEFAULT_SIGN_DISCLOSURE` and the certificate - wording, and price `sign.completed_requests` with a new rate card. + both KMS keys (the seal's is `alias/coss-production-sign-seal`, unused: AATL CAs want key + attestation KMS can't give). +3. **Seal (done):** Cal.com, Inc.'s OV document signing certificate (SSL.com, order + `co-5b1lenc0l11`, valid until 2027-11-06) in eSigner, converted to eSealing ("Remove 2FA", + `authMode: implicit`) → `SIGN_SEALER=csc` with `SIGN_CSC_*` (worker; an OAuth application at + login.ssl.com, the account `pumfleet`; the client secret and password in the stage secret). + `SIGN_TSA_URL` is DigiCert's. A test PDF sealed with the same values (`bun run --cwd + packages/sign seal:test --out `) validates (pyHanko: trusted to SSL.com's root, DigiCert + timestamp, OCSP in the DSS) and opens in Acrobat as trusted and "LTV enabled". **Renew the + certificate before it expires**: a new certificate is a new credential id + (`SIGN_CSC_CREDENTIAL_ID`); seal a test PDF with it before switching. +4. **Switch on (done):** `SIGN_DOCUMENTS_BUCKET` (web, api, worker, sign), `terraform apply`, + deploy. `SIGN_NOTIFICATIONS_FROM` is unset (`NOTIFICATIONS_FROM`'s address). +5. **Before announcing:** legal reviewed `DEFAULT_SIGN_DISCLOSURE` and the certificate wording + (2026-11-06; change them only with another review); `sign.completed_requests` still needs a + price (a new rate card).