Skip to content

🤖 fix: a CoderProvisioner keeps every key record with IDs until Coder confirms its key gone - #425

Merged
ThomasK33 merged 4 commits into
mainfrom
provisioner-keep-live-records
Oct 30, 2026
Merged

ThomasK33 merged 4 commits into
mainfrom
provisioner-keep-live-records

Conversation

@ThomasK33

Copy link
Copy Markdown
Member

Summary

A CoderProvisioner no longer drops a key record that has a key ID before Coder confirms its key gone. Status written by an older operator can hold several records of the target key under different organization IDs. Each such key is now deleted by its recorded IDs before its record goes, and a record whose delete fails stays. This fixes #424 item 1 and documents items 2 and 3 as limits in the user docs.

Background

Organization names in Coder can be renamed and reused. An older operator could leave two live target records under different organization IDs. Before this PR, two paths dropped such a record while its key was still valid:

  1. A replacement kept only the newest target record with IDs before it wrote the next create intent.
  2. A reconcile with no replacement (usable Secret, no drift, every record published) removed every target-slot record at the publish step, without any delete. An independent pre-PR reviewer found this path. One way to reach it: a TLS service-port round trip changes the Coder API URL while status.url stays the same.

#420 stopped new replacements from building that state. Upgraded status could still hold it.

Implementation

  • internal/controller/coderprovisioner_controller.go:
    • The replacement keeps every target record with a key ID. It drops only ID-less target records, because the new create intent replaces them. The existing replacement loop then deletes each kept record by ID with DeleteProvisionerKeyByID at the current URL and token. That call returns success only on Coder's own 404 or a key list without that ID. Any other error stops the reconcile before the create, and status keeps the record.
    • A published target record with a key ID other than status.provisionerKeyID now starts a rotation, so it goes through the same delete-by-ID path.
  • Token and URL rules from 🤖 fix: a CoderProvisioner replaces only keys it recorded and binds key records to the control plane UID #420 are unchanged. Records with another control plane UID are not in the target slot. They still go through tokenBoundTo and ControlPlaneChangedKeyKept.
  • docs/tutorials/deploy-coderprovisioner.md documents the two remaining limits: a legacy record without a UID whose URL now reaches another backend (item 2), and Coder's list-then-delete-by-name race (item 3).

Validation

  • New envtest TestCoderProvisionerKey_UpgradedStatusKeepsTargetRecords runs against a real SDK client and an httptest Coder with two live target keys under different organization IDs:
    • both keys deleted and published records: the rotation deletes both keys and the replaced key, records the new key, and deletion of the provisioner leaves no key in Coder.
    • failed delete keeps the record: a failed key list in the older key's organization returns an error, and status keeps both records. The next reconcile deletes both.
  • Test-first history: commit 27074ea fails on main in both of its subtests. Commit 9a75a8f fails on 9aa56d3 in published records. Each following fix commit makes them pass.
  • The verifier characterization TestVerifierR2OlderPendingPruning from the 🤖 fix: a CoderProvisioner replaces only keys it recorded and binds key records to the control plane UID #420 evidence passed on main. It now fails at its "pruned, key still live" assertion.
  • Local gates: make verify-vendor, make test, make build, make lint, make test-integration. Docs: the docs-reference diff, mkdocs build --strict, markdownlint-cli2 and cspell all pass.

Risks

Fixes #424
Refs #420


Generated with mux • Model: anthropic:claude-opus-5-5 • Thinking: high

#424)

Change-Id: Iaa18cc18a5f4885026329506558d91f959884569
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…er confirms its key gone (#424)

Change-Id: I0b46d9be08ccaca81a9fea4bdb4324b12d30c854
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…lete (#424)

Change-Id: I524273d9b6e00e8fd83a9810c172f2c3be163793
Signed-off-by: Thomas Kosiewski <tk@coder.com>
… the applied key (#424)

Change-Id: If519b7e06e051ef7efa2cbc01658dc401e53eec4
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-30T21:57:42.775298Z 12c9c2f PR opened
🔒 Security Review ✅ Completed 2026-10-30T22:01:23.235449Z 12c9c2f Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: 12c9c2f334

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@ThomasK33
ThomasK33 added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit 03eca05 Oct 30, 2026
12 of 13 checks passed
@ThomasK33
ThomasK33 deleted the provisioner-keep-live-records branch October 30, 2026 22:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CoderProvisioner: inherited key-record pruning and legacy-record limits after #420

1 participant