From 2ea4aad5de3685ab49310b441fe27cbd1700a05c Mon Sep 17 00:00:00 2001 From: Arsolitt Date: Mon, 25 May 2026 12:28:51 +0300 Subject: [PATCH] feat(redis): add operator-managed TLS via cert-manager PKI chain Expose tls.enabled in the Redis chart values, mirroring the pattern already in use by mariadb, nats, opensearch, qdrant and rabbitmq. The flag is tri-state: when unset, TLS follows the value of `external`; an explicit boolean overrides the default. When TLS is enabled the chart renders the standard cozystack 4-resource cert-manager chain in the release namespace: - Issuer (self-signed bootstrap) - Certificate (CA, ECDSA, isCA, rotationPolicy: Never, 5y) - Issuer (CA-backed) - Certificate (leaf, secret -tls, ECDSA, 1y, rotationPolicy: Always) The leaf certificate covers the SAN list the redis-operator computes for its own Certificate (operator/redisfailover/service/generator.go: redisCertificateDNSNames): short, namespace, .svc and .svc. forms for rfr- / rfrm- / rfrs- / rfs- services plus a wildcard for per-pod headless DNS names of the redis statefulset. Localhost is included for in-pod readiness probes; when `external` is true the tenant LB hostname is added too. The chart hands the resulting Secret to the operator via spec.tls.certificateSecret.secretName so the operator never creates a duplicate Certificate; it just mounts the Secret into both Redis and Sentinel pods and wires the redis tls-* directives. tls.authClients maps to the Redis tls-auth-clients directive and defaults to "no". The dashboard-resources Role is extended with the new TLS secret names only when TLS is on, so the cozystack dashboard can read them to display connection details. Helm unit tests in tests/tls_test.yaml cover the tri-state resolution (four enable/external combinations), SAN list coverage for all four service names and the wildcard, custom cluster-domain handling, the external SAN injection path, authClients propagation, and dashboard RBAC expansion. The comment above the CA usages named the wrong bits: cert-manager maps signing to the digital signature bit, and crl sign and ocsp signing are separate usages this chart does not request. Assisted-By: Claude Signed-off-by: Arsolitt --- api/apps/v1alpha1/redis/types.go | 10 + packages/apps/redis/README.md | 11 +- packages/apps/redis/templates/_tls.tpl | 40 ++ .../apps/redis/templates/certmanager.yaml | 104 +++++ .../templates/dashboard-resourcemap.yaml | 5 + .../apps/redis/templates/redisfailover.yaml | 8 + packages/apps/redis/tests/tls_test.yaml | 421 ++++++++++++++++++ packages/apps/redis/values.schema.json | 15 + packages/apps/redis/values.yaml | 11 + packages/system/redis-rd/cozyrds/redis.yaml | 4 +- 10 files changed, 626 insertions(+), 3 deletions(-) create mode 100644 packages/apps/redis/templates/_tls.tpl create mode 100644 packages/apps/redis/templates/certmanager.yaml create mode 100644 packages/apps/redis/tests/tls_test.yaml diff --git a/api/apps/v1alpha1/redis/types.go b/api/apps/v1alpha1/redis/types.go index 53d537176..a255ad7ce 100644 --- a/api/apps/v1alpha1/redis/types.go +++ b/api/apps/v1alpha1/redis/types.go @@ -36,6 +36,9 @@ type ConfigSpec struct { // Enable external access from outside the cluster. // +kubebuilder:default:=false External bool `json:"external"` + // TLS configuration. When omitted, TLS is enabled automatically when `external` is true. + // +kubebuilder:default:={} + Tls TLS `json:"tls,omitempty"` // Redis major version to deploy // +kubebuilder:default:="v8" Version Version `json:"version"` @@ -51,6 +54,13 @@ type Resources struct { Memory resource.Quantity `json:"memory,omitempty"` } +type TLS struct { + // Maps to the Redis `tls-auth-clients` directive. One of `no`, `optional`, `yes`. Defaults to `no` — the server certificate is presented but client certificates are not validated. + AuthClients string `json:"authClients,omitempty"` + // Enable TLS for Redis and Sentinel connections. When omitted, defaults to the value of `external`. Encryption is provided by the redis-operator fork that mounts the certificate Secret into both Redis and Sentinel pods at `/tls`. + Enabled *bool `json:"enabled,omitempty"` +} + // +kubebuilder:validation:Enum="t1.nano";"t1.micro";"t1.small";"t1.medium";"t1.large";"t1.xlarge";"t1.2xlarge";"t1.4xlarge";"c1.nano";"c1.micro";"c1.small";"c1.medium";"c1.large";"c1.xlarge";"c1.2xlarge";"c1.4xlarge";"s1.nano";"s1.micro";"s1.small";"s1.medium";"s1.large";"s1.xlarge";"s1.2xlarge";"s1.4xlarge";"u1.nano";"u1.micro";"u1.small";"u1.medium";"u1.large";"u1.xlarge";"u1.2xlarge";"u1.4xlarge";"m1.nano";"m1.micro";"m1.small";"m1.medium";"m1.large";"m1.xlarge";"m1.2xlarge";"m1.4xlarge";"nano";"micro";"small";"medium";"large";"xlarge";"2xlarge" type ResourcesPreset string diff --git a/packages/apps/redis/README.md b/packages/apps/redis/README.md index ae5104e73..45154593f 100644 --- a/packages/apps/redis/README.md +++ b/packages/apps/redis/README.md @@ -25,7 +25,16 @@ Service utilizes the freshworks-oss Redis Operator (a maintained fork of the arc | `size` | Persistent Volume Claim size available for application data. | `quantity` | `1Gi` | | `storageClass` | StorageClass used to store the data. | `string` | `""` | | `external` | Enable external access from outside the cluster. | `bool` | `false` | -| `version` | Redis major version to deploy | `string` | `v8` | + + +### TLS parameters + +| Name | Description | Type | Value | +| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------ | +| `tls` | TLS configuration. When omitted, TLS is enabled automatically when `external` is true. | `object` | `{}` | +| `tls.enabled` | Enable TLS for Redis and Sentinel connections. When omitted, defaults to the value of `external`. Encryption is provided by the redis-operator fork that mounts the certificate Secret into both Redis and Sentinel pods at `/tls`. | `*bool` | `null` | +| `tls.authClients` | Maps to the Redis `tls-auth-clients` directive. One of `no`, `optional`, `yes`. Defaults to `no` — the server certificate is presented but client certificates are not validated. | `string` | `""` | +| `version` | Redis major version to deploy | `string` | `v8` | ### Application-specific parameters diff --git a/packages/apps/redis/templates/_tls.tpl b/packages/apps/redis/templates/_tls.tpl new file mode 100644 index 000000000..e65e35af0 --- /dev/null +++ b/packages/apps/redis/templates/_tls.tpl @@ -0,0 +1,40 @@ +{{/* +redis.tls.enabled resolves the tri-state tls.enabled field to a plain +string "true" or "false" so callers can use it with `eq`: + + {{- $tlsEnabled := (include "redis.tls.enabled" .) | eq "true" -}} + +Tri-state semantics: + - tls.enabled explicitly set (bool) → use that value + - tls.enabled unset or null → auto-on when external is true + - tls: null → treated as unset, falls back to external + +`default (dict)` guards against tls: null (nil map). `kindIs "invalid"` +catches the case where the tls.enabled key is present but set to null +(hasKey returns true but index gives nil, and nil | toString = "" +which silently breaks the tri-state). Null value is treated the same as +unset: fall back to external. +*/}} +{{- define "redis.tls.enabled" -}} +{{- $tlsMap := default (dict) .Values.tls -}} +{{- $enabled := index $tlsMap "enabled" -}} +{{- if kindIs "invalid" $enabled -}} + {{- .Values.external | default false | toString -}} +{{- else -}} + {{- $enabled | toString -}} +{{- end -}} +{{- end -}} + +{{/* +redis.tls.authClients resolves the optional tls.authClients field. +Returns "no" when unset to match the redis-operator default. +*/}} +{{- define "redis.tls.authClients" -}} +{{- $tlsMap := default (dict) .Values.tls -}} +{{- $authClients := index $tlsMap "authClients" -}} +{{- if kindIs "invalid" $authClients -}} +no +{{- else -}} +{{- $authClients -}} +{{- end -}} +{{- end -}} diff --git a/packages/apps/redis/templates/certmanager.yaml b/packages/apps/redis/templates/certmanager.yaml new file mode 100644 index 000000000..d7c7d7e82 --- /dev/null +++ b/packages/apps/redis/templates/certmanager.yaml @@ -0,0 +1,104 @@ +{{- $tlsEnabled := (include "redis.tls.enabled" .) | eq "true" -}} +{{- if $tlsEnabled }} +{{- $clusterDomain := (index .Values._cluster "cluster-domain") | default "cluster.local" }} +--- +# Self-signed bootstrap Issuer — used only to sign the CA Certificate below. +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: {{ .Release.Name }}-selfsigned + namespace: {{ .Release.Namespace }} +spec: + selfSigned: {} +--- +# CA Certificate. rotationPolicy: Never keeps the CA stable so existing +# client trust bundles remain valid across cert-manager renewals. +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ .Release.Name }}-ca + namespace: {{ .Release.Namespace }} +spec: + isCA: true + commonName: {{ .Release.Name }}-ca + # A dot, not a hyphen: the Secret suffix set holds both -ca-tls and -tls, so + # hyphenated, release `foo`'s CA Secret and release `foo-ca`'s leaf Secret + # are both redis-foo-ca-tls. The Certificate and Issuer names keep the + # hyphen; their suffixes cannot overlap that way. + secretName: {{ .Release.Name }}.ca-tls + duration: 43800h + issuerRef: + name: {{ .Release.Name }}-selfsigned + kind: Issuer + group: cert-manager.io + privateKey: + rotationPolicy: Never + algorithm: ECDSA + size: 256 + # cert sign is what lets this CA issue the leaf below; signing is the + # digital signature bit. CRL and OCSP signing are separate cert-manager + # usages and are absent, since nothing here publishes revocation. + usages: + - signing + - cert sign +--- +# CA Issuer — used to sign the leaf server certificate. +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: {{ .Release.Name }}-ca + namespace: {{ .Release.Namespace }} +spec: + ca: + secretName: {{ .Release.Name }}.ca-tls +--- +# Leaf server TLS Certificate passed to redis-operator via spec.tls.certificateSecret. +# The SAN list mirrors what the operator computes for its own Certificate +# (operator/redisfailover/service/generator.go: redisCertificateDNSNames): +# - per-service short, namespace, .svc and .svc. forms for +# rfr-/rfrm-/rfrs-/rfs- services +# - wildcard covering per-pod headless DNS for the redis statefulset +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ .Release.Name }}-tls + namespace: {{ .Release.Namespace }} +spec: + secretName: {{ .Release.Name }}.tls + duration: 8760h + renewBefore: 720h + isCA: false + usages: + - server auth + - client auth + - key encipherment + dnsNames: + {{- $ns := .Release.Namespace }} + {{- $rfName := .Release.Name }} + {{- $redisSvc := printf "rfr-%s" $rfName }} + {{- $masterSvc := printf "rfrm-%s" $rfName }} + {{- $slaveSvc := printf "rfrs-%s" $rfName }} + {{- $sentinelSvc := printf "rfs-%s" $rfName }} + - localhost + {{- range $svc := list $redisSvc $masterSvc $slaveSvc $sentinelSvc }} + - {{ $svc | quote }} + - {{ printf "%s.%s" $svc $ns | quote }} + - {{ printf "%s.%s.svc" $svc $ns | quote }} + - {{ printf "%s.%s.svc.%s" $svc $ns $clusterDomain | quote }} + {{- end }} + - {{ printf "*.%s" $redisSvc | quote }} + - {{ printf "*.%s.%s" $redisSvc $ns | quote }} + - {{ printf "*.%s.%s.svc" $redisSvc $ns | quote }} + - {{ printf "*.%s.%s.svc.%s" $redisSvc $ns $clusterDomain | quote }} + {{- if .Values.external | default false }} + - {{ printf "%s.%s" .Release.Name .Values._namespace.host | quote }} + {{- end }} + privateKey: + rotationPolicy: Always + algorithm: ECDSA + size: 256 + issuerRef: + name: {{ .Release.Name }}-ca + kind: Issuer + group: cert-manager.io +{{- end }} diff --git a/packages/apps/redis/templates/dashboard-resourcemap.yaml b/packages/apps/redis/templates/dashboard-resourcemap.yaml index 63f82a7d1..12af01914 100644 --- a/packages/apps/redis/templates/dashboard-resourcemap.yaml +++ b/packages/apps/redis/templates/dashboard-resourcemap.yaml @@ -1,3 +1,4 @@ +{{- $tlsEnabled := (include "redis.tls.enabled" .) | eq "true" -}} apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: @@ -19,6 +20,10 @@ rules: - secrets resourceNames: - "{{ .Release.Name }}-auth" + {{- if $tlsEnabled }} + - "{{ .Release.Name }}.tls" + - "{{ .Release.Name }}.ca-tls" + {{- end }} verbs: ["get", "list", "watch"] - apiGroups: - cozystack.io diff --git a/packages/apps/redis/templates/redisfailover.yaml b/packages/apps/redis/templates/redisfailover.yaml index 26b8ca1c6..4681cbfd9 100644 --- a/packages/apps/redis/templates/redisfailover.yaml +++ b/packages/apps/redis/templates/redisfailover.yaml @@ -1,3 +1,4 @@ +{{- $tlsEnabled := (include "redis.tls.enabled" .) | eq "true" -}} {{- if .Values.authEnabled }} {{- $existingPassword := lookup "v1" "Secret" .Release.Namespace (printf "%s-auth" .Release.Name) }} {{- $password := randAlphaNum 32 | b64enc }} @@ -28,6 +29,13 @@ metadata: # silently ignored. Keep both sides in sync. redis-failover.freshworks.com/operator-group: cozystack spec: + {{- if $tlsEnabled }} + tls: + enabled: true + authClients: {{ include "redis.tls.authClients" . | quote }} + certificateSecret: + secretName: {{ .Release.Name }}.tls + {{- end }} sentinel: # Pin the sentinel engine. The freshworks-oss operator's compiled-in default # moved 6.2.6→7.2.4; the data pods run a deliberate versionMap image below, diff --git a/packages/apps/redis/tests/tls_test.yaml b/packages/apps/redis/tests/tls_test.yaml new file mode 100644 index 000000000..56093277a --- /dev/null +++ b/packages/apps/redis/tests/tls_test.yaml @@ -0,0 +1,421 @@ +suite: TLS configuration for Redis + +# Tests cover the tri-state tls.enabled logic: +# 1. external=false, tls.enabled unset -> TLS disabled +# 2. external=true, tls.enabled unset -> TLS enabled (inherits external) +# 3. external=true, tls.enabled=false -> TLS disabled (explicit overrides) +# 4. external=false, tls.enabled=true -> TLS enabled (explicit overrides) +# Plus authClients propagation, SAN coverage and dashboard RBAC. + +release: + name: my-redis + namespace: tenant-test + +set: + _cluster: + cluster-domain: cluster.local + _namespace: + host: example.com + +tests: + # ------------------------------------------------------------------ + # Case 1: external=false, tls.enabled unset -> no TLS resources + # ------------------------------------------------------------------ + - it: "certmanager.yaml renders no documents when external=false and tls.enabled is unset" + templates: + - templates/certmanager.yaml + set: + external: false + asserts: + - hasDocuments: + count: 0 + + - it: "redisfailover.yaml does not have spec.tls when external=false and tls.enabled is unset" + templates: + - templates/redisfailover.yaml + set: + external: false + documentSelector: + path: kind + value: RedisFailover + asserts: + - notExists: + path: spec.tls + + - it: "dashboard-resourcemap.yaml does not list TLS secrets when external=false and tls.enabled is unset" + templates: + - templates/dashboard-resourcemap.yaml + set: + external: false + documentSelector: + path: kind + value: Role + asserts: + # The negatives below address rules[1] by index, and notContains is + # fail-open on a path that does not exist, so a SECOND secrets rule + # granting the key-bearing Secrets would satisfy every one of them. + # Pin the rule count: the Role renders services, secrets and + # workloadmonitors, and nothing else. + - lengthEqual: + path: rules + count: 3 + - notContains: + path: rules[1].resourceNames + content: "my-redis.tls" + - notContains: + path: rules[1].resourceNames + content: "my-redis.ca-tls" + + # ------------------------------------------------------------------ + # Case 2: external=true, tls.enabled unset -> TLS enabled via external + # ------------------------------------------------------------------ + - it: "certmanager.yaml renders 4 documents when external=true and tls.enabled is unset" + templates: + - templates/certmanager.yaml + set: + external: true + asserts: + - hasDocuments: + count: 4 + + - it: "first document is a selfsigned Issuer when TLS enabled via external" + templates: + - templates/certmanager.yaml + set: + external: true + documentIndex: 0 + asserts: + - equal: + path: apiVersion + value: cert-manager.io/v1 + - equal: + path: kind + value: Issuer + - equal: + path: metadata.name + value: my-redis-selfsigned + - exists: + path: spec.selfSigned + + - it: "second document is the CA Certificate when TLS enabled via external" + templates: + - templates/certmanager.yaml + set: + external: true + documentIndex: 1 + asserts: + - equal: + path: kind + value: Certificate + - equal: + path: metadata.name + value: my-redis-ca + - equal: + path: spec.isCA + value: true + - equal: + path: spec.secretName + value: my-redis.ca-tls + - equal: + path: spec.issuerRef.name + value: my-redis-selfsigned + - equal: + path: spec.privateKey.rotationPolicy + value: Never + - equal: + path: spec.privateKey.algorithm + value: ECDSA + + - it: "third document is the CA Issuer when TLS enabled via external" + templates: + - templates/certmanager.yaml + set: + external: true + documentIndex: 2 + asserts: + - equal: + path: kind + value: Issuer + - equal: + path: metadata.name + value: my-redis-ca + - equal: + path: spec.ca.secretName + value: my-redis.ca-tls + + - it: "fourth document is the leaf Certificate when TLS enabled via external" + templates: + - templates/certmanager.yaml + set: + external: true + documentIndex: 3 + asserts: + - equal: + path: kind + value: Certificate + - equal: + path: metadata.name + value: my-redis-tls + - equal: + path: spec.secretName + value: my-redis.tls + - equal: + path: spec.isCA + value: false + - equal: + path: spec.issuerRef.name + value: my-redis-ca + - equal: + path: spec.issuerRef.kind + value: Issuer + - equal: + path: spec.privateKey.rotationPolicy + value: Always + - equal: + path: spec.privateKey.algorithm + value: ECDSA + + - it: "leaf Certificate SAN list covers all four redis-operator service names with all DNS variants" + templates: + - templates/certmanager.yaml + set: + external: true + documentIndex: 3 + asserts: + - contains: + path: spec.dnsNames + content: "localhost" + # Redis (headless) service + - contains: + path: spec.dnsNames + content: "rfr-my-redis" + - contains: + path: spec.dnsNames + content: "rfr-my-redis.tenant-test" + - contains: + path: spec.dnsNames + content: "rfr-my-redis.tenant-test.svc" + - contains: + path: spec.dnsNames + content: "rfr-my-redis.tenant-test.svc.cluster.local" + # Redis master service + - contains: + path: spec.dnsNames + content: "rfrm-my-redis" + - contains: + path: spec.dnsNames + content: "rfrm-my-redis.tenant-test.svc.cluster.local" + # Redis slaves service + - contains: + path: spec.dnsNames + content: "rfrs-my-redis" + - contains: + path: spec.dnsNames + content: "rfrs-my-redis.tenant-test.svc.cluster.local" + # Sentinel service + - contains: + path: spec.dnsNames + content: "rfs-my-redis" + - contains: + path: spec.dnsNames + content: "rfs-my-redis.tenant-test.svc.cluster.local" + + - it: "leaf Certificate SAN list includes wildcard for per-pod headless DNS" + templates: + - templates/certmanager.yaml + set: + external: true + documentIndex: 3 + asserts: + - contains: + path: spec.dnsNames + content: "*.rfr-my-redis" + - contains: + path: spec.dnsNames + content: "*.rfr-my-redis.tenant-test.svc" + - contains: + path: spec.dnsNames + content: "*.rfr-my-redis.tenant-test.svc.cluster.local" + + - it: "leaf Certificate SAN list includes external tenant hostname when external=true" + templates: + - templates/certmanager.yaml + set: + external: true + documentIndex: 3 + asserts: + - contains: + path: spec.dnsNames + content: "my-redis.example.com" + + - it: "redisfailover.yaml passes TLS settings to the operator when external=true" + templates: + - templates/redisfailover.yaml + set: + external: true + documentSelector: + path: kind + value: RedisFailover + asserts: + - equal: + path: spec.tls.enabled + value: true + - equal: + path: spec.tls.authClients + value: "no" + - equal: + path: spec.tls.certificateSecret.secretName + value: my-redis.tls + + - it: "dashboard-resourcemap.yaml lists TLS secrets in RBAC when external=true" + templates: + - templates/dashboard-resourcemap.yaml + set: + external: true + documentSelector: + path: kind + value: Role + asserts: + # The negatives below address rules[1] by index, and notContains is + # fail-open on a path that does not exist, so a SECOND secrets rule + # granting the key-bearing Secrets would satisfy every one of them. + # Pin the rule count: the Role renders services, secrets and + # workloadmonitors, and nothing else. + - lengthEqual: + path: rules + count: 3 + - contains: + path: rules[1].resourceNames + content: "my-redis.tls" + - contains: + path: rules[1].resourceNames + content: "my-redis.ca-tls" + + # ------------------------------------------------------------------ + # Case 3: external=true, tls.enabled=false -> TLS explicitly off + # ------------------------------------------------------------------ + - it: "certmanager.yaml renders no documents when tls.enabled=false overrides external=true" + templates: + - templates/certmanager.yaml + set: + external: true + tls: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: "redisfailover.yaml has no spec.tls when tls.enabled=false overrides external=true" + templates: + - templates/redisfailover.yaml + set: + external: true + tls: + enabled: false + documentSelector: + path: kind + value: RedisFailover + asserts: + - notExists: + path: spec.tls + + # ------------------------------------------------------------------ + # Case 4: external=false, tls.enabled=true -> TLS explicitly on + # ------------------------------------------------------------------ + - it: "certmanager.yaml renders 4 documents when tls.enabled=true overrides external=false" + templates: + - templates/certmanager.yaml + set: + external: false + tls: + enabled: true + asserts: + - hasDocuments: + count: 4 + + - it: "redisfailover.yaml has spec.tls.enabled=true when tls.enabled=true overrides external=false" + templates: + - templates/redisfailover.yaml + set: + external: false + tls: + enabled: true + documentSelector: + path: kind + value: RedisFailover + asserts: + - equal: + path: spec.tls.enabled + value: true + + - it: "leaf Certificate SAN list omits external hostname when external=false" + templates: + - templates/certmanager.yaml + set: + external: false + tls: + enabled: true + documentIndex: 3 + asserts: + - notContains: + path: spec.dnsNames + content: "my-redis.example.com" + + # ------------------------------------------------------------------ + # authClients propagation + # ------------------------------------------------------------------ + - it: "redisfailover.yaml passes tls.authClients=yes to spec.tls.authClients" + templates: + - templates/redisfailover.yaml + set: + tls: + enabled: true + authClients: "yes" + documentSelector: + path: kind + value: RedisFailover + asserts: + - equal: + path: spec.tls.authClients + value: "yes" + + - it: "redisfailover.yaml passes tls.authClients=optional to spec.tls.authClients" + templates: + - templates/redisfailover.yaml + set: + tls: + enabled: true + authClients: "optional" + documentSelector: + path: kind + value: RedisFailover + asserts: + - equal: + path: spec.tls.authClients + value: "optional" + + # ------------------------------------------------------------------ + # Cluster domain customization + # ------------------------------------------------------------------ + - it: "leaf Certificate SAN list honours a custom cluster domain" + templates: + - templates/certmanager.yaml + set: + tls: + enabled: true + _cluster: + cluster-domain: my.cluster.local + documentIndex: 3 + asserts: + - contains: + path: spec.dnsNames + content: "rfr-my-redis.tenant-test.svc.my.cluster.local" + - contains: + path: spec.dnsNames + content: "rfrm-my-redis.tenant-test.svc.my.cluster.local" + - contains: + path: spec.dnsNames + content: "rfs-my-redis.tenant-test.svc.my.cluster.local" + - contains: + path: spec.dnsNames + content: "*.rfr-my-redis.tenant-test.svc.my.cluster.local" diff --git a/packages/apps/redis/values.schema.json b/packages/apps/redis/values.schema.json index 534fd0d81..440cf61f0 100644 --- a/packages/apps/redis/values.schema.json +++ b/packages/apps/redis/values.schema.json @@ -127,6 +127,21 @@ "type": "boolean", "default": false }, + "tls": { + "description": "TLS configuration. When omitted, TLS is enabled automatically when `external` is true.", + "type": "object", + "default": {}, + "properties": { + "authClients": { + "description": "Maps to the Redis `tls-auth-clients` directive. One of `no`, `optional`, `yes`. Defaults to `no` — the server certificate is presented but client certificates are not validated.", + "type": "string" + }, + "enabled": { + "description": "Enable TLS for Redis and Sentinel connections. When omitted, defaults to the value of `external`. Encryption is provided by the redis-operator fork that mounts the certificate Secret into both Redis and Sentinel pods at `/tls`.", + "type": "boolean" + } + } + }, "version": { "description": "Redis major version to deploy", "type": "string", diff --git a/packages/apps/redis/values.yaml b/packages/apps/redis/values.yaml index 533b0fe5f..0b47a2daf 100644 --- a/packages/apps/redis/values.yaml +++ b/packages/apps/redis/values.yaml @@ -75,6 +75,17 @@ storageClass: "" ## @param {bool} external - Enable external access from outside the cluster. external: false +## +## @section TLS parameters +## + +## @typedef {struct} TLS - TLS configuration. +## @field {*bool} enabled - Enable TLS for Redis and Sentinel connections. When omitted, defaults to the value of `external`. Encryption is provided by the redis-operator fork that mounts the certificate Secret into both Redis and Sentinel pods at `/tls`. +## @field {string} [authClients] - Maps to the Redis `tls-auth-clients` directive. One of `no`, `optional`, `yes`. Defaults to `no` — the server certificate is presented but client certificates are not validated. + +## @param {TLS} [tls] - TLS configuration. When omitted, TLS is enabled automatically when `external` is true. +tls: {} + ## @enum {string} Version ## @value v8 ## @value v7 diff --git a/packages/system/redis-rd/cozyrds/redis.yaml b/packages/system/redis-rd/cozyrds/redis.yaml index 45110d4a5..2f9253b16 100644 --- a/packages/system/redis-rd/cozyrds/redis.yaml +++ b/packages/system/redis-rd/cozyrds/redis.yaml @@ -8,7 +8,7 @@ spec: plural: redises singular: redis openAPISchema: |- - {"title":"Chart Values","type":"object","properties":{"replicas":{"description":"Number of Redis replicas.","type":"integer","default":2},"resources":{"description":"Explicit CPU and memory configuration for each Redis replica. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available to each replica.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available to each replica.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"t1.nano","enum":["t1.nano","t1.micro","t1.small","t1.medium","t1.large","t1.xlarge","t1.2xlarge","t1.4xlarge","c1.nano","c1.micro","c1.small","c1.medium","c1.large","c1.xlarge","c1.2xlarge","c1.4xlarge","s1.nano","s1.micro","s1.small","s1.medium","s1.large","s1.xlarge","s1.2xlarge","s1.4xlarge","u1.nano","u1.micro","u1.small","u1.medium","u1.large","u1.xlarge","u1.2xlarge","u1.4xlarge","m1.nano","m1.micro","m1.small","m1.medium","m1.large","m1.xlarge","m1.2xlarge","m1.4xlarge","nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume Claim size available for application data.","default":"1Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":"","x-kubernetes-validations":[{"rule":"self == oldSelf","message":"storageClass is immutable"}],"x-cozystack-options":{"source":"storageclass"}},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"version":{"description":"Redis major version to deploy","type":"string","default":"v8","enum":["v8","v7"]},"authEnabled":{"description":"Enable password generation.","type":"boolean","default":true}}} + {"title":"Chart Values","type":"object","properties":{"replicas":{"description":"Number of Redis replicas.","type":"integer","default":2},"resources":{"description":"Explicit CPU and memory configuration for each Redis replica. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available to each replica.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available to each replica.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"t1.nano","enum":["t1.nano","t1.micro","t1.small","t1.medium","t1.large","t1.xlarge","t1.2xlarge","t1.4xlarge","c1.nano","c1.micro","c1.small","c1.medium","c1.large","c1.xlarge","c1.2xlarge","c1.4xlarge","s1.nano","s1.micro","s1.small","s1.medium","s1.large","s1.xlarge","s1.2xlarge","s1.4xlarge","u1.nano","u1.micro","u1.small","u1.medium","u1.large","u1.xlarge","u1.2xlarge","u1.4xlarge","m1.nano","m1.micro","m1.small","m1.medium","m1.large","m1.xlarge","m1.2xlarge","m1.4xlarge","nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume Claim size available for application data.","default":"1Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":"","x-kubernetes-validations":[{"rule":"self == oldSelf","message":"storageClass is immutable"}]},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"tls":{"description":"TLS configuration. When omitted, TLS is enabled automatically when `external` is true.","type":"object","default":{},"properties":{"authClients":{"description":"Maps to the Redis `tls-auth-clients` directive. One of `no`, `optional`, `yes`. Defaults to `no` — the server certificate is presented but client certificates are not validated.","type":"string"},"enabled":{"description":"Enable TLS for Redis and Sentinel connections. When omitted, defaults to the value of `external`. Encryption is provided by the redis-operator fork that mounts the certificate Secret into both Redis and Sentinel pods at `/tls`.","type":"boolean"}}},"version":{"description":"Redis major version to deploy","type":"string","default":"v8","enum":["v8","v7"]},"authEnabled":{"description":"Enable password generation.","type":"boolean","default":true}}} release: prefix: redis- labels: @@ -25,7 +25,7 @@ spec: tags: - cache icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl82ODNfMzIxMykiLz4KPHBhdGggZD0iTTEyMC4xNDkgOTUuNTQ5MUMxMTQuNTg2IDk4LjQ0ODUgODUuNzcwOSAxMTAuMjk2IDc5LjYzNjMgMTEzLjQ5NUM3My41MDE2IDExNi42OTMgNzAuMDkzNyAxMTYuNjYyIDY1LjI0NzIgMTE0LjM0NkM2MC40MDEyIDExMi4wMjkgMjkuNzM2NCA5OS42NDIzIDI0LjIxMjUgOTcuMDAxOUMyMS40NTE5IDk1LjY4MjcgMjAgOTQuNTY4NyAyMCA5My41MTY2VjgyLjk4MDlDMjAgODIuOTgwOSA1OS45MjIgNzQuMjkwMSA2Ni4zNjY5IDcxLjk3NzhDNzIuODExNSA2OS42NjU2IDc1LjA0NzYgNjkuNTgyMSA4MC41MzIgNzEuNTkxQzg2LjAxNzMgNzMuNjAwOCAxMTguODEyIDc5LjUxNzYgMTI0LjIzMyA4MS41MDI5TDEyNC4yMyA5MS44ODk2QzEyNC4yMzEgOTIuOTMxMSAxMjIuOTggOTQuMDczNiAxMjAuMTQ5IDk1LjU0OTFaIiBmaWxsPSIjOTEyNjI2Ii8+CjxwYXRoIGQ9Ik0xMjAuMTQ3IDg1LjA3NTJDMTE0LjU4NSA4Ny45NzM0IDg1Ljc3IDk5LjgyMTggNzkuNjM1NCAxMDMuMDJDNzMuNTAxMSAxMDYuMjE5IDcwLjA5MzIgMTA2LjE4NyA2NS4yNDcyIDEwMy44NzFDNjAuNDAwNyAxMDEuNTU1IDI5LjczNzEgODkuMTY2OCAyNC4yMTM2IDg2LjUyOEMxOC42OTAxIDgzLjg4NzYgMTguNTc0NCA4Mi4wNzA0IDI0LjAwMDMgNzkuOTQ1OEMyOS40MjYxIDc3LjgyMDUgNTkuOTIxNSA2NS44NTYxIDY2LjM2NzIgNjMuNTQzOEM3Mi44MTE4IDYxLjIzMjQgNzUuMDQ3NSA2MS4xNDgxIDgwLjUzMTkgNjMuMTU3OEM4Ni4wMTY4IDY1LjE2NjggMTE0LjY2IDc2LjU2NzYgMTIwLjA3OSA3OC41NTI1QzEyNS41MDEgODAuNTM5OSAxMjUuNzA5IDgyLjE3NjMgMTIwLjE0NyA4NS4wNzUyWiIgZmlsbD0iI0M2MzAyQiIvPgo8cGF0aCBkPSJNMTIwLjE0OSA3OC41MDJDMTE0LjU4NiA4MS40MDE4IDg1Ljc3MDkgOTMuMjQ5MyA3OS42MzYzIDk2LjQ0ODhDNzMuNTAxNiA5OS42NDYyIDcwLjA5MzcgOTkuNjE1MiA2NS4yNDcyIDk3LjI5ODVDNjAuNDAwOCA5NC45ODMgMjkuNzM2NCA4Mi41OTUyIDI0LjIxMjUgNzkuOTU0N0MyMS40NTE5IDc4LjYzNTUgMjAgNzcuNTIzMiAyMCA3Ni40NzA3VjY1LjkzMzhDMjAgNjUuOTMzOCA1OS45MjIgNTcuMjQzNCA2Ni4zNjY5IDU0LjkzMTFDNzIuODExNSA1Mi42MTg5IDc1LjA0NzYgNTIuNTM1IDgwLjUzMiA1NC41NDQzQzg2LjAxNzcgNTYuNTUzNiAxMTguODEzIDYyLjQ2OTMgMTI0LjIzMyA2NC40NTVMMTI0LjIzIDc0Ljg0MjhDMTI0LjIzMSA3NS44ODQgMTIyLjk4IDc3LjAyNjQgMTIwLjE0OSA3OC41MDJaIiBmaWxsPSIjOTEyNjI2Ii8+CjxwYXRoIGQ9Ik0xMjAuMTQ3IDY4LjAyODJDMTE0LjU4NSA3MC45MjcxIDg1Ljc3IDgyLjc3NDcgNzkuNjM1NCA4NS45NzM3QzczLjUwMTEgODkuMTcxNiA3MC4wOTMyIDg5LjE0MDIgNjUuMjQ3MiA4Ni44MjM1QzYwLjQwMDcgODQuNTA4NCAyOS43MzcxIDcyLjEyMDEgMjQuMjEzNiA2OS40ODA5QzE4LjY5MDEgNjYuODQxMyAxOC41NzQ0IDY1LjAyMzcgMjQuMDAwMyA2Mi44OTg0QzI5LjQyNjEgNjAuNzc0MiA1OS45MjE5IDQ4LjgwOSA2Ni4zNjcyIDQ2LjQ5NzJDNzIuODExOCA0NC4xODUzIDc1LjA0NzUgNDQuMTAxNCA4MC41MzE5IDQ2LjExMDhDODYuMDE2OCA0OC4xMTk3IDExNC42NiA1OS41MTk3IDEyMC4wNzkgNjEuNTA1NUMxMjUuNTAxIDYzLjQ5MjQgMTI1LjcwOSA2NS4xMjkyIDEyMC4xNDcgNjguMDI4MloiIGZpbGw9IiNDNjMwMkIiLz4KPHBhdGggZD0iTTEyMC4xNDkgNjAuODIyNEMxMTQuNTg2IDYzLjcyMTQgODUuNzcwOSA3NS41Njk4IDc5LjYzNjMgNzguNzY5MkM3My41MDE2IDgxLjk2NzEgNzAuMDkzNyA4MS45MzU3IDY1LjI0NzIgNzkuNjE5QzYwLjQwMDggNzcuMzAzNSAyOS43MzY0IDY0LjkxNTIgMjQuMjEyNSA2Mi4yNzZDMjEuNDUxOSA2MC45NTU2IDIwIDU5Ljg0MjggMjAgNTguNzkxNVY0OC4yNTQyQzIwIDQ4LjI1NDIgNTkuOTIyIDM5LjU2NDIgNjYuMzY2OSAzNy4yNTI0QzcyLjgxMTUgMzQuOTM5NyA3NS4wNDc2IDM0Ljg1NjcgODAuNTMyIDM2Ljg2NTZDODYuMDE3NyAzOC44NzQ5IDExOC44MTMgNDQuNzkwNSAxMjQuMjMzIDQ2Ljc3NjNMMTI0LjIzIDU3LjE2MzdDMTI0LjIzMSA1OC4yMDQgMTIyLjk4IDU5LjM0NjUgMTIwLjE0OSA2MC44MjI0WiIgZmlsbD0iIzkxMjYyNiIvPgo8cGF0aCBkPSJNMTIwLjE0NyA1MC4zNDlDMTE0LjU4NSA1My4yNDc5IDg1Ljc2OTggNjUuMDk2MyA3OS42MzUyIDY4LjI5NDFDNzMuNTAwOSA3MS40OTIgNzAuMDkzIDcxLjQ2MDYgNjUuMjQ2OSA2OS4xNDUxQzYwLjQwMDkgNjYuODI4MyAyOS43MzY5IDU0LjQ0MDkgMjQuMjEzOCA1MS44MDEzQzE4LjY4OTkgNDkuMTYyMSAxOC41NzQ2IDQ3LjM0NDEgMjQgNDUuMjE5MkMyOS40MjU5IDQzLjA5NDYgNTkuOTIxNyAzMS4xMzEgNjYuMzY3IDI4LjgxODRDNzIuODExNiAyNi41MDYxIDc1LjA0NzMgMjYuNDIzIDgwLjUzMTcgMjguNDMyNEM4Ni4wMTY2IDMwLjQ0MTcgMTE0LjY1OSA0MS44NDE4IDEyMC4wNzkgNDMuODI3NUMxMjUuNTAxIDQ1LjgxMjggMTI1LjcwOSA0Ny40NSAxMjAuMTQ3IDUwLjM0OVoiIGZpbGw9IiNDNjMwMkIiLz4KPHBhdGggZD0iTTg0Ljg1NDEgNDAuMDk5NEw3NS44OTI2IDQxLjAyOThMNzMuODg2NSA0NS44NTdMNzAuNjQ2MyA0MC40NzAzTDYwLjI5ODMgMzkuNTQwNEw2OC4wMTk3IDM2Ljc1NThMNjUuNzAzIDMyLjQ4MTRMNzIuOTMyMSAzNS4zMDg4TDc5Ljc0NzEgMzMuMDc3NUw3Ny45MDUyIDM3LjQ5NzJMODQuODU0MSA0MC4wOTk0Wk03My4zNTE1IDYzLjUxODRMNTYuNjI2NiA1Ni41ODE2TDgwLjU5MiA1Mi45MDI5TDczLjM1MTUgNjMuNTE4NFpNNTAuMTYzNyA0Mi43ODI2QzU3LjIzODEgNDIuNzgyNiA2Mi45NzMgNDUuMDA1NyA2Mi45NzMgNDcuNzQ3NUM2Mi45NzMgNTAuNDkwMSA1Ny4yMzgxIDUyLjcxMjggNTAuMTYzNyA1Mi43MTI4QzQzLjA4OTMgNTIuNzEyOCAzNy4zNTQ1IDUwLjQ4OTcgMzcuMzU0NSA0Ny43NDc1QzM3LjM1NDUgNDUuMDA1NyA0My4wODkzIDQyLjc4MjYgNTAuMTYzNyA0Mi43ODI2WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTk1LjQ0MzQgNDEuNDE3NEwxMDkuNjI3IDQ3LjAyMjRMOTUuNDU1NiA1Mi42MjJMOTUuNDQzNCA0MS40MTc0WiIgZmlsbD0iIzYyMUIxQyIvPgo8cGF0aCBkPSJNNzkuNzUyOSA0Ny42MjYxTDk1LjQ0NDkgNDEuNDE4OUw5NS40NTcxIDUyLjYyMzZMOTMuOTE4NCA1My4yMjU0TDc5Ljc1MjkgNDcuNjI2MVoiIGZpbGw9IiM5QTI5MjgiLz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhcl82ODNfMzIxMyIgeDE9IjE4OSIgeTE9IjIxMC41IiB4Mj0iMCIgeTI9IjAiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0E4MDAwMCIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNGRkNGQ0YiLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4K - keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resourcesPreset"], ["spec", "size"], ["spec", "storageClass"], ["spec", "external"], ["spec", "version"], ["spec", "authEnabled"]] + keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resourcesPreset"], ["spec", "size"], ["spec", "storageClass"], ["spec", "external"], ["spec", "tls"], ["spec", "version"], ["spec", "authEnabled"]] secrets: exclude: [] include: