From 6a1ab838335c8832a2effd519635d55f71bd9759 Mon Sep 17 00:00:00 2001 From: yaroslavmokflmg Date: Wed, 16 Sep 2026 00:42:28 +0400 Subject: [PATCH] helm: add Redis TLS to the fleet chart, mirroring database.tls A managed Redis answers only over TLS and signs with a private CA, so the client has to be handed one. cache.tls follows database.tls exactly, and is wired into both workloads that open a Redis connection: the server deployment and the vuln-processing cron. Off by default, so an in-cluster Redis keeps connecting in plain text. --- charts/fleet/templates/deployment.yaml | 24 +++++++++++++++++++ .../templates/vulnprocessing/cronjob.yaml | 24 +++++++++++++++++++ charts/fleet/values.yaml | 9 +++++++ openframe/docs/helm-chart.md | 24 ++++++++++++++++--- 4 files changed, 78 insertions(+), 3 deletions(-) diff --git a/charts/fleet/templates/deployment.yaml b/charts/fleet/templates/deployment.yaml index 1b2234d5d31..1bc1718a457 100644 --- a/charts/fleet/templates/deployment.yaml +++ b/charts/fleet/templates/deployment.yaml @@ -359,6 +359,20 @@ spec: # <<< OPENFRAME(helm) - name: FLEET_REDIS_DATABASE value: "{{ .Values.cache.database }}" + # >>> OPENFRAME(helm): fork-added Redis TLS — openframe/docs/helm-chart.md + {{- if .Values.cache.tls.enabled }} + - name: FLEET_REDIS_USE_TLS + value: "true" + {{- if .Values.cache.tls.caCertKey }} + - name: FLEET_REDIS_TLS_CA + value: "/secrets/redis/{{ .Values.cache.tls.caCertKey }}" + {{- end }} + {{- if .Values.cache.tls.serverName }} + - name: FLEET_REDIS_TLS_SERVER_NAME + value: "{{ .Values.cache.tls.serverName }}" + {{- end }} + {{- end }} + # <<< OPENFRAME(helm) # >>> OPENFRAME(helm): retry the first Redis dial — openframe/docs/helm-chart.md - name: FLEET_REDIS_CONNECT_RETRY_ATTEMPTS value: "{{ .Values.cache.connectRetryAttempts }}" @@ -577,6 +591,11 @@ spec: readOnly: true mountPath: /secrets/mysql {{- end }} + {{- if .Values.cache.tls.enabled }} + - name: redis-tls + readOnly: true + mountPath: /secrets/redis + {{- end }} {{- if or (eq .Values.osquery.logging.statusPlugin "filesystem") (eq .Values.osquery.logging.resultPlugin "filesystem") }} - name: osquery-logs mountPath: /logs @@ -645,6 +664,11 @@ spec: secretName: "{{ default .Values.database.secretName .Values.database.existingSecret }}" # <<< OPENFRAME(helm) {{- end }} + {{- if .Values.cache.tls.enabled }} + - name: redis-tls + secret: + secretName: "{{ default .Values.cache.secretName .Values.cache.tls.existingSecret }}" + {{- end }} {{- if or (eq .Values.osquery.logging.statusPlugin "filesystem") (eq .Values.osquery.logging.resultPlugin "filesystem") }} - name: osquery-logs emptyDir: diff --git a/charts/fleet/templates/vulnprocessing/cronjob.yaml b/charts/fleet/templates/vulnprocessing/cronjob.yaml index 4702b699e40..bee0b4141a9 100644 --- a/charts/fleet/templates/vulnprocessing/cronjob.yaml +++ b/charts/fleet/templates/vulnprocessing/cronjob.yaml @@ -269,6 +269,20 @@ spec: # <<< OPENFRAME(helm) - name: FLEET_REDIS_DATABASE value: "{{ .Values.cache.database }}" + # >>> OPENFRAME(helm): fork-added Redis TLS — openframe/docs/helm-chart.md + {{- if .Values.cache.tls.enabled }} + - name: FLEET_REDIS_USE_TLS + value: "true" + {{- if .Values.cache.tls.caCertKey }} + - name: FLEET_REDIS_TLS_CA + value: "/secrets/redis/{{ .Values.cache.tls.caCertKey }}" + {{- end }} + {{- if .Values.cache.tls.serverName }} + - name: FLEET_REDIS_TLS_SERVER_NAME + value: "{{ .Values.cache.tls.serverName }}" + {{- end }} + {{- end }} + # <<< OPENFRAME(helm) {{- if .Values.cache.usePassword }} - name: FLEET_REDIS_PASSWORD valueFrom: @@ -325,6 +339,11 @@ spec: readOnly: true mountPath: /secrets/mysql {{- end }} + {{- if .Values.cache.tls.enabled }} + - name: redis-tls + readOnly: true + mountPath: /secrets/redis + {{- end }} # >>> OPENFRAME(helm): fork-added CA truststore volume mount — openframe/docs/helm-chart.md {{- if include "fleet.additionalCAs.enabled" . }} - name: ca-certs @@ -380,6 +399,11 @@ spec: secretName: "{{ default .Values.database.secretName .Values.database.existingSecret }}" # <<< OPENFRAME(helm) {{- end }} + {{- if .Values.cache.tls.enabled }} + - name: redis-tls + secret: + secretName: "{{ default .Values.cache.secretName .Values.cache.tls.existingSecret }}" + {{- end }} # >>> OPENFRAME(helm): fork-added CA truststore volumes (configmaps/secrets) — openframe/docs/helm-chart.md {{- if include "fleet.additionalCAs.enabled" . }} {{- range .Values.fleet.additionalCAs.configMaps }} diff --git a/charts/fleet/values.yaml b/charts/fleet/values.yaml index 39e2a0cfdbf..b77be391163 100644 --- a/charts/fleet/values.yaml +++ b/charts/fleet/values.yaml @@ -390,6 +390,15 @@ cache: # >>> OPENFRAME(helm): upstream defaults this to 0, so a boot without Redis dies at once — openframe/docs/helm-chart.md connectRetryAttempts: 10 # <<< OPENFRAME(helm) + # >>> OPENFRAME(helm): fork-added Redis TLS, mirrors database.tls — openframe/docs/helm-chart.md + # Managed Redis (Memorystore) answers only over TLS. The CA rides in an externally managed Secret, + # mounted whole at /secrets/redis, exactly like the MySQL server CA. + tls: + enabled: false + existingSecret: "" # defaults to cache.secretName + caCertKey: "" # key inside that Secret holding the server CA in PEM + serverName: "" # only when the certificate names a host the address does not + # <<< OPENFRAME(helm) # >>> OPENFRAME(redis-key-prefix): fork-added per-tenant Redis key prefix — openframe/docs/redis-key-prefix.md # When set, wires FLEET_REDIS_KEY_PREFIX from existingConfigMap[keyPrefixKey]. # Required when sharing one Redis across multiple Fleet servers (multi-tenant). diff --git a/openframe/docs/helm-chart.md b/openframe/docs/helm-chart.md index c2502f772a9..5850bc0b65b 100644 --- a/openframe/docs/helm-chart.md +++ b/openframe/docs/helm-chart.md @@ -99,7 +99,7 @@ from it as an env var, and — when `database.tls.enabled` — the same Secret i precedence over the legacy `database.secretName`, which still applies when `existingSecret` is unset. The mount is whole-Secret (no `items:` projection), so every key in it surfaces as a file in the Fleet container; keep unrelated material out of that Secret if that matters to you. -| Cache (Redis) | `cache.*` | `cache.existingConfigMap` | `fleet-cache` ConfigMap (address, key prefix) | +| Cache (Redis) | `cache.*` | `cache.existingConfigMap`, `cache.tls.existingSecret` | `fleet-cache` ConfigMap (address, key prefix) | | Tenant UUID (multi-tenancy) | `fleet.openframe.multiTenancy.*` | `fleet.openframe.multiTenancy.existingConfigMap` | `fleet-openframe-tenant` ConfigMap (`FLEET_OPENFRAME_TENANT_UUID` = `tenantUuid`, empty in shared mode) | | Admin setup | `fleet.setup.*` | `fleet.setup.adminPassword.existingSecret` | `fleet-setup` Secret (`FLEET_SETUP_ADMIN_PASSWORD`) | @@ -127,6 +127,24 @@ It reads from the cache ConfigMap, so the prefix (typically the tenant ID) is managed alongside the Redis address. See [redis-key-prefix.md](redis-key-prefix.md) for what the prefix does in the server. +## Redis TLS + +A managed Redis (Memorystore, ElastiCache) answers only over TLS, and its CA is private, so the +client has to be handed one. `cache.tls` mirrors `database.tls` exactly: + +| values key | Env var | Notes | +|------------|---------|-------| +| `cache.tls.enabled` | `FLEET_REDIS_USE_TLS` | Off by default; an in-cluster Redis stays plain. | +| `cache.tls.caCertKey` | `FLEET_REDIS_TLS_CA` | Key inside the Secret; becomes `/secrets/redis/`. | +| `cache.tls.serverName` | `FLEET_REDIS_TLS_SERVER_NAME` | Only when the certificate names a host the address does not. | +| `cache.tls.existingSecret` | — | Secret mounted at `/secrets/redis`; falls back to `cache.secretName`. | + +The mount is whole-Secret, same as `/secrets/mysql`, and it is wired into both workloads that open a +Redis connection: the server deployment and the vuln-processing cron. + +Fleet itself needs nothing else for a clustered managed Redis — `redis_cluster_follow_redirections` +defaults to true, so MOVED replies from the discovery endpoint are followed. + ## Migration job [`templates/job-migration.yaml`](../../charts/fleet/templates/job-migration.yaml) @@ -357,10 +375,10 @@ helm upgrade --install fleet oci://ghcr.io/flamingo-stack/fleetmdm/helm-charts/f | File | Purpose | |------|---------| -| `charts/fleet/values.yaml` | OpenFrame mode, externalized DB/cache/setup config, `cache.keyPrefixKey`, `cache.connectRetryAttempts`, `waitForMysql`, `probes`, `additionalCAs`, `vulnProcessing`, `deploymentAnnotations` | +| `charts/fleet/values.yaml` | OpenFrame mode, externalized DB/cache/setup config, `cache.keyPrefixKey`, `cache.connectRetryAttempts`, `cache.tls`, `waitForMysql`, `probes`, `additionalCAs`, `vulnProcessing`, `deploymentAnnotations` | | `charts/fleet/templates/configmap.yaml` | **New** — generated DB/cache ConfigMaps | | `charts/fleet/templates/secret.yaml` | **New** — generated DB password / admin-setup Secrets | -| `charts/fleet/templates/deployment.yaml` | `FLEET_OPENFRAME_MODE`, `FLEET_OPENFRAME_MULTI_TENANCY_ENABLED` / `FLEET_OPENFRAME_TENANT_UUID` / `FLEET_OPENFRAME_TEAM_ID`, `FLEET_REDIS_KEY_PREFIX`, ConfigMap/Secret refs, annotations, CA init container, probe split | +| `charts/fleet/templates/deployment.yaml` | `FLEET_OPENFRAME_MODE`, `FLEET_OPENFRAME_MULTI_TENANCY_ENABLED` / `FLEET_OPENFRAME_TENANT_UUID` / `FLEET_OPENFRAME_TEAM_ID`, `FLEET_REDIS_KEY_PREFIX`, Redis TLS, ConfigMap/Secret refs, annotations, CA init container, probe split | | `charts/fleet/templates/job-migration.yaml` | `waitForMysql` init container, hook removal, TTL removal | | `charts/fleet/templates/vulnprocessing/cronjob.yaml` | Dedicated vuln-processing cron + `FLEET_REDIS_KEY_PREFIX`, feed-cache PVC mount, fsGroup, schedule stagger (moved from `templates/cron-vulnprocessing.yaml`) | | `charts/fleet/templates/vulnprocessing/pvc.yaml` | **New** — PVC persisting the vulnerability feed cache across cron runs |