Skip to content

refactor(api)!: move ValkeyCluster TLS under spec.networking - #339

Merged
jdheyburn merged 1 commit into
valkey-io:mainfrom
daanvinken:feat/networking-tls-move
Aug 5, 2026
Merged

refactor(api)!: move ValkeyCluster TLS under spec.networking#339
jdheyburn merged 1 commit into
valkey-io:mainfrom
daanvinken:feat/networking-tls-move

Conversation

@daanvinken

@daanvinken daanvinken commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Phase 1a of #318: introduce spec.networking and move TLS to spec.networking.tls.

Hard break (no dual-read): top-level spec.tls is removed from the API. GetTLS() only reads spec.networking.tls. There is no fallback and no deprecation warning path. When TLS is set, certificate.secretName is required.

spec:
  networking:
    tls:
      certificate:
        secretName: valkey-tls

Breaking upgrade / release note

Same approach as the scheduling move (v0.4.0): document in release notes + docs. For TLS the failure mode is worse than scheduling (security downgrade), so the wording must state the consequence, not only that the field moved.

Migrate every ValkeyCluster to spec.networking.tls before rolling the new CRD/operator.

After the CRD drops top-level tls, the API server prunes that field from objects already in etcd. The operator then sees no TLS config and reconciles the cluster without TLS (plaintext). Clients that expected TLS fail. That is intentional 0.x behaviour; the release note is the protection, not a dual-read.

Draft for the next operator GitHub release

Copy into the release body (do not use a soft “fields moved” line alone):

### Breaking: TLS under `spec.networking`

- Top-level `spec.tls` is **removed**. Use `spec.networking.tls`.
- **Upgrade order:** migrate every ValkeyCluster to `spec.networking.tls`
  **before** applying the new CRD / operator image.
- **If you upgrade with only top-level `spec.tls` still set, TLS is turned
  off.** The unknown field is dropped from the object; the cluster comes
  back up serving **plaintext**. This is not a silent rename of a working
  field—migrate first, then roll CRDs.

```yaml
# before
spec:
  tls:
    certificate:
      secretName: valkey-tls

# after
spec:
  networking:
    tls:
      certificate:
        secretName: valkey-tls

Also in-repo: `docs/valkeycluster.md` (TLS section) has the same upgrade-order warning.

## Behaviour

| Spec after this change | Result |
|---|---|
| `networking.tls` set | TLS used |
| only old top-level `tls` (pre-migration) | pruned after CRD roll → **no TLS** |
| neither | no TLS |

`ValkeyNode` still uses top-level `node.Spec.TLS` as an **internal** primitive filled from the cluster (not a mirrored `NetworkingSpec`).

## Out of scope

- Hostname discovery (`networking.discovery`, `clusterDomain`) — #296 / follow-up under #318
- External access (`networking.external`) — #276
- Split-TLS / mTLS under `networking.tls` — design follow-up
- Dual-read of top-level `spec.tls` (explicitly not done; see review thread)

## Testing

- `go test ./api/v1alpha1/ ./internal/controller/`
- `make generate manifests`
- e2e TLS manifest uses `networking.tls`

Umbrella #318 stays open for discovery/external (sub-issues next).

@daanvinken
daanvinken marked this pull request as ready for review July 27, 2026 15:17
@greptile-apps

greptile-apps Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This change moves ValkeyCluster TLS configuration from spec.tls to spec.networking.tls, updates generated API assets and documentation, and routes the new configuration through Valkey configuration generation, child-node creation, and controller cluster-state connections.

Disproved: configuring TLS solely through spec.networking.tls.certificate.secretName does not fall back to plaintext in the exercised controller paths. A focused test verified TLS-only server configuration, certificate-secret propagation to child ValkeyNodes, and a CA-verified TLS handshake through the cluster-state connection.

Confidence Score: 5/5

Safe to merge; no blocking failure remains.

The final defect set is empty. The new TLS configuration path was exercised through generated configuration, node propagation, and a verified controller TLS connection.

T-Rex T-Rex Logs

What T-Rex did

  • Before capture, the TLS review test did not compile against main due to missing Networking types.
  • After capture, the TLS review test passes on PR HEAD with a real TLS handshake.
  • Ran a self-authored Go controller test against a disposable main archive and then against this change; the baseline did not compile due to missing Networking types, while the change passes after TLS propagation setup and a CA-verified TLS handshake.
  • Ran the repository TestGetTLS cases for nil, absent, and populated networking TLS configurations; they passed, confirming the new TLS API accessor uses spec.networking.tls rather than plaintext.

View all artifacts

T-Rex Ran code and verified through T-Rex

Reviews (3): Last reviewed commit: "refactor(api)!: move ValkeyCluster TLS u..." | Re-trigger Greptile

Comment thread internal/controller/valkeycluster_controller.go Outdated
Comment thread api/v1alpha1/valkeycluster_types.go Outdated
// 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"`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, we can remove them. I did the same for the scheduling changes I made.

@bjosv bjosv Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok let's go with democracy. Updating PR :)

Comment thread api/v1alpha1/valkeycluster_types.go Outdated
@@ -287,6 +304,27 @@ type TLSConfig struct {
Certificate CertificateRef `json:"certificate,omitempty"`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mind adding some validation here, until we have additional TLS fields added in future?

Suggested change
Certificate CertificateRef `json:"certificate,omitempty"`
// +kubebuilder:validation:Required
Certificate CertificateRef `json:"certificate,omitempty"`

Comment thread api/v1alpha1/valkeycluster_types.go Outdated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to a previous comment, we want this to be mandatory if its defined:

Suggested change
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=253
SecretName string `json:"secretName,omitempty"`

@jdheyburn jdheyburn linked an issue Jul 27, 2026 that may be closed by this pull request
5 tasks
@melancholictheory

Copy link
Copy Markdown
Contributor

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.

@daanvinken

Copy link
Copy Markdown
Contributor Author

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 ;)).
Happy to discuss further.

@daanvinken

Copy link
Copy Markdown
Contributor Author

See thread - moving forward with cleanup of old spec in this PR.

Comment on lines +302 to 307
func (c *ValkeyCluster) GetTLS() *TLSConfig {
if c == nil || c.Spec.Networking == nil {
return nil
}
return c.Spec.Networking.TLS
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security 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

Repro: focused Go test exercising legacy YAML decoding, GetTLS, configuration rendering, and desired ValkeyNode generation

  • Evidence file captured while the check ran.

Repro: Go overlay used to execute the focused test against the controller package without modifying tracked source

  • 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.

View artifacts

T-Rex Ran code and verified through T-Rex

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@greptile-apps If the user switches spec.tls to spec.networking.tls, things would continue as normal - right?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.TLS

The 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved on the description: dual-read was dropped by design after review.
GetTLS only reads networking.tls. Protection is migrate-before-CRD + release notes (stating TLS turns off if you don't migrate), not a fallback path.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it — thanks for the clarification. The PR description does state this explicitly, and the GetTLS() implementation matches the stated design. My concern was valid as a flag (silent plaintext on missed migration), and it sounds like the release note wording — explicitly calling out the TLS-off consequence rather than a soft "field moved" message — is the intended mitigation.

No code change needed here. The implementation is consistent with the design decision.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

@melancholictheory

Copy link
Copy Markdown
Contributor

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 spec.tls set comes back up with TLS off after the CRD rolls, so it reads as "this turns TLS off, migrate first" rather than "a field moved". the failure mode is silent and security-facing, so the wording is what does the protecting here once the dual-read is gone. and worth stating the ordering explicitly: migrate the CR to spec.networking.tls before rolling the new CRD, since after that the value is already gone from etcd's view.

@jdheyburn

Copy link
Copy Markdown
Collaborator

@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

@melancholictheory

Copy link
Copy Markdown
Contributor

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 spec.tls still set doesn't move anything, it drops it, and the cluster comes back up serving plaintext with auth off.

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 spec.tls -> spec.networking.tls continues as normal): yes, an explicit migration is seamless, the gap is only the upgrade-without-migrating path, which is exactly what the note covers.

Comment thread docs/valkeycluster.md Outdated
| `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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can put this in the release notes instead, or we will remove this in 0.6?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I'm on board with stripping at 0.6.

Introduce NetworkingSpec and move TLS to networking.tls. Drop top-level
spec.tls (alpha hard break). Require certificate.secretName when TLS is
set. Migrate every ValkeyCluster to networking.tls before rolling the new
CRD, or the cluster comes back up with TLS off.

Refs: valkey-io#318

Testing
- go test ./api/v1alpha1/ ./internal/controller/
- make generate manifests

Signed-off-by: daanvinken <daanvinken@tythus.com>
@daanvinken
daanvinken force-pushed the feat/networking-tls-move branch from 19b9a0b to c6163cc Compare August 5, 2026 11:39
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

ValkeyCluster TLS nesting

Layer / File(s) Summary
TLS API contract and copying
api/v1alpha1/valkeycluster_types.go, api/v1alpha1/zz_generated.deepcopy.go, api/v1alpha1/valkeycluster_tls_test.go
TLS moves into NetworkingSpec. GetTLS resolves nested TLS safely. Certificate and secret-name fields require values. Deep-copy methods and unit tests cover the new structure.
CRD and manifest schema updates
config/crd/bases/valkey.io_valkeyclusters.yaml, config/crd/bases/valkey.io_valkeynodes.yaml, docs/valkeycluster.md, test/e2e/valkeycluster_tls_test.go
CRD schemas, documentation, and the end-to-end manifest use spec.networking.tls. Secret names have length validation, and required TLS fields are declared.
Controller TLS resolution
internal/controller/config.go, internal/controller/valkeycluster_controller.go
Controller configuration, ValkeyNode generation, and cluster-state TLS setup use GetTLS() instead of direct top-level TLS access.

Possibly related issues

  • valkey-io/valkey-operator#318 — The PR implements the issue’s Phase 1a change to move TLS to spec.networking.tls.
  • valkey-io/valkey-operator#297 — The PR introduces the shared spec.networking block and nests TLS under it.

Possibly related PRs

Suggested reviewers: jdheyburn

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately summarizes the main change: moving ValkeyCluster TLS configuration under spec.networking. The breaking-change indicator (!) is appropriate and clearly communicated.
Description check ✅ Passed The description is comprehensive and covers all required template sections: summary explains Phase 1a and the hard break; features/behaviour changes detail the moving and required fields; implementation describes GetTLS() and API changes; limitations identify out-of-scope items; testing outlines verification steps. Checklist is present.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@jdheyburn jdheyburn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thank you!

@bjosv bjosv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

@jdheyburn
jdheyburn merged commit 93f137a into valkey-io:main Aug 5, 2026
10 checks passed
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.

5 participants