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: