From 59f4496b8e1543fc3167efed951bac0236be3bdb Mon Sep 17 00:00:00 2001 From: Rui Fu Date: Thu, 24 Sep 2026 15:26:20 +0800 Subject: [PATCH 1/3] fix: make webhook certificate Secret file mode configurable --- charts/function-mesh-operator/README.md | 1 + .../templates/_helpers.tpl | 2 +- charts/function-mesh-operator/values.yaml | 2 ++ docs/security.md | 29 +++++++++++++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/charts/function-mesh-operator/README.md b/charts/function-mesh-operator/README.md index 8c5d0cd37..118ec096a 100644 --- a/charts/function-mesh-operator/README.md +++ b/charts/function-mesh-operator/README.md @@ -26,6 +26,7 @@ function mesh operator Helm chart for Kubernetes | Key | Type | Default | Description | |-----|------|---------|-------------| +| admissionWebhook.certSecretDefaultMode | int | `420` | Webhook certificate Secret file mode as a decimal integer (0-511). Default 420 (0644) preserves existing behavior. For non-root hardening, use 288 (0440) with controllerManager.podSecurityContext.fsGroup; see docs/security.md. | | admissionWebhook.enabled | bool | `true` | | | controllerManager.addDefaultAffinity | bool | `true` | | | controllerManager.affinity | object | `{}` | | diff --git a/charts/function-mesh-operator/templates/_helpers.tpl b/charts/function-mesh-operator/templates/_helpers.tpl index 0d448c823..3412a9c6b 100644 --- a/charts/function-mesh-operator/templates/_helpers.tpl +++ b/charts/function-mesh-operator/templates/_helpers.tpl @@ -77,7 +77,7 @@ Volumes {{- if .Values.admissionWebhook.enabled }} - name: cert secret: - defaultMode: 420 + defaultMode: {{ .Values.admissionWebhook.certSecretDefaultMode }} secretName: {{ include "function-mesh-operator.certificate.secret" . }} {{- end }} {{- end }} diff --git a/charts/function-mesh-operator/values.yaml b/charts/function-mesh-operator/values.yaml index b6f4180da..c2105392b 100644 --- a/charts/function-mesh-operator/values.yaml +++ b/charts/function-mesh-operator/values.yaml @@ -85,3 +85,5 @@ controllerManager: admissionWebhook: enabled: true + # -- Webhook certificate Secret file mode as a decimal integer (0-511). Default 420 (0644) preserves existing behavior. For non-root hardening, use 288 (0440) with controllerManager.podSecurityContext.fsGroup; see docs/security.md. + certSecretDefaultMode: 420 diff --git a/docs/security.md b/docs/security.md index 28b4605b8..b039b0952 100644 --- a/docs/security.md +++ b/docs/security.md @@ -38,6 +38,35 @@ election. Setting this value to `false` alone breaks the default in-cluster authentication for new pods. Prefer a narrowly scoped policy exception when token access is required; this setting does not provision alternative credentials. +## Webhook certificate file permissions + +`admissionWebhook.certSecretDefaultMode` controls the controller's webhook +certificate Secret volume file permissions. The default is `420` (0644), +preserving existing behavior. It has no effect when `admissionWebhook.enabled` +is `false`, and does not change ConfigMap or ServiceAccount token permissions. + +To remove world-readable access while allowing the non-root controller to read +its certificate and private key, merge these values with the hardening settings +above: + +```yaml +admissionWebhook: + certSecretDefaultMode: 288 # 0440; use decimal for Helm --set and JSON too. +controllerManager: + podSecurityContext: + fsGroup: 10001 # Suitable for the chart default image; verify for other images. +``` + +`runAsGroup` alone does not change the Secret volume's group ownership. Set +`fsGroup` so kubelet makes the mounted files group-readable by the controller. +Do not use `256` (0400) alone for a non-root controller: Secret files are owned +by root. With `fsGroup`, kubelet may add group-read permission even when the +requested mode is 0400, so do not rely on it for owner-only access. + +Validate the rendered Deployment against your actual admission policies and +verify controller readiness and webhook requests after rollout. This setting +applies only to the operator's webhook certificate mount, not to runner Secrets. + ## Metrics authentication and authorization The operator serves HTTPS metrics with Kubernetes authentication and authorization. From 8e73c63b5dc0e0e4091c0717f253b50f8eba9b04 Mon Sep 17 00:00:00 2001 From: Rui Fu Date: Thu, 24 Sep 2026 15:35:24 +0800 Subject: [PATCH 2/3] fix: validate webhook certificate mode and clarify fsGroup guidance --- .../function-mesh-operator/values.schema.json | 19 ++++++++++++++ docs/security.md | 26 +++++++++++++------ 2 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 charts/function-mesh-operator/values.schema.json diff --git a/charts/function-mesh-operator/values.schema.json b/charts/function-mesh-operator/values.schema.json new file mode 100644 index 000000000..de6685c9b --- /dev/null +++ b/charts/function-mesh-operator/values.schema.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": ["admissionWebhook"], + "properties": { + "admissionWebhook": { + "type": "object", + "required": ["certSecretDefaultMode"], + "properties": { + "certSecretDefaultMode": { + "description": "Webhook certificate Secret file mode as an integer from 0 to 511. Use decimal 288 for 0440.", + "type": "integer", + "minimum": 0, + "maximum": 511 + } + } + } + } +} diff --git a/docs/security.md b/docs/security.md index b039b0952..83e63b58c 100644 --- a/docs/security.md +++ b/docs/security.md @@ -10,8 +10,8 @@ operator image and admission policies: controllerManager: podSecurityContext: runAsNonRoot: true - # UID/GID for the chart default image streamnative/function-mesh:v0.29.0. - # Verify these IDs before using a different image. + # UID/GID for images built with operator.Dockerfile (USER pulsar). + # Dockerfile uses 65532:65532 instead; verify your deployed image. runAsUser: 10000 runAsGroup: 10001 seccompProfile: @@ -24,11 +24,13 @@ controllerManager: ``` These settings affect only the controller manager, not Function, Source or Sink -pods. The chart default image declares `USER pulsar`. With only -`runAsNonRoot: true`, kubelet cannot verify that this non-numeric image user is -non-root and refuses to start the container. Set an image-appropriate numeric -`runAsUser`, as shown above. Do not assume every operator image uses the same -UID/GID; these values are opt-in, not new chart defaults. +pods. Images built with `operator.Dockerfile` declare `USER pulsar` +(UID 10000, GID 10001); the separate distroless `Dockerfile` declares +`USER 65532:65532`. For a non-numeric image user, `runAsNonRoot: true` alone +prevents startup because kubelet cannot verify the user is non-root. Set an +image-appropriate numeric `runAsUser`, as shown above. Verify the deployed +image's UID/GID rather than inferring them from a different build path; these +values are opt-in, not new chart defaults. `controllerManager.automountServiceAccountToken` optionally sets the field on the chart-managed ServiceAccount; its default `null` omits the field. It has no @@ -44,6 +46,11 @@ token access is required; this setting does not provision alternative credential certificate Secret volume file permissions. The default is `420` (0644), preserving existing behavior. It has no effect when `admissionWebhook.enabled` is `false`, and does not change ConfigMap or ServiceAccount token permissions. +The chart schema requires an integer from 0 to 511. Use decimal values such as +`--set admissionWebhook.certSecretDefaultMode=288`; strings (including +`--set ...=0440` or `--set-string ...=288`), null, empty values, and out-of-range +values are rejected by Helm schema validation. Omitting the override retains +the chart default. To remove world-readable access while allowing the non-root controller to read its certificate and private key, merge these values with the hardening settings @@ -54,11 +61,14 @@ admissionWebhook: certSecretDefaultMode: 288 # 0440; use decimal for Helm --set and JSON too. controllerManager: podSecurityContext: - fsGroup: 10001 # Suitable for the chart default image; verify for other images. + fsGroup: 10001 # Example non-zero supplemental GID; choose one allowed by your policy. ``` `runAsGroup` alone does not change the Secret volume's group ownership. Set `fsGroup` so kubelet makes the mounted files group-readable by the controller. +Kubernetes also adds `fsGroup` to the process's supplementary groups; it does +not need to match the image's primary GID. Any non-zero GID allowed by your +cluster policy can be used for this purpose, including 65532 for distroless. Do not use `256` (0400) alone for a non-root controller: Secret files are owned by root. With `fsGroup`, kubelet may add group-read permission even when the requested mode is 0400, so do not rely on it for owner-only access. From 39681d669cd3574427395db50332b7e922478b33 Mon Sep 17 00:00:00 2001 From: Rui Fu Date: Thu, 24 Sep 2026 15:44:24 +0800 Subject: [PATCH 3/3] fix: preserve reuse-values upgrades for webhook certificate mode --- charts/function-mesh-operator/templates/_helpers.tpl | 7 ++++++- charts/function-mesh-operator/values.schema.json | 2 -- docs/security.md | 11 ++++++++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/charts/function-mesh-operator/templates/_helpers.tpl b/charts/function-mesh-operator/templates/_helpers.tpl index 3412a9c6b..e72baa107 100644 --- a/charts/function-mesh-operator/templates/_helpers.tpl +++ b/charts/function-mesh-operator/templates/_helpers.tpl @@ -75,9 +75,14 @@ Volumes name: function-mesh-controller-manager-configs defaultMode: 420 {{- if .Values.admissionWebhook.enabled }} +{{- /* Older releases lack this key when upgrading with --reuse-values. Preserve explicit 0. */}} +{{- $certMode := 420 }} +{{- if hasKey .Values.admissionWebhook "certSecretDefaultMode" }} +{{- $certMode = .Values.admissionWebhook.certSecretDefaultMode }} +{{- end }} - name: cert secret: - defaultMode: {{ .Values.admissionWebhook.certSecretDefaultMode }} + defaultMode: {{ $certMode }} secretName: {{ include "function-mesh-operator.certificate.secret" . }} {{- end }} {{- end }} diff --git a/charts/function-mesh-operator/values.schema.json b/charts/function-mesh-operator/values.schema.json index de6685c9b..759f18fa8 100644 --- a/charts/function-mesh-operator/values.schema.json +++ b/charts/function-mesh-operator/values.schema.json @@ -1,11 +1,9 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", - "required": ["admissionWebhook"], "properties": { "admissionWebhook": { "type": "object", - "required": ["certSecretDefaultMode"], "properties": { "certSecretDefaultMode": { "description": "Webhook certificate Secret file mode as an integer from 0 to 511. Use decimal 288 for 0440.", diff --git a/docs/security.md b/docs/security.md index 83e63b58c..cdd64baa2 100644 --- a/docs/security.md +++ b/docs/security.md @@ -46,11 +46,16 @@ token access is required; this setting does not provision alternative credential certificate Secret volume file permissions. The default is `420` (0644), preserving existing behavior. It has no effect when `admissionWebhook.enabled` is `false`, and does not change ConfigMap or ServiceAccount token permissions. -The chart schema requires an integer from 0 to 511. Use decimal values such as +When present, this value must be an integer from 0 to 511. Use decimal values such as `--set admissionWebhook.certSecretDefaultMode=288`; strings (including -`--set ...=0440` or `--set-string ...=288`), null, empty values, and out-of-range +`--set ...=0440` or `--set-string ...=288`), empty strings, and out-of-range values are rejected by Helm schema validation. Omitting the override retains -the chart default. +the chart default. If the key is missing (for example, when upgrading an older +release with `--reuse-values`), the template falls back to `420` (0644). With +the current chart defaults, a null override removes the key during Helm value +coalescing and also falls back to `420`; null does not enable hardening. If a +null remains after coalescing (for example, with older reused values that lack +this key), schema validation rejects it. An explicit `0` is preserved. To remove world-readable access while allowing the non-root controller to read its certificate and private key, merge these values with the hardening settings