diff --git a/helm/README.md b/helm/README.md index f47e9b896..776dd8fd2 100644 --- a/helm/README.md +++ b/helm/README.md @@ -18,6 +18,7 @@ RustFS helm chart supports **standalone and distributed mode**. For standalone m | affinity.nodeAffinity | object | `{}` | | | affinity.podAntiAffinity.enabled | bool | `true` | | | affinity.podAntiAffinity.topologyKey | string | `"kubernetes.io/hostname"` | | +| clusterDomain | string | `"cluster.local"` | Kubernetes cluster DNS domain used to build in-cluster FQDNs for `RUSTFS_VOLUMES` (distributed mode) and mTLS server certificate SANs. Override for clusters not using the default `cluster.local`. Provide the DNS root only, without a `svc.` prefix or leading/trailing dots. | | commonLabels | object | `{}` | Labels to add to all deployed objects. | | config.rustfs.address | string | `":9000"` | | | config.rustfs.console_address | string | `":9001"` | | diff --git a/helm/rustfs/templates/_helpers.tpl b/helm/rustfs/templates/_helpers.tpl index 948847f51..e5353a389 100644 --- a/helm/rustfs/templates/_helpers.tpl +++ b/helm/rustfs/templates/_helpers.tpl @@ -159,6 +159,15 @@ Merges (in order of increasing precedence): {{- end }} {{- end }} +{{/* +Resolve the Kubernetes cluster DNS domain (defaults to cluster.local). +Trims any leading/trailing dots so callers can safely append it after `svc.`, +falling back to cluster.local when the value is empty or only dots. +*/}} +{{- define "rustfs.clusterDomain" -}} +{{- .Values.clusterDomain | default "cluster.local" | trimAll "." | default "cluster.local" -}} +{{- end -}} + {{/* Render RUSTFS_VOLUMES */}} @@ -170,10 +179,10 @@ Render RUSTFS_VOLUMES {{- end -}} {{- if eq (int .Values.replicaCount) 4 }} -{{- printf "%s://%s-{0...%d}.%s-headless.%s.svc.cluster.local:%d/data/rustfs{0...%d}" $protocol (include "rustfs.fullname" .) (sub (.Values.replicaCount | int) 1) (include "rustfs.fullname" . ) .Release.Namespace (.Values.service.endpoint.port | int) (sub (.Values.replicaCount | int) 1) }} +{{- printf "%s://%s-{0...%d}.%s-headless.%s.svc.%s:%d/data/rustfs{0...%d}" $protocol (include "rustfs.fullname" .) (sub (.Values.replicaCount | int) 1) (include "rustfs.fullname" . ) .Release.Namespace (include "rustfs.clusterDomain" .) (.Values.service.endpoint.port | int) (sub (.Values.replicaCount | int) 1) }} {{- end }} {{- if eq (int .Values.replicaCount) 16 }} -{{- printf "%s://%s-{0...%d}.%s-headless.%s.svc.cluster.local:%d/data" $protocol (include "rustfs.fullname" .) (sub (.Values.replicaCount | int) 1) (include "rustfs.fullname" .) .Release.Namespace (.Values.service.endpoint.port | int) }} +{{- printf "%s://%s-{0...%d}.%s-headless.%s.svc.%s:%d/data" $protocol (include "rustfs.fullname" .) (sub (.Values.replicaCount | int) 1) (include "rustfs.fullname" .) .Release.Namespace (include "rustfs.clusterDomain" .) (.Values.service.endpoint.port | int) }} {{- end }} {{- end }} diff --git a/helm/rustfs/templates/cert-manager-mtls/04-server-cert.yaml b/helm/rustfs/templates/cert-manager-mtls/04-server-cert.yaml index 5ede034fa..5da0dddfa 100644 --- a/helm/rustfs/templates/cert-manager-mtls/04-server-cert.yaml +++ b/helm/rustfs/templates/cert-manager-mtls/04-server-cert.yaml @@ -19,8 +19,8 @@ spec: commonName: {{ include "rustfs.fullname" . }}-cluster dnsNames: - "*.{{ include "rustfs.fullname" . }}-headless" - - "*.{{ include "rustfs.fullname" . }}-headless.{{ .Release.Namespace }}.svc.cluster.local" - - "{{ include "rustfs.fullname" . }}-svc.{{ .Release.Namespace }}.svc.cluster.local" + - "*.{{ include "rustfs.fullname" . }}-headless.{{ .Release.Namespace }}.svc.{{ include "rustfs.clusterDomain" . }}" + - "{{ include "rustfs.fullname" . }}-svc.{{ .Release.Namespace }}.svc.{{ include "rustfs.clusterDomain" . }}" {{- range .Values.ingress.hosts }} - {{ .host | quote }} - {{ printf "*.%s" .host | quote }} diff --git a/helm/rustfs/values.yaml b/helm/rustfs/values.yaml index 9065cbdbf..3dd6ec1ae 100644 --- a/helm/rustfs/values.yaml +++ b/helm/rustfs/values.yaml @@ -5,6 +5,13 @@ # This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/ replicaCount: 4 +# Kubernetes cluster DNS domain used to build in-cluster FQDNs for +# RUSTFS_VOLUMES (distributed mode) and mTLS server certificate SANs. +# Override this only if your cluster does not use the default `cluster.local` +# (for example, on-premise clusters configured with a custom domain). +# Provide the DNS root only, without a `svc.` prefix or leading/trailing dots. +clusterDomain: cluster.local + # This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/ image: rustfs: # This sets the rustfs image repository and tag. diff --git a/scripts/test_helm_templates.sh b/scripts/test_helm_templates.sh index b99e79472..645d22491 100755 --- a/scripts/test_helm_templates.sh +++ b/scripts/test_helm_templates.sh @@ -36,6 +36,33 @@ render_distributed_statefulset() { ' } +render_distributed_configmap() { + helm template rustfs "$CHART_DIR" \ + --namespace rustfs \ + --set secret.rustfs.access_key=test-access-key \ + --set secret.rustfs.secret_key=test-secret-key \ + "$@" | + awk ' + /^# Source: rustfs\/templates\/configmap.yaml$/ { in_configmap = 1 } + in_configmap && /^---$/ { exit } + in_configmap { print } + ' +} + +render_server_cert() { + helm template rustfs "$CHART_DIR" \ + --namespace rustfs \ + --set secret.rustfs.access_key=test-access-key \ + --set secret.rustfs.secret_key=test-secret-key \ + --set mtls.enabled=true \ + "$@" | + awk ' + /^# Source: rustfs\/templates\/cert-manager-mtls\/04-server-cert.yaml$/ { in_cert = 1 } + in_cert && /^---$/ { exit } + in_cert { print } + ' +} + recreate_output=$(render_standalone_deployment --set mode.standalone.strategy.type=Recreate) grep -q "type: Recreate" <<<"$recreate_output" if grep -q "rollingUpdate:" <<<"$recreate_output"; then @@ -225,3 +252,54 @@ assert_extra_volumes_wired "$distributed_extra_output" "distributed StatefulSet" # Empty extraVolumes must not inject ca-bundle into distributed StatefulSet volumes. distributed_default_output=$(render_distributed_statefulset) assert_extra_volumes_absent "$distributed_default_output" "distributed StatefulSet" + +# clusterDomain (issue #3857): a custom Kubernetes cluster domain must flow into +# the RUSTFS_VOLUMES FQDN and mTLS server cert SANs, defaulting to cluster.local. +volumes_default=$(render_distributed_configmap | grep 'RUSTFS_VOLUMES:' || true) +if ! grep -q 'svc\.cluster\.local' <<<"$volumes_default"; then + echo "Default RUSTFS_VOLUMES must use svc.cluster.local" >&2 + exit 1 +fi + +volumes_custom=$(render_distributed_configmap --set clusterDomain=cluster.internal | grep 'RUSTFS_VOLUMES:' || true) +if ! grep -q 'svc\.cluster\.internal' <<<"$volumes_custom"; then + echo "Custom clusterDomain must appear in the RUSTFS_VOLUMES FQDN" >&2 + exit 1 +fi +if grep -q 'cluster\.local' <<<"$volumes_custom"; then + echo "Custom clusterDomain must fully replace cluster.local in RUSTFS_VOLUMES" >&2 + exit 1 +fi + +# An explicit config.rustfs.volumes stays authoritative regardless of clusterDomain. +volumes_explicit=$(render_distributed_configmap \ + --set config.rustfs.volumes=http://example.test/data \ + --set clusterDomain=cluster.internal | grep 'RUSTFS_VOLUMES:' || true) +if ! grep -q 'RUSTFS_VOLUMES: "http://example.test/data"' <<<"$volumes_explicit"; then + echo "Explicit config.rustfs.volumes must remain authoritative regardless of clusterDomain" >&2 + exit 1 +fi + +# A dot-only clusterDomain must fall back to cluster.local instead of an empty domain. +volumes_dots=$(render_distributed_configmap --set clusterDomain=. | grep 'RUSTFS_VOLUMES:' || true) +if ! grep -q 'svc\.cluster\.local' <<<"$volumes_dots"; then + echo "Dot-only clusterDomain must fall back to cluster.local in RUSTFS_VOLUMES" >&2 + exit 1 +fi + +# mTLS server certificate SANs must honor clusterDomain too. +cert_default=$(render_server_cert) +if ! grep -q 'svc\.cluster\.local"' <<<"$cert_default"; then + echo "Default mTLS server cert SANs must use svc.cluster.local" >&2 + exit 1 +fi + +cert_custom=$(render_server_cert --set clusterDomain=cluster.internal) +if ! grep -q 'svc\.cluster\.internal"' <<<"$cert_custom"; then + echo "Custom clusterDomain must appear in mTLS server cert SANs" >&2 + exit 1 +fi +if grep -q 'svc\.cluster\.local"' <<<"$cert_custom"; then + echo "Custom clusterDomain must fully replace cluster.local in mTLS server cert SANs" >&2 + exit 1 +fi