diff --git a/charts/function-mesh-operator/README.md b/charts/function-mesh-operator/README.md index dd81428d..8c5d0cd3 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 09f57f69..063d8a19 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 4034fcc6..aea5c764 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 @@ -22,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/charts/function-mesh-operator/values.yaml b/charts/function-mesh-operator/values.yaml index e6e18938..b6f4180d 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 00000000..28b4605b --- /dev/null +++ b/docs/security.md @@ -0,0 +1,116 @@ +# 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 + # 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: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: [ALL] +``` + +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. + +`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. + +## 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 +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.