Skip to content

feat: verify the aggregated API server certificate through the APIService caBundle - #141

Merged
ThomasK33 merged 6 commits into
mainfrom
feat/apiserver-cabundle-sync
Sep 25, 2026
Merged

ThomasK33 merged 6 commits into
mainfrom
feat/apiserver-cabundle-sync

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Refs #137. Step 2 of 3.

The aggregated API server now puts its serving CA into the APIService caBundle and turns insecureSkipTLSVerify off, so kube-apiserver verifies the server's certificate. The certificate itself comes from #140.

What changed

  • New apiservicetrust controller in the aggregated API server process. It runs only when the managed serving certificate is in use, so never with --app=controller and never outside a Pod.
    • It watches only v1alpha1.aggregation.coder.com, using a metadata.name field selector, and reacts to CA changes. It does not poll.
    • It sends one merge patch, {caBundle, insecureSkipTLSVerify:false}, only when something differs, with FieldManager coder-k8s-apiservice-cabundle.
    • The CA comes from the live Secret after the startup checks pass, so every replica writes the same bytes and invalid bytes are never written.
    • Failures never affect serving or readiness. A missing permission is logged at most every 5 minutes, and retries back off to at most 60 s.
  • Opt-out: coder.com/manage-ca-bundle=false on the APIService, for clusters where cert-manager or GitOps owns caBundle.
  • RBAC: config/rbac/apiservice-cabundle-role.yaml grants get/list/watch/patch on that one APIService, limited by resourceNames. It is left out of dist/install.yaml (the controller-only bundle), which is byte-identical.
  • Manifest: deploy/apiserver-apiservice.yaml no longer sets insecureSkipTLSVerify: true.
  • Docs: how kube-apiserver trusts the server, upgrade order, rollback, the opt-out, and restarting every replica after a CA rotation.
  • CI: both checks send a real request through kube-apiserver.
    • Positive: caBundle equals the Secret CA, and kubectl get --raw /apis/aggregation.coder.com/v1alpha1 succeeds.
    • Negative: revoke the sync's binding, set a wrong CA, and require a 503 plus kube-apiserver's x509 "unknown authority" log line. Then restore the binding and require recovery.

Finding: Available does not show that verification works

With a wrong caBundle, the APIService stayed Available=True ("all checks passed"), while proxied requests failed with 503 and kube-apiserver logged an x509 error. So neither check relies on Available.

Risks

  • A CA rotation (delete the Secret, restart every replica) causes about 10 s of 503s on proxied requests while old pods drain. Renewing the serving certificate causes none.
  • If the new image rolls out before its RBAC, it logs the missing permission (rate-limited) and the APIService keeps working without verification until the RBAC is applied.
  • Applying the new manifest before the new image gives 503s until the image runs. The docs give the order to follow.
  • The sync would fight any other tool that sets caBundle; the opt-out annotation covers that.

Validation

  • make verify-vendor, build, lint, test, test-integration, docs-check, markdownlint, cspell, actionlint, and the installer freshness check all pass.
  • 12 of 12 mutation checks were killed, including RBAC without resourceNames, no field selector, ignoring the opt-out, writing unvalidated bytes, and a missing FieldManager.
  • An envtest proves the resourceNames list/watch works with the field selector, and that drift is repaired.
  • Kind v1.32 dogfood covered all three upgrade orders, rollback, the opt-out, and the CI steps copied from ci.yaml (the x509 line appeared in 4 of 4 runs). A 2-replica CA rotation produced exactly one patch and no flapping.

CI checks and opt-out on Kind

Two-replica CA rotation timeline

Upgrade from insecureSkipTLSVerify

optout-ci.mp4

Next

Step 3 adds an x509 troubleshooting entry and more rotation docs; #137 will be closed after it lands. Follow-ups: a namespaced Role for the serving-CA Secret, and bring-your-own Secret support.


Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: medium

Refs #137

The aggregated API server now keeps APIService v1alpha1.aggregation.coder.com
trusting the CA from its serving-certificate Secret, so kube-apiserver
verifies the aggregated API server instead of skipping TLS verification.

- New internal/aggregated/apiservicetrust controller, started with the
  managed serving certificate (never in --app=controller or outside a
  Pod). It watches only that APIService (metadata.name field selector)
  and is a servingcert.Manager listener; it does not poll.
- It sets spec.caBundle to the Secret's CA and insecureSkipTLSVerify to
  false in one merge patch (field manager
  coder-k8s-apiservice-cabundle), only when something differs. The CA is
  read from the live Secret and validated with servingcert.Parse, so
  replicas cannot flap during a CA rotation and unvalidated bytes are
  never written.
- Failures never affect serving or readiness: they are logged at most
  every 5 minutes (naming the missing permission and the docs) and
  retried with backoff capped at 60s.
- The annotation coder.com/manage-ca-bundle=false makes it leave the
  APIService alone (cert-manager or GitOps ownership).
- config/rbac/apiservice-cabundle-role.yaml: get/list/watch/patch on that
  one APIService via resourceNames; excluded from dist/install.yaml,
  which stays byte-identical.
- deploy/apiserver-apiservice.yaml no longer sets insecureSkipTLSVerify
  (the API rejects it once a caBundle is set).
- CI e2e: wait for the caBundle to match the Secret CA, then Available;
  as the last step, revoke the sync's permission, set a wrong CA, require
  Available=False with an x509 message, restore, require recovery.
- Docs: how kube-apiserver trusts the server, RBAC, upgrade order,
  rollback, opt-out, and restarting every replica after a CA rotation.

---
_Generated with `xum` • Model: `anthropic:claude-opus-5-5` • Thinking: `high`_
Refs #137

---
_Generated with `xum` • Model: `anthropic:claude-opus-5-5` • Thinking: `high`_
…t out before rolling back

Refs #137

Without list/watch the informer never syncs, so the missing-permission
warning never appeared (seen in the Kind dogfood). Log it from the watch
error handler, rate-limited like the patch warning. The documented
rollback now sets the opt-out annotation first; otherwise the running
server re-applies the caBundle immediately.

---
_Generated with `xum` • Model: `anthropic:claude-opus-5-5` • Thinking: `high`_
…condition

Refs #137

The Kind dogfood showed kube-apiserver's APIService availability check
does not verify the serving certificate: with a wrong caBundle the
APIService stayed Available=True/Passed while proxied requests failed
with 503 and kube-apiserver logged x509 errors. The CI checks now use a
request proxied by kube-apiserver (and its x509 log line), and the docs
describe the 503 window instead of Available=False.

---
_Generated with `xum` • Model: `anthropic:claude-opus-5-5` • Thinking: `high`_
Refs #137

---
_Generated with `xum` • Model: `anthropic:claude-opus-5-5` • Thinking: `high`_
Refs #137

---
_Generated with `xum` • Model: `anthropic:claude-opus-5-5` • Thinking: `high`_
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 25, 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-09-25T11:24:26.614996Z 17a93c2 PR opened
🔒 Security Review ✅ Completed 2026-09-25T11:26:58.349423Z 17a93c2 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.

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

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

Reviewed commit: 17a93c2121

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 Sep 25, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 25, 2026
@ThomasK33
ThomasK33 added this pull request to the merge queue Sep 25, 2026
Merged via the queue into main with commit a158658 Sep 25, 2026
13 checks passed
@ThomasK33
ThomasK33 deleted the feat/apiserver-cabundle-sync branch September 25, 2026 11:47
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.

1 participant