Repository navigation
[deploy] Fix and harden cert handling - #4387
Conversation
Runs daily and fails if any NUC or local.thecombine.app cert stored in S3 expires within 50 days. The cert-proxy-server renews certs with 60 days left, so this catches a stalled renewal before the NUCs and laptops are left with an expired cert. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The watch on the TLS secrets had no timeout, so when its connection died without being closed, monitor.py waited forever and never pushed renewed certs to S3. The watch now ends every 30 minutes, has a read timeout to detect a dead connection, and is restarted after any error. Each new watch re-pushes all the monitored secrets. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A dropped connection to S3 failed a push and exited monitor.py, which could leave a new key.pem in S3 next to an old cert.pem. Each push now has a 60-second timeout and up to 5 attempts. If all of them fail, the watch is restarted, which re-pushes all the monitored secrets. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Let's Encrypt is moving to 64-day certs in 2027 and 45-day certs in 2028, which breaks the fixed 60-day renewal thresholds on both ends. The cert-proxy-server now renews NUC certs with 92% of their lifetime left (renew-before-percentage), so the certs in S3 stay fresh for NUCs and laptops that go offline. update_cert.py checks S3 when 88% of its cert's lifetime is left, replaces the secret only with a cert that expires later, and applies it in place instead of deleting and recreating it, so the secret is never missing. monitor.py only watches TLS secrets, skips pushes of unchanged secrets, logs the exit code of a failed push, and sets separate connect and read timeouts on the watch. The S3 cert check runs every 12 hours. It fails if a key does not match its cert or if a cert has less than 91% of its lifetime left, which warns of a stalled renewal before clients start polling S3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe changes use percentage-based certificate renewal thresholds, update certificate validation and application, add retries and duplicate-push checks to certificate monitoring, and introduce scheduled S3 certificate checks. ChangesCertificate renewal
Certificate monitoring and push handling
Scheduled certificate checks
Priority: ➖ Normal Estimated code review effort: 4 (Complex) | ~45 minutes Change: Bug fix Sequence Diagram(s)sequenceDiagram
participant update_cert.py
participant Kubernetes
participant S3
update_cert.py->>Kubernetes: Read current TLS secret
update_cert.py->>S3: Fetch certificate data
update_cert.py->>update_cert.py: Validate validity and compare expiration
update_cert.py->>S3: Fetch key data when the S3 certificate is newer
update_cert.py->>Kubernetes: Apply TLS secret YAML
Merge Risk: 🟡 Moderate · up to The certificate update Job can exit instead of renewing when the stored certificate is corrupt. It can also install a certificate that is not yet valid, which breaks TLS until that certificate's start date. Separately, the monitor can send rapid repeated requests to the API server when a watch closes early. These issues should be fixed before merge. Pre-merge checks |
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #4387 +/- ##
=======================================
Coverage 76.26% 76.26%
=======================================
Files 305 305
Lines 11452 11452
Branches 1409 1409
=======================================
Hits 8734 8734
Misses 2324 2324
Partials 394 394
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The replay of failed pushes depends on each watch starting without a resource version, which was not obvious from the comment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
update_cert.py wrote the generated secret manifest, which contains the TLS private key, to a temp file before applying it. It now sends the manifest to kubectl apply on stdin, using a new stdin_text parameter on run_cmd. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
monitor.py now waits before every new watch, not only after an error, so a watch that keeps closing cleanly cannot cause a tight request loop. update_cert.py now treats a current cert that fails to parse as needing renewal instead of exiting, so a malformed secret gets replaced. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jasonleenaylor
left a comment
There was a problem hiding this comment.
LGTM. The key-before-cert push order, the newer-expiry check and the in-place apply fit together well.
Summary
The NUC and laptop certs in S3 could go stale without anyone noticing. The fixed 60-day renewal windows also break when Let's Encrypt moves to 64-day certs (Feb 2027) and 45-day certs (Feb 2028).
Thresholds
certRenewBeforePercentageincert-proxy-servercert_check.ymlcertRenewBeforePercentageincert-proxy-clientcert_check.yml(new workflow)local.thecombine.appcert in S3, it fails if:key.pemdoesn't matchcert.pem, orcombineCertProxyListinprod.yaml.monitor.py(cert-proxy-server)kubernetes.io/tlssecrets, so Helm release secrets no longer pass through the watch.-9for an OOM kill.AwsBackup.pushtakescheck_resultsandtimeout.update_cert.py(cert-proxy-client)CERT_RENEW_BEFORE_PERCENTAGEpercent of the lifetime is left. It replaces the fixedCERT_RENEW_BEFOREdays.kubectl apply -f -on stdin, so it's never written to disk.run_cmdtakes a new optionalstdin_text.Charts
cert-proxy-server:renew-before: 1440h→renew-before-percentage: "92".cert-proxy-client: env varCERT_RENEW_BEFORE→CERT_RENEW_BEFORE_PERCENTAGE.cert_renew_beforevalue is removed.Evidence
flake8,isort,blackandmypypass onmaintenance/scripts.helm templaterenders:cert-manager.io/renew-before-percentage: "92"CERT_RENEW_BEFORE_PERCENTAGE: "88"The threshold and key checks from
cert_check.yml, run against generated 90-day and 45-day certs:Not yet run:
cert_check.yml:workflow_dispatchneeds the workflow onmaster.update_cert.pyagainst a cluster.Merge Danger
Door: two-way
Every change reverts cleanly, and certs already issued are unaffected. One constraint: the
combine_maintimage and thecert-proxy-clientchart must ship together. A new image with an old chart fails withKeyError: 'CERT_RENEW_BEFORE_PERCENTAGE'on every run.Blast Radius: NUC/laptop TLS
combine-cert-proxyOOM restarts are fixed by the memory limits in Raise memory limit for the cert update jobs #4350, not here. This PR reduces the memorymonitor.pyuses.🤖 Generated with Claude Code
This change is
Summary by CodeRabbit
New Features
Bug Fixes