Repository navigation
docs(operations): add encryption at rest guides for etcd and Keycloak - #742
Andrei Kvapil (kvaps) wants to merge 1 commit into
Conversation
✅ Deploy Preview for cozystack ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
Aleksei Sviridkin (lexfrei)
left a comment
There was a problem hiding this comment.
Andrei Kvapil (@kvaps) NOT LGTM. The pages themselves hold up against the code I checked; the Keycloak page has to wait for cozystack/cozystack#4818, which I just sent back with changes requested.
The Keycloak page says "The chart refuses to enable encryption without it". On cozystack main 52f542c4 that is false: deksetSecretName is optional there and the proxy falls back to an in-memory DEK. Since next/ is published from main, merging this first would document a guard that does not exist. #4818 may also still change in ways this page should mention, for example how the static KEK Secret survives disabling encryption. So please re-check the page against what #4818 merges with.
The PR description starts with "Do not merge until cozystack/cozystack#4818 is merged." This repo uses the description as the merge commit message, so that line would stay in main's history. Please drop it before merging.
What I checked on the Keycloak page and found correct: every encryption.* key and its default against packages/system/keycloak/values.yaml, the backfill flags (generate-dekset, encrypt-rows, -vault-mount, -dsn) against keycloak-kms-proxy v0.2.3, the encrypted columns against internal/config/fields.go, and the metrics port and kkp_decrypt_failures_total. The Vault policy is enough too, because the proxy only calls encrypt/ and decrypt/ on the key. The suspend step is safe as well: the Package reconciler keeps the HelmRelease's spec.suspend as it finds it. For the tenant part, --encryption-provider-config is not one of the flags the kubernetes chart drops or refuses, and secret volumes are allowed in extraVolumes.
One non-blocking note: the prerequisites say "Talos v1.14.1 runs etcd v3.7.1", but next already pins Talos v1.14.2, so the etcd version in that sentence should follow the pinned Talos.
|
Aleksei Sviridkin (@lexfrei) merged main and fixed the Talos version, v1.14.2 runs the same etcd 3.7.1. Re-checked the Keycloak page against #4818 as it is now: the page covers only Vault Transit, so the keep annotation and the stricter KEK rule don't change anything there. I'll drop the "Do not merge" line from the description once #4818 is merged. |
## What this PR does Two fixes for the optional KMS proxy of the platform Keycloak (`encryption.*`). The default static backend never started. The chart stored 32 alphanumeric characters as the KEK, but the proxy wants base64 of 32 bytes and exits with `config: KKP_KEK must decode to 32 bytes (got 24)`. The KEK is now generated with `randBytes`, and a stored key of the old form is replaced. The proxy never ran with such a key, so nothing was encrypted under it. Any other stored key is kept if the proxy accepts it and fails the render otherwise, so the chart never overwrites key material. The KEK Secret now carries `helm.sh/resource-policy: keep`: the DEK set is wrapped with it, so it has to survive turning encryption off or reinstalling the package. Without `deksetSecretName` the proxy mints a new DEK set on every start (`buildCipher` in `cmd/proxy/main.go` of keycloak-kms-proxy) and keeps it only in memory. After the first restart of the proxy pod it cannot decrypt anything it wrote before. The values comment said this is safe on a fresh DB, it is not. The chart now refuses to render the proxy without a DEK set unless `fields: disabled`. Passthrough still works without one, so the static backend can get its KEK Secret created before the DEK set is minted with it. Breaking: a release with encryption on and no `deksetSecretName` fails to render on upgrade until a DEK set is configured. Such a setup loses its data on the next proxy restart anyway, so failing loudly is the point. A stored static KEK the proxy cannot decode fails the render too, instead of being replaced. Checked with `helm unittest` (100 tests, the new ones fail on the old template) and by starting keycloak-kms-proxy v0.2.3 with the KEK the chart renders now. With the KEK from the old template it fails as quoted above. ### Screenshots No UI changes. ### Downstream repositories - [ ] No downstream repository is affected by this change - [x] [cozystack/website](https://github.com/cozystack/website) - follow-up: cozystack/website#742 documents the setup with a DEK set - [ ] [cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack) - follow-up: - [ ] [cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack) - follow-up: - [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up: - [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up: - [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) - follow-up: - [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) - follow-up: - [ ] [cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server) - follow-up: - [ ] [cozystack/external-apps-example](https://github.com/cozystack/external-apps-example) - follow-up: - [ ] [cozystack/examples](https://github.com/cozystack/examples) - follow-up: - [ ] [cozystack/community](https://github.com/cozystack/community) - follow-up: The other repositories do not touch the keycloak chart values. ### Release note ```release-note fix(keycloak)!: The KMS proxy of the platform Keycloak now requires `encryption.deksetSecretName` unless `encryption.fields` is `disabled`. Without it the proxy generated a new data key on every start and could not read data encrypted before a restart. The static backend now generates a KEK the proxy accepts; before, the proxy could not start with it. The KEK Secret is no longer deleted when encryption is turned off or the package is removed. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Configuration** * Enabling field encryption requires a persistent DEK set. Chart rendering reports an error if it is missing; setting fields to `disabled` does not require one. * For the static backend, a valid stored KEK is reused, and a missing or legacy-format KEK is replaced. An unusable stored KEK causes rendering to fail. * The KEK Secret is retained when encryption is disabled or the package is removed. * **Documentation** * Clarified DEK set requirements and sharing across starts and replicas, and updated guidance on configuring multiple proxy replicas. * Warns against deleting the retained KEK while encrypted values remain. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Aleksei Sviridkin (lexfrei)
left a comment
There was a problem hiding this comment.
Andrei Kvapil (@kvaps) NOT LGTM. The Talos version fix is correct: Talos v1.14.2 sets DefaultEtcdVersion = "3.7.1" (pkg/machinery/constants/constants.go:443), and main pins v1.14.2. The other two points from my last review are still open, and a full pass found four more problems.
Still open from the last review:
- The Keycloak page still describes a guard that main does not have. The alert at
keycloak.md:28says "The chart refuses to enable encryption without it". On cozystack main b3662866 the only guard is forreplicas > 1(packages/system/keycloak/templates/proxy.yaml:55).helm templatewithenabled: true,kms.backend: vault-transitand nodeksetSecretNamerenders without error there, and fails on #4818 (b62f1caf) as the page says. v1.7.0-rc.2 does not have that guard either, andnext/becomes the v1.7 docs when that release is cut. So #4818 has to merge first, or the sentence has to name the release that has the guard. The same applies to the body of commit 8da73b3 ("The keycloak chart now refuses..."). This repo merges with merge commits, so that sentence lands in main as written. - The description still starts with "Do not merge until...", and the description becomes the merge commit message. Dropping it once #4818 merges is fine, I only note that it is still there.
New:
- The Keycloak page has nothing for users who already run encryption without a DEK set.
encryptionwith an optionaldeksetSecretNamehas shipped since v1.6.0. Anyone who enabled it that way will get the render failure from #4818 on upgrade and will probably come to this page. Its steps don't help them. A new DEK set cannot open values written under the old in-memory DEK, andencrypt-rowsskips every value that already starts with$KKP$(cmd/backfill/encrypt.go:103in v0.2.3). Those rows stay unreadable. Please add a warning for this case and the recovery path. As far as I can tell, the only one is restoring the backup taken before encryption was enabled. - The Vault token in the Keycloak prerequisites can't run the backfill. Line 34 asks for a token that "can create a Transit key and use it once to encrypt". Step 3 runs
encrypt-rowswith the same$VAULT_TOKEN, and that command unwraps the DEK set throughkms.OpenCipher(internal/kms/envelope.go:68-79). The unwrap callstransit/decrypt/keycloak(internal/kms/vault.go:99). With the token scoped as described, the backfill fails with 403 in the middle of the maintenance window, while Keycloak is down. The token needsupdateontransit/decrypt/keycloaktoo. "Once" is also not accurate, becausegenerate-deksetwraps more than one key. - The node files also hold the secretbox key in plain text, and the Git advice misses it. Until step 7 that key (
etcd-kms.md:211) decrypts every Secret written before the switch. The alert at:231-232moves only the pod definition and the two AppRole IDs into templates and encrypted values. Following it as written still commits the secretbox key to the repo history. Please add the key to that list. - The rollback section loses every HelmRelease.
etcd-kms.md:444says to rewrite "all Secrets" before removing thekmsprovider. Step 5 moved HelmReleases to KMS too, so after a rollback done exactly as written, they all become unreadable once the provider is gone, and Flux loses every application. The rewrite has to include the HelmRelease command from step 5. Separately, withidentityfirst the rollback leaves Secrets in plain text, which is weaker than thesecretboxsetup the cluster started from. Putting thesecretboxkey first brings it back to the starting point.
Non-blocking:
- The private CA advice at
etcd-kms.md:229doesn't work as written. The static pod mounts only/var/kms, so a CA file placed anywhere else on the node is not visible to-vault-ca-cert. Either say to put it under/var/kms, or show a hostPath mount for it. - The rotation section (
etcd-kms.md:427) has the same Secrets-only wording before raisingmin_decryption_version. Also, the check in step 6 only looks at thek8s:enc:kms:v2:prefix and can't show the key version. A way to confirm nothing is left on an old version would help. - The Talm prerequisite is stronger than the Talm docs. "Earlier releases cannot render configuration for Talos v1.14 nodes" vs
docs/configuration/talos-versions.md:22at v0.35.0, which says v1.14 nodes are served by a v1.13 contract. - The plugin also calls
auth/token/renew-self(pkg/vault/lease.go:100at v1.4.0). It works only because AppRole tokens get thedefaultpolicy, so one sentence for roles withtoken_no_default_policywould be enough. - The pod name
keycloak-db-1atkeycloak.md:183may not exist after a failover or instance replacement.--selector cnpg.io/cluster=keycloak-db,cnpg.io/instanceRole=primaryavoids that.
I checked these and found them correct: the vault-kubernetes-kms v1.4.0 binary path, flags and env names, the KubeEtcdEncryptionConfig shape and the exact conflict error in Talos v1.14.2, UID 65534 for kube-apiserver, and the Kubernetes range against the Talos support matrix. Same for the tenant controlPlane.apiServer values, which render into the KamajiControlPlane on main and are not refused, and for the Cozystack API having no etcd of its own. The Keycloak resource names match main, and every internal link and anchor resolves in a local build with next/ enabled.
IvanHunters
left a comment
There was a problem hiding this comment.
LGTM.
I verified every tool flag, chart value and version claim in these three pages against upstream source at the pinned versions, rather than trusting the prose: vault-kubernetes-kms v1.4.0 (flags, env, default health port, the /live behaviour), keycloak-kms-proxy v0.2.3 (backfill subcommands and flags, the $KKP$ marker and backfill idempotency, buildCipher, the encrypted columns and their modes), the cozystack.keycloak and kubernetes chart values on main, and the Talos 1.14 / Kubernetes / etcd facts. Everything checks out. Notably etcd 3.7.1 in Talos v1.14.2 is correct (constants.go + the v1.14.2 image list), the keycloak encryption.* values and the deksetSecretName guard are present on main, and all internal links resolve (the deploy preview builds green).
A few optional, non-blocking precision notes are inline. None of them is wrong at the version this page pins today (Talos v1.14.2); they are about claims that are a little broader than the thing they rest on.
One thing outside the pages themselves: the PR description still says "do not merge until cozystack/cozystack#4818 is merged", but #4818 merged on 2026-10-27, so that gate is already lifted.
|
|
||
| Skip this step if the control-plane nodes already run Talos v1.14 or later (`talosctl --nodes <cp-ip> version`). | ||
|
|
||
| Talos v1.14 no longer loads kernel modules on demand, and DRBD loads its network transport that way. Without the module loaded explicitly, DRBD resources on an upgraded node stay in `Connecting`. The module is already present on Talos v1.13, so add it to the `values.yaml` of your Talm project and apply it to every node before the upgrade: |
There was a problem hiding this comment.
[MINOR] DRBD module claim is version-fragile and already reverted upstream
[MINOR] This is correct at the Talos version the page pins today (v1.14.2), but the blanket phrasing "Talos v1.14 no longer loads kernel modules on demand" holds only for v1.14.0-v1.14.4. Talos v1.14.5 (2026-10-26) restored on-demand loading, so from that patch drbd_transport_tcp no longer needs to be listed, and this whole step becomes unnecessary. Consider scoping the sentence to the pinned version, or noting that v1.14.5 reverted it. Separately, from Talm v0.36.0 the cozystack preset already carries drbd_transport_tcp in its built-in module list, so the manual extraKernelModules entry is redundant there.
|
|
||
| - Talos Linux v1.14 or later on all control-plane nodes. Talos v1.13 generates the encryption configuration itself and does not allow replacing it; the `KubeEtcdEncryptionConfig` document first appeared in [Talos v1.14](https://docs.siderolabs.com/talos/v1.14/reference/configuration/kubernetes/kubeetcdencryptionconfig). Clusters installed with an earlier Cozystack release may still run Talos v1.13; upgrade them first (see [step 1](#1-upgrade-talos)). | ||
| - A Kubernetes version supported by Talos v1.14: 1.33 to 1.37, per the [Talos support matrix](https://docs.siderolabs.com/talos/v1.14/getting-started/support-matrix). | ||
| - Talm v0.35.0 or later. Earlier releases cannot render configuration for Talos v1.14 nodes. Keep `talosVersion` in `Chart.yaml` at `v1.13` or lower, as the Talm documentation requires. |
There was a problem hiding this comment.
[NIT] Talm v0.35.0 prerequisite reason is stronger than verified
[NIT] Requiring Talm v0.35.0+ is right (it is the first release built against the Talos v1.14 machinery). The justification "Earlier releases cannot render configuration for Talos v1.14 nodes" is the part I could not confirm: a v1.14 node still accepts a v1.13-contract v1alpha1 config (the deprecated fields are not removed), and this guide deliberately keeps talosVersion at v1.13. What is genuinely gated on a newer Talm is rendering the v1.14 contract with typed Kube* documents (Talm v0.36.0). Optional: soften the reason, or keep just "v0.35.0 is the first release built against Talos v1.14".
|
|
||
| A few things to know about this configuration: | ||
|
|
||
| - `apiVersion: v2` is required. Without it the provider defaults to KMS v1, which is disabled since Kubernetes 1.29, and kube-apiserver refuses the configuration. |
There was a problem hiding this comment.
[NIT] KMS v1 is disabled by default, not unconditionally
[NIT] KMS v1 is disabled by default since Kubernetes 1.29; it can still be force-enabled with --feature-gates=KMSv1=true. The practical advice (apiVersion: v2 is what you want) is unaffected; this is just precision.
| Encrypted values start with the `$KKP$` marker. | ||
|
|
||
| {{% alert color="warning" %}} | ||
| The proxy needs a persistent DEK set, `encryption.deksetSecretName`. Without one it would generate a new DEK every time it starts and keep it only in memory, so after a restart of the proxy pod it could no longer decrypt anything it had encrypted before. The chart refuses to enable encryption without it. |
There was a problem hiding this comment.
[NIT] The deksetSecretName guard is conditional on encryption.fields
[NIT] The chart's fail on an empty deksetSecretName only fires when encryption.fields is not "disabled" (templates/proxy.yaml on main). With fields: disabled (passthrough, no column encryption) the chart renders without a DEK set. That is defensible, since fields: disabled means nothing is being encrypted, so "refuses to enable encryption without it" is true in spirit. Flagging only in case you want the alert to be exact.
Add an Encryption at Rest section to the next docs. It explains what Cozystack stores where and how it is protected by default, and has two guides: - encrypting the management cluster etcd with KMS v2 and HashiCorp Vault Transit on Talos v1.14, including Flux HelmReleases, which carry application values; with notes for aggregated API servers and the local-key option for tenant Kubernetes clusters, where KMS is not supported yet; - encrypting the platform Keycloak user data with keycloak-kms-proxy, which needs a persistent DEK set since cozystack/cozystack#4818. Assisted-by: LLM Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
bf28380 to
e7c6f4b
Compare
|
Aleksei Sviridkin (@lexfrei) thanks, all of it is in.
Non-blocking: the CA goes under IvanHunters took all four: the DRBD step is scoped to v1.14.0 to v1.14.4 with the v1.14.5 and Talm v0.36.0 notes, plus the Talm reason, KMS v1 "by default" and the |
New section under Operations: Encryption at Rest, in
next/only. The etcd guide needs Talos v1.14, and the Keycloak guide relies on the DEK-set check from cozystack/cozystack#4818, which is on main and not in v1.7.Three pages:
vault-kubernetes-kmsrunning as a Talos static pod. It also encrypts Flux HelmReleases, because application values (passwords included) live inspec.values. Short notes for aggregated API servers (the Cozystack API has no storage of its own, so it is already covered) and for tenant clusters, where KMS is not possible yet and the page shows the local-key option through the existingcontrolPlane.apiServervalues. Rotation, rollback back to the Talossecretboxsetup, troubleshooting.Every field, flag and command was checked against the source at pinned versions: Talos v1.14.2, Talm v0.35.0, vault-kubernetes-kms v1.4.0, keycloak-kms-proxy v0.2.3, Kubernetes 1.34, and the
kubernetesandkeycloakcharts on main. The tenant values and the Keycloak values render withhelm template. The same setup runs on a production installation. Not run end to end on a clean cluster: the Talm node-file flow with$patch: deleteforsecretboxEncryptionSecret, and the local-key option for tenant clusters.