diff --git a/k8s/charts/seaweedfs/Chart.yaml b/k8s/charts/seaweedfs/Chart.yaml index fc868fcd8..12750e822 100644 --- a/k8s/charts/seaweedfs/Chart.yaml +++ b/k8s/charts/seaweedfs/Chart.yaml @@ -3,4 +3,4 @@ description: SeaweedFS name: seaweedfs appVersion: "4.46" # Dev note: Trigger a helm chart release by `git tag -a helm-` -version: 4.46.0 +version: 4.46.1 diff --git a/k8s/charts/seaweedfs/README.md b/k8s/charts/seaweedfs/README.md index f787f8bb4..bf4943b63 100644 --- a/k8s/charts/seaweedfs/README.md +++ b/k8s/charts/seaweedfs/README.md @@ -363,6 +363,27 @@ If `adminPassword` is empty or not set, the admin interface runs without authent As an alternative, a kubernetes Secret can be used (`admin.secret.existingSecret`). +### Admin listen address + +Since SeaweedFS 4.46, `weed admin` defaults to listening on loopback (`127.0.0.1`). +The chart's httpGet readiness/liveness probes dial the pod IP, so the admin +server must bind a non-loopback address for the probes to succeed. The chart +therefore passes `-ip={{ .Values.admin.ip }}`, defaulting `admin.ip` to `0.0.0.0` +(the pre-4.46 behaviour of listening on all interfaces). + +Binding a non-loopback address requires authentication: `weed admin` refuses to +start on a non-loopback address without `-adminPassword`, so the chart fails at +render time if `admin.ip` is non-loopback and authentication is not configured via +`admin.secret.adminPassword`, `admin.secret.existingSecret`, or +`WEED_ADMIN_PASSWORD` supplied through `admin.extraEnvironmentVars` / +`admin.secretExtraEnvironmentVars`. The whole `127.0.0.0/8` range and `::1` are +treated as loopback (matching `weed admin`); `localhost` is treated as +non-loopback. Set `admin.ip` to a loopback address only if you also replace the +httpGet probes (e.g. with an `exec` probe that checks `127.0.0.1`). + +The `-ip` flag requires SeaweedFS 4.46 or newer; pinning `admin.imageOverride` +to an older image is not supported with this chart version. + ### Admin Data Persistence The admin component can store configuration and maintenance data. You can configure storage in several ways: diff --git a/k8s/charts/seaweedfs/ci/admin-values.yaml b/k8s/charts/seaweedfs/ci/admin-values.yaml new file mode 100644 index 000000000..021d5302c --- /dev/null +++ b/k8s/charts/seaweedfs/ci/admin-values.yaml @@ -0,0 +1,7 @@ +# Admin install: exercises the admin StatefulSet, which passes -ip (default +# 0.0.0.0) and therefore requires authentication to bind a non-loopback address. +admin: + enabled: true + secret: + adminUser: "admin" + adminPassword: "ci-admin-password" diff --git a/k8s/charts/seaweedfs/templates/admin/admin-statefulset.yaml b/k8s/charts/seaweedfs/templates/admin/admin-statefulset.yaml index ad112981f..d6b2e79ca 100644 --- a/k8s/charts/seaweedfs/templates/admin/admin-statefulset.yaml +++ b/k8s/charts/seaweedfs/templates/admin/admin-statefulset.yaml @@ -6,6 +6,11 @@ {{- if and (not .Values.admin.masters) (not .Values.global.seaweedfs.masterServer) (not .Values.master.enabled) }} {{- fail "admin.masters or global.seaweedfs.masterServer must be set if master.enabled is false" -}} {{- end }} +{{- $adminAuthEnabled := include "seaweedfs.admin.authEnabled" . }} +{{- $adminIp := .Values.admin.ip | default "0.0.0.0" }} +{{- if and (not (include "seaweedfs.admin.isLoopbackIp" $adminIp)) (ne $adminAuthEnabled "true") }} +{{- fail (printf "admin.ip is set to %q (non-loopback) but admin authentication is not configured. Since `weed admin` 4.46 refuses to bind a non-loopback address without authentication, the admin container would exit on startup. Set admin.secret.adminPassword or admin.secret.existingSecret, or supply WEED_ADMIN_PASSWORD via admin.extraEnvironmentVars / admin.secretExtraEnvironmentVars, or set admin.ip to a loopback address such as 127.0.0.1 (note: a loopback bind makes the chart's httpGet readiness/liveness probes fail)." $adminIp) -}} +{{- end }} apiVersion: apps/v1 kind: StatefulSet metadata: @@ -162,6 +167,7 @@ spec: -v={{ .Values.global.seaweedfs.loggingLevel }} \ {{- end }} admin \ + -ip={{ .Values.admin.ip | default "0.0.0.0" }} \ -port={{ .Values.admin.port }} \ -port.grpc={{ .Values.admin.grpcPort }} \ {{- if or (eq .Values.admin.data.type "hostPath") (eq .Values.admin.data.type "persistentVolumeClaim") (eq .Values.admin.data.type "emptyDir") (eq .Values.admin.data.type "existingClaim") }} diff --git a/k8s/charts/seaweedfs/templates/shared/_helpers.tpl b/k8s/charts/seaweedfs/templates/shared/_helpers.tpl index 3ac343075..3836e9cff 100644 --- a/k8s/charts/seaweedfs/templates/shared/_helpers.tpl +++ b/k8s/charts/seaweedfs/templates/shared/_helpers.tpl @@ -88,6 +88,43 @@ true {{- end -}} {{- end -}} +{{/* Classify an admin bind address as loopback, mirroring weed admin's + isLoopbackIp (net.ParseIP + IsLoopback). Helm templates cannot call + net.ParseIP, so we approximate: valid IPv4 addresses in 127.0.0.0/8 + (validated via regex to reject malformed values like "127.not-an-ip") + and the IPv6 loopback "::1" / its expanded form "0:0:0:0:0:0:0:1" are + loopback. Hostnames (e.g. "localhost") and wildcard addresses + ("0.0.0.0", "::") are non-loopback, matching the binary, which + treats unparseable hostnames as non-loopback to be safe. Other IPv6 + loopback representations are not matched; the binary's own runtime + validation is the authoritative guard. */}} +{{- define "seaweedfs.admin.isLoopbackIp" -}} +{{- $ip := toString . -}} +{{- if or (regexMatch "^127\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$" $ip) (eq $ip "::1") (eq $ip "0:0:0:0:0:0:0:1") -}} +true +{{- end -}} +{{- end -}} + +{{/* Whether admin authentication is enabled from any supported source: + admin.secret (adminPassword or existingSecret), or WEED_ADMIN_PASSWORD + supplied via extraEnvironmentVars / secretExtraEnvironmentVars (which + weed admin picks up through viper's AutomaticEnv). A secret-backed + entry counts as enabled even though the chart cannot read its value. */}} +{{- define "seaweedfs.admin.authEnabled" -}} +{{- if or .Values.admin.secret.existingSecret .Values.admin.secret.adminPassword -}} +true +{{- else -}} +{{- $merged := dict -}} +{{- $_ := include "seaweedfs.mergeExtraEnvironmentVars" (dict "global" .Values.global.seaweedfs "component" .Values.admin "target" $merged) -}} +{{- $envPassword := index $merged "WEED_ADMIN_PASSWORD" -}} +{{- if or (kindIs "map" $envPassword) (hasKey (.Values.admin.secretExtraEnvironmentVars | default dict) "WEED_ADMIN_PASSWORD") -}} +true +{{- else if and $envPassword (ne (toString $envPassword) "") -}} +true +{{- end -}} +{{- end -}} +{{- end -}} + {{/* Return the proper filer image */}} {{- define "seaweedfs.filer.image" -}} {{- if .Values.filer.imageOverride -}} diff --git a/k8s/charts/seaweedfs/values.yaml b/k8s/charts/seaweedfs/values.yaml index f1f1a3fe5..47db70440 100644 --- a/k8s/charts/seaweedfs/values.yaml +++ b/k8s/charts/seaweedfs/values.yaml @@ -1328,6 +1328,20 @@ admin: replicas: 1 port: 23646 # Default admin port grpcPort: 33646 # Default gRPC port for worker connections + # IP address the admin server listens on. Since `weed admin` 4.46 defaults to + # loopback (127.0.0.1), the chart must bind a non-loopback address for the + # kubelet's httpGet readiness/liveness probes (which dial the pod IP) to ever + # succeed. "0.0.0.0" restores the pre-4.46 behaviour of listening on all + # interfaces. A non-loopback address requires authentication: set + # admin.secret.adminPassword or admin.secret.existingSecret, or supply + # WEED_ADMIN_PASSWORD via admin.extraEnvironmentVars / + # admin.secretExtraEnvironmentVars; otherwise the admin container will exit + # with a clear error rather than silently staying unready. The whole + # 127.0.0.0/8 range and ::1 are treated as loopback (matching weed admin). + # Set to a loopback address only if you also replace the httpGet probes. + # Note: the -ip flag requires SeaweedFS 4.46 or newer; pinning + # admin.imageOverride to an older image is not supported with this chart. + ip: "0.0.0.0" loggingOverrideLevel: null # Admin authentication