From 40b29c4accd52e79708bf752a6c4481bd377a8d0 Mon Sep 17 00:00:00 2001 From: Rui Fu Date: Tue, 15 Sep 2026 21:49:06 +0800 Subject: [PATCH 1/2] fix: support optional operator security settings --- charts/function-mesh-operator/README.md | 3 + .../controller-manager-deployment.yaml | 10 ++- .../templates/controller-manager-rbac.yaml | 3 + charts/function-mesh-operator/values.yaml | 6 ++ docs/security.md | 81 +++++++++++++++++++ 5 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 docs/security.md diff --git a/charts/function-mesh-operator/README.md b/charts/function-mesh-operator/README.md index dd81428d5..8c5d0cd37 100644 --- a/charts/function-mesh-operator/README.md +++ b/charts/function-mesh-operator/README.md @@ -30,6 +30,7 @@ function mesh operator Helm chart for Kubernetes | controllerManager.addDefaultAffinity | bool | `true` | | | controllerManager.affinity | object | `{}` | | | controllerManager.autoFailover | bool | `true` | | +| controllerManager.automountServiceAccountToken | string | `nil` | Optional setting on the chart-managed ServiceAccount. Null preserves the Kubernetes default. The controller requires Kubernetes API credentials; do not disable token mounting without an alternative. | | controllerManager.configFile | string | `"/etc/config/config.yaml"` | | | controllerManager.create | bool | `true` | | | controllerManager.enableInitContainers | bool | `false` | | @@ -42,11 +43,13 @@ function mesh operator Helm chart for Kubernetes | controllerManager.namespacedBackendConfig | string | `"backend-config"` | | | controllerManager.nodeSelector | object | `{}` | | | controllerManager.pauseRollout | bool | `false` | | +| controllerManager.podSecurityContext | object | `{}` | Optional controller Pod security context. Empty preserves existing behavior. | | controllerManager.pprof.enable | bool | `false` | | | controllerManager.pprof.port | int | `8090` | | | controllerManager.replicas | int | `1` | | | controllerManager.resources.requests.cpu | string | `"80m"` | | | controllerManager.resources.requests.memory | string | `"50Mi"` | | +| controllerManager.securityContext | object | `{}` | Optional controller container security context. Empty preserves existing behavior. | | controllerManager.selector | list | `[]` | | | controllerManager.serviceAccount | string | `"function-mesh-controller-manager"` | | | controllerManager.tolerations | list | `[]` | | diff --git a/charts/function-mesh-operator/templates/controller-manager-deployment.yaml b/charts/function-mesh-operator/templates/controller-manager-deployment.yaml index 09f57f692..063d8a194 100644 --- a/charts/function-mesh-operator/templates/controller-manager-deployment.yaml +++ b/charts/function-mesh-operator/templates/controller-manager-deployment.yaml @@ -32,6 +32,10 @@ spec: {{- if .Values.controllerManager.serviceAccount }} serviceAccount: {{ .Values.controllerManager.serviceAccount }} {{- end }} + {{- with .Values.controllerManager.podSecurityContext }} + securityContext: +{{ toYaml . | indent 8 }} + {{- end }} {{- if .Values.imagePullSecrets }} imagePullSecrets: {{ toYaml .Values.imagePullSecrets | indent 6 }} @@ -40,6 +44,10 @@ spec: - name: function-mesh-operator image: {{ .Values.operatorImage }} imagePullPolicy: {{ .Values.imagePullPolicy | default "IfNotPresent" }} + {{- with .Values.controllerManager.securityContext }} + securityContext: +{{ toYaml . | indent 10 }} + {{- end }} {{- if .Values.controllerManager.resources }} resources: {{ toYaml .Values.controllerManager.resources | indent 12 }} @@ -105,4 +113,4 @@ spec: priorityClassName: {{ .Values.controllerManager.priorityClassName }} {{- end }} volumes: - {{- include "function-mesh-operator.volumes" . | nindent 6 -}} \ No newline at end of file + {{- include "function-mesh-operator.volumes" . | nindent 6 -}} diff --git a/charts/function-mesh-operator/templates/controller-manager-rbac.yaml b/charts/function-mesh-operator/templates/controller-manager-rbac.yaml index 4034fcc66..574440850 100644 --- a/charts/function-mesh-operator/templates/controller-manager-rbac.yaml +++ b/charts/function-mesh-operator/templates/controller-manager-rbac.yaml @@ -10,6 +10,9 @@ metadata: app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/component: controller-manager helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} +{{- if kindIs "bool" .Values.controllerManager.automountServiceAccountToken }} +automountServiceAccountToken: {{ .Values.controllerManager.automountServiceAccountToken }} +{{- end }} --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/function-mesh-operator/values.yaml b/charts/function-mesh-operator/values.yaml index e6e189387..b6f4180da 100644 --- a/charts/function-mesh-operator/values.yaml +++ b/charts/function-mesh-operator/values.yaml @@ -21,6 +21,12 @@ controllerManager: # With rbac.create=true, this service account will be created # Also see rbac.create and clusterScoped serviceAccount: function-mesh-controller-manager + # -- Optional setting on the chart-managed ServiceAccount. Null preserves the Kubernetes default. The controller requires Kubernetes API credentials; do not disable token mounting without an alternative. + automountServiceAccountToken: null + # -- Optional controller Pod security context. Empty preserves existing behavior. + podSecurityContext: {} + # -- Optional controller container security context. Empty preserves existing behavior. + securityContext: {} replicas: 1 resources: requests: diff --git a/docs/security.md b/docs/security.md new file mode 100644 index 000000000..0b74bfc87 --- /dev/null +++ b/docs/security.md @@ -0,0 +1,81 @@ +# Security configuration + +## Operator + +The Helm chart preserves existing security settings by default. To opt in to +container hardening, use the following values and verify them against your +operator image and admission policies: + +```yaml +controllerManager: + podSecurityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: [ALL] +``` + +These settings affect only the controller manager, not Function, Source or Sink +pods. Do not assume every operator image uses the same numeric user ID. + +`controllerManager.automountServiceAccountToken` optionally sets the field on +the chart-managed ServiceAccount; its default `null` omits the field. It has no +effect on externally managed ServiceAccounts when `rbac.create: false`. The +controller needs Kubernetes API credentials for reconciliation and leader +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. + +## Explicit ServiceAccount token mounting for sinks + +Function Mesh does not create runtime ServiceAccounts. A user-managed account +with `automountServiceAccountToken: false` disables automatic mounting but still +allows explicit projected tokens. No additional CRD field is required. + +For a ServiceAccount named `pulsar-sink-job-sac` in the Sink namespace, merge +the following fields into the existing Sink spec. Preserve any existing volumes +and volume mounts. This example supplies the standard Kubernetes in-cluster +client paths: + +```yaml +spec: + pod: + serviceAccountName: pulsar-sink-job-sac + volumes: + - name: explicit-kube-api-access + projected: + sources: + - serviceAccountToken: + path: token + expirationSeconds: 3600 + - configMap: + name: kube-root-ca.crt + items: + - key: ca.crt + path: ca.crt + - downwardAPI: + items: + - path: namespace + fieldRef: + fieldPath: metadata.namespace + volumeMounts: + - name: explicit-kube-api-access + mountPath: /var/run/secrets/kubernetes.io/serviceaccount + readOnly: true +``` + +The token identifies the Pod ServiceAccount; its Kubernetes API permissions +still depend on RBAC. Omitting `audience` uses the API server default. For another +service, set its expected audience and adjust the mount path as needed. + +Kubelet rotates projected tokens. Do not use `subPath` for the token mount, and +ensure the client reloads the token. Custom `spec.volumeMounts` also propagate +to built-in downloader, filebeat and cleanup containers when enabled; the mount +is not necessarily exclusive to the sink main container. Verify that the +admission policy permits explicit token projection. This does not satisfy a +policy that separately requires an explicit Pod-level +`automountServiceAccountToken: false` field. From 394735aeeeba84d70740e8927e245cc518d797bc Mon Sep 17 00:00:00 2001 From: Rui Fu Date: Thu, 17 Sep 2026 20:42:16 +0800 Subject: [PATCH 2/2] fix: grant operator metrics review permissions --- .../templates/controller-manager-rbac.yaml | 13 +++++++ docs/security.md | 37 ++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/charts/function-mesh-operator/templates/controller-manager-rbac.yaml b/charts/function-mesh-operator/templates/controller-manager-rbac.yaml index 574440850..aea5c764a 100644 --- a/charts/function-mesh-operator/templates/controller-manager-rbac.yaml +++ b/charts/function-mesh-operator/templates/controller-manager-rbac.yaml @@ -25,6 +25,19 @@ metadata: app.kubernetes.io/component: controller-manager helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} rules: + # Required by the controller metrics authentication and authorization filter. + - apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create + - apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create - apiGroups: - apps resources: diff --git a/docs/security.md b/docs/security.md index 0b74bfc87..28b4605b8 100644 --- a/docs/security.md +++ b/docs/security.md @@ -10,6 +10,10 @@ 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. + runAsUser: 10000 + runAsGroup: 10001 seccompProfile: type: RuntimeDefault securityContext: @@ -20,7 +24,11 @@ controllerManager: ``` These settings affect only the controller manager, not Function, Source or Sink -pods. Do not assume every operator image uses the same numeric user ID. +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. `controllerManager.automountServiceAccountToken` optionally sets the field on the chart-managed ServiceAccount; its default `null` omits the field. It has no @@ -30,6 +38,33 @@ 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. +## Metrics authentication and authorization + +The operator serves HTTPS metrics with Kubernetes authentication and authorization. +The chart grants its ServiceAccount `create` on +`tokenreviews.authentication.k8s.io` and +`subjectaccessreviews.authorization.k8s.io` so it can validate scrape requests. +When `rbac.create: false`, include these permissions in the externally managed +ClusterRole and bind it to the operator ServiceAccount. Missing permissions cause +authenticated scrapes to return HTTP 500. + +The scraping client (for example, Prometheus) separately needs a ClusterRole with: + +```yaml +rules: + - nonResourceURLs: ["/metrics"] + verbs: ["get"] +``` + +Bind that role to the actual scraping ServiceAccount using a ClusterRoleBinding +and configure the client to send its bearer token over HTTPS with the appropriate +TLS trust configuration. The chart does not grant metrics access to arbitrary +clients. Requests without a bearer token return HTTP 401; authenticated clients +without permission return HTTP 403; authorized requests return HTTP 200. The +current controller-runtime filter reports authentication errors, including +invalid bearer token errors, as HTTP 500; check the operator logs to distinguish +these from missing RBAC permissions. + ## Explicit ServiceAccount token mounting for sinks Function Mesh does not create runtime ServiceAccounts. A user-managed account