On a compromised node, the fluid-csi service account can be used to modify node specs
Description
Fluid is an open source Kubernetes-native distributed dataset orchestrator and accelerator for data-intensive applications. Starting in version 0.7.0 and prior to version 0.8.6, if a malicious user gains control of a Kubernetes node running fluid csi pod (controlled by the csi-nodeplugin-fluid node-daemonset), they can leverage the fluid-csi service account to modify specs of all the nodes in the cluster. However, since this service account lacks list node permissions, the attacker may need to use other techniques to identify vulnerable nodes.
Once the attacker identifies and modifies the node specs, they can manipulate system-level-privileged components to access all secrets in the cluster or execute pods on other nodes. This allows them to elevate privileges beyond the compromised node and potentially gain full privileged access to the whole cluster.
To exploit this vulnerability, the attacker can make all other nodes unschedulable (for example, patch node with taints) and wait for system-critical components with high privilege to appear on the compromised node. However, this attack requires two prerequisites: a compromised node and identifying all vulnerable nodes through other means.
Version 0.8.6 contains a patch for this issue. As a workaround, delete the csi-nodeplugin-fluid daemonset in fluid-system namespace and avoid using CSI mode to mount FUSE file systems. Alternatively, using sidecar mode to mount FUSE file systems is recommended.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Fluid's CSI plugin before v0.8.6 allowed a compromised node to use the fluid-csi service account to modify node specs, enabling cluster-wide privilege escalation.
Vulnerability
Overview In Fluid versions 0.7.0 through 0.8.5, the CSI node plugin (csi-nodeplugin-fluid) runs with a service account that has overly broad RBAC permissions. The patch analysis shows that the service account previously had get, list, watch, create, update, patch on events, plus get, patch on nodes and * on nodes/proxy [3]. After the fix, these were reduced to only create and patch on events, removing node-level permissions entirely. The CSI plugin also previously mounted the host /etc directory, which was replaced with more specific mounts for kubelet configuration and certificates [1][3]. This change prevents the CSI container from accessing sensitive system files across the node.
Exploitation
Prerequisites and Method Exploitation requires two preconditions: an attacker must first compromise a node running the csi-nodeplugin-fluid pod, and must identify all vulnerable nodes through other means (since the service account cannot list nodes). Once these are met, the attacker can use the compromised CSI pod's service account to modify node specs across the cluster. A described attack pattern involves tainting all other nodes to make them unschedulable, then waiting for high-privilege system components to land on the compromised node, which can then be leveraged to access secrets or execute pods elsewhere [2].
Impact
Successful exploitation allows the attacker to escalate from a single compromised node to full cluster compromise. By manipulating node specs and leveraging system-level privileged components that arrive on the compromised node, an attacker could access all secrets in the cluster or execute arbitrary pods on other nodes. The CVSS score could be severe, as the attack chain bridges container escape and lateral movement to achieve privilege escalation across the entire Kubernetes cluster [2].
Mitigation
Fluid version 0.8.6 patches this vulnerability by fixing the CSI plugin's RBAC roles and changing host mounts to use a node-authorized kubelet client instead of the broad node-proxy permissions [1][3][4]. As a workaround, administrators can delete the csi-nodeplugin-fluid DaemonSet and avoid using CSI mode for mounting FUSE filesystems; using sidecar mode is recommended instead [2].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/fluid-cloudnative/fluidGo | >= 0.7.0, < 0.8.6 | 0.8.6 |
Affected products
3>=0.7.0 <0.8.6+ 1 more
- (no CPE)range: >=0.7.0 <0.8.6
- (no CPE)range: >= 0.7.0, < 0.8.6
Patches
277c8110a3d1eMerge pull request from GHSA-93xx-cvmc-9w3v
12 files changed · +203 −60
charts/fluid/fluid/CHANGELOG.md+2 −1 modified@@ -53,4 +53,5 @@ * Scale runtime controllers on demand ### 0.9.0 -* Support pass image pull secrets from fluid charts to alluxioruntime controller \ No newline at end of file +* Support pass image pull secrets from fluid charts to alluxioruntime controller +* Fix components rbacs and set Fluid CSI Plugin with node-authorized kube-client \ No newline at end of file
charts/fluid/fluid/templates/csi/daemonset.yaml+24 −4 modified@@ -104,8 +104,16 @@ spec: - name: fluid-src-dir mountPath: {{ .Values.runtime.mountRoot | quote }} mountPropagation: "Bidirectional" - - name: host-etc-dir - mountPath: /host-etc + - name: kubelet-kube-config + mountPath: /etc/kubernetes/kubelet.conf + readOnly: true + - name: kubelet-cert-dir + mountPath: {{ .Values.csi.kubelet.certDir | quote }} + readOnly: true + - name: updatedb-conf + mountPath: /host-etc/updatedb.conf + - name: updatedb-conf-bak + mountPath: /host-etc/updatedb.conf.bak volumes: - name: kubelet-dir hostPath: @@ -124,6 +132,18 @@ spec: type: DirectoryOrCreate name: fluid-src-dir - hostPath: - path: /etc + path: {{ .Values.csi.kubelet.kubeConfigFile | quote }} + type: File + name: kubelet-kube-config + - hostPath: + path: {{ .Values.csi.kubelet.certDir | quote }} type: Directory - name: host-etc-dir + name: kubelet-cert-dir + - hostPath: + path: /etc/updatedb.conf + type: FileOrCreate + name: updatedb-conf + - hostPath: + path: /etc/updatedb.conf.backup + type: FileOrCreate + name: updatedb-conf-bak
charts/fluid/fluid/templates/role/csi/rbac.yaml+1 −7 modified@@ -41,13 +41,7 @@ rules: verbs: ["get"] - apiGroups: [""] resources: ["events"] - verbs: ["get", "list", "watch", "create", "update", "patch"] - - apiGroups: [""] - resources: ["nodes"] - verbs: ["get", "patch"] - - apiGroups: [""] - resources: ["nodes/proxy"] - verbs: ["*"] + verbs: ["create", "patch"] --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1
charts/fluid/fluid/templates/role/webhook/rabc.yaml+45 −10 modified@@ -1,16 +1,59 @@ {{ if .Values.webhook.enabled -}} apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: fluid-webhook + namespace: {{ include "fluid.namespace" . }} +rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - update + resourceNames: + - fluid-webhook-certs + # resourceNames won't protect create verb, so individually specify it for readability + - apiGroups: + - "" + resources: + - secrets + verbs: + - create +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: fluid-webhook-rolebinding + namespace: {{ include "fluid.namespace" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: fluid-webhook +subjects: + - kind: ServiceAccount + name: fluid-webhook + namespace: {{ include "fluid.namespace" . }} +--- +apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: fluid-webhook rules: + # Can only list and watch secret `mutatingwebhookconfiguration` with a metadata.name field selector + # See https://kubernetes.io/docs/reference/access-authn-authz/rbac/#referring-to-resources - apiGroups: - admissionregistration.k8s.io resources: - - validatingwebhookconfigurations - mutatingwebhookconfigurations + resourceNames: + - fluid-pod-admission-webhook verbs: - - '*' + - get + - patch + - list + - watch - apiGroups: - data.fluid.io resources: @@ -38,9 +81,7 @@ rules: - apiGroups: - "" resources: - - secrets - configmaps - - events verbs: - get - create @@ -56,12 +97,6 @@ rules: - get - list - watch - - apiGroups: - - coordination.k8s.io - resources: - - leases - verbs: - - '*' --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding
charts/fluid/fluid/templates/webhook/webhook.yaml+2 −0 modified@@ -16,6 +16,8 @@ spec: labels: control-plane: fluid-webhook spec: + tolerations: + - operator: Exists {{- with .Values.image.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }}
charts/fluid/fluid/values.yaml+2 −0 modified@@ -26,6 +26,8 @@ csi: plugins: image: fluidcloudnative/fluid-csi:v0.9.0-085b23e kubelet: + kubeConfigFile: /etc/kubernetes/kubelet.conf + certDir: /var/lib/kubelet/pki rootDir: /var/lib/kubelet pruneFs: fuse.alluxio-fuse,fuse.jindofs-fuse,fuse.juicefs,fuse.goosefs-fuse,ossfs,alifuse.aliyun-alinas-efc
cmd/csi/app/csi.go+13 −10 modified@@ -39,12 +39,13 @@ import ( ) var ( - endpoint string - nodeID string - metricsAddr string - pprofAddr string - pruneFs []string - prunePath string + endpoint string + nodeID string + metricsAddr string + pprofAddr string + pruneFs []string + prunePath string + kubeletKubeConfigPath string ) var scheme = runtime.NewScheme() @@ -81,6 +82,7 @@ func init() { startCmd.Flags().StringVarP(&prunePath, "prune-path", "", "/runtime-mnt", "Prune path to add in /etc/updatedb.conf") startCmd.Flags().StringVarP(&metricsAddr, "metrics-addr", "", ":8080", "The address the metrics endpoint binds to.") startCmd.Flags().StringVarP(&pprofAddr, "pprof-addr", "", "", "The address for pprof to use while exporting profiling results") + startCmd.Flags().StringVarP(&kubeletKubeConfigPath, "kubelet-kube-config", "", "/etc/kubernetes/kubelet.conf", "The file path to kubelet kube config") utilfeature.DefaultMutableFeatureGate.AddFlag(startCmd.Flags()) startCmd.Flags().AddGoFlagSet(flag.CommandLine) } @@ -109,10 +111,11 @@ func handle() { } config := config.Config{ - NodeId: nodeID, - Endpoint: endpoint, - PruneFs: pruneFs, - PrunePath: prunePath, + NodeId: nodeID, + Endpoint: endpoint, + PruneFs: pruneFs, + PrunePath: prunePath, + KubeletConfigPath: kubeletKubeConfigPath, } if err = csi.SetupWithManager(mgr, config); err != nil {
pkg/csi/config/config.go+5 −4 modified@@ -17,8 +17,9 @@ limitations under the License. package config type Config struct { - NodeId string - Endpoint string - PruneFs []string - PrunePath string + NodeId string + Endpoint string + PruneFs []string + PrunePath string + KubeletConfigPath string }
pkg/csi/plugins/driver.go+18 −14 modified@@ -23,6 +23,7 @@ import ( "path/filepath" "strings" + "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/manager" @@ -38,15 +39,16 @@ const ( ) type driver struct { - client client.Client - apiReader client.Reader - csiDriver *csicommon.CSIDriver - nodeId, endpoint string + client client.Client + apiReader client.Reader + nodeAuthorizedClient *kubernetes.Clientset + csiDriver *csicommon.CSIDriver + nodeId, endpoint string } var _ manager.Runnable = &driver{} -func NewDriver(nodeID, endpoint string, client client.Client, apiReader client.Reader) *driver { +func NewDriver(nodeID, endpoint string, client client.Client, apiReader client.Reader, nodeAuthorizedClient *kubernetes.Clientset) *driver { glog.Infof("Driver: %v version: %v", driverName, version) proto, addr := utils.SplitSchemaAddr(endpoint) @@ -68,11 +70,12 @@ func NewDriver(nodeID, endpoint string, client client.Client, apiReader client.R csiDriver.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER}) return &driver{ - nodeId: nodeID, - endpoint: endpoint, - csiDriver: csiDriver, - client: client, - apiReader: apiReader, + nodeId: nodeID, + endpoint: endpoint, + csiDriver: csiDriver, + client: client, + nodeAuthorizedClient: nodeAuthorizedClient, + apiReader: apiReader, } } @@ -84,10 +87,11 @@ func (d *driver) newControllerServer() *controllerServer { func (d *driver) newNodeServer() *nodeServer { return &nodeServer{ - nodeId: d.nodeId, - DefaultNodeServer: csicommon.NewDefaultNodeServer(d.csiDriver), - client: d.client, - apiReader: d.apiReader, + nodeId: d.nodeId, + DefaultNodeServer: csicommon.NewDefaultNodeServer(d.csiDriver), + client: d.client, + apiReader: d.apiReader, + nodeAuthorizedClient: d.nodeAuthorizedClient, } }
pkg/csi/plugins/nodeserver.go+60 −9 modified@@ -17,6 +17,7 @@ limitations under the License. package plugins import ( + "encoding/json" "fmt" "os" "os/exec" @@ -31,9 +32,11 @@ import ( "github.com/fluid-cloudnative/fluid/pkg/ddc/base" "github.com/fluid-cloudnative/fluid/pkg/utils" "github.com/fluid-cloudnative/fluid/pkg/utils/dataset/volume" - "github.com/fluid-cloudnative/fluid/pkg/utils/kubeclient" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" "k8s.io/utils/mount" "sigs.k8s.io/controller-runtime/pkg/client" @@ -52,10 +55,11 @@ const ( type nodeServer struct { nodeId string *csicommon.DefaultNodeServer - client client.Client - apiReader client.Reader - mutex sync.Mutex - node *v1.Node + client client.Client + apiReader client.Reader + nodeAuthorizedClient *kubernetes.Clientset + mutex sync.Mutex + node *v1.Node } func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) { @@ -267,7 +271,8 @@ func (ns *nodeServer) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstag return nil, errors.Wrapf(err, "NodeUnstageVolume: can't get node %s", ns.nodeId) } - _, err = utils.ChangeNodeLabelWithPatchMode(ns.client, node, labelsToModify) + // _, err = utils.ChangeNodeLabelWithPatchMode(ns.client, node, labelsToModify) + err = ns.patchNodeWithLabel(node, labelsToModify) if err != nil { glog.Errorf("NodeUnstageVolume: error when patching labels on node %s: %v", ns.nodeId, err) return nil, errors.Wrapf(err, "NodeUnstageVolume: error when patching labels on node %s", ns.nodeId) @@ -315,7 +320,8 @@ func (ns *nodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVol return nil, errors.Wrapf(err, "NodeStageVolume: can't get node %s", ns.nodeId) } - _, err = utils.ChangeNodeLabelWithPatchMode(ns.client, node, labelsToModify) + // _, err = utils.ChangeNodeLabelWithPatchMode(ns.client, node, labelsToModify) + err = ns.patchNodeWithLabel(node, labelsToModify) if err != nil { glog.Errorf("NodeStageVolume: error when patching labels on node %s: %v", ns.nodeId, err) return nil, errors.Wrapf(err, "NodeStageVolume: error when patching labels on node %s", ns.nodeId) @@ -373,14 +379,58 @@ func (ns *nodeServer) getNode() (node *v1.Node, err error) { } } - if node, err = kubeclient.GetNode(ns.apiReader, ns.nodeId); err != nil { + if node, err = ns.nodeAuthorizedClient.CoreV1().Nodes().Get(context.TODO(), ns.nodeId, metav1.GetOptions{}); err != nil { return nil, err } + + // if node, err = kubeclient.GetNode(ns.apiReader, ns.nodeId); err != nil { + // return nil, err + // } + glog.V(1).Infof("Got node %s from api server", node.Name) ns.node = node return ns.node, nil } +func (ns *nodeServer) patchNodeWithLabel(node *v1.Node, labelsToModify common.LabelsToModify) error { + labels := labelsToModify.GetLabels() + labelValuePair := map[string]interface{}{} + + for _, labelToModify := range labels { + operationType := labelToModify.GetOperationType() + labelToModifyKey := labelToModify.GetLabelKey() + labelToModifyValue := labelToModify.GetLabelValue() + + switch operationType { + case common.AddLabel, common.UpdateLabel: + labelValuePair[labelToModifyKey] = labelToModifyValue + case common.DeleteLabel: + labelValuePair[labelToModifyKey] = nil + default: + err := fmt.Errorf("fail to update the label due to the wrong operation: %s", operationType) + return err + } + } + + metadata := map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": labelValuePair, + }, + } + + patchByteData, err := json.Marshal(metadata) + if err != nil { + return err + } + + _, err = ns.nodeAuthorizedClient.CoreV1().Nodes().Patch(context.TODO(), node.Name, types.StrategicMergePatchType, patchByteData, metav1.PatchOptions{}) + if err != nil { + return err + } + + return nil +} + func checkMountInUse(volumeName string) (bool, error) { var inUse bool glog.Infof("Try to check if the volume %s is being used", volumeName) @@ -454,7 +504,8 @@ func (ns *nodeServer) prepareSessMgr(workDir string) error { return errors.Wrapf(err, "can't get node %s", ns.nodeId) } - _, err = utils.ChangeNodeLabelWithPatchMode(ns.client, node, labelsToModify) + // _, err = utils.ChangeNodeLabelWithPatchMode(ns.client, node, labelsToModify) + err = ns.patchNodeWithLabel(node, labelsToModify) if err != nil { return errors.Wrapf(err, "error when patching labels on node %s", ns.nodeId) }
pkg/csi/plugins/register.go+7 −1 modified@@ -18,12 +18,18 @@ package plugins import ( "github.com/fluid-cloudnative/fluid/pkg/csi/config" + "github.com/fluid-cloudnative/fluid/pkg/utils/kubelet" "sigs.k8s.io/controller-runtime/pkg/manager" ) // Register initializes the csi driver and registers it to the controller manager. func Register(mgr manager.Manager, cfg config.Config) error { - csiDriver := NewDriver(cfg.NodeId, cfg.Endpoint, mgr.GetClient(), mgr.GetAPIReader()) + client, err := kubelet.InitNodeAuthorizedClient(cfg.KubeletConfigPath) + if err != nil { + return err + } + + csiDriver := NewDriver(cfg.NodeId, cfg.Endpoint, mgr.GetClient(), mgr.GetAPIReader(), client) if err := mgr.Add(csiDriver); err != nil { return err
pkg/utils/kubelet/node_auth_client.go+24 −0 added@@ -0,0 +1,24 @@ +package kubelet + +import ( + "github.com/pkg/errors" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" +) + +// InitNodeAuthorizedClient initializes node authorized client with kubelet's kube config. +// This is now an available workaround to implement a node-scoped daemonset. +// See discussion https://github.com/kubernetes/enhancements/pull/944#issuecomment-490242290 +func InitNodeAuthorizedClient(kubeletKubeConfigPath string) (*kubernetes.Clientset, error) { + config, err := clientcmd.BuildConfigFromFlags("", kubeletKubeConfigPath) + if err != nil { + return nil, errors.Wrapf(err, "fail to build kubelet config") + } + + client, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, errors.Wrap(err, "fail to build client-go client from kubelet kubeconfig") + } + + return client, nil +}
91c05c32db13Merge pull request from GHSA-93xx-cvmc-9w3v
13 files changed · +215 −73
charts/fluid/fluid/CHANGELOG.md+1 −0 modified@@ -54,3 +54,4 @@ ### 0.9.0 * Support pass image pull secrets from fluid charts to alluxioruntime controller +* Fix components rbacs and set Fluid CSI Plugin with node-authorized kube-client
charts/fluid/fluid/Chart.yaml+1 −1 modified@@ -18,7 +18,7 @@ version: 0.8.6 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. -appVersion: 0.8.5-00f609e +appVersion: 0.8.6-2131f34 home: https://github.com/fluid-cloudnative/fluid keywords: - category:data
charts/fluid/fluid/templates/csi/daemonset.yaml+24 −4 modified@@ -104,8 +104,16 @@ spec: - name: fluid-src-dir mountPath: {{ .Values.runtime.mountRoot | quote }} mountPropagation: "Bidirectional" - - name: host-etc-dir - mountPath: /host-etc + - name: kubelet-kube-config + mountPath: /etc/kubernetes/kubelet.conf + readOnly: true + - name: kubelet-cert-dir + mountPath: {{ .Values.csi.kubelet.certDir | quote }} + readOnly: true + - name: updatedb-conf + mountPath: /host-etc/updatedb.conf + - name: updatedb-conf-bak + mountPath: /host-etc/updatedb.conf.bak volumes: - name: kubelet-dir hostPath: @@ -124,6 +132,18 @@ spec: type: DirectoryOrCreate name: fluid-src-dir - hostPath: - path: /etc + path: {{ .Values.csi.kubelet.kubeConfigFile | quote }} + type: File + name: kubelet-kube-config + - hostPath: + path: {{ .Values.csi.kubelet.certDir | quote }} type: Directory - name: host-etc-dir + name: kubelet-cert-dir + - hostPath: + path: /etc/updatedb.conf + type: FileOrCreate + name: updatedb-conf + - hostPath: + path: /etc/updatedb.conf.backup + type: FileOrCreate + name: updatedb-conf-bak
charts/fluid/fluid/templates/role/csi/rbac.yaml+1 −7 modified@@ -37,13 +37,7 @@ rules: verbs: ["get"] - apiGroups: [""] resources: ["events"] - verbs: ["get", "list", "watch", "create", "update", "patch"] - - apiGroups: [""] - resources: ["nodes"] - verbs: ["get", "patch"] - - apiGroups: [""] - resources: ["nodes/proxy"] - verbs: ["*"] + verbs: ["create", "patch"] --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1
charts/fluid/fluid/templates/role/webhook/rabc.yaml+45 −10 modified@@ -1,16 +1,59 @@ {{ if .Values.webhook.enabled -}} apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: fluid-webhook + namespace: fluid-system +rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - update + resourceNames: + - fluid-webhook-certs + # resourceNames won't protect create verb, so individually specify it for readability + - apiGroups: + - "" + resources: + - secrets + verbs: + - create +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: fluid-webhook-rolebinding + namespace: fluid-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: fluid-webhook +subjects: + - kind: ServiceAccount + name: fluid-webhook + namespace: fluid-system +--- +apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: fluid-webhook rules: + # Can only list and watch secret `mutatingwebhookconfiguration` with a metadata.name field selector + # See https://kubernetes.io/docs/reference/access-authn-authz/rbac/#referring-to-resources - apiGroups: - admissionregistration.k8s.io resources: - - validatingwebhookconfigurations - mutatingwebhookconfigurations + resourceNames: + - fluid-pod-admission-webhook verbs: - - '*' + - get + - patch + - list + - watch - apiGroups: - data.fluid.io resources: @@ -36,9 +79,7 @@ rules: - apiGroups: - "" resources: - - secrets - configmaps - - events verbs: - get - create @@ -54,12 +95,6 @@ rules: - get - list - watch - - apiGroups: - - coordination.k8s.io - resources: - - leases - verbs: - - '*' --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding
charts/fluid/fluid/templates/webhook/webhook.yaml+2 −1 modified@@ -16,6 +16,8 @@ spec: labels: control-plane: fluid-webhook spec: + tolerations: + - operator: Exists {{- with .Values.image.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} @@ -29,7 +31,6 @@ spec: - --development=false - --full-go-profile=false - --pprof-addr=:6060 - - --enable-leader-election env: - name: MY_POD_NAMESPACE valueFrom:
charts/fluid/fluid/values.yaml+14 −12 modified@@ -4,15 +4,15 @@ workdir: /tmp crdUpgrade: - image: fluidcloudnative/fluid-crd-upgrader:v0.8.5-00f609e + image: fluidcloudnative/fluid-crd-upgrader:v0.8.6-2131f34 image: imagePullSecrets: [] dataset: replicas: 1 controller: - image: fluidcloudnative/dataset-controller:v0.8.5-00f609e + image: fluidcloudnative/dataset-controller:v0.8.6-2131f34 csi: featureGates: "FuseRecovery=false" @@ -21,8 +21,10 @@ csi: registrar: image: registry.aliyuncs.com/acs/csi-node-driver-registrar:v2.3.0-038aeb6-aliyun plugins: - image: fluidcloudnative/fluid-csi:v0.8.5-00f609e + image: fluidcloudnative/fluid-csi:v0.8.6-2131f34 kubelet: + kubeConfigFile: /etc/kubernetes/kubelet.conf + certDir: /var/lib/kubelet/pki rootDir: /var/lib/kubelet pruneFs: fuse.alluxio-fuse,fuse.jindofs-fuse,fuse.juicefs,fuse.goosefs-fuse,ossfs @@ -37,9 +39,9 @@ runtime: portAllocatePolicy: random enabled: false init: - image: fluidcloudnative/init-users:v0.8.5-00f609e + image: fluidcloudnative/init-users:v0.8.6-2131f34 controller: - image: fluidcloudnative/alluxioruntime-controller:v0.8.5-00f609e + image: fluidcloudnative/alluxioruntime-controller:v0.8.6-2131f34 runtime: # image: fluidcloudnative/alluxio:release-2.7.3-SNAPSHOT-a7154f1 image: fluidcloudnative/alluxio:release-2.8.1-SNAPSHOT-0433ade @@ -59,21 +61,21 @@ runtime: fuse: image: registry.cn-shanghai.aliyuncs.com/jindofs/jindo-fuse:4.5.1 controller: - image: fluidcloudnative/jindoruntime-controller:v0.8.5-00f609e + image: fluidcloudnative/jindoruntime-controller:v0.8.6-2131f34 init: portCheck: enabled: false - image: fluidcloudnative/init-users:v0.8.5-00f609e + image: fluidcloudnative/init-users:v0.8.6-2131f34 goosefs: replicas: 1 runtimeWorkers: 3 portRange: 26000-32000 portAllocatePolicy: random enabled: false init: - image: fluidcloudnative/init-users:v0.8.5-00f609e + image: fluidcloudnative/init-users:v0.8.6-2131f34 controller: - image: fluidcloudnative/goosefsruntime-controller:v0.8.5-00f609e + image: fluidcloudnative/goosefsruntime-controller:v0.8.6-2131f34 runtime: image: ccr.ccs.tencentyun.com/qcloud/goosefs:v1.2.0 fuse: @@ -82,18 +84,18 @@ runtime: replicas: 1 enabled: false controller: - image: fluidcloudnative/juicefsruntime-controller:v0.8.5-00f609e + image: fluidcloudnative/juicefsruntime-controller:v0.8.6-2131f34 fuse: image: juicedata/juicefs-fuse:v1.0.0-4.8.0 webhook: enabled: true - image: fluidcloudnative/fluid-webhook:v0.8.5-00f609e + image: fluidcloudnative/fluid-webhook:v0.8.6-2131f34 replicas: 1 reinvocationPolicy: Never fluidapp: enabled: true replicas: 1 controller: - image: fluidcloudnative/application-controller:v0.8.5-00f609e + image: fluidcloudnative/application-controller:v0.8.6-2131f34
cmd/csi/app/csi.go+13 −10 modified@@ -39,12 +39,13 @@ import ( ) var ( - endpoint string - nodeID string - metricsAddr string - pprofAddr string - pruneFs []string - prunePath string + endpoint string + nodeID string + metricsAddr string + pprofAddr string + pruneFs []string + prunePath string + kubeletKubeConfigPath string ) var scheme = runtime.NewScheme() @@ -81,6 +82,7 @@ func init() { startCmd.Flags().StringVarP(&prunePath, "prune-path", "", "/runtime-mnt", "Prune path to add in /etc/updatedb.conf") startCmd.Flags().StringVarP(&metricsAddr, "metrics-addr", "", ":8080", "The address the metrics endpoint binds to.") startCmd.Flags().StringVarP(&pprofAddr, "pprof-addr", "", "", "The address for pprof to use while exporting profiling results") + startCmd.Flags().StringVarP(&kubeletKubeConfigPath, "kubelet-kube-config", "", "/etc/kubernetes/kubelet.conf", "The file path to kubelet kube config") utilfeature.DefaultMutableFeatureGate.AddFlag(startCmd.Flags()) startCmd.Flags().AddGoFlagSet(flag.CommandLine) } @@ -109,10 +111,11 @@ func handle() { } config := config.Config{ - NodeId: nodeID, - Endpoint: endpoint, - PruneFs: pruneFs, - PrunePath: prunePath, + NodeId: nodeID, + Endpoint: endpoint, + PruneFs: pruneFs, + PrunePath: prunePath, + KubeletConfigPath: kubeletKubeConfigPath, } if err = csi.SetupWithManager(mgr, config); err != nil {
pkg/csi/config/config.go+5 −4 modified@@ -17,8 +17,9 @@ limitations under the License. package config type Config struct { - NodeId string - Endpoint string - PruneFs []string - PrunePath string + NodeId string + Endpoint string + PruneFs []string + PrunePath string + KubeletConfigPath string }
pkg/csi/plugins/driver.go+20 −15 modified@@ -20,9 +20,11 @@ import ( "fmt" "os" "path/filepath" + "strings" + + "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/manager" - "strings" "github.com/container-storage-interface/spec/lib/go/csi" "github.com/fluid-cloudnative/fluid/pkg/utils" @@ -36,15 +38,16 @@ const ( ) type driver struct { - client client.Client - apiReader client.Reader - csiDriver *csicommon.CSIDriver - nodeId, endpoint string + client client.Client + apiReader client.Reader + nodeAuthorizedClient *kubernetes.Clientset + csiDriver *csicommon.CSIDriver + nodeId, endpoint string } var _ manager.Runnable = &driver{} -func NewDriver(nodeID, endpoint string, client client.Client, apiReader client.Reader) *driver { +func NewDriver(nodeID, endpoint string, client client.Client, apiReader client.Reader, nodeAuthorizedClient *kubernetes.Clientset) *driver { glog.Infof("Driver: %v version: %v", driverName, version) proto, addr := utils.SplitSchemaAddr(endpoint) @@ -66,11 +69,12 @@ func NewDriver(nodeID, endpoint string, client client.Client, apiReader client.R csiDriver.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER}) return &driver{ - nodeId: nodeID, - endpoint: endpoint, - csiDriver: csiDriver, - client: client, - apiReader: apiReader, + nodeId: nodeID, + endpoint: endpoint, + csiDriver: csiDriver, + client: client, + nodeAuthorizedClient: nodeAuthorizedClient, + apiReader: apiReader, } } @@ -82,10 +86,11 @@ func (d *driver) newControllerServer() *controllerServer { func (d *driver) newNodeServer() *nodeServer { return &nodeServer{ - nodeId: d.nodeId, - DefaultNodeServer: csicommon.NewDefaultNodeServer(d.csiDriver), - client: d.client, - apiReader: d.apiReader, + nodeId: d.nodeId, + DefaultNodeServer: csicommon.NewDefaultNodeServer(d.csiDriver), + client: d.client, + apiReader: d.apiReader, + nodeAuthorizedClient: d.nodeAuthorizedClient, } }
pkg/csi/plugins/nodeserver.go+58 −8 modified@@ -16,6 +16,7 @@ limitations under the License. package plugins import ( + "encoding/json" "fmt" "os" "os/exec" @@ -28,9 +29,11 @@ import ( "github.com/fluid-cloudnative/fluid/pkg/ddc/base" "github.com/fluid-cloudnative/fluid/pkg/utils" "github.com/fluid-cloudnative/fluid/pkg/utils/dataset/volume" - "github.com/fluid-cloudnative/fluid/pkg/utils/kubeclient" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" "k8s.io/utils/mount" "sigs.k8s.io/controller-runtime/pkg/client" @@ -49,10 +52,11 @@ const ( type nodeServer struct { nodeId string *csicommon.DefaultNodeServer - client client.Client - apiReader client.Reader - mutex sync.Mutex - node *v1.Node + client client.Client + apiReader client.Reader + nodeAuthorizedClient *kubernetes.Clientset + mutex sync.Mutex + node *v1.Node } func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) { @@ -247,7 +251,8 @@ func (ns *nodeServer) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstag return nil, errors.Wrapf(err, "NodeUnstageVolume: can't get node %s", ns.nodeId) } - _, err = utils.ChangeNodeLabelWithPatchMode(ns.client, node, labelsToModify) + // _, err = utils.ChangeNodeLabelWithPatchMode(ns.client, node, labelsToModify) + err = ns.patchNodeWithLabel(node, labelsToModify) if err != nil { glog.Errorf("NodeUnstageVolume: error when patching labels on node %s: %v", ns.nodeId, err) return nil, errors.Wrapf(err, "NodeUnstageVolume: error when patching labels on node %s", ns.nodeId) @@ -286,7 +291,8 @@ func (ns *nodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVol return nil, errors.Wrapf(err, "NodeStageVolume: can't get node %s", ns.nodeId) } - _, err = utils.ChangeNodeLabelWithPatchMode(ns.client, node, labelsToModify) + // _, err = utils.ChangeNodeLabelWithPatchMode(ns.client, node, labelsToModify) + err = ns.patchNodeWithLabel(node, labelsToModify) if err != nil { glog.Errorf("NodeStageVolume: error when patching labels on node %s: %v", ns.nodeId, err) return nil, errors.Wrapf(err, "NodeStageVolume: error when patching labels on node %s", ns.nodeId) @@ -344,14 +350,58 @@ func (ns *nodeServer) getNode() (node *v1.Node, err error) { } } - if node, err = kubeclient.GetNode(ns.apiReader, ns.nodeId); err != nil { + if node, err = ns.nodeAuthorizedClient.CoreV1().Nodes().Get(context.TODO(), ns.nodeId, metav1.GetOptions{}); err != nil { return nil, err } + + // if node, err = kubeclient.GetNode(ns.apiReader, ns.nodeId); err != nil { + // return nil, err + // } + glog.V(1).Infof("Got node %s from api server", node.Name) ns.node = node return ns.node, nil } +func (ns *nodeServer) patchNodeWithLabel(node *v1.Node, labelsToModify common.LabelsToModify) error { + labels := labelsToModify.GetLabels() + labelValuePair := map[string]interface{}{} + + for _, labelToModify := range labels { + operationType := labelToModify.GetOperationType() + labelToModifyKey := labelToModify.GetLabelKey() + labelToModifyValue := labelToModify.GetLabelValue() + + switch operationType { + case common.AddLabel, common.UpdateLabel: + labelValuePair[labelToModifyKey] = labelToModifyValue + case common.DeleteLabel: + labelValuePair[labelToModifyKey] = nil + default: + err := fmt.Errorf("fail to update the label due to the wrong operation: %s", operationType) + return err + } + } + + metadata := map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": labelValuePair, + }, + } + + patchByteData, err := json.Marshal(metadata) + if err != nil { + return err + } + + _, err = ns.nodeAuthorizedClient.CoreV1().Nodes().Patch(context.TODO(), node.Name, types.StrategicMergePatchType, patchByteData, metav1.PatchOptions{}) + if err != nil { + return err + } + + return nil +} + func checkMountInUse(volumeName string) (bool, error) { var inUse bool glog.Infof("Try to check if the volume %s is being used", volumeName)
pkg/csi/plugins/register.go+7 −1 modified@@ -18,12 +18,18 @@ package plugins import ( "github.com/fluid-cloudnative/fluid/pkg/csi/config" + "github.com/fluid-cloudnative/fluid/pkg/utils/kubelet" "sigs.k8s.io/controller-runtime/pkg/manager" ) // Register initializes the csi driver and registers it to the controller manager. func Register(mgr manager.Manager, cfg config.Config) error { - csiDriver := NewDriver(cfg.NodeId, cfg.Endpoint, mgr.GetClient(), mgr.GetAPIReader()) + client, err := kubelet.InitNodeAuthorizedClient(cfg.KubeletConfigPath) + if err != nil { + return err + } + + csiDriver := NewDriver(cfg.NodeId, cfg.Endpoint, mgr.GetClient(), mgr.GetAPIReader(), client) if err := mgr.Add(csiDriver); err != nil { return err
pkg/utils/kubelet/node_auth_client.go+24 −0 added@@ -0,0 +1,24 @@ +package kubelet + +import ( + "github.com/pkg/errors" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" +) + +// InitNodeAuthorizedClient initializes node authorized client with kubelet's kube config. +// This is now an available workaround to implement a node-scoped daemonset. +// See discussion https://github.com/kubernetes/enhancements/pull/944#issuecomment-490242290 +func InitNodeAuthorizedClient(kubeletKubeConfigPath string) (*kubernetes.Clientset, error) { + config, err := clientcmd.BuildConfigFromFlags("", kubeletKubeConfigPath) + if err != nil { + return nil, errors.Wrapf(err, "fail to build kubelet config") + } + + client, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, errors.Wrap(err, "fail to build client-go client from kubelet kubeconfig") + } + + return client, nil +}
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-93xx-cvmc-9w3vghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-30840ghsaADVISORY
- github.com/fluid-cloudnative/fluid/commit/77c8110a3d1ec077ae2bce6bd88d296505db1550ghsax_refsource_MISCWEB
- github.com/fluid-cloudnative/fluid/commit/91c05c32db131997b5ca065e869c9918a125c149ghsax_refsource_MISCWEB
- github.com/fluid-cloudnative/fluid/releases/tag/v0.8.6ghsax_refsource_MISCWEB
- github.com/fluid-cloudnative/fluid/security/advisories/GHSA-93xx-cvmc-9w3vghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.