Skip to content

Commit 8e4c7ac

Browse files
committed
abstracting the relevant code for node modification using a new interface
Signed-off-by: xliuqq <xlzq1992@gmail.com>
1 parent 2601658 commit 8e4c7ac

9 files changed

Lines changed: 144 additions & 45 deletions

File tree

charts/fluid/fluid/templates/csi/daemonset.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ spec:
112112
- name: fluid-src-dir
113113
mountPath: {{ .Values.runtime.mountRoot | quote }}
114114
mountPropagation: "HostToContainer"
115-
{{- if .Values.csi.useNodeAuthorization }}
115+
{{- if or (.Values.csi.useNodeAuthorization) (semverCompare "<1.30.0-0" .Capabilities.KubeVersion.Version) }}
116116
- name: kubelet-kube-config
117117
mountPath: /etc/kubernetes/kubelet.conf
118118
mountPropagation: "HostToContainer"
@@ -134,7 +134,7 @@ spec:
134134
hostPath:
135135
path: {{ .Values.csi.kubelet.rootDir | quote }}
136136
type: Directory
137-
{{- if .Values.csi.useNodeAuthorization }}
137+
{{- if or (.Values.csi.useNodeAuthorization) (semverCompare "<1.30.0-0" .Capabilities.KubeVersion.Version) }}
138138
{{- $kubeletRootDir := ternary ( .Values.csi.kubelet.rootDir ) ( print .Values.csi.kubelet.rootDir "/" ) ( hasSuffix "/" .Values.csi.kubelet.rootDir ) }}
139139
{{- if not ( hasPrefix $kubeletRootDir .Values.csi.kubelet.certDir ) }}
140140
- name: kubelet-cert-dir
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{{- if and (not .Values.csi.useNodeAuthorization) (semverCompare ">=1.30.0-0" .Capabilities.KubeVersion.Version) -}}
2+
apiVersion: admissionregistration.k8s.io/v1
3+
kind: ValidatingAdmissionPolicy
4+
metadata:
5+
name: "fluid-csi-node-policy"
6+
spec:
7+
failurePolicy: Fail
8+
matchConstraints:
9+
resourceRules:
10+
- apiGroups: [""]
11+
apiVersions: ["v1"]
12+
# supported values: "*", "CONNECT", "CREATE", "DELETE", "UPDATE"
13+
operations: ["UPDATE"]
14+
resources: ["nodes"]
15+
matchConditions:
16+
# only fluid-csi request will be checked.
17+
- name: isRestrictedUser
18+
expression: request.userInfo.username == "system:serviceaccount:fluid-system:fluid-csi"
19+
variables:
20+
- name: userNodeName
21+
expression: >-
22+
request.userInfo.extra[?'authentication.kubernetes.io/node-name'][0].orValue('')
23+
- name: objectNodeName
24+
expression: >-
25+
object.?metadata.name.orValue('')
26+
validations:
27+
- expression: "variables.userNodeName != ''"
28+
message: "userNodeName is empty, user token does not container node name."
29+
- expression: "variables.objectNodeName == variables.userNodeName"
30+
messageExpression: >-
31+
"objectNodeName '" + variables.objectNodeName + "' is not equal to userNodeName '" + variables.userNodeName + "'"
32+
{{- end }}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{{- if and (not .Values.csi.useNodeAuthorization) (semverCompare ">=1.30.0-0" .Capabilities.KubeVersion.Version) -}}
2+
apiVersion: admissionregistration.k8s.io/v1
3+
kind: ValidatingAdmissionPolicyBinding
4+
metadata:
5+
name: "fluid-csi-node-policy-binding"
6+
spec:
7+
policyName: "fluid-csi-node-policy"
8+
validationActions: [Deny]
9+
{{- end }}

charts/fluid/fluid/templates/role/csi/rbac.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ rules:
4444
- apiGroups: [""]
4545
resources: ["events"]
4646
verbs: ["create", "patch"]
47-
{{- if not .Values.csi.useNodeAuthorization }}
47+
{{- if and (not .Values.csi.useNodeAuthorization) (semverCompare ">=1.30.0-0" .Capabilities.KubeVersion.Version) }}
4848
- apiGroups: [""]
4949
resources: ["nodes"]
5050
verbs: ["get", "patch"]

charts/fluid/fluid/values.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ csi:
6161
# Whether or not to borrow kubelet's config file to use node authorization to restrict CSI Plugin's permission
6262
# See why Fluid's CSI Plugins need node-specific authorization at https://github.com/fluid-cloudnative/fluid/security/advisories/GHSA-93xx-cvmc-9w3v
6363
# See node authorization at https://kubernetes.io/docs/reference/access-authn-authz/node/
64+
# If false, use NodeBinding Token with ValidatingAdmissionPolicy instead of kubelet config for Node-Specific Restrictions.
65+
# can only be set false when k8s.version >= 1.30 and the below kubelet.kubeConfigFile is useless.
6466
useNodeAuthorization: true
6567
kubelet:
6668
kubeConfigFile: /etc/kubernetes/kubelet.conf

pkg/csi/plugins/driver.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
"path/filepath"
2424
"strings"
2525

26-
"k8s.io/client-go/kubernetes"
2726
"sigs.k8s.io/controller-runtime/pkg/client"
2827
"sigs.k8s.io/controller-runtime/pkg/manager"
2928

@@ -41,7 +40,7 @@ const (
4140
type driver struct {
4241
client client.Client
4342
apiReader client.Reader
44-
nodeAuthorizedClient *kubernetes.Clientset
43+
nodeAuthorizedClient NodeAuthorizedClient
4544
csiDriver *csicommon.CSIDriver
4645
nodeId, endpoint string
4746

@@ -50,7 +49,7 @@ type driver struct {
5049

5150
var _ manager.Runnable = &driver{}
5251

53-
func NewDriver(nodeID, endpoint string, client client.Client, apiReader client.Reader, nodeAuthorizedClient *kubernetes.Clientset, locks *utils.VolumeLocks) *driver {
52+
func NewDriver(nodeID, endpoint string, client client.Client, apiReader client.Reader, nodeAuthorizedClient NodeAuthorizedClient, locks *utils.VolumeLocks) *driver {
5453
glog.Infof("Driver: %v version: %v", driverName, version)
5554

5655
proto, addr := utils.SplitSchemaAddr(endpoint)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
Copyright 2025 The Fluid Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package plugins
18+
19+
import (
20+
"context"
21+
corev1 "k8s.io/api/core/v1"
22+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
"k8s.io/apimachinery/pkg/types"
24+
"k8s.io/client-go/kubernetes"
25+
"sigs.k8s.io/controller-runtime/pkg/client"
26+
)
27+
28+
type NodeAuthorizedClient interface {
29+
Get(nodeName string) (*corev1.Node, error)
30+
Patch(node *corev1.Node, patchType types.PatchType, data []byte) error
31+
}
32+
33+
// restrictedNodeClient uses node binding token with validating policy to avoid security problems.
34+
type restrictedNodeClient struct {
35+
Client client.Client
36+
}
37+
38+
// kubeletNodeClient uses mounted kubelet config to avoid security problems.
39+
type kubeletNodeClient struct {
40+
Clientset *kubernetes.Clientset
41+
}
42+
43+
func (p *restrictedNodeClient) Get(nodeName string) (*corev1.Node, error) {
44+
node := &corev1.Node{}
45+
key := types.NamespacedName{Name: nodeName}
46+
if err := p.Client.Get(context.TODO(), key, node); err != nil {
47+
return nil, err
48+
}
49+
return node, nil
50+
}
51+
52+
func (p *restrictedNodeClient) Patch(node *corev1.Node, patchType types.PatchType, data []byte) error {
53+
err := p.Client.Patch(context.TODO(), node, client.RawPatch(patchType, data))
54+
return err
55+
}
56+
57+
func (p *kubeletNodeClient) Get(nodeName string) (*corev1.Node, error) {
58+
return p.Clientset.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{})
59+
}
60+
61+
func (p *kubeletNodeClient) Patch(node *corev1.Node, patchType types.PatchType, data []byte) error {
62+
_, err := p.Clientset.CoreV1().Nodes().Patch(context.TODO(), node.Name, patchType, data, metav1.PatchOptions{})
63+
return err
64+
}

pkg/csi/plugins/nodeserver.go

Lines changed: 11 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,10 @@ import (
3232
"github.com/fluid-cloudnative/fluid/pkg/utils"
3333
"github.com/fluid-cloudnative/fluid/pkg/utils/cmdguard"
3434
"github.com/fluid-cloudnative/fluid/pkg/utils/dataset/volume"
35-
"github.com/fluid-cloudnative/fluid/pkg/utils/kubeclient"
3635
"github.com/pkg/errors"
3736
corev1 "k8s.io/api/core/v1"
38-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3937
"k8s.io/apimachinery/pkg/types"
4038
"k8s.io/apimachinery/pkg/util/wait"
41-
"k8s.io/client-go/kubernetes"
4239
"k8s.io/utils/mount"
4340
"sigs.k8s.io/controller-runtime/pkg/client"
4441

@@ -59,7 +56,7 @@ type nodeServer struct {
5956
*csicommon.DefaultNodeServer
6057
client client.Client
6158
apiReader client.Reader
62-
nodeAuthorizedClient *kubernetes.Clientset
59+
nodeAuthorizedClient NodeAuthorizedClient
6360
locks *utils.VolumeLocks
6461
node *corev1.Node
6562
}
@@ -474,17 +471,14 @@ func (ns *nodeServer) getNode() (node *corev1.Node, err error) {
474471
}
475472
}
476473

477-
useNodeAuthorization := ns.nodeAuthorizedClient != nil
478-
if useNodeAuthorization {
479-
if node, err = ns.nodeAuthorizedClient.CoreV1().Nodes().Get(context.TODO(), ns.nodeId, metav1.GetOptions{}); err != nil {
480-
return nil, err
481-
}
482-
} else {
483-
if node, err = kubeclient.GetNode(ns.apiReader, ns.nodeId); err != nil {
484-
return nil, err
485-
}
474+
if node, err = ns.nodeAuthorizedClient.Get(ns.nodeId); err != nil {
475+
return nil, err
486476
}
487477

478+
// if node, err = kubeclient.Get(ns.apiReader, ns.nodeId); err != nil {
479+
// return nil, err
480+
// }
481+
488482
glog.V(1).Infof("Got node %s from api server", node.Name)
489483
ns.node = node
490484
return ns.node, nil
@@ -520,22 +514,10 @@ func (ns *nodeServer) patchNodeWithLabel(node *corev1.Node, labelsToModify commo
520514
if err != nil {
521515
return err
522516
}
523-
useNodeAuthorization := ns.nodeAuthorizedClient != nil
524-
if useNodeAuthorization {
525-
_, err = ns.nodeAuthorizedClient.CoreV1().Nodes().Patch(context.TODO(), node.Name, types.StrategicMergePatchType, patchByteData, metav1.PatchOptions{})
526-
if err != nil {
527-
return err
528-
}
529-
} else {
530-
nodeToPatch := &corev1.Node{
531-
ObjectMeta: metav1.ObjectMeta{
532-
Name: node.Name,
533-
},
534-
}
535-
err = ns.client.Patch(context.TODO(), nodeToPatch, client.RawPatch(types.StrategicMergePatchType, patchByteData))
536-
if err != nil {
537-
return err
538-
}
517+
518+
err = ns.nodeAuthorizedClient.Patch(node, types.StrategicMergePatchType, patchByteData)
519+
if err != nil {
520+
return err
539521
}
540522

541523
return nil

pkg/csi/plugins/register.go

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,37 +20,35 @@ import (
2020
"os"
2121

2222
"github.com/fluid-cloudnative/fluid/pkg/csi/config"
23+
"github.com/fluid-cloudnative/fluid/pkg/utils/compatibility"
2324
"github.com/fluid-cloudnative/fluid/pkg/utils/kubelet"
2425
"github.com/golang/glog"
25-
"github.com/pkg/errors"
26-
"k8s.io/client-go/kubernetes"
2726
"sigs.k8s.io/controller-runtime/pkg/manager"
2827
)
2928

30-
// getNodeAuthorizedClientFromKubeletConfig retrieves a node-authorized Kubernetes client from the Kubelet configuration file.
31-
// This function checks if the specified Kubelet configuration file exists. If the file does not exist, it returns an empty client without an error .
29+
// isUseKubeletConfig checks if the specified Kubelet configuration file exists. If the file does not exist, it returns an empty client without an error .
3230
// If the file exists, it attempts to initialize and return a node-authorized Kubernetes client.
33-
func getNodeAuthorizedClientFromKubeletConfig(kubeletConfigPath string) (*kubernetes.Clientset, error) {
31+
func isUseKubeletConfig(kubeletConfigPath string) bool {
3432
_, err := os.Stat(kubeletConfigPath)
3533
if err != nil {
3634
if os.IsNotExist(err) {
3735
glog.Warningf("kubelet config file %s not exists, continue without node authorization...", kubeletConfigPath)
38-
return nil, nil
36+
return false
3937
}
40-
return nil, errors.Wrapf(err, "fail to stat kubelet config file %s", kubeletConfigPath)
38+
glog.Warningf("fail to stat kubelet config file %s", kubeletConfigPath)
4139
}
4240

43-
return kubelet.InitNodeAuthorizedClient(kubeletConfigPath)
41+
return true
4442
}
4543

4644
// Register initializes the csi driver and registers it to the controller manager.
4745
func Register(mgr manager.Manager, ctx config.RunningContext) error {
48-
client, err := getNodeAuthorizedClientFromKubeletConfig(ctx.KubeletConfigPath)
46+
nodeAuthClient, err := getNodeAuthClient(mgr, ctx)
4947
if err != nil {
5048
return err
5149
}
5250

53-
csiDriver := NewDriver(ctx.NodeId, ctx.Endpoint, mgr.GetClient(), mgr.GetAPIReader(), client, ctx.VolumeLocks)
51+
csiDriver := NewDriver(ctx.NodeId, ctx.Endpoint, mgr.GetClient(), mgr.GetAPIReader(), nodeAuthClient, ctx.VolumeLocks)
5452

5553
if err := mgr.Add(csiDriver); err != nil {
5654
return err
@@ -59,6 +57,19 @@ func Register(mgr manager.Manager, ctx config.RunningContext) error {
5957
return nil
6058
}
6159

60+
func getNodeAuthClient(mgr manager.Manager, ctx config.RunningContext) (NodeAuthorizedClient, error) {
61+
// use and support node binding token
62+
if !isUseKubeletConfig(ctx.KubeletConfigPath) && compatibility.IsNodeBindingTokenSupported() {
63+
return &restrictedNodeClient{mgr.GetClient()}, nil
64+
}
65+
// otherwise, use kubelet config
66+
nodeAuthClient, err := kubelet.InitNodeAuthorizedClient(ctx.KubeletConfigPath)
67+
if err != nil {
68+
return nil, err
69+
}
70+
return &kubeletNodeClient{nodeAuthClient}, nil
71+
}
72+
6273
// Enabled checks if the csi driver should be enabled.
6374
func Enabled() bool {
6475
return true

0 commit comments

Comments
 (0)