refactor(api)!: move ValkeyCluster TLS under spec.networking - #339
refactor(api)!: move ValkeyCluster TLS under spec.networking#339daanvinken wants to merge 2 commits into
Conversation
Introduce NetworkingSpec and prefer networking.tls. Keep top-level spec.tls for one-release dual-read so CRD upgrade does not prune TLS from existing objects. CEL rejects setting both. Deprecation warning when only top-level tls is set. Top-level tls will be removed later. Refs: valkey-io#318 Testing - go test ./api/v1alpha1/ ./internal/controller/ - make generate manifests Signed-off-by: daanvinken <daanvinken@tythus.com>
|
| Filename | Overview |
|---|---|
| api/v1alpha1/valkeycluster_types.go | Introduces nested networking TLS but prematurely removes the compatibility field and fallback required for existing TLS clusters. |
| internal/controller/config.go | Routes server TLS rendering through GetTLS, making legacy clusters render plaintext configuration after the compatibility removal. |
| internal/controller/valkeycluster_controller.go | Propagates GetTLS into managed ValkeyNodes while removing the prior deprecation-event path. |
| config/crd/bases/valkey.io_valkeyclusters.yaml | Adds the nested networking TLS schema but removes the top-level TLS schema before the migration window is complete. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
A[Existing cluster with spec.tls] --> B[Upgrade operator and CRD]
B --> C[GetTLS returns nil]
C --> D[Render configuration without TLS]
C --> E[Set ValkeyNode TLS to nil]
E --> F[Roll pods without TLS]
D --> F
Reviews (2): Last reviewed commit: "refactor(api)!: drop top-level spec.tls;..." | Re-trigger Greptile
| // objects are dual-read and not silently pruned on CRD upgrade. Cannot be | ||
| // set together with networking.tls (CEL). Will be removed in a later release. | ||
| // +optional | ||
| TLS *TLSConfig `json:"tls,omitempty"` |
There was a problem hiding this comment.
i think we can remove these old fields within same PR. as 0.* version is still not mature and causes breaking changes between operator upgrades?
There was a problem hiding this comment.
Agree, we can remove them. I did the same for the scheduling changes I made.
There was a problem hiding this comment.
Also agree, it will simplify this PR a lot. We give a warning/heads up in the release notes instead of in the docs as previously done then.
There was a problem hiding this comment.
Ok let's go with democracy. Updating PR :)
| @@ -287,6 +304,27 @@ type TLSConfig struct { | |||
| Certificate CertificateRef `json:"certificate,omitempty"` | |||
There was a problem hiding this comment.
Do you mind adding some validation here, until we have additional TLS fields added in future?
| Certificate CertificateRef `json:"certificate,omitempty"` | |
| // +kubebuilder:validation:Required | |
| Certificate CertificateRef `json:"certificate,omitempty"` |
There was a problem hiding this comment.
Similar to a previous comment, we want this to be mandatory if its defined:
| // +kubebuilder:validation:Required | |
| // +kubebuilder:validation:MinLength=1 | |
| // +kubebuilder:validation:MaxLength=253 | |
| SecretName string `json:"secretName,omitempty"` |
|
this is the migration i flagged on #318, done the careful way, and for TLS specifically i'd argue against the "just remove it, we're 0.x" that came up in the thread. the reason is the failure mode. for the scheduling move, dropping the top-level field on an already-stored cluster meant pods reschedule, annoying but recoverable. for tls it means the cluster comes back up with tls off: an encrypted, authenticated cluster silently becomes plaintext on a CRD roll, and every client that expected tls fails. that's a security downgrade triggered by an operator upgrade, not a reschedule, so it's exactly the field where the silent prune is worth avoiding. the dual-read + CEL-rejects-both + deprecation warning you've got is the right shape for that, and it also sets the pattern for the external-access move coming next, which has the same breaking-reshape problem. if the decision is still to remove top-level tls now, the mitigation has to be loud: a release note that says migrate the CR before rolling the CRD, not just the deprecation event, since the event only helps people who are still applying manifests, not the objects sitting in etcd. |
|
Yeah I should have clarified but that's exactly the reason why I kept it, it's a strong security regression due to a simple operator upgrade. I do understand the point of @jdheyburn and @sandeepkunusoth though that this operator is not meant for critical workloads yet. I'd be also in favor of keeping backwards compatibility for at least one version and then clean up. This makes upgrades a lot easier and it's nice for us maintainers to gain some mileage with these kind of migrations (hoping we never need them ;)). |
|
See thread - moving forward with cleanup of old spec in this PR. |
Remove dual-read of legacy ValkeyCluster spec.tls per review consensus. TLS lives only under networking.tls. Require TLSConfig.certificate and CertificateRef.secretName when TLS is set. Docs call out hard migration before CRD roll. Testing - go test ./api/v1alpha1/ ./internal/controller/ - make generate manifests Signed-off-by: daanvinken <daanvinken@tythus.com>
| func (c *ValkeyCluster) GetTLS() *TLSConfig { | ||
| if c == nil || c.Spec.Networking == nil { | ||
| return nil | ||
| } | ||
| return c.Spec.Networking.TLS | ||
| } |
There was a problem hiding this comment.
Legacy TLS silently becomes plaintext
When an operator and CRD upgrade reconciles an existing cluster that uses only top-level spec.tls, GetTLS returns nil because the compatibility field and fallback have been removed. Configuration rendering then omits TLS and the desired ValkeyNode specs clear TLS, causing the pods to roll with plaintext communication.
Artifacts
- Evidence file captured while the check ran.
- Evidence file captured while the check ran.
Repro: verbose failing test output showing nil TLS and omitted TLS directives
- The full command output behind this check.
There was a problem hiding this comment.
@greptile-apps If the user switches spec.tls to spec.networking.tls, things would continue as normal - right?
There was a problem hiding this comment.
Yes, exactly — if a user explicitly updates their manifest to spec.networking.tls, GetTLS() will return the config correctly and TLS continues to work.
The concern in my original comment is specifically about the upgrade window: objects already persisted in etcd with only spec.tls set (no user action taken yet) will have GetTLS() return nil after the operator upgrade, because there's no fallback path like:
if c.Spec.Networking != nil && c.Spec.Networking.TLS != nil {
return c.Spec.Networking.TLS
}
// fallback to legacy field
return c.Spec.TLSThe PR description says top-level spec.tls stays for "one-release dual-read" and emits a deprecation warning, but GetTLS() as written doesn't implement that fallback — it only reads from spec.networking.tls. So any cluster that hasn't been manually migrated yet would silently lose TLS on the next reconcile after upgrading the operator.
|
makes sense for 0.x, no objection to going with the removal. the one thing i'd ask of the release note then: have it say plainly that a cluster with top-level |
|
@melancholictheory Would a message in the release notes as we did for scheduling be sufficient? https://github.com/valkey-io/valkey-operator/releases/tag/v0.4.0 |
|
release notes are the right place, yes, but the scheduling wording isn't quite enough as a template for this one. that note says "these fields moved to spec.scheduling", which is accurate for scheduling because the failure mode is cosmetic, pods reschedule. for tls the same "it moved" sentence hides the actual effect: an upgrade with top-level so the note needs one extra sentence the scheduling one didn't: not just "tls moved to spec.networking.tls", but "if you upgrade with top-level spec.tls still set, TLS is disabled on the cluster, migrate the field before rolling the new CRD". same format as v0.4.0, just with the consequence spelled out and the ordering made explicit. that's the whole ask, the mechanism (release note) is fine, it's the wording that has to name the security effect rather than describe a field move. and to your inline question (switching |
| | `tls.crt` | Server certificate (or chain) | | ||
| | `tls.key` | Private key for the certificate | | ||
|
|
||
| > **Breaking (alpha):** top-level `spec.tls` is removed. Use `spec.networking.tls`. Migrate every ValkeyCluster **before** rolling the CRD; the API server prunes unknown fields on existing objects, so an unmigrated CR quietly loses TLS after the CRD upgrade. |
There was a problem hiding this comment.
I think we can put this in the release notes instead, or we will remove this in 0.6?
Summary
Phase 1a of #318: introduce
spec.networkingand preferspec.networking.tls.Top-level
spec.tlsstays for one-release dual-read so rolling the CRD does not prune TLS from objects already in etcd. CEL rejects setting both. Using only top-leveltlsemits a deprecation Warning event. Top-level field will be removed in a later release.Behaviour
networking.tlsonlytlsonlyValkeyNodestill uses top-levelnode.Spec.TLS(internal copy from the cluster).Out of scope
networking.discovery) — feat: announce cluster nodes by hostname for TLS-friendly discovery #296 / Umbrella: spec.networking for in-cluster endpoint & connectivity configuration #297networking.external) — [Design] External cluster access for client connections outside Kubernetes #276spec.tls(follow-up after migration window)Testing
go test ./api/v1alpha1/ ./internal/controller/make generate manifestsnetworking.tlsUmbrella #318 stays open for discovery/external.