fix(gatewayapi): reject entire cert chain when leaf is expired#9248
fix(gatewayapi): reject entire cert chain when leaf is expired#9248AruneshDwivedi wants to merge 4 commits into
Conversation
When a TLS secret contains a certificate chain where the leaf certificate is expired but the intermediate CA is still valid, filterValidCertificates was filtering out only the expired leaf and keeping the intermediate. This caused the intermediate CA to be passed to Envoy as the 'certificate', which doesn't match the private key (the private key matches the leaf). Envoy then rejects this with KEY_VALUES_MISMATCH, causing a configuration stall where no envoy configurations get updated. Fix: track whether the first PEM block (leaf certificate) was invalid, and if so, reject the entire chain with InvalidCertificateRef. This ensures that a partial chain is never passed to Envoy. Also adds: - TestFilterValidCertificates_ExpiredLeafWithValidChain: generates an expired leaf + valid intermediate programmatically and verifies the entire chain is rejected Fixes envoyproxy#9225 Signed-off-by: Arunesh Dwivedi <[email protected]>
✅ Deploy Preview for cerulean-figolla-1f9435 ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2f9e4f9cf9
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if blockIndex == 0 { | ||
| firstBlockInvalid = true | ||
| } |
There was a problem hiding this comment.
Don't reject recoverable leading malformed blocks
When the first PEM block is malformed but a later block is a valid certificate, this branch now makes filterValidCertificates return InvalidCertificateRef with no data before reaching the existing partial-invalid path. The existing TestFilterValidCertificates/bundle-first-invalid fixture covers exactly that case (malformed-cert.pem + rsa-cert.pem) and expects the valid RSA cert to be kept, so this change both breaks the unit test and rejects previously recoverable Secrets; the expired-leaf stall only requires treating a parsed, time-invalid leaf as chain-fatal, not parse errors that can still be skipped.
Useful? React with 👍 / 👎.
…blocks Malformed PEM blocks (parse errors) are now skipped instead of being treated as chain-fatal. Only the first PEM block being expired or not-yet-valid triggers InvalidCertificateRef. This allows recovery of valid certs from bundles where the first block is malformed. Signed-off-by: Arunesh Dwivedi <[email protected]>
A malformed first block (parse failure) should not invalidate the whole chain — subsequent valid certs can still be used. Only treat as fully-invalid when the leaf cert parses but fails validation (expired/not-yet-valid). Signed-off-by: Arunesh Dwivedi <[email protected]>
|
Parse failures and validation failures are handled differently here. When x509.ParseCertificates fails on the first block, blockIndex is incremented before the continue, so the firstBlockInvalid check is never reached for parse errors. firstBlockInvalid only triggers when the first block parses successfully but fails time-validation (expired/not-yet-valid). A malformed leading PEM block would fall through and let later valid blocks be recovered, preserving the existing PartiallyInvalidCertificateRef behavior. |
Signed-off-by: Arunesh Dwivedi <[email protected]>
|
The Codex review misunderstands the control flow. When fails (malformed PEM block), is incremented before the at line 238. This means:
is only set when a cert parses successfully but fails time validation (). That's exactly the intended behavior: reject the entire chain only when the leaf cert (first block) is expired/not-yet-valid, because the private key matches the leaf. The existing fixture () validates this — malformed first block is skipped, valid second block is accepted. The new test validates the opposite: expired leaf + valid chain → entire chain rejected. The code is correct as written. |
When a TLS secret contains a certificate chain where the leaf certificate is expired but the intermediate CA is still valid,
filterValidCertificateswas filtering out only the expired leaf. The remaining chain (intermediate CA only) was passed to Envoy, which then rejected it with KEY_VALUES_MISMATCH because the private key belongs to the leaf, not the CA.This fix checks if the first PEM block (the leaf) is expired. If it is, the entire chain is rejected -- same as if all certs were expired.
Fixes #9225