toga-ai 1.0.833 → 1.0.834

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -61,5 +61,5 @@
61
61
  | [USPS DPV Deliverability Verdict (is this address actually insurable/shippable?)](features/usps-dpv-deliverability.md) | **USPS returning HTTP 200 with a populated address is NOT evidence that the address is deliverable.** The authoritative signal is USPS's **DPV (Delivery Point V |
62
62
  | [Refreshing a Local Dev Database from Beta (dev-sandbox)](workflows/local-db-refresh-from-beta.md) | How to reset a local 2.0 dev database from the **beta / dev-sandbox** environment: dump each schema (`Core`, `Client_<Id>`, `Logs_<Id>`, …) from the beta host, |
63
63
  | [Deleting a shared branch does not remove bad commits — a stale local clone merges them back](workflows/recreated-shared-branch-stale-local-remerge.md) | **Deleting and recreating a shared environment branch removes only the *ref*.** Every teammate who still has that branch checked out locally keeps the full pre- |
64
- | [Rotating Public TLS Certificates (ACM, CloudFront, ALB, Elastic Beanstalk)](workflows/rotating-public-tls-certificates.md) | How to replace the public TLS certificates that terminate HTTPS for TOGa front-end domains (togahub, togacommerce, togadesk, togaretail, togasupply, togaview, t |
64
+ | [Rotating Public TLS Certificates (ACM, CloudFront, ALB, Elastic Beanstalk, API Gateway)](workflows/rotating-public-tls-certificates.md) | How to replace the public TLS certificates that terminate HTTPS for TOGa front-end domains (togahub, togacommerce, togadesk, togaretail, togasupply, togaview, t |
65
65
  | [Running a 2.0 App Locally (browser, end-to-end via api2)](workflows/running-a-2.0-app-locally.md) | The full dependency chain required to run a 2.0 client app **through the browser**, end-to-end, against a **local `api2`** (e.g. |
@@ -1,12 +1,12 @@
1
1
  ---
2
- title: Rotating Public TLS Certificates (ACM, CloudFront, ALB, Elastic Beanstalk)
2
+ title: Rotating Public TLS Certificates (ACM, CloudFront, ALB, Elastic Beanstalk, API Gateway)
3
3
  framework: "2.0"
4
4
  repo: _underscore
5
5
  project: _Underscore
6
6
  client: shared
7
7
  type: workflow
8
8
  status: active
9
- updated: 2026-09-16
9
+ updated: 2026-09-17
10
10
  owners: ["rgirish"]
11
11
  files: []
12
12
  related:
@@ -27,13 +27,15 @@ This runbook was written from the 2026-09-16 rotation in AWS account **654654170
27
27
  one IMPORTED multi-domain cert (14 SANs, copied into us-east-1, us-west-2 and eu-west-1) served
28
28
  4 CloudFront distributions and 12 ALB listeners and expired the same day.
29
29
 
30
- **The three things that bite you:**
30
+ **The four things that bite you:**
31
31
  1. **Elastic Beanstalk keeps its own saved cert setting**, separate from the ALB listener. Fixing
32
32
  the listener is not enough — the next deploy puts the old cert straight back.
33
33
  2. **CloudFront holds exactly ONE cert per distribution.** There is no staging. The swap *is* the
34
34
  cutover, and it takes 5-15 minutes.
35
35
  3. **An ACM wildcard matches exactly one label.** `*.togasupply.com` covers
36
36
  `elite.togasupply.com` but **not** `compass.beta.togasupply.com`.
37
+ 4. **API Gateway custom domains hold their own cert** and are invisible to a CloudFront/ALB/EB
38
+ sweep. This is what the 2026-09-16 rotation missed — see step 6b.
37
39
 
38
40
  **Root cause to avoid repeating:** an **IMPORTED** ACM cert never auto-renews. Always replace with
39
41
  **Amazon-issued, DNS-validated** ACM certs, which renew themselves.
@@ -123,6 +125,60 @@ one), and every environment stayed Green/Ok through the update — no traffic di
123
125
  **Gotcha:** an EB environment whose CloudFormation stack is in `DELETE_FAILED` cannot be updated at
124
126
  all — `update-environment` is refused. That needs separate stack cleanup.
125
127
 
128
+ ## Step 6b — Audit API Gateway custom domains (the 2026-09-16 miss)
129
+
130
+ **API Gateway is a FOURTH resource type.** The 2026-09-16 rotation swept CloudFront, ALB listeners
131
+ and EB saved configs, and reported "0 endpoints at risk". The next morning `webhook.togahub.com`
132
+ was hard-down — it is an API Gateway custom domain, so none of those three sweeps could ever have
133
+ found it. Proof of the failure, not just a stale cert:
134
+
135
+ ```
136
+ $ curl -sS -o /dev/null -w "http=%{http_code} ssl=%{ssl_verify_result}\n" https://webhook.togahub.com/
137
+ curl: (60) SSL certificate problem: certificate has expired
138
+ http=000 ssl=10
139
+ ```
140
+
141
+ **Find them from DNS — an API Gateway domain CNAMEs to `*.execute-api.<region>.amazonaws.com`:**
142
+
143
+ ```
144
+ aws route53 list-resource-record-sets --hosted-zone-id <zid> --max-items 400 \
145
+ --query "ResourceRecordSets[?Type=='CNAME'].[Name,ResourceRecords[0].Value]" --output text \
146
+ | grep -i execute-api
147
+ ```
148
+
149
+ Across all seven TOGa zones this found exactly one: `webhook.togahub.com` →
150
+ `d-yiy4od2ade.execute-api.us-east-1.amazonaws.com`.
151
+
152
+ **An unknown account ID in `InUseBy` can be AWS itself.** The expired cert's `InUseBy` listed 3
153
+ ALBs in account **250044486744**, which no one has a profile for. That is AWS-managed edge
154
+ infrastructure — an edge-optimized API Gateway domain terminates TLS on AWS's own load balancers.
155
+ Treat it as a pointer to API Gateway, **not** as a rogue account to go hunt credentials for.
156
+
157
+ **Permissions block this from the CLI.** The `GoAgilant-Developers` SSO role has **no**
158
+ `apigateway:GET` in any of the three accounts (654654170868, 502614707982, 975050298201) —
159
+ `get-domain-names`, `get-domain-name` and the `apigatewayv2` equivalents all return
160
+ `AccessDeniedException`. The domain cannot be listed, read or fixed with the standard developer
161
+ role. Use the Console, or get `apigateway:GET` + `apigateway:PATCH` added first.
162
+
163
+ **Check the endpoint type FIRST — it decides which field you patch.** `get-domain-name` returns
164
+ `endpointConfiguration.types`. An **EDGE** domain reads its cert from `us-east-1` and uses
165
+ `/certificateArn`; a **REGIONAL** domain reads from its own region and uses
166
+ `/regionalCertificateArn`. Patching the wrong field silently does nothing useful.
167
+ `webhook.togahub.com` turned out to be **REGIONAL** (us-east-1), despite `InUseBy` showing
168
+ AWS-managed edge ALBs — so do not infer the type from `InUseBy`.
169
+
170
+ ```
171
+ aws apigateway get-domain-name --domain-name webhook.togahub.com \
172
+ --query 'endpointConfiguration.types'
173
+
174
+ aws apigateway update-domain-name --domain-name webhook.togahub.com \
175
+ --patch-operations op=replace,path=/regionalCertificateArn,value=<new-arn>
176
+ ```
177
+
178
+ The domain goes `UPDATING` and takes a few minutes to return to `AVAILABLE` (~4 min observed).
179
+ Poll `domainNameStatus` before verifying — checking too early shows the old cert and looks like a
180
+ failed patch. No downtime beyond the already-broken TLS; base path mappings are untouched.
181
+
126
182
  ## Step 7 — Check alias coverage before any CloudFront swap
127
183
 
128
184
  CloudFront **rejects** an update if any alias on the distribution is not covered by the new cert.
@@ -155,11 +211,22 @@ echo | openssl s_client -servername HOST -connect HOST:443 2>/dev/null \
155
211
  | openssl x509 -noout -issuer -enddate
156
212
  ```
157
213
 
214
+ `openssl` shows you the cert, but it does **not** prove a client can connect. Add a real verify —
215
+ and treat a known-bad host as the control that proves your check actually fails:
216
+
217
+ ```
218
+ curl -sS -o /dev/null -w "http=%{http_code} ssl=%{ssl_verify_result}\n" https://HOST/
219
+ ```
220
+
221
+ A live expired cert returns `http=000` with `SSL certificate problem: certificate has expired`.
222
+
158
223
  Also run it **without** `-servername` to check the no-SNI / default-cert path — that path can still
159
224
  be serving the old cert after everything else looks fine.
160
225
 
161
- Finish by re-running the step 1 audit: every CloudFront distribution, every ALB listener, and every
162
- EB saved config should name a cert with a future `NotAfter`.
226
+ Finish by re-running the step 1 audit: every CloudFront distribution, every ALB listener, every
227
+ EB saved config **and every API Gateway custom domain** should name a cert with a future
228
+ `NotAfter`. A sweep that omits any one of those four resource types is not a clean result — it is
229
+ an untested one.
163
230
 
164
231
  ## Gotchas
165
232
 
@@ -170,9 +237,28 @@ EB saved config should name a cert with a future `NotAfter`.
170
237
  - **CloudFront takes one cert; ALBs take many.** Two completely different risk profiles in the same
171
238
  rotation — plan them separately.
172
239
  - **Wildcards match one label only.** Verify alias coverage with code, not by eye.
240
+ - **API Gateway custom domains are a fourth resource type.** A CloudFront + ALB + EB sweep misses
241
+ them completely. Find them by `execute-api` CNAMEs in Route 53. See step 6b.
242
+ - **An unknown account ID in `InUseBy` may be AWS itself.** `250044486744` is AWS-managed edge
243
+ infrastructure for API Gateway, not a rogue account. See step 6b.
244
+ - **The `GoAgilant-Developers` role needs `apigateway:GET` + `apigateway:PATCH`** on
245
+ `arn:aws:apigateway:*::/domainnames*`. Added to the permission set 2026-09-17; it was missing
246
+ during the rotation. Granted via IAM Identity Center, then **provisioned** to the account — the
247
+ provision step is what actually applies it. See step 6b.
248
+ - **Patch the field that matches the endpoint type.** REGIONAL uses `/regionalCertificateArn`,
249
+ EDGE uses `/certificateArn`. See step 6b.
250
+ - **`openssl` is not proof of health.** It prints the cert even when clients cannot connect. Use
251
+ `curl` and check for `http=000`. See step 9.
173
252
  - **Expired certs hide.** Sweep the whole account; do not trust `InUseBy`.
174
253
 
175
254
  ## Change history
255
+ - 2026-09-17 — Added step 6b (API Gateway custom domains) after `webhook.togahub.com` was found
256
+ serving an expired cert and failing TLS the day after the rotation reported all-clear; added the
257
+ `curl` proof step, the AWS-owned-account `InUseBy` tell, and the missing `apigateway` permission.
258
+ **Fixed the same day:** `apigateway:GET`/`PATCH` added to the permission set, domain confirmed
259
+ REGIONAL, `regionalCertificateArn` swapped to the Amazon-issued `*.togahub.com` cert
260
+ (`a53d1ff6-...`, expires 2027-03-31). Verified `http=404 ssl=0` (was `http=000 ssl=10`), with
261
+ `expired.badssl.com` as the control that still fails. (rgirish)
176
262
  - 2026-09-16 — Created from the account 654654170868 rotation: replaced an expiring 14-SAN IMPORTED
177
263
  cert with 6 per-domain Amazon-issued DNS-validated certs, relinked 12 ALB listeners, 6 CloudFront
178
264
  distributions and 11 EB environment configs; documented the EB saved-config trap. (rgirish)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.833",
3
+ "version": "1.0.834",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",