diff --git a/.github/workflows/helm_ci.yml b/.github/workflows/helm_ci.yml index 0bc8318e3..60001a6d1 100644 --- a/.github/workflows/helm_ci.yml +++ b/.github/workflows/helm_ci.yml @@ -68,6 +68,135 @@ jobs: grep -q "security-config" /tmp/security.yaml echo "✓ Security configuration renders correctly" + echo "" + echo "=== Testing IAM gRPC opt-in path ===" + # Regression test: the filer registers the IAM gRPC service the + # Admin UI Users tab calls only when jwt.filer_signing.key is in + # security.toml. Operators must be able to enable that without + # the cert-manager mTLS bundle. + # Install PyYAML explicitly: this block runs before the later + # security+S3 block that does the same install, and we don't + # want to rely on the runner image shipping it. + pip install pyyaml -q + python3 - "$CHART_DIR" <<'PYEOF' + import subprocess, sys, yaml + chart = sys.argv[1] + + def render(values): + args = ["helm", "template", "test", chart] + for k, v in values.items(): + args += ["--set", f"{k}={v}"] + return subprocess.check_output(args, text=True) + + def docs(manifest): + return [d for d in yaml.safe_load_all(manifest) if d] + + def configmap(manifest, name): + for d in docs(manifest): + if d.get("kind") == "ConfigMap" and d["metadata"]["name"] == name: + return d + return None + + def workload_mounts(manifest, name): + for d in docs(manifest): + if d.get("kind") not in ("Deployment", "StatefulSet"): + continue + if d["metadata"]["name"] != name: + continue + pod = d["spec"]["template"]["spec"] + vols = {v["name"] for v in pod.get("volumes", [])} + mounts = set() + for c in pod.get("containers", []): + for vm in c.get("volumeMounts", []): + mounts.add(vm["name"]) + return vols, mounts + return None, None + + failed = [] + + # Case 1: defaults. The chart historically rendered nothing + # security-related; preserve that so this PR is non-breaking on + # existing installs. + out = render({}) + if configmap(out, "test-seaweedfs-security-config") is not None: + failed.append("defaults: security ConfigMap should not render") + else: + print("✓ defaults: no security-config ConfigMap (unchanged)") + + # Case 2: filerWrite=true alone is the documented opt-in for + # the Admin UI Users tab. Configmap must render with + # [jwt.filer_signing] and NO [grpc.*] sections (cert paths + # only exist with mTLS). + out = render({ + "global.seaweedfs.securityConfig.jwtSigning.filerWrite": "true", + "admin.enabled": "true", + }) + cm = configmap(out, "test-seaweedfs-security-config") + if cm is None: + failed.append("filerWrite=true: security ConfigMap missing") + else: + toml = cm["data"]["security.toml"] + if "[jwt.filer_signing]" not in toml: + failed.append("filerWrite=true: security.toml missing [jwt.filer_signing]") + if "[grpc" in toml: + failed.append("filerWrite=true: security.toml unexpectedly has [grpc.*] (would need cert mounts)") + if "[jwt.filer_signing]" in toml and "[grpc" not in toml: + print("✓ filerWrite=true: security.toml has [jwt.filer_signing], no [grpc.*]") + + # Case 3: filer + admin pods must MOUNT the security ConfigMap + # under filerWrite=true so the JWT key reaches both processes. + # Cert volumes must NOT be present (no mTLS). + for wl in ("test-seaweedfs-filer", "test-seaweedfs-admin"): + vols, mounts = workload_mounts(out, wl) + if vols is None: + failed.append(f"filerWrite=true: workload {wl} not found") + continue + if "security-config" not in vols or "security-config" not in mounts: + failed.append(f"filerWrite=true: {wl} does not mount security-config (IAM gRPC would still fail)") + else: + print(f"✓ filerWrite=true: {wl} mounts security-config") + cert_vols = {v for v in vols if v.endswith("-cert")} + if cert_vols: + failed.append(f"filerWrite=true: {wl} unexpectedly has cert volumes {sorted(cert_vols)}") + + # Case 4: enableSecurity=true must still render the full toml + # with both [jwt.signing] and [grpc.*]. Guards against the + # decoupling change accidentally regressing the mTLS path. + out = render({"global.seaweedfs.enableSecurity": "true"}) + cm = configmap(out, "test-seaweedfs-security-config") + if cm is None: + failed.append("enableSecurity=true: security ConfigMap missing") + else: + toml = cm["data"]["security.toml"] + missing = [s for s in ("[jwt.signing]", "[grpc.master]") if s not in toml] + if missing: + failed.append(f"enableSecurity=true: security.toml missing {missing}") + else: + print("✓ enableSecurity=true: security.toml has [jwt.signing] + [grpc.*] preserved") + + # Case 5: helper must tolerate explicit nulls (gemini-code-assist + # PR review). securityConfig=null was the parens-pattern crash + # the helper review caught. + for null_path in ("global.seaweedfs.securityConfig", + "global.seaweedfs.securityConfig.jwtSigning"): + try: + out = render({null_path: "null"}) + except subprocess.CalledProcessError as e: + failed.append(f"{null_path}=null: render failed: {e.output[:200] if e.output else e}") + continue + if configmap(out, "test-seaweedfs-security-config") is not None: + failed.append(f"{null_path}=null: should not render configmap") + else: + print(f"✓ {null_path}=null: render tolerates explicit null") + + if failed: + print("\nFAIL:", file=sys.stderr) + for f in failed: + print(f" - {f}", file=sys.stderr) + sys.exit(1) + PYEOF + echo "✓ IAM gRPC decoupling tests passed" + echo "=== Testing with monitoring enabled ===" helm template test $CHART_DIR \ --set global.seaweedfs.monitoring.enabled=true \ diff --git a/k8s/charts/seaweedfs/Chart.yaml b/k8s/charts/seaweedfs/Chart.yaml index 82cc0bde0..2da5c5046 100644 --- a/k8s/charts/seaweedfs/Chart.yaml +++ b/k8s/charts/seaweedfs/Chart.yaml @@ -3,4 +3,4 @@ description: SeaweedFS name: seaweedfs appVersion: "4.25" # Dev note: Trigger a helm chart release by `git tag -a helm-` -version: 4.25.0 +version: 4.25.1 diff --git a/k8s/charts/seaweedfs/templates/admin/admin-statefulset.yaml b/k8s/charts/seaweedfs/templates/admin/admin-statefulset.yaml index 88c170a9d..63955f796 100644 --- a/k8s/charts/seaweedfs/templates/admin/admin-statefulset.yaml +++ b/k8s/charts/seaweedfs/templates/admin/admin-statefulset.yaml @@ -180,11 +180,13 @@ spec: - name: admin-logs mountPath: /logs {{- end }} - {{- if .Values.global.seaweedfs.enableSecurity }} + {{- if include "seaweedfs.securityConfigEnabled" . }} - name: security-config readOnly: true mountPath: /etc/seaweedfs/security.toml subPath: security.toml + {{- end }} + {{- if .Values.global.seaweedfs.enableSecurity }} - name: ca-cert readOnly: true mountPath: /usr/local/share/ca-certificates/ca/ @@ -275,10 +277,12 @@ spec: persistentVolumeClaim: claimName: {{ .Values.admin.logs.claimName }} {{- end }} - {{- if .Values.global.seaweedfs.enableSecurity }} + {{- if include "seaweedfs.securityConfigEnabled" . }} - name: security-config configMap: name: {{ include "seaweedfs.fullname" . }}-security-config + {{- end }} + {{- if .Values.global.seaweedfs.enableSecurity }} - name: ca-cert secret: secretName: {{ include "seaweedfs.fullname" . }}-ca-cert diff --git a/k8s/charts/seaweedfs/templates/all-in-one/all-in-one-deployment.yaml b/k8s/charts/seaweedfs/templates/all-in-one/all-in-one-deployment.yaml index 3b4d7a405..b0d1fc784 100644 --- a/k8s/charts/seaweedfs/templates/all-in-one/all-in-one-deployment.yaml +++ b/k8s/charts/seaweedfs/templates/all-in-one/all-in-one-deployment.yaml @@ -330,11 +330,13 @@ spec: mountPath: /etc/seaweedfs/master.toml subPath: master.toml readOnly: true - {{- if .Values.global.seaweedfs.enableSecurity }} + {{- if include "seaweedfs.securityConfigEnabled" . }} - name: security-config mountPath: /etc/seaweedfs/security.toml subPath: security.toml readOnly: true + {{- end }} + {{- if .Values.global.seaweedfs.enableSecurity }} - name: ca-cert mountPath: /usr/local/share/ca-certificates/ca/ readOnly: true @@ -461,10 +463,12 @@ spec: - name: master-config configMap: name: {{ include "seaweedfs.fullname" . }}-master-config - {{- if .Values.global.seaweedfs.enableSecurity }} + {{- if include "seaweedfs.securityConfigEnabled" . }} - name: security-config configMap: name: {{ include "seaweedfs.fullname" . }}-security-config + {{- end }} + {{- if .Values.global.seaweedfs.enableSecurity }} - name: ca-cert secret: secretName: {{ include "seaweedfs.fullname" . }}-ca-cert diff --git a/k8s/charts/seaweedfs/templates/cosi/cosi-deployment.yaml b/k8s/charts/seaweedfs/templates/cosi/cosi-deployment.yaml index ffeccd2a3..aefc9d369 100644 --- a/k8s/charts/seaweedfs/templates/cosi/cosi-deployment.yaml +++ b/k8s/charts/seaweedfs/templates/cosi/cosi-deployment.yaml @@ -117,11 +117,13 @@ spec: name: config-users readOnly: true {{- end }} - {{- if .Values.global.seaweedfs.enableSecurity }} + {{- if include "seaweedfs.securityConfigEnabled" . }} - name: security-config readOnly: true mountPath: /etc/seaweedfs/security.toml subPath: security.toml + {{- end }} + {{- if .Values.global.seaweedfs.enableSecurity }} - name: ca-cert readOnly: true mountPath: /usr/local/share/ca-certificates/ca/ @@ -179,10 +181,12 @@ spec: secretName: {{ include "seaweedfs.fullname" . }}-s3-secret {{- end }} {{- end }} - {{- if .Values.global.seaweedfs.enableSecurity }} + {{- if include "seaweedfs.securityConfigEnabled" . }} - name: security-config configMap: name: {{ include "seaweedfs.fullname" . }}-security-config + {{- end }} + {{- if .Values.global.seaweedfs.enableSecurity }} - name: ca-cert secret: secretName: {{ include "seaweedfs.fullname" . }}-ca-cert diff --git a/k8s/charts/seaweedfs/templates/filer/filer-statefulset.yaml b/k8s/charts/seaweedfs/templates/filer/filer-statefulset.yaml index 89e8666c3..691d1cf4f 100644 --- a/k8s/charts/seaweedfs/templates/filer/filer-statefulset.yaml +++ b/k8s/charts/seaweedfs/templates/filer/filer-statefulset.yaml @@ -234,11 +234,13 @@ spec: mountPath: /etc/seaweedfs/notification.toml subPath: notification.toml {{- end }} - {{- if .Values.global.seaweedfs.enableSecurity }} + {{- if include "seaweedfs.securityConfigEnabled" . }} - name: security-config readOnly: true mountPath: /etc/seaweedfs/security.toml subPath: security.toml + {{- end }} + {{- if .Values.global.seaweedfs.enableSecurity }} - name: ca-cert readOnly: true mountPath: /usr/local/share/ca-certificates/ca/ @@ -274,7 +276,8 @@ spec: name: swfs-s3-tls {{- end }} {{- end }} - {{- $isJwtEnabled := or .Values.global.seaweedfs.securityConfig.jwtSigning.filerWrite .Values.global.seaweedfs.securityConfig.jwtSigning.filerRead }} + {{- $jwt := (.Values.global.seaweedfs.securityConfig).jwtSigning | default dict }} + {{- $isJwtEnabled := or $jwt.filerWrite $jwt.filerRead }} {{- if .Values.filer.readinessProbe.enabled }} readinessProbe: {{- if or $isJwtEnabled .Values.filer.readinessProbe.tcpSocket }} @@ -368,10 +371,12 @@ spec: configMap: name: {{ include "seaweedfs.fullname" . }}-notification-config {{- end }} - {{- if .Values.global.seaweedfs.enableSecurity }} + {{- if include "seaweedfs.securityConfigEnabled" . }} - name: security-config configMap: name: {{ include "seaweedfs.fullname" . }}-security-config + {{- end }} + {{- if .Values.global.seaweedfs.enableSecurity }} - name: ca-cert secret: secretName: {{ include "seaweedfs.fullname" . }}-ca-cert diff --git a/k8s/charts/seaweedfs/templates/master/master-statefulset.yaml b/k8s/charts/seaweedfs/templates/master/master-statefulset.yaml index 536928125..21e2bada5 100644 --- a/k8s/charts/seaweedfs/templates/master/master-statefulset.yaml +++ b/k8s/charts/seaweedfs/templates/master/master-statefulset.yaml @@ -188,11 +188,13 @@ spec: readOnly: true mountPath: /etc/seaweedfs/master.toml subPath: master.toml - {{- if .Values.global.seaweedfs.enableSecurity }} + {{- if include "seaweedfs.securityConfigEnabled" . }} - name: security-config readOnly: true mountPath: /etc/seaweedfs/security.toml subPath: security.toml + {{- end }} + {{- if .Values.global.seaweedfs.enableSecurity }} - name: ca-cert readOnly: true mountPath: /usr/local/share/ca-certificates/ca/ @@ -287,10 +289,12 @@ spec: - name: master-config configMap: name: {{ include "seaweedfs.fullname" . }}-master-config - {{- if .Values.global.seaweedfs.enableSecurity }} + {{- if include "seaweedfs.securityConfigEnabled" . }} - name: security-config configMap: name: {{ include "seaweedfs.fullname" . }}-security-config + {{- end }} + {{- if .Values.global.seaweedfs.enableSecurity }} - name: ca-cert secret: secretName: {{ include "seaweedfs.fullname" . }}-ca-cert diff --git a/k8s/charts/seaweedfs/templates/s3/s3-deployment.yaml b/k8s/charts/seaweedfs/templates/s3/s3-deployment.yaml index 0fe5374b3..db231fc96 100644 --- a/k8s/charts/seaweedfs/templates/s3/s3-deployment.yaml +++ b/k8s/charts/seaweedfs/templates/s3/s3-deployment.yaml @@ -156,11 +156,13 @@ spec: name: config-users readOnly: true {{- end }} - {{- if .Values.global.seaweedfs.enableSecurity }} + {{- if include "seaweedfs.securityConfigEnabled" . }} - name: security-config readOnly: true mountPath: /etc/seaweedfs/security.toml subPath: security.toml + {{- end }} + {{- if .Values.global.seaweedfs.enableSecurity }} - name: ca-cert readOnly: true mountPath: /usr/local/share/ca-certificates/ca/ @@ -249,10 +251,12 @@ spec: - name: logs emptyDir: {} {{- end }} - {{- if .Values.global.seaweedfs.enableSecurity }} + {{- if include "seaweedfs.securityConfigEnabled" . }} - name: security-config configMap: name: {{ include "seaweedfs.fullname" . }}-security-config + {{- end }} + {{- if .Values.global.seaweedfs.enableSecurity }} - name: ca-cert secret: secretName: {{ include "seaweedfs.fullname" . }}-ca-cert diff --git a/k8s/charts/seaweedfs/templates/sftp/sftp-deployment.yaml b/k8s/charts/seaweedfs/templates/sftp/sftp-deployment.yaml index 356fc7dbe..46bb32207 100644 --- a/k8s/charts/seaweedfs/templates/sftp/sftp-deployment.yaml +++ b/k8s/charts/seaweedfs/templates/sftp/sftp-deployment.yaml @@ -176,11 +176,13 @@ spec: - mountPath: /etc/sw/ssh name: config-ssh readOnly: true - {{- if .Values.global.seaweedfs.enableSecurity }} + {{- if include "seaweedfs.securityConfigEnabled" . }} - name: security-config readOnly: true mountPath: /etc/seaweedfs/security.toml subPath: security.toml + {{- end }} + {{- if .Values.global.seaweedfs.enableSecurity }} - name: ca-cert readOnly: true mountPath: /usr/local/share/ca-certificates/ca/ @@ -264,10 +266,12 @@ spec: - name: logs emptyDir: {} {{- end }} - {{- if .Values.global.seaweedfs.enableSecurity }} + {{- if include "seaweedfs.securityConfigEnabled" . }} - name: security-config configMap: name: {{ include "seaweedfs.fullname" . }}-security-config + {{- end }} + {{- if .Values.global.seaweedfs.enableSecurity }} - name: ca-cert secret: secretName: {{ include "seaweedfs.fullname" . }}-ca-cert diff --git a/k8s/charts/seaweedfs/templates/shared/_helpers.tpl b/k8s/charts/seaweedfs/templates/shared/_helpers.tpl index 0576b1487..1fa92a0c1 100644 --- a/k8s/charts/seaweedfs/templates/shared/_helpers.tpl +++ b/k8s/charts/seaweedfs/templates/shared/_helpers.tpl @@ -332,6 +332,16 @@ Create the name of the service account to use {{- .Values.global.seaweedfs.serviceAccountName | default "seaweedfs" -}} {{- end -}} +{{/* True when security.toml should be rendered and mounted. volumeWrite is + excluded since it defaults to true. */}} +{{- define "seaweedfs.securityConfigEnabled" -}} +{{- $sec := (.Values.global.seaweedfs).securityConfig | default dict -}} +{{- $jwt := $sec.jwtSigning | default dict -}} +{{- if or .Values.global.seaweedfs.enableSecurity $jwt.volumeRead $jwt.filerWrite $jwt.filerRead -}} +true +{{- end -}} +{{- end -}} + {{/* S3 TLS cert/key arguments, using custom secret if s3.tlsSecret is set */}} {{- define "seaweedfs.s3.tlsArgs" -}} {{- $prefix := .prefix -}} diff --git a/k8s/charts/seaweedfs/templates/shared/security-configmap.yaml b/k8s/charts/seaweedfs/templates/shared/security-configmap.yaml index 667ed8533..8b9486e13 100644 --- a/k8s/charts/seaweedfs/templates/shared/security-configmap.yaml +++ b/k8s/charts/seaweedfs/templates/shared/security-configmap.yaml @@ -1,5 +1,5 @@ {{- include "seaweedfs.compat" . -}} -{{- if .Values.global.seaweedfs.enableSecurity }} +{{- if include "seaweedfs.securityConfigEnabled" . }} apiVersion: v1 kind: ConfigMap metadata: @@ -55,6 +55,7 @@ data: key = "{{ dig "jwt" "filer_signing" "read" "key" (randAlphaNum 10 | b64enc) $securityConfig }}" {{- end }} + {{- if .Values.global.seaweedfs.enableSecurity }} # all grpc tls authentications are mutual # the values for the following ca, cert, and key are paths to the PERM files. [grpc] @@ -94,4 +95,5 @@ data: [https.volume] cert = "" key = "" + {{- end }} {{- end }} diff --git a/k8s/charts/seaweedfs/templates/volume/volume-statefulset.yaml b/k8s/charts/seaweedfs/templates/volume/volume-statefulset.yaml index 231f28ce1..4b70d7f71 100644 --- a/k8s/charts/seaweedfs/templates/volume/volume-statefulset.yaml +++ b/k8s/charts/seaweedfs/templates/volume/volume-statefulset.yaml @@ -211,11 +211,13 @@ spec: - name: idx mountPath: "/idx/" {{- end }} - {{- if $.Values.global.seaweedfs.enableSecurity }} + {{- if include "seaweedfs.securityConfigEnabled" $ }} - name: security-config readOnly: true mountPath: /etc/seaweedfs/security.toml subPath: security.toml + {{- end }} + {{- if $.Values.global.seaweedfs.enableSecurity }} - name: ca-cert readOnly: true mountPath: /usr/local/share/ca-certificates/ca/ @@ -333,10 +335,12 @@ spec: emptyDir: {} {{- end }} {{- end }} - {{- if $.Values.global.seaweedfs.enableSecurity }} + {{- if include "seaweedfs.securityConfigEnabled" $ }} - name: security-config configMap: name: {{ include "seaweedfs.fullname" $ }}-security-config + {{- end }} + {{- if $.Values.global.seaweedfs.enableSecurity }} - name: ca-cert secret: secretName: {{ include "seaweedfs.fullname" $ }}-ca-cert diff --git a/k8s/charts/seaweedfs/templates/worker/worker-deployment.yaml b/k8s/charts/seaweedfs/templates/worker/worker-deployment.yaml index bf49095df..1b9d80099 100644 --- a/k8s/charts/seaweedfs/templates/worker/worker-deployment.yaml +++ b/k8s/charts/seaweedfs/templates/worker/worker-deployment.yaml @@ -149,11 +149,13 @@ spec: - name: worker-logs mountPath: /logs {{- end }} - {{- if .Values.global.seaweedfs.enableSecurity }} + {{- if include "seaweedfs.securityConfigEnabled" . }} - name: security-config readOnly: true mountPath: /etc/seaweedfs/security.toml subPath: security.toml + {{- end }} + {{- if .Values.global.seaweedfs.enableSecurity }} - name: ca-cert readOnly: true mountPath: /usr/local/share/ca-certificates/ca/ @@ -252,10 +254,12 @@ spec: persistentVolumeClaim: claimName: {{ .Values.worker.logs.claimName }} {{- end }} - {{- if .Values.global.seaweedfs.enableSecurity }} + {{- if include "seaweedfs.securityConfigEnabled" . }} - name: security-config configMap: name: {{ include "seaweedfs.fullname" . }}-security-config + {{- end }} + {{- if .Values.global.seaweedfs.enableSecurity }} - name: ca-cert secret: secretName: {{ include "seaweedfs.fullname" . }}-ca-cert diff --git a/k8s/charts/seaweedfs/values.yaml b/k8s/charts/seaweedfs/values.yaml index c54fb81e6..4b76c408b 100644 --- a/k8s/charts/seaweedfs/values.yaml +++ b/k8s/charts/seaweedfs/values.yaml @@ -18,6 +18,8 @@ global: loggingLevel: 1 enableSecurity: false masterServer: null + # filerWrite: true mounts security.toml on filer + admin without needing + # enableSecurity (mTLS); required for the Admin UI Users tab. securityConfig: jwtSigning: volumeWrite: true