From dec96df2c962d82d3d8105472d7e4ed717918b1f Mon Sep 17 00:00:00 2001 From: andyzhangx Date: Fri, 27 Dec 2024 09:43:01 +0000 Subject: [PATCH] feat: support windows host-process deployment fix fix fix fix fix fix fix --- Makefile | 27 ++ charts/README.md | 1 + charts/latest/csi-driver-smb-v0.0.0.tgz | Bin 5265 -> 5645 bytes .../csi-smb-node-windows-hostprocess.yaml | 124 +++++++++ .../templates/csi-smb-node-windows.yaml | 2 +- charts/latest/csi-driver-smb/values.yaml | 3 +- cmd/smbplugin/Dockerfile.WindowsHostProcess | 24 ++ cmd/smbplugin/main.go | 2 + deploy/csi-smb-node-windows-hostprocess.yaml | 95 +++++++ deploy/install-driver.sh | 13 +- hack/release-image.sh | 3 +- .../safe_mounter_host_process_windows.go | 247 ++++++++++++++++++ pkg/mounter/safe_mounter_unix.go | 2 +- pkg/mounter/safe_mounter_unix_test.go | 2 +- pkg/mounter/safe_mounter_windows.go | 11 +- pkg/os/filesystem/filesystem.go | 166 ++++++++++++ pkg/os/smb/smb.go | 113 ++++++++ pkg/os/smb/smb_test.go | 59 +++++ pkg/smb/fake_mounter.go | 2 +- pkg/smb/smb.go | 5 +- pkg/util/util.go | 19 ++ 21 files changed, 910 insertions(+), 10 deletions(-) create mode 100644 charts/latest/csi-driver-smb/templates/csi-smb-node-windows-hostprocess.yaml create mode 100644 cmd/smbplugin/Dockerfile.WindowsHostProcess create mode 100644 deploy/csi-smb-node-windows-hostprocess.yaml create mode 100644 pkg/mounter/safe_mounter_host_process_windows.go create mode 100644 pkg/os/filesystem/filesystem.go create mode 100644 pkg/os/smb/smb.go create mode 100644 pkg/os/smb/smb_test.go diff --git a/Makefile b/Makefile index 36da33ee4c3..da85cf4dedf 100644 --- a/Makefile +++ b/Makefile @@ -102,11 +102,16 @@ e2e-test: .PHONY: e2e-bootstrap e2e-bootstrap: install-helm +ifdef WINDOWS_USE_HOST_PROCESS_CONTAINERS + (docker pull $(IMAGE_TAG) && docker pull $(IMAGE_TAG)-windows-hp) || make container-all push-manifest +else docker pull $(IMAGE_TAG) || make container-all push-manifest +endif ifdef TEST_WINDOWS helm upgrade csi-driver-smb charts/$(VERSION)/csi-driver-smb --namespace kube-system --wait --timeout=15m -v=5 --debug --install \ ${E2E_HELM_OPTIONS} \ --set windows.enabled=true \ + --set windows.useHostProcessContainers=${WINDOWS_USE_HOST_PROCESS_CONTAINERS} \ --set linux.enabled=false \ --set controller.replicas=1 \ --set controller.logLevel=6 \ @@ -162,6 +167,24 @@ container-windows: -t $(IMAGE_TAG)-windows-$(OSVERSION)-$(ARCH) --build-arg OSVERSION=$(OSVERSION) \ --provenance=false --sbom=false \ --build-arg ARCH=$(ARCH) -f ./cmd/smbplugin/Dockerfile.Windows . +# workaround: only build hostprocess image once +ifdef WINDOWS_USE_HOST_PROCESS_CONTAINERS +ifeq ($(OSVERSION),ltsc2022) + $(MAKE) container-windows-hostprocess + $(MAKE) container-windows-hostprocess-latest +endif +endif + +# Set --provenance=false to not generate the provenance (which is what causes the multi-platform index to be generated, even for a single platform). +.PHONY: container-windows-hostprocess +container-windows-hostprocess: + docker buildx build --pull --output=type=$(OUTPUT_TYPE) --platform="windows/$(ARCH)" --provenance=false --sbom=false \ + -t $(IMAGE_TAG)-windows-hp -f ./cmd/smbplugin/Dockerfile.WindowsHostProcess . + +.PHONY: container-windows-hostprocess-latest +container-windows-hostprocess-latest: + docker buildx build --pull --output=type=$(OUTPUT_TYPE) --platform="windows/$(ARCH)" --provenance=false --sbom=false \ + -t $(IMAGE_TAG_LATEST)-windows-hp -f ./cmd/smbplugin/Dockerfile.WindowsHostProcess . .PHONY: container-all container-all: smb-windows @@ -206,14 +229,18 @@ ifdef PUBLISH done; \ done docker manifest inspect $(IMAGE_TAG_LATEST) + docker manifest create --amend $(IMAGE_TAG_LATEST)-windows-hp $(IMAGE_TAG_LATEST)-windows-hp + docker manifest inspect $(IMAGE_TAG_LATEST)-windows-hp endif .PHONY: push-latest push-latest: ifdef CI docker manifest push --purge $(IMAGE_TAG_LATEST) + docker manifest push --purge $(IMAGE_TAG_LATEST)-windows-hp else docker push $(IMAGE_TAG_LATEST) + docker push $(IMAGE_TAG_LATEST)-windows-hp endif .PHONY: install-smb-provisioner diff --git a/charts/README.md b/charts/README.md index c2eeb04ad38..c293bcae3c0 100644 --- a/charts/README.md +++ b/charts/README.md @@ -112,6 +112,7 @@ The following table lists the configurable parameters of the latest SMB CSI Driv | `linux.resources.smb.requests.cpu` | smb-csi-driver cpu requests limits | `10m` | | `linux.resources.smb.requests.memory` | smb-csi-driver memory requests limits | `20Mi` | | `windows.enabled` | whether enable windows feature | `false` | +| `windows.useHostProcessContainers` | whether deploy driver daemonset with HostProcess containers on windows | `true` | | `windows.dsName` | name of driver daemonset on windows | `csi-smb-node-win` | | `windows.removeSMBMappingDuringUnmount` | remove SMBMapping during unmount on Windows node windows | `true` | | `windows.resources.livenessProbe.limits.memory` | liveness-probe memory limits | `200Mi` | diff --git a/charts/latest/csi-driver-smb-v0.0.0.tgz b/charts/latest/csi-driver-smb-v0.0.0.tgz index e310443510541022dfa203a09c444b6ccdd6a180..9f027384978c472272102c65cd49c39e130b6a4a 100644 GIT binary patch delta 5614 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 IO#rR{0GFN+*Z=?k delta 5231 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 diff --git a/charts/latest/csi-driver-smb/templates/csi-smb-node-windows-hostprocess.yaml b/charts/latest/csi-driver-smb/templates/csi-smb-node-windows-hostprocess.yaml new file mode 100644 index 00000000000..e342c5afec1 --- /dev/null +++ b/charts/latest/csi-driver-smb/templates/csi-smb-node-windows-hostprocess.yaml @@ -0,0 +1,124 @@ +{{- if and .Values.windows.enabled .Values.windows.useHostProcessContainers }} +kind: DaemonSet +apiVersion: apps/v1 +metadata: + name: {{ .Values.windows.dsName }} + namespace: {{ .Release.Namespace }} +{{ include "smb.labels" . | indent 2 }} +spec: + updateStrategy: + rollingUpdate: + maxUnavailable: {{ .Values.node.maxUnavailable }} + type: RollingUpdate + selector: + matchLabels: + app: {{ .Values.windows.dsName }} + template: + metadata: +{{ include "smb.labels" . | indent 6 }} + app: {{ .Values.windows.dsName }} + spec: +{{- with .Values.windows.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} +{{- end }} + nodeSelector: + kubernetes.io/os: windows +{{- with .Values.node.nodeSelector }} +{{ toYaml . | indent 8 }} +{{- end }} +{{- with .Values.node.affinity }} + affinity: +{{ toYaml . | indent 8 }} +{{- end }} + priorityClassName: {{ .Values.priorityClassName | quote }} + {{- if .Values.securityContext }} + securityContext: {{- toYaml .Values.securityContext | nindent 8 }} + {{- end }} + serviceAccountName: {{ .Values.serviceAccount.node }} + {{- include "smb.pullSecrets" . | indent 6 }} + securityContext: + seccompProfile: + type: RuntimeDefault + windowsOptions: + hostProcess: true + runAsUserName: "NT AUTHORITY\\SYSTEM" + hostNetwork: true + initContainers: + - name: init +{{- if hasPrefix "/" .Values.image.smb.repository }} + image: "{{ .Values.image.baseRepo }}{{ .Values.image.smb.repository }}:{{ .Values.image.smb.tag }}-windows-hp" +{{- else }} + image: "{{ .Values.image.smb.repository }}:{{ .Values.image.smb.tag }}-windows-hp" +{{- end }} + imagePullPolicy: {{ .Values.image.smb.pullPolicy }} + command: + - "powershell.exe" + - "-c" + - "New-Item -ItemType Directory -Path C:\\var\\lib\\kubelet\\plugins\\{{ .Values.driver.name }}\\ -Force" + securityContext: + capabilities: + drop: + - ALL + containers: + - name: node-driver-registrar +{{- if hasPrefix "/" .Values.image.nodeDriverRegistrar.repository }} + image: "{{ .Values.image.baseRepo }}{{ .Values.image.nodeDriverRegistrar.repository }}:{{ .Values.image.nodeDriverRegistrar.tag }}" +{{- else }} + image: "{{ .Values.image.nodeDriverRegistrar.repository }}:{{ .Values.image.nodeDriverRegistrar.tag }}" +{{- end }} + command: + - "csi-node-driver-registrar.exe" + args: + - "--csi-address=$(CSI_ENDPOINT)" + - "--kubelet-registration-path=$(DRIVER_REG_SOCK_PATH)" + - "--plugin-registration-path=$(PLUGIN_REG_DIR)" + - "--v=2" + env: + - name: CSI_ENDPOINT + value: unix://{{ .Values.windows.kubelet }}\plugins\{{ .Values.driver.name }}\csi.sock + - name: DRIVER_REG_SOCK_PATH + value: C:\\var\\lib\\kubelet\\plugins\\{{ .Values.driver.name }}\\csi.sock + - name: PLUGIN_REG_DIR + value: C:\\var\\lib\\kubelet\\plugins_registry\\ + - name: KUBE_NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + imagePullPolicy: {{ .Values.image.nodeDriverRegistrar.pullPolicy }} + resources: {{- toYaml .Values.windows.resources.nodeDriverRegistrar | nindent 12 }} + securityContext: + capabilities: + drop: + - ALL + - name: smb +{{- if hasPrefix "/" .Values.image.smb.repository }} + image: "{{ .Values.image.baseRepo }}{{ .Values.image.smb.repository }}:{{ .Values.image.smb.tag }}-windows-hp" +{{- else }} + image: "{{ .Values.image.smb.repository }}:{{ .Values.image.smb.tag }}-windows-hp" +{{- end }} + imagePullPolicy: {{ .Values.image.smb.pullPolicy }} + command: + - "smbplugin.exe" + args: + - "--v={{ .Values.node.logLevel }}" + - "--drivername={{ .Values.driver.name }}" + - --endpoint=$(CSI_ENDPOINT) + - --nodeid=$(KUBE_NODE_NAME) + - "--enable-get-volume-stats={{ .Values.feature.enableGetVolumeStats }}" + - "--remove-smb-mapping-during-unmount={{ .Values.windows.removeSMBMappingDuringUnmount }}" + - "--enable-windows-host-process=true" + env: + - name: CSI_ENDPOINT + value: unix://{{ .Values.windows.kubelet }}\plugins\{{ .Values.driver.name }}\csi.sock + - name: KUBE_NODE_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: spec.nodeName + resources: {{- toYaml .Values.windows.resources.smb | nindent 12 }} + securityContext: + capabilities: + drop: + - ALL +{{- end -}} diff --git a/charts/latest/csi-driver-smb/templates/csi-smb-node-windows.yaml b/charts/latest/csi-driver-smb/templates/csi-smb-node-windows.yaml index fafb2fa5f36..8633a42c98d 100755 --- a/charts/latest/csi-driver-smb/templates/csi-smb-node-windows.yaml +++ b/charts/latest/csi-driver-smb/templates/csi-smb-node-windows.yaml @@ -1,4 +1,4 @@ -{{- if .Values.windows.enabled}} +{{- if and .Values.windows.enabled (not .Values.windows.useHostProcessContainers) }} kind: DaemonSet apiVersion: apps/v1 metadata: diff --git a/charts/latest/csi-driver-smb/values.yaml b/charts/latest/csi-driver-smb/values.yaml index aa828a0acb5..a2136e251cd 100755 --- a/charts/latest/csi-driver-smb/values.yaml +++ b/charts/latest/csi-driver-smb/values.yaml @@ -131,6 +131,7 @@ linux: windows: enabled: false # Unless you already had csi proxy installed, windows.csiproxy.enabled=true is required + useHostProcessContainers: false dsName: csi-smb-node-win # daemonset name kubelet: 'C:\var\lib\kubelet' removeSMBMappingDuringUnmount: true @@ -153,7 +154,7 @@ windows: memory: 40Mi smb: limits: - memory: 200Mi + memory: 600Mi requests: cpu: 10m memory: 40Mi diff --git a/cmd/smbplugin/Dockerfile.WindowsHostProcess b/cmd/smbplugin/Dockerfile.WindowsHostProcess new file mode 100644 index 00000000000..d8131b7dc66 --- /dev/null +++ b/cmd/smbplugin/Dockerfile.WindowsHostProcess @@ -0,0 +1,24 @@ +# Copyright 2022 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# these arguments come from BUILD_PLATFORMS used in release-tools +FROM mcr.microsoft.com/oss/kubernetes/windows-host-process-containers-base-image:v1.0.0 +LABEL description="CSI SMB plugin" + +ARG ARCH=amd64 +ARG binary=./_output/${ARCH}/smbplugin.exe +COPY ${binary} /smbplugin.exe +ENV PATH="C:\Windows\system32;C:\Windows;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;" +USER ContainerAdministrator +ENTRYPOINT ["/smbplugin.exe"] diff --git a/cmd/smbplugin/main.go b/cmd/smbplugin/main.go index efa04e0756f..723bf2c4b18 100644 --- a/cmd/smbplugin/main.go +++ b/cmd/smbplugin/main.go @@ -49,6 +49,7 @@ var ( krb5Prefix = flag.String("krb5-prefix", smb.DefaultKrb5CCName, "The prefix for kerberos cache") defaultOnDeletePolicy = flag.String("default-ondelete-policy", "", "default policy for deleting subdirectory when deleting a volume") removeArchivedVolumePath = flag.Bool("remove-archived-volume-path", true, "remove archived volume path in DeleteVolume") + enableWindowsHostProcess = flag.Bool("enable-windows-host-process", false, "enable windows host process") ) // exit is a separate function to handle program termination @@ -87,6 +88,7 @@ func handle() { Krb5CacheDirectory: *krb5CacheDirectory, Krb5Prefix: *krb5Prefix, DefaultOnDeletePolicy: *defaultOnDeletePolicy, + EnableWindowsHostProcess: *enableWindowsHostProcess, } driver := smb.NewDriver(&driverOptions) driver.Run(*endpoint, *kubeconfig, false) diff --git a/deploy/csi-smb-node-windows-hostprocess.yaml b/deploy/csi-smb-node-windows-hostprocess.yaml new file mode 100644 index 00000000000..64f03a0e59e --- /dev/null +++ b/deploy/csi-smb-node-windows-hostprocess.yaml @@ -0,0 +1,95 @@ +--- +kind: DaemonSet +apiVersion: apps/v1 +metadata: + name: csi-smb-node-win + namespace: kube-system +spec: + updateStrategy: + rollingUpdate: + maxUnavailable: 1 + type: RollingUpdate + selector: + matchLabels: + app: csi-smb-node-win + template: + metadata: + labels: + app: csi-smb-node-win + spec: + serviceAccountName: csi-smb-node-sa + tolerations: + - key: "node.kubernetes.io/os" + operator: "Exists" + effect: "NoSchedule" + nodeSelector: + kubernetes.io/os: windows + priorityClassName: system-node-critical + securityContext: + seccompProfile: + type: RuntimeDefault + windowsOptions: + hostProcess: true + runAsUserName: "NT AUTHORITY\\SYSTEM" + hostNetwork: true + initContainers: + - name: init + image: mcr.microsoft.com/k8s/csi/smb-csi:latest-windows-hp + imagePullPolicy: IfNotPresent + command: + - "powershell.exe" + - "-c" + - "New-Item -ItemType Directory -Path C:\\var\\lib\\kubelet\\plugins\\smb.csi.k8s.io\\ -Force" + containers: + - name: node-driver-registrar + image: mcr.microsoft.com/oss/kubernetes-csi/csi-node-driver-registrar:v2.10.0 + imagePullPolicy: IfNotPresent + command: + - "csi-node-driver-registrar.exe" + args: + - "--v=2" + - "--csi-address=$(CSI_ENDPOINT)" + - "--kubelet-registration-path=$(DRIVER_REG_SOCK_PATH)" + - "--plugin-registration-path=$(PLUGIN_REG_DIR)" + env: + - name: CSI_ENDPOINT + value: unix://C:\\var\\lib\\kubelet\\plugins\\smb.csi.k8s.io\\csi.sock + - name: DRIVER_REG_SOCK_PATH + value: C:\\var\\lib\\kubelet\\plugins\\smb.csi.k8s.io\\csi.sock + - name: PLUGIN_REG_DIR + value: C:\\var\\lib\\kubelet\\plugins_registry\\ + - name: KUBE_NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + resources: + limits: + memory: 150Mi + requests: + cpu: 30m + memory: 40Mi + - name: smb + image: mcr.microsoft.com/k8s/csi/smb-csi:latest-windows-hp + imagePullPolicy: IfNotPresent + command: + - "smbplugin.exe" + args: + - --v=5 + - --endpoint=$(CSI_ENDPOINT) + - --nodeid=$(KUBE_NODE_NAME) + - --enable-windows-host-process=true + - --remove-smb-mapping-during-unmount=true + env: + - name: CSI_ENDPOINT + value: unix://C:\\var\\lib\\kubelet\\plugins\\smb.csi.k8s.io\\csi.sock + - name: KUBE_NODE_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: spec.nodeName + resources: + limits: + memory: 600Mi + requests: + cpu: 10m + memory: 40Mi diff --git a/deploy/install-driver.sh b/deploy/install-driver.sh index 73d85291f32..62737d751b8 100755 --- a/deploy/install-driver.sh +++ b/deploy/install-driver.sh @@ -22,11 +22,16 @@ if [[ "$#" -gt 0 ]]; then fi repo="https://raw.githubusercontent.com/kubernetes-csi/csi-driver-smb/$ver/deploy" + +windowsMode="csi-proxy" if [[ "$#" -gt 1 ]]; then if [[ "$2" == *"local"* ]]; then echo "use local deploy" repo="./deploy" fi + if [[ "$2" == *"hostprocess"* ]]; then + windowsMode="hostprocess" + fi fi if [ $ver != "master" ]; then @@ -38,5 +43,11 @@ kubectl apply -f $repo/rbac-csi-smb.yaml kubectl apply -f $repo/csi-smb-driver.yaml kubectl apply -f $repo/csi-smb-controller.yaml kubectl apply -f $repo/csi-smb-node.yaml -kubectl apply -f $repo/csi-smb-node-windows.yaml +if [[ "$windowsMode" == *"hostprocess"* ]]; then + echo "deploy windows driver with hostprocess mode..." + kubectl apply -f $repo/csi-smb-node-windows-hostprocess.yaml +else + echo "deploy windows driver with csi-proxy mode ..." + kubectl apply -f $repo/csi-smb-node-windows.yaml +fi echo 'SMB CSI driver installed successfully.' diff --git a/hack/release-image.sh b/hack/release-image.sh index 949f7518fd6..445ad3bb1ba 100755 --- a/hack/release-image.sh +++ b/hack/release-image.sh @@ -26,9 +26,10 @@ export REGISTRY=$REGISTRY_NAME.azurecr.io export IMAGENAME=public/k8s/csi/smb-csi export CI=1 export PUBLISH=1 +export WINDOWS_USE_HOST_PROCESS_CONTAINERS=true az acr login --name $REGISTRY_NAME -make container-all push-manifest push-latest +make container-all container-windows-hostprocess-latest push-manifest push-latest echo "sleep 60s ..." sleep 60 diff --git a/pkg/mounter/safe_mounter_host_process_windows.go b/pkg/mounter/safe_mounter_host_process_windows.go new file mode 100644 index 00000000000..32331238ae5 --- /dev/null +++ b/pkg/mounter/safe_mounter_host_process_windows.go @@ -0,0 +1,247 @@ +//go:build windows +// +build windows + +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mounter + +import ( + "context" + "fmt" + "os" + filepath "path/filepath" + "strings" + + "k8s.io/klog/v2" + mount "k8s.io/mount-utils" + + "github.com/kubernetes-csi/csi-driver-smb/pkg/os/filesystem" + "github.com/kubernetes-csi/csi-driver-smb/pkg/os/smb" +) + +var driverGlobalMountPath = "C:\\var\\lib\\kubelet\\plugins\\kubernetes.io\\csi\\file.csi.azure.com" + +var _ CSIProxyMounter = &winMounter{} + +type winMounter struct{} + +func NewWinMounter() *winMounter { + return &winMounter{} +} + +func (mounter *winMounter) SMBMount(source, target, fsType string, mountOptions, sensitiveMountOptions []string, _ string) error { + klog.V(2).Infof("SMBMount: remote path: %s local path: %s", source, target) + + if len(mountOptions) == 0 || len(sensitiveMountOptions) == 0 { + return fmt.Errorf("empty mountOptions(len: %d) or sensitiveMountOptions(len: %d) is not allowed", len(mountOptions), len(sensitiveMountOptions)) + } + + parentDir := filepath.Dir(target) + parentExists, err := mounter.ExistsPath(parentDir) + if err != nil { + return fmt.Errorf("parent dir: %s exist check failed with err: %v", parentDir, err) + } + + if !parentExists { + klog.V(2).Infof("Parent directory %s does not exists. Creating the directory", parentDir) + if err := mounter.MakeDir(parentDir); err != nil { + return fmt.Errorf("create of parent dir: %s dailed with error: %v", parentDir, err) + } + } + + source = strings.Replace(source, "/", "\\", -1) + normalizedTarget := normalizeWindowsPath(target) + + klog.V(2).Infof("begin to mount %s on %s", source, normalizedTarget) + + remotePath := source + localPath := normalizedTarget + + if remotePath == "" { + return fmt.Errorf("remote path is empty") + } + + isMapped, err := smb.IsSmbMapped(remotePath) + if err != nil { + isMapped = false + } + + if isMapped { + valid, err := filesystem.PathValid(context.Background(), remotePath) + if err != nil { + klog.Warningf("PathValid(%s) failed with %v, ignore error", remotePath, err) + } + + if !valid { + klog.Warningf("RemotePath %s is not valid, removing now", remotePath) + if err := smb.RemoveSmbGlobalMapping(remotePath); err != nil { + klog.Errorf("RemoveSmbGlobalMapping(%s) failed with %v", remotePath, err) + return err + } + isMapped = false + } + } + + if !isMapped { + klog.V(2).Infof("Remote %s not mapped. Mapping now!", remotePath) + username := mountOptions[0] + password := sensitiveMountOptions[0] + if err := smb.NewSmbGlobalMapping(remotePath, username, password); err != nil { + klog.Errorf("NewSmbGlobalMapping(%s) failed with %v", remotePath, err) + return err + } + } + + if len(localPath) != 0 { + if err := filesystem.ValidatePathWindows(localPath); err != nil { + return err + } + if err := os.Symlink(remotePath, localPath); err != nil { + return fmt.Errorf("os.Symlink(%s, %s) failed with %v", remotePath, localPath, err) + } + } + klog.V(2).Infof("mount %s on %s successfully", source, normalizedTarget) + return nil +} + +// Mount just creates a soft link at target pointing to source. +func (mounter *winMounter) Mount(source, target, fstype string, options []string) error { + return os.Symlink(normalizeWindowsPath(source), normalizeWindowsPath(target)) +} + +// Rmdir - delete the given directory +func (mounter *winMounter) Rmdir(path string) error { + return filesystem.Rmdir(normalizeWindowsPath(path), true) +} + +// Unmount - Removes the directory - equivalent to unmount on Linux. +func (mounter *winMounter) Unmount(target string) error { + klog.V(4).Infof("Unmount: %s", target) + return mounter.Rmdir(target) +} + +// Unmount - Removes the directory - equivalent to unmount on Linux. +func (mounter *winMounter) SMBUnmount(target, _ string) error { + target = normalizeWindowsPath(target) + remoteServer, err := smb.GetRemoteServerFromTarget(target) + if err == nil { + klog.V(2).Infof("remote server path: %s, local path: %s", remoteServer, target) + if hasDupSMBMount, err := smb.CheckForDuplicateSMBMounts(driverGlobalMountPath, target, remoteServer); err == nil { + if !hasDupSMBMount { + if err := smb.RemoveSmbGlobalMapping(remoteServer); err != nil { + klog.Errorf("RemoveSmbGlobalMapping(%s) failed with %v", target, err) + } + } else { + klog.V(2).Infof("skip unmount as there are other SMB mounts on the same remote server %s", remoteServer) + } + } else { + klog.Errorf("CheckForDuplicateSMBMounts(%s, %s) failed with %v", target, remoteServer, err) + } + } else { + klog.Errorf("GetRemoteServerFromTarget(%s) failed with %v", target, err) + } + + klog.V(2).Infof("Unmount: remote path: %s local path: %s", remoteServer, target) + return mounter.Rmdir(target) +} + +func (mounter *winMounter) List() ([]mount.MountPoint, error) { + return []mount.MountPoint{}, fmt.Errorf("List not implemented for CSIProxyMounter") +} + +func (mounter *winMounter) IsMountPoint(file string) (bool, error) { + isNotMnt, err := mounter.IsLikelyNotMountPoint(file) + if err != nil { + return false, err + } + return !isNotMnt, nil +} + +func (mounter *winMounter) IsMountPointMatch(mp mount.MountPoint, dir string) bool { + return mp.Path == dir +} + +// IsLikelyMountPoint - If the directory does not exists, the function will return os.ErrNotExist error. +// If the path exists, will check if its a link, if its a link then existence of target path is checked. +func (mounter *winMounter) IsLikelyNotMountPoint(path string) (bool, error) { + isExists, err := mounter.ExistsPath(path) + if err != nil { + return false, err + } + if !isExists { + return true, os.ErrNotExist + } + + response, err := filesystem.IsMountPoint(normalizeWindowsPath(path)) + if err != nil { + return false, err + } + return !response, nil +} + +// MakeDir - Creates a directory. +// Currently the make dir is only used from the staging code path, hence we call it +// with Plugin context.. +func (mounter *winMounter) MakeDir(path string) error { + return os.MkdirAll(normalizeWindowsPath(path), 0755) +} + +// ExistsPath - Checks if a path exists. Unlike util ExistsPath, this call does not perform follow link. +func (mounter *winMounter) ExistsPath(path string) (bool, error) { + return filesystem.PathExists(normalizeWindowsPath(path)) +} + +func (mounter *winMounter) MountSensitive(source string, target string, fstype string, options []string, sensitiveOptions []string) error { + return fmt.Errorf("MountSensitive not implemented for winMounter") +} + +func (mounter *winMounter) MountSensitiveWithoutSystemd(source string, target string, fstype string, options []string, sensitiveOptions []string) error { + return fmt.Errorf("MountSensitiveWithoutSystemd not implemented for winMounter") +} + +func (mounter *winMounter) MountSensitiveWithoutSystemdWithMountFlags(source string, target string, fstype string, options []string, sensitiveOptions []string, mountFlags []string) error { + return mounter.MountSensitive(source, target, fstype, options, sensitiveOptions /* sensitiveOptions */) +} + +func (mounter *winMounter) GetMountRefs(pathname string) ([]string, error) { + return []string{}, fmt.Errorf("GetMountRefs not implemented for winMounter") +} + +func (mounter *winMounter) EvalHostSymlinks(pathname string) (string, error) { + return "", fmt.Errorf("EvalHostSymlinks not implemented for winMounter") +} + +func (mounter *winMounter) GetFSGroup(pathname string) (int64, error) { + return -1, fmt.Errorf("GetFSGroup not implemented for winMounter") +} + +func (mounter *winMounter) GetSELinuxSupport(pathname string) (bool, error) { + return false, fmt.Errorf("GetSELinuxSupport not implemented for winMounter") +} + +func (mounter *winMounter) GetMode(pathname string) (os.FileMode, error) { + return 0, fmt.Errorf("GetMode not implemented for winMounter") +} + +// GetAPIVersions returns the versions of the client APIs this mounter is using. +func (mounter *winMounter) GetAPIVersions() string { + return "" +} + +func (mounter *winMounter) CanSafelySkipMountPointCheck() bool { + return false +} diff --git a/pkg/mounter/safe_mounter_unix.go b/pkg/mounter/safe_mounter_unix.go index 36a386d8069..efbf6f64e51 100644 --- a/pkg/mounter/safe_mounter_unix.go +++ b/pkg/mounter/safe_mounter_unix.go @@ -24,7 +24,7 @@ import ( utilexec "k8s.io/utils/exec" ) -func NewSafeMounter(_ bool) (*mount.SafeFormatAndMount, error) { +func NewSafeMounter(_, _ bool) (*mount.SafeFormatAndMount, error) { return &mount.SafeFormatAndMount{ Interface: mount.New(""), Exec: utilexec.New(), diff --git a/pkg/mounter/safe_mounter_unix_test.go b/pkg/mounter/safe_mounter_unix_test.go index 7991c4736b9..e620101f63c 100644 --- a/pkg/mounter/safe_mounter_unix_test.go +++ b/pkg/mounter/safe_mounter_unix_test.go @@ -23,7 +23,7 @@ import ( ) func TestNewSafeMounter(t *testing.T) { - resp, err := NewSafeMounter(true) + resp, err := NewSafeMounter(true, true) assert.NotNil(t, resp) assert.Nil(t, err) } diff --git a/pkg/mounter/safe_mounter_windows.go b/pkg/mounter/safe_mounter_windows.go index 52f0da75562..57e7694a188 100644 --- a/pkg/mounter/safe_mounter_windows.go +++ b/pkg/mounter/safe_mounter_windows.go @@ -131,7 +131,7 @@ func (mounter *csiProxyMounter) SMBMount(source, target, fsType string, mountOpt return nil } -func (mounter *csiProxyMounter) SMBUnmount(target string, volumeID string) error { +func (mounter *csiProxyMounter) SMBUnmount(target, volumeID string) error { klog.V(4).Infof("SMBUnmount: local path: %s", target) if remotePath, err := os.Readlink(target); err != nil { @@ -369,7 +369,14 @@ func NewCSIProxyMounter(removeSMBMappingDuringUnmount bool) (*csiProxyMounter, e }, nil } -func NewSafeMounter(removeSMBMappingDuringUnmount bool) (*mount.SafeFormatAndMount, error) { +func NewSafeMounter(enableWindowsHostProcess, removeSMBMappingDuringUnmount bool) (*mount.SafeFormatAndMount, error) { + if enableWindowsHostProcess { + klog.V(2).Infof("using windows host process mounter") + return &mount.SafeFormatAndMount{ + Interface: NewWinMounter(), + Exec: utilexec.New(), + }, nil + } csiProxyMounter, err := NewCSIProxyMounter(removeSMBMappingDuringUnmount) if err == nil { klog.V(2).Infof("using CSIProxyMounterV1, %s", csiProxyMounter.GetAPIVersions()) diff --git a/pkg/os/filesystem/filesystem.go b/pkg/os/filesystem/filesystem.go new file mode 100644 index 00000000000..47de07d8c61 --- /dev/null +++ b/pkg/os/filesystem/filesystem.go @@ -0,0 +1,166 @@ +//go:build windows +// +build windows + +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package filesystem + +import ( + "context" + "fmt" + "os" + "regexp" + "strings" + + "github.com/kubernetes-csi/csi-driver-smb/pkg/util" + "k8s.io/klog/v2" +) + +var invalidPathCharsRegexWindows = regexp.MustCompile(`["/\:\?\*|]`) +var absPathRegexWindows = regexp.MustCompile(`^[a-zA-Z]:\\`) + +func containsInvalidCharactersWindows(path string) bool { + if isAbsWindows(path) { + path = path[3:] + } + if invalidPathCharsRegexWindows.MatchString(path) { + return true + } + if strings.Contains(path, `..`) { + return true + } + return false +} + +func isUNCPathWindows(path string) bool { + // check for UNC/pipe prefixes like "\\" + if len(path) < 2 { + return false + } + if path[0] == '\\' && path[1] == '\\' { + return true + } + return false +} + +func isAbsWindows(path string) bool { + // for Windows check for C:\\.. prefix only + // UNC prefixes of the form \\ are not considered + return absPathRegexWindows.MatchString(path) +} + +func pathExists(path string) (bool, error) { + _, err := os.Lstat(path) + if err == nil { + return true, nil + } + if os.IsNotExist(err) { + return false, nil + } + return false, err +} + +// PathExists checks if the given path exists on the host. +func PathExists(path string) (bool, error) { + if err := ValidatePathWindows(path); err != nil { + klog.Errorf("failed validatePathWindows %v", err) + return false, err + } + return pathExists(path) +} + +func PathValid(_ context.Context, path string) (bool, error) { + cmd := `Test-Path $Env:remotepath` + cmdEnv := fmt.Sprintf("remotepath=%s", path) + output, err := util.RunPowershellCmd(cmd, cmdEnv) + if err != nil { + return false, fmt.Errorf("returned output: %s, error: %v", string(output), err) + } + + return strings.HasPrefix(strings.ToLower(string(output)), "true"), nil +} + +func ValidatePathWindows(path string) error { + pathlen := len(path) + + if pathlen > util.MaxPathLengthWindows { + return fmt.Errorf("path length %d exceeds maximum characters: %d", pathlen, util.MaxPathLengthWindows) + } + + if pathlen > 0 && (path[0] == '\\') { + return fmt.Errorf("invalid character \\ at beginning of path: %s", path) + } + + if isUNCPathWindows(path) { + return fmt.Errorf("unsupported UNC path prefix: %s", path) + } + + if containsInvalidCharactersWindows(path) { + return fmt.Errorf("path contains invalid characters: %s", path) + } + + if !isAbsWindows(path) { + return fmt.Errorf("not an absolute Windows path: %s", path) + } + + return nil +} + +func Rmdir(path string, force bool) error { + if err := ValidatePathWindows(path); err != nil { + return err + } + + if force { + return os.RemoveAll(path) + } + return os.Remove(path) +} + +func IsMountPoint(path string) (bool, error) { + return IsSymlink(path) +} + +// IsSymlink - returns true if tgt is a mount point. +// A path is considered a mount point if: +// - directory exists and +// - it is a soft link and +// - the target path of the link exists. +// If tgt path does not exist, it returns an error +// if tgt path exists, but the source path tgt points to does not exist, it returns false without error. +func IsSymlink(tgt string) (bool, error) { + // This code is similar to k8s.io/kubernetes/pkg/util/mount except the pathExists usage. + stat, err := os.Lstat(tgt) + if err != nil { + return false, err + } + + // If its a link and it points to an existing file then its a mount point. + if stat.Mode()&os.ModeSymlink != 0 { + target, err := os.Readlink(tgt) + if err != nil { + return false, fmt.Errorf("readlink error: %v", err) + } + exists, err := pathExists(target) + if err != nil { + return false, err + } + return exists, nil + } + + return false, nil +} diff --git a/pkg/os/smb/smb.go b/pkg/os/smb/smb.go new file mode 100644 index 00000000000..5784037b652 --- /dev/null +++ b/pkg/os/smb/smb.go @@ -0,0 +1,113 @@ +//go:build windows +// +build windows + +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package smb + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/kubernetes-csi/csi-driver-smb/pkg/util" + "k8s.io/klog/v2" +) + +func IsSmbMapped(remotePath string) (bool, error) { + cmdLine := `$(Get-SmbGlobalMapping -RemotePath $Env:smbremotepath -ErrorAction Stop).Status` + cmdEnv := fmt.Sprintf("smbremotepath=%s", remotePath) + out, err := util.RunPowershellCmd(cmdLine, cmdEnv) + if err != nil { + return false, fmt.Errorf("error checking smb mapping. cmd %s, output: %s, err: %v", remotePath, string(out), err) + } + + if len(out) == 0 || !strings.EqualFold(strings.TrimSpace(string(out)), "OK") { + return false, nil + } + return true, nil +} + +func NewSmbGlobalMapping(remotePath, username, password string) error { + // use PowerShell Environment Variables to store user input string to prevent command line injection + // https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables?view=powershell-5.1 + cmdLine := fmt.Sprintf(`$PWord = ConvertTo-SecureString -String $Env:smbpassword -AsPlainText -Force` + + `;$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Env:smbuser, $PWord` + + `;New-SmbGlobalMapping -RemotePath $Env:smbremotepath -Credential $Credential -RequirePrivacy $true`) + + klog.V(2).Infof("begin to run NewSmbGlobalMapping with %s, %s", remotePath, username) + if output, err := util.RunPowershellCmd(cmdLine, fmt.Sprintf("smbuser=%s", username), + fmt.Sprintf("smbpassword=%s", password), + fmt.Sprintf("smbremotepath=%s", remotePath)); err != nil { + return fmt.Errorf("NewSmbGlobalMapping failed. output: %q, err: %v", string(output), err) + } + return nil +} + +func RemoveSmbGlobalMapping(remotePath string) error { + remotePath = strings.TrimSuffix(remotePath, `\`) + cmd := `Remove-SmbGlobalMapping -RemotePath $Env:smbremotepath -Force` + klog.V(2).Infof("begin to run RemoveSmbGlobalMapping with %s", remotePath) + if output, err := util.RunPowershellCmd(cmd, fmt.Sprintf("smbremotepath=%s", remotePath)); err != nil { + return fmt.Errorf("UnmountSmbShare failed. output: %q, err: %v", string(output), err) + } + return nil +} + +// GetRemoteServerFromTarget- gets the remote server path given a mount point, the function is recursive until it find the remote server or errors out +func GetRemoteServerFromTarget(mount string) (string, error) { + target, err := os.Readlink(mount) + klog.V(2).Infof("read link for mount %s, target: %s", mount, target) + if err != nil || len(target) == 0 { + return "", fmt.Errorf("error reading link for mount %s. target %s err: %v", mount, target, err) + } + return strings.TrimSpace(target), nil +} + +// CheckForDuplicateSMBMounts checks if there is any other SMB mount exists on the same remote server +func CheckForDuplicateSMBMounts(dir, mount, remoteServer string) (bool, error) { + files, err := os.ReadDir(dir) + if err != nil { + return false, err + } + + for _, file := range files { + klog.V(6).Infof("checking file %s", file.Name()) + if file.IsDir() { + globalMountPath := filepath.Join(dir, file.Name(), "globalmount") + if strings.EqualFold(filepath.Clean(globalMountPath), filepath.Clean(mount)) { + klog.V(2).Infof("skip current mount path %s", mount) + } else { + fileInfo, err := os.Lstat(globalMountPath) + // check if the file is a symlink, if yes, check if it is pointing to the same remote server + if err == nil && fileInfo.Mode()&os.ModeSymlink != 0 { + remoteServerPath, err := GetRemoteServerFromTarget(globalMountPath) + klog.V(2).Infof("checking remote server path %s on local path %s", remoteServerPath, globalMountPath) + if err == nil { + if remoteServerPath == remoteServer { + return true, nil + } + } else { + klog.Errorf("GetRemoteServerFromTarget(%s) failed with %v", globalMountPath, err) + } + } + } + } + } + return false, err +} diff --git a/pkg/os/smb/smb_test.go b/pkg/os/smb/smb_test.go new file mode 100644 index 00000000000..98dc67406c4 --- /dev/null +++ b/pkg/os/smb/smb_test.go @@ -0,0 +1,59 @@ +//go:build windows +// +build windows + +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package smb + +import ( + "fmt" + "testing" +) + +func TestCheckForDuplicateSMBMounts(t *testing.T) { + tests := []struct { + name string + dir string + mount string + remoteServer string + expectedResult bool + expectedError error + }{ + { + name: "directory does not exist", + dir: "non-existing-mount", + expectedResult: false, + expectedError: fmt.Errorf("open non-existing-mount: The system cannot find the file specified."), + }, + } + + for _, test := range tests { + result, err := CheckForDuplicateSMBMounts(test.dir, test.mount, test.remoteServer) + if result != test.expectedResult { + t.Errorf("Expected %v, got %v", test.expectedResult, result) + } + if err == nil && test.expectedError != nil { + t.Errorf("Expected error %v, got nil", test.expectedError) + } + if err != nil && test.expectedError == nil { + t.Errorf("Expected nil, got %v", err) + } + if err != nil && test.expectedError != nil && err.Error() != test.expectedError.Error() { + t.Errorf("Expected error %v, got %v", test.expectedError, err) + } + } +} diff --git a/pkg/smb/fake_mounter.go b/pkg/smb/fake_mounter.go index c874ed6a33a..c06fdd2a6a0 100644 --- a/pkg/smb/fake_mounter.go +++ b/pkg/smb/fake_mounter.go @@ -65,7 +65,7 @@ func (f *fakeMounter) IsLikelyNotMountPoint(file string) (bool, error) { func NewFakeMounter() (*mount.SafeFormatAndMount, error) { if runtime.GOOS == "windows" { - return mounter.NewSafeMounter(true) + return mounter.NewSafeMounter(true, true) } return &mount.SafeFormatAndMount{ Interface: &fakeMounter{}, diff --git a/pkg/smb/smb.go b/pkg/smb/smb.go index ffb18ca0d8e..a4e27abf061 100644 --- a/pkg/smb/smb.go +++ b/pkg/smb/smb.go @@ -73,6 +73,7 @@ type DriverOptions struct { Krb5Prefix string DefaultOnDeletePolicy string RemoveArchivedVolumePath bool + EnableWindowsHostProcess bool } // Driver implements all interfaces of CSI drivers @@ -100,6 +101,7 @@ type Driver struct { krb5Prefix string defaultOnDeletePolicy string removeArchivedVolumePath bool + enableWindowsHostProcess bool } // NewDriver Creates a NewCSIDriver object. Assumes vendor version is equal to driver version & @@ -113,6 +115,7 @@ func NewDriver(options *DriverOptions) *Driver { driver.removeSMBMappingDuringUnmount = options.RemoveSMBMappingDuringUnmount driver.removeArchivedVolumePath = options.RemoveArchivedVolumePath driver.workingMountDir = options.WorkingMountDir + driver.enableWindowsHostProcess = options.EnableWindowsHostProcess driver.volumeLocks = newVolumeLocks() driver.krb5CacheDirectory = options.Krb5CacheDirectory @@ -146,7 +149,7 @@ func (d *Driver) Run(endpoint, _ string, testMode bool) { } klog.V(2).Infof("\nDRIVER INFORMATION:\n-------------------\n%s\n\nStreaming logs below:", versionMeta) - d.mounter, err = mounter.NewSafeMounter(d.removeSMBMappingDuringUnmount) + d.mounter, err = mounter.NewSafeMounter(d.enableWindowsHostProcess, d.removeSMBMappingDuringUnmount) if err != nil { klog.Fatalf("Failed to get safe mounter. Error: %v", err) } diff --git a/pkg/util/util.go b/pkg/util/util.go index 06b626cb849..638e8391bf3 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -17,9 +17,17 @@ limitations under the License. package util import ( + "k8s.io/klog/v2" + "os" + "os/exec" "time" ) +const MaxPathLengthWindows = 260 + +// control the number of concurrent powershell commands running on Windows node +var powershellCmdSem = make(chan struct{}, 3) + // ExecFunc returns a exec function's output and error type ExecFunc func() (err error) @@ -46,3 +54,14 @@ func WaitUntilTimeout(timeout time.Duration, execFunc ExecFunc, timeoutFunc Time return timeoutFunc() } } + +func RunPowershellCmd(command string, envs ...string) ([]byte, error) { + // acquire a semaphore to limit the number of concurrent operations + powershellCmdSem <- struct{}{} + defer func() { <-powershellCmdSem }() + + cmd := exec.Command("powershell", "-Mta", "-NoProfile", "-Command", command) + cmd.Env = append(os.Environ(), envs...) + klog.V(6).Infof("Executing command: %q", cmd.String()) + return cmd.CombinedOutput() +}