diff --git a/images/csi-nfs/patches/0001-add-functionality-of-the-tar-utility-improved.patch b/images/csi-nfs/patches/0001-add-functionality-of-the-tar-utility-improved.patch new file mode 100644 index 0000000..743fb8f --- /dev/null +++ b/images/csi-nfs/patches/0001-add-functionality-of-the-tar-utility-improved.patch @@ -0,0 +1,811 @@ +diff --git a/go.mod b/go.mod +index 9c84c6ac..b1458639 100644 +--- a/go.mod ++++ b/go.mod +@@ -1,6 +1,8 @@ + module github.com/kubernetes-csi/csi-driver-nfs + +-go 1.21 ++go 1.22.0 ++ ++toolchain go1.23.1 + + require ( + github.com/container-storage-interface/spec v1.8.0 +@@ -96,6 +98,7 @@ require ( + go.uber.org/zap v1.19.0 // indirect + golang.org/x/crypto v0.22.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect ++ golang.org/x/mod v0.22.0 + golang.org/x/oauth2 v0.17.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.19.0 // indirect +diff --git a/go.sum b/go.sum +index 437b932d..1a9f74e4 100644 +--- a/go.sum ++++ b/go.sum +@@ -458,6 +458,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB + golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= + golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= ++golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= ++golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= + golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= + golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= + golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +diff --git a/pkg/nfs/controllerserver.go b/pkg/nfs/controllerserver.go +index 7fdad5dd..fcede86a 100644 +--- a/pkg/nfs/controllerserver.go ++++ b/pkg/nfs/controllerserver.go +@@ -359,10 +359,11 @@ func (cs *ControllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS + + srcPath := getInternalVolumePath(cs.Driver.workingMountDir, srcVol) + dstPath := filepath.Join(snapInternalVolPath, snapshot.archiveName()) +- klog.V(2).Infof("archiving %v -> %v", srcPath, dstPath) +- out, err := exec.Command("tar", "-C", srcPath, "-czvf", dstPath, ".").CombinedOutput() ++ ++ klog.V(2).Infof("tar %v -> %v", srcPath, dstPath) ++ err = TarPack(srcPath, dstPath, true) + if err != nil { +- return nil, status.Errorf(codes.Internal, "failed to create archive for snapshot: %v: %v", err, string(out)) ++ return nil, status.Errorf(codes.Internal, "failed to create archive for snapshot: %v", err) + } + klog.V(2).Infof("archived %s -> %s", srcPath, dstPath) + +@@ -515,9 +516,10 @@ func (cs *ControllerServer) copyFromSnapshot(ctx context.Context, req *csi.Creat + snapPath := filepath.Join(getInternalVolumePath(cs.Driver.workingMountDir, snapVol), snap.archiveName()) + dstPath := getInternalVolumePath(cs.Driver.workingMountDir, dstVol) + klog.V(2).Infof("copy volume from snapshot %v -> %v", snapPath, dstPath) +- out, err := exec.Command("tar", "-xzvf", snapPath, "-C", dstPath).CombinedOutput() ++ ++ err = TarUnpack(snapPath, dstPath, true) + if err != nil { +- return status.Errorf(codes.Internal, "failed to copy volume for snapshot: %v: %v", err, string(out)) ++ return status.Errorf(codes.Internal, "failed to copy volume for snapshot: %v", err) + } + klog.V(2).Infof("volume copied from snapshot %v -> %v", snapPath, dstPath) + return nil +diff --git a/pkg/nfs/tar.go b/pkg/nfs/tar.go +new file mode 100644 +index 00000000..b4326215 +--- /dev/null ++++ b/pkg/nfs/tar.go +@@ -0,0 +1,241 @@ ++/* ++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 nfs ++ ++import ( ++ "archive/tar" ++ "compress/gzip" ++ "errors" ++ "fmt" ++ "io" ++ "io/fs" ++ "os" ++ "path" ++ "path/filepath" ++ "strings" ++) ++ ++func TarPack(srcDirPath string, dstPath string, enableCompression bool) error { ++ // normalize all paths to be absolute and clean ++ dstPath, err := filepath.Abs(dstPath) ++ if err != nil { ++ return fmt.Errorf("normalizing destination path: %w", err) ++ } ++ ++ srcDirPath, err = filepath.Abs(srcDirPath) ++ if err != nil { ++ return fmt.Errorf("normalizing source path: %w", err) ++ } ++ ++ if strings.Index(path.Dir(dstPath), srcDirPath) == 0 { ++ return fmt.Errorf("destination file %s cannot be under source directory %s", dstPath, srcDirPath) ++ } ++ ++ tarFile, err := os.Create(dstPath) ++ if err != nil { ++ return fmt.Errorf("creating destination file: %w", err) ++ } ++ defer func() { ++ err = errors.Join(err, closeAndWrapErr(tarFile, "closing destination file %s: %w", dstPath)) ++ }() ++ ++ var tarDst io.Writer = tarFile ++ if enableCompression { ++ gzipWriter := gzip.NewWriter(tarFile) ++ defer func() { ++ err = errors.Join(err, closeAndWrapErr(gzipWriter, "closing gzip writer")) ++ }() ++ tarDst = gzipWriter ++ } ++ ++ tarWriter := tar.NewWriter(tarDst) ++ defer func() { ++ err = errors.Join(err, closeAndWrapErr(tarWriter, "closing tar writer")) ++ }() ++ ++ // recursively visit every file and write it ++ if err = filepath.Walk( ++ srcDirPath, ++ func(srcSubPath string, fileInfo fs.FileInfo, walkErr error) error { ++ return tarVisitFileToPack(tarWriter, srcDirPath, srcSubPath, fileInfo, walkErr) ++ }, ++ ); err != nil { ++ return fmt.Errorf("walking source directory: %w", err) ++ } ++ ++ return nil ++} ++ ++func tarVisitFileToPack( ++ tarWriter *tar.Writer, ++ srcPath string, ++ srcSubPath string, ++ fileInfo os.FileInfo, ++ walkErr error, ++) (err error) { ++ if walkErr != nil { ++ return walkErr ++ } ++ ++ linkTarget := "" ++ if fileInfo.Mode()&fs.ModeSymlink != 0 { ++ linkTarget, err = os.Readlink(srcSubPath) ++ if err != nil { ++ return fmt.Errorf("reading link %s: %w", srcSubPath, err) ++ } ++ } ++ ++ tarHeader, err := tar.FileInfoHeader(fileInfo, linkTarget) ++ if err != nil { ++ return fmt.Errorf("creating tar header for %s: %w", srcSubPath, err) ++ } ++ ++ // srcSubPath always starts with srcPath and both are absolute ++ tarHeader.Name, err = filepath.Rel(srcPath, srcSubPath) ++ if err != nil { ++ return fmt.Errorf("making tar header name for file %s: %w", srcSubPath, err) ++ } ++ ++ if err = tarWriter.WriteHeader(tarHeader); err != nil { ++ return fmt.Errorf("writing tar header for file %s: %w", srcSubPath, err) ++ } ++ ++ if !fileInfo.Mode().IsRegular() { ++ return nil ++ } ++ ++ srcFile, err := os.Open(srcSubPath) ++ if err != nil { ++ return fmt.Errorf("opening file being packed %s: %w", srcSubPath, err) ++ } ++ defer func() { ++ err = errors.Join(err, closeAndWrapErr(srcFile, "closing file being packed %s: %w", srcSubPath)) ++ }() ++ _, err = io.Copy(tarWriter, srcFile) ++ if err != nil { ++ return fmt.Errorf("packing file %s: %w", srcSubPath, err) ++ } ++ return nil ++} ++ ++func TarUnpack(srcPath, dstDirPath string, enableCompression bool) (err error) { ++ // normalize all paths to be absolute and clean ++ srcPath, err = filepath.Abs(srcPath) ++ if err != nil { ++ return fmt.Errorf("normalizing archive path: %w", err) ++ } ++ ++ dstDirPath, err = filepath.Abs(dstDirPath) ++ if err != nil { ++ return fmt.Errorf("normalizing archive destination path: %w", err) ++ } ++ ++ tarFile, err := os.Open(srcPath) ++ if err != nil { ++ return fmt.Errorf("opening archive %s: %w", srcPath, err) ++ } ++ defer func() { ++ err = errors.Join(err, closeAndWrapErr(tarFile, "closing archive %s: %w", srcPath)) ++ }() ++ ++ var tarDst io.Reader = tarFile ++ if enableCompression { ++ var gzipReader *gzip.Reader ++ gzipReader, err = gzip.NewReader(tarFile) ++ if err != nil { ++ return fmt.Errorf("creating gzip reader: %w", err) ++ } ++ defer func() { ++ err = errors.Join(err, closeAndWrapErr(gzipReader, "closing gzip reader: %w")) ++ }() ++ ++ tarDst = gzipReader ++ } ++ ++ tarReader := tar.NewReader(tarDst) ++ ++ for { ++ var tarHeader *tar.Header ++ tarHeader, err = tarReader.Next() ++ if err == io.EOF { ++ break ++ } ++ if err != nil { ++ return fmt.Errorf("reading tar header of %s: %w", srcPath, err) ++ } ++ ++ fileInfo := tarHeader.FileInfo() ++ ++ filePath := filepath.Join(dstDirPath, tarHeader.Name) ++ ++ // protect against "Zip Slip" ++ if strings.Index(filePath, dstDirPath) != 0 { ++ // mimic standard error, which will be returned in future versions of Go by default ++ // more info can be found by "tarinsecurepath" variable name ++ return tar.ErrInsecurePath ++ } ++ ++ fileDirPath := filePath ++ if !fileInfo.Mode().IsDir() { ++ fileDirPath = filepath.Dir(fileDirPath) ++ } ++ ++ if err = os.MkdirAll(fileDirPath, 0755); err != nil { ++ return fmt.Errorf("making dirs for path %s: %w", fileDirPath, err) ++ } ++ ++ if fileInfo.Mode().IsDir() { ++ continue ++ } ++ ++ err = tarUnpackFile(filePath, tarReader, fileInfo) ++ if err != nil { ++ return fmt.Errorf("unpacking archive %s: %w", filePath, err) ++ } ++ } ++ return nil ++} ++ ++func tarUnpackFile(dstFileName string, src io.Reader, srcFileInfo fs.FileInfo) (err error) { ++ var dstFile *os.File ++ dstFile, err = os.OpenFile(dstFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, srcFileInfo.Mode().Perm()) ++ if err != nil { ++ return fmt.Errorf("opening destination file %s: %w", dstFileName, err) ++ } ++ defer func() { ++ err = errors.Join(err, closeAndWrapErr(dstFile, "closing destination file %s: %w", dstFile)) ++ }() ++ ++ n, err := io.Copy(dstFile, src) ++ if err != nil { ++ return fmt.Errorf("copying to destination file %s: %w", dstFileName, err) ++ } ++ ++ if srcFileInfo.Mode().IsRegular() && n != srcFileInfo.Size() { ++ return fmt.Errorf("written size check failed for %s: wrote %d, want %d", dstFileName, n, srcFileInfo.Size()) ++ } ++ ++ return nil ++} ++ ++func closeAndWrapErr(closer io.Closer, errFormat string, a ...any) error { ++ if err := closer.Close(); err != nil { ++ a = append(a, err) ++ return fmt.Errorf(errFormat, a...) ++ } ++ return nil ++} +diff --git a/pkg/nfs/tar_test.go b/pkg/nfs/tar_test.go +new file mode 100644 +index 00000000..fc16fc55 +--- /dev/null ++++ b/pkg/nfs/tar_test.go +@@ -0,0 +1,268 @@ ++/* ++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 nfs ++ ++import ( ++ "archive/tar" ++ "bytes" ++ "compress/gzip" ++ "errors" ++ "fmt" ++ "maps" ++ "math" ++ "os" ++ "os/exec" ++ "path" ++ "path/filepath" ++ "slices" ++ "strings" ++ "testing" ++ ++ "golang.org/x/mod/sumdb/dirhash" ++) ++ ++const ( ++ code producedFrom = '0' ++ cli producedFrom = '1' ++) ++ ++type producedFrom byte ++ ++const archiveFileExt = ".tar.gz" ++ ++func TestPackUnpack(t *testing.T) { ++ inputPath := t.TempDir() ++ generateFileSystem(t, inputPath) ++ ++ outputPath := t.TempDir() ++ ++ // produced file names (without extensions) have a suffix, ++ // which determine the last operation: ++ // "0" means that it was produced from code ++ // "1" means that it was produced from CLI ++ // e.g.: "testdata011.tar.gz" - was packed from code, ++ // then unpacked from cli and packed again from cli ++ ++ pathsBySuffix := make(map[string]string) ++ ++ // number of pack/unpack operations ++ opNum := 4 ++ ++ // generate all operation combinations ++ fileNum := int(math.Pow(2, float64(opNum))) ++ for i := 0; i < fileNum; i++ { ++ binStr := fmt.Sprintf("%b", i) ++ ++ // left-pad with zeroes ++ binStr = strings.Repeat("0", opNum-len(binStr)) + binStr ++ ++ // copy slices to satisfy type system ++ ops := make([]producedFrom, opNum) ++ for opIdx := 0; opIdx < opNum; opIdx++ { ++ ops[opIdx] = producedFrom(binStr[opIdx]) ++ } ++ ++ // produce folders and archives ++ produce(t, pathsBySuffix, inputPath, outputPath, ops...) ++ } ++ ++ // compare all unpacked directories ++ paths := slices.Collect(maps.Values(pathsBySuffix)) ++ assertUnpackedFilesEqual(t, inputPath, paths) ++} ++ ++func TestUnpackZipSlip(t *testing.T) { ++ // Arrange: produce malicious archive ++ inputDir := t.TempDir() ++ ++ const mContent = "malicious content" ++ const mFileName = "malicious.txt" ++ const mHeaderPath = "../" + mFileName // attack: path traversal ++ var mArchivePath = filepath.Join(inputDir, "malicious.tar.gz") ++ ++ // temp file to pack ++ maliciousFile, err := os.Create(mArchivePath) ++ if err != nil { ++ t.Fatalf("failed to create temp file: %v", err) ++ } ++ ++ gzWriter := gzip.NewWriter(maliciousFile) ++ tarWriter := tar.NewWriter(gzWriter) ++ ++ // define a malicious file header ++ maliciousHeader := &tar.Header{ ++ Name: mHeaderPath, ++ Size: int64(len(mContent)), ++ Mode: 0600, ++ } ++ ++ err = tarWriter.WriteHeader(maliciousHeader) ++ if err != nil { ++ t.Fatalf("failed to write malicious header: %v", err) ++ } ++ ++ // write malicious content ++ _, err = tarWriter.Write([]byte(mContent)) ++ if err != nil { ++ t.Fatalf("failed to write content: %v", err) ++ } ++ ++ err = errors.Join(tarWriter.Close(), gzWriter.Close()) ++ if err != nil { ++ t.Fatalf("failed to close writers: %v", err) ++ } ++ ++ // Act & Assert: unpack nearby, expect error ++ var outputDir = filepath.Join(inputDir, "output") ++ if err := TarUnpack(mArchivePath, outputDir, true); err != nil { ++ if !errors.Is(err, tar.ErrInsecurePath) { ++ t.Fatalf("expected error tar.ErrInsecurePath, got: %v", err) ++ } ++ } else { ++ t.Error("unpack of malicious file succeeded, expected it to fail") ++ } ++ ++ // Assert: check that file did not escape ++ var attackPath = filepath.Join(inputDir, mFileName) ++ if _, err := os.Stat(attackPath); err != nil { ++ if !errors.Is(err, os.ErrNotExist) { ++ t.Fatalf("failed to check the existense of the malicious file: %v", err) ++ } ++ } else { ++ t.Errorf("malicious file escaped the destination: %s", attackPath) ++ } ++} ++ ++func produce( ++ t *testing.T, ++ results map[string]string, ++ inputDirPath string, ++ outputDirPath string, ++ ops ...producedFrom, ++) { ++ baseName := path.Base(inputDirPath) ++ ++ for i := 0; i < len(ops); i++ { ++ packing := i%2 == 0 ++ ++ srcPath := inputDirPath ++ if i > 0 { ++ prevSuffix := string(ops[:i]) ++ srcPath = path.Join(outputDirPath, baseName+prevSuffix) ++ if !packing { ++ srcPath += archiveFileExt ++ } ++ } ++ ++ suffix := string(ops[:i+1]) ++ dstPath := path.Join(outputDirPath, baseName+suffix) ++ if packing { ++ dstPath += archiveFileExt ++ } ++ ++ if _, ok := results[suffix]; ok { ++ continue ++ } ++ ++ switch { ++ case packing && ops[i] == code: ++ // packing from code ++ if err := TarPack(srcPath, dstPath, true); err != nil { ++ t.Fatalf("packing '%s' with TarPack into '%s': %v", srcPath, dstPath, err) ++ } ++ case packing && ops[i] == cli: ++ // packing from CLI ++ if out, err := exec.Command("tar", "-C", srcPath, "-czvf", dstPath, ".").CombinedOutput(); err != nil { ++ t.Log("TAR OUTPUT:", string(out)) ++ t.Fatalf("packing '%s' with tar into '%s': %v", srcPath, dstPath, err) ++ } ++ case !packing && ops[i] == code: ++ // unpacking from code ++ if err := TarUnpack(srcPath, dstPath, true); err != nil { ++ t.Fatalf("unpacking '%s' with TarUnpack into '%s': %v", srcPath, dstPath, err) ++ } ++ case !packing && ops[i] == cli: ++ // unpacking from CLI ++ // tar requires destination directory to exist ++ if err := os.MkdirAll(dstPath, 0755); err != nil { ++ t.Fatalf("making dir '%s' for unpacking with tar: %v", dstPath, err) ++ } ++ if out, err := exec.Command("tar", "-xzvf", srcPath, "-C", dstPath).CombinedOutput(); err != nil { ++ t.Log("TAR OUTPUT:", string(out)) ++ t.Fatalf("unpacking '%s' with tar into '%s': %v", srcPath, dstPath, err) ++ } ++ default: ++ t.Fatalf("unknown suffix: %s", string(ops[i])) ++ } ++ ++ results[suffix] = dstPath ++ } ++} ++ ++func assertUnpackedFilesEqual(t *testing.T, originalDir string, paths []string) { ++ originalDirHash, err := dirhash.HashDir(originalDir, "_", dirhash.DefaultHash) ++ if err != nil { ++ t.Fatal("failed hashing original dir ", err) ++ } ++ ++ for _, p := range paths { ++ if strings.HasSuffix(p, archiveFileExt) { ++ // archive, not a directory ++ continue ++ } ++ ++ // unpacked directory ++ hs, err := dirhash.HashDir(p, "_", dirhash.DefaultHash) ++ if err != nil { ++ t.Fatal("failed hashing dir ", err) ++ } ++ ++ if hs != originalDirHash { ++ t.Errorf("expected '%s' to have the same hash as '%s', got different", originalDir, p) ++ } ++ } ++} ++ ++func generateFileSystem(t *testing.T, inputPath string) { ++ // empty directory ++ if err := os.MkdirAll(path.Join(inputPath, "empty_dir"), 0755); err != nil { ++ t.Fatalf("generating empty directory: %v", err) ++ } ++ ++ // deep empty directories ++ deepEmptyDirPath := path.Join(inputPath, "deep_empty_dir", strings.Repeat("/0/1/2", 20)) ++ if err := os.MkdirAll(deepEmptyDirPath, 0755); err != nil { ++ t.Fatalf("generating deep empty directory '%s': %v", deepEmptyDirPath, err) ++ } ++ ++ // empty file ++ f, err := os.Create(path.Join(inputPath, "empty_file")) ++ if err != nil { ++ t.Fatalf("generating empty file: %v", err) ++ } ++ f.Close() ++ ++ // big (100MB) file ++ bigFilePath := path.Join(inputPath, "big_file") ++ for i := byte(0); i < 100; i++ { ++ // write 1MB ++ err := os.WriteFile(bigFilePath, bytes.Repeat([]byte{i}, 1024*1024), 0755) ++ if err != nil { ++ t.Fatalf("generating empty file: %v", err) ++ } ++ } ++} +diff --git a/vendor/golang.org/x/mod/LICENSE b/vendor/golang.org/x/mod/LICENSE +new file mode 100644 +index 00000000..2a7cf70d +--- /dev/null ++++ b/vendor/golang.org/x/mod/LICENSE +@@ -0,0 +1,27 @@ ++Copyright 2009 The Go Authors. ++ ++Redistribution and use in source and binary forms, with or without ++modification, are permitted provided that the following conditions are ++met: ++ ++ * Redistributions of source code must retain the above copyright ++notice, this list of conditions and the following disclaimer. ++ * Redistributions in binary form must reproduce the above ++copyright notice, this list of conditions and the following disclaimer ++in the documentation and/or other materials provided with the ++distribution. ++ * Neither the name of Google LLC nor the names of its ++contributors may be used to endorse or promote products derived from ++this software without specific prior written permission. ++ ++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ++"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ++LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ++A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ++OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ++SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ++LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ++DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ++THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ++OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +diff --git a/vendor/golang.org/x/mod/PATENTS b/vendor/golang.org/x/mod/PATENTS +new file mode 100644 +index 00000000..73309904 +--- /dev/null ++++ b/vendor/golang.org/x/mod/PATENTS +@@ -0,0 +1,22 @@ ++Additional IP Rights Grant (Patents) ++ ++"This implementation" means the copyrightable works distributed by ++Google as part of the Go project. ++ ++Google hereby grants to You a perpetual, worldwide, non-exclusive, ++no-charge, royalty-free, irrevocable (except as stated in this section) ++patent license to make, have made, use, offer to sell, sell, import, ++transfer and otherwise run, modify and propagate the contents of this ++implementation of Go, where such license applies only to those patent ++claims, both currently owned or controlled by Google and acquired in ++the future, licensable by Google that are necessarily infringed by this ++implementation of Go. This grant does not include claims that would be ++infringed only as a consequence of further modification of this ++implementation. If you or your agent or exclusive licensee institute or ++order or agree to the institution of patent litigation against any ++entity (including a cross-claim or counterclaim in a lawsuit) alleging ++that this implementation of Go or any code incorporated within this ++implementation of Go constitutes direct or contributory patent ++infringement, or inducement of patent infringement, then any patent ++rights granted to you under this License for this implementation of Go ++shall terminate as of the date such litigation is filed. +diff --git a/vendor/golang.org/x/mod/sumdb/dirhash/hash.go b/vendor/golang.org/x/mod/sumdb/dirhash/hash.go +new file mode 100644 +index 00000000..51ec4db8 +--- /dev/null ++++ b/vendor/golang.org/x/mod/sumdb/dirhash/hash.go +@@ -0,0 +1,135 @@ ++// Copyright 2018 The Go Authors. All rights reserved. ++// Use of this source code is governed by a BSD-style ++// license that can be found in the LICENSE file. ++ ++// Package dirhash defines hashes over directory trees. ++// These hashes are recorded in go.sum files and in the Go checksum database, ++// to allow verifying that a newly-downloaded module has the expected content. ++package dirhash ++ ++import ( ++ "archive/zip" ++ "crypto/sha256" ++ "encoding/base64" ++ "errors" ++ "fmt" ++ "io" ++ "os" ++ "path/filepath" ++ "sort" ++ "strings" ++) ++ ++// DefaultHash is the default hash function used in new go.sum entries. ++var DefaultHash Hash = Hash1 ++ ++// A Hash is a directory hash function. ++// It accepts a list of files along with a function that opens the content of each file. ++// It opens, reads, hashes, and closes each file and returns the overall directory hash. ++type Hash func(files []string, open func(string) (io.ReadCloser, error)) (string, error) ++ ++// Hash1 is the "h1:" directory hash function, using SHA-256. ++// ++// Hash1 is "h1:" followed by the base64-encoded SHA-256 hash of a summary ++// prepared as if by the Unix command: ++// ++// sha256sum $(find . -type f | sort) | sha256sum ++// ++// More precisely, the hashed summary contains a single line for each file in the list, ++// ordered by sort.Strings applied to the file names, where each line consists of ++// the hexadecimal SHA-256 hash of the file content, ++// two spaces (U+0020), the file name, and a newline (U+000A). ++// ++// File names with newlines (U+000A) are disallowed. ++func Hash1(files []string, open func(string) (io.ReadCloser, error)) (string, error) { ++ h := sha256.New() ++ files = append([]string(nil), files...) ++ sort.Strings(files) ++ for _, file := range files { ++ if strings.Contains(file, "\n") { ++ return "", errors.New("dirhash: filenames with newlines are not supported") ++ } ++ r, err := open(file) ++ if err != nil { ++ return "", err ++ } ++ hf := sha256.New() ++ _, err = io.Copy(hf, r) ++ r.Close() ++ if err != nil { ++ return "", err ++ } ++ fmt.Fprintf(h, "%x %s\n", hf.Sum(nil), file) ++ } ++ return "h1:" + base64.StdEncoding.EncodeToString(h.Sum(nil)), nil ++} ++ ++// HashDir returns the hash of the local file system directory dir, ++// replacing the directory name itself with prefix in the file names ++// used in the hash function. ++func HashDir(dir, prefix string, hash Hash) (string, error) { ++ files, err := DirFiles(dir, prefix) ++ if err != nil { ++ return "", err ++ } ++ osOpen := func(name string) (io.ReadCloser, error) { ++ return os.Open(filepath.Join(dir, strings.TrimPrefix(name, prefix))) ++ } ++ return hash(files, osOpen) ++} ++ ++// DirFiles returns the list of files in the tree rooted at dir, ++// replacing the directory name dir with prefix in each name. ++// The resulting names always use forward slashes. ++func DirFiles(dir, prefix string) ([]string, error) { ++ var files []string ++ dir = filepath.Clean(dir) ++ err := filepath.Walk(dir, func(file string, info os.FileInfo, err error) error { ++ if err != nil { ++ return err ++ } ++ if info.IsDir() { ++ return nil ++ } else if file == dir { ++ return fmt.Errorf("%s is not a directory", dir) ++ } ++ ++ rel := file ++ if dir != "." { ++ rel = file[len(dir)+1:] ++ } ++ f := filepath.Join(prefix, rel) ++ files = append(files, filepath.ToSlash(f)) ++ return nil ++ }) ++ if err != nil { ++ return nil, err ++ } ++ return files, nil ++} ++ ++// HashZip returns the hash of the file content in the named zip file. ++// Only the file names and their contents are included in the hash: ++// the exact zip file format encoding, compression method, ++// per-file modification times, and other metadata are ignored. ++func HashZip(zipfile string, hash Hash) (string, error) { ++ z, err := zip.OpenReader(zipfile) ++ if err != nil { ++ return "", err ++ } ++ defer z.Close() ++ var files []string ++ zfiles := make(map[string]*zip.File) ++ for _, file := range z.File { ++ files = append(files, file.Name) ++ zfiles[file.Name] = file ++ } ++ zipOpen := func(name string) (io.ReadCloser, error) { ++ f := zfiles[name] ++ if f == nil { ++ return nil, fmt.Errorf("file %q not found in zip", name) // should never happen ++ } ++ return f.Open() ++ } ++ return hash(files, zipOpen) ++} +diff --git a/vendor/k8s.io/kubernetes/test/e2e/framework/testfiles/testdata/a/foo.txt b/vendor/k8s.io/kubernetes/test/e2e/framework/testfiles/testdata/a/foo.txt +deleted file mode 100644 +index 557db03d..00000000 +--- a/vendor/k8s.io/kubernetes/test/e2e/framework/testfiles/testdata/a/foo.txt ++++ /dev/null +@@ -1 +0,0 @@ +-Hello World +diff --git a/vendor/modules.txt b/vendor/modules.txt +index 761ba0b7..36e14469 100644 +--- a/vendor/modules.txt ++++ b/vendor/modules.txt +@@ -378,6 +378,9 @@ golang.org/x/crypto/ssh/internal/bcrypt_pbkdf + ## explicit; go 1.20 + golang.org/x/exp/constraints + golang.org/x/exp/slices ++# golang.org/x/mod v0.22.0 ++## explicit; go 1.22.0 ++golang.org/x/mod/sumdb/dirhash + # golang.org/x/net v0.24.0 + ## explicit; go 1.18 + golang.org/x/net/context diff --git a/images/csi-nfs/patches/0001-add-functionality-of-the-tar-utility.patch b/images/csi-nfs/patches/0001-add-functionality-of-the-tar-utility.patch deleted file mode 100644 index e47ca43..0000000 --- a/images/csi-nfs/patches/0001-add-functionality-of-the-tar-utility.patch +++ /dev/null @@ -1,208 +0,0 @@ -From f514b56bbe619c7d85db12483182620fb6349819 Mon Sep 17 00:00:00 2001 -From: Alexandr Ohrimenko -Date: Mon, 20 May 2024 12:25:31 +0300 -Subject: [PATCH] add functionality of the tar utility - -Signed-off-by: Alexandr Ohrimenko ---- - pkg/nfs/controllerserver.go | 10 ++- - pkg/nfs/func_tar.go | 157 ++++++++++++++++++++++++++++++++++++ - 2 files changed, 163 insertions(+), 4 deletions(-) - create mode 100644 pkg/nfs/func_tar.go - -diff --git a/pkg/nfs/controllerserver.go b/pkg/nfs/controllerserver.go -index 7fdad5dd..726df875 100644 ---- a/pkg/nfs/controllerserver.go -+++ b/pkg/nfs/controllerserver.go -@@ -360,9 +360,10 @@ func (cs *ControllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS - srcPath := getInternalVolumePath(cs.Driver.workingMountDir, srcVol) - dstPath := filepath.Join(snapInternalVolPath, snapshot.archiveName()) - klog.V(2).Infof("archiving %v -> %v", srcPath, dstPath) -- out, err := exec.Command("tar", "-C", srcPath, "-czvf", dstPath, ".").CombinedOutput() -+ //out, err := exec.Command("tar", "-C", srcPath, "-czvf", dstPath, ".").CombinedOutput() -+ err = tarPack(dstPath, srcPath, true) - if err != nil { -- return nil, status.Errorf(codes.Internal, "failed to create archive for snapshot: %v: %v", err, string(out)) -+ return nil, status.Errorf(codes.Internal, "failed to create archive for snapshot: %v", err) - } - klog.V(2).Infof("archived %s -> %s", srcPath, dstPath) - -@@ -515,9 +516,10 @@ func (cs *ControllerServer) copyFromSnapshot(ctx context.Context, req *csi.Creat - snapPath := filepath.Join(getInternalVolumePath(cs.Driver.workingMountDir, snapVol), snap.archiveName()) - dstPath := getInternalVolumePath(cs.Driver.workingMountDir, dstVol) - klog.V(2).Infof("copy volume from snapshot %v -> %v", snapPath, dstPath) -- out, err := exec.Command("tar", "-xzvf", snapPath, "-C", dstPath).CombinedOutput() -+ //out, err := exec.Command("tar", "-xzvf", snapPath, "-C", dstPath).CombinedOutput() -+ err = tarUnPack(snapPath, dstPath, true) - if err != nil { -- return status.Errorf(codes.Internal, "failed to copy volume for snapshot: %v: %v", err, string(out)) -+ return status.Errorf(codes.Internal, "failed to copy volume for snapshot: %v", err) - } - klog.V(2).Infof("volume copied from snapshot %v -> %v", snapPath, dstPath) - return nil -diff --git a/pkg/nfs/func_tar.go b/pkg/nfs/func_tar.go -new file mode 100644 -index 00000000..0131b564 ---- /dev/null -+++ b/pkg/nfs/func_tar.go -@@ -0,0 +1,157 @@ -+package nfs -+ -+import ( -+ "archive/tar" -+ "compress/gzip" -+ "errors" -+ "fmt" -+ "io" -+ "os" -+ "path/filepath" -+) -+ -+func tarPack(dstPathAndFileName, srcPath string, enableCompression bool) error { -+ tarFile, err := os.Create(dstPathAndFileName) -+ if err != nil { -+ return err -+ } -+ defer func() { -+ err = tarFile.Close() -+ }() -+ -+ absDstPathAndFileName, err := filepath.Abs(dstPathAndFileName) -+ if err != nil { -+ return err -+ } -+ -+ tarWriter := tar.NewWriter(tarFile) -+ if enableCompression { -+ gzipWriter := gzip.NewWriter(tarFile) -+ defer gzipWriter.Close() -+ tarWriter = tar.NewWriter(gzipWriter) -+ } -+ defer tarWriter.Close() -+ -+ srcPath = filepath.Clean(srcPath) -+ absSrcPath, err := filepath.Abs(srcPath) -+ if err != nil { -+ return err -+ } -+ if absSrcPath == absDstPathAndFileName { -+ return errors.New(fmt.Sprintf("tar file %s cannot be the source\n", dstPathAndFileName)) -+ } -+ if absSrcPath == filepath.Dir(absDstPathAndFileName) { -+ return errors.New(fmt.Sprintf("tar file %s cannot be in source directory %s\n", dstPathAndFileName, absSrcPath)) -+ } -+ -+ walker := func(file string, fileInfo os.FileInfo, err error) error { -+ if err != nil { -+ return err -+ } -+ -+ tarHeader, err := tar.FileInfoHeader(fileInfo, fileInfo.Name()) -+ if err != nil { -+ return err -+ } -+ -+ relFilePath := file -+ if filepath.IsAbs(srcPath) { -+ relFilePath, err = filepath.Rel(srcPath, file) -+ if err != nil { -+ return err -+ } -+ } -+ tarHeader.Name = relFilePath -+ if err = tarWriter.WriteHeader(tarHeader); err != nil { -+ return err -+ } -+ -+ if fileInfo.Mode().IsDir() { -+ return nil -+ } -+ -+ srcFile, err := os.Open(file) -+ if err != nil { -+ return err -+ } -+ defer srcFile.Close() -+ _, err = io.Copy(tarWriter, srcFile) -+ if err != nil { -+ return err -+ } -+ return nil -+ } -+ -+ if err = filepath.Walk(srcPath, walker); err != nil { -+ return err -+ } -+ return nil -+} -+ -+func tarUnPack(sourcePathAndFileName, desPath string, enableCompression bool) error { -+ tarFile, err := os.Open(sourcePathAndFileName) -+ if err != nil { -+ return err -+ } -+ defer func() { -+ err = tarFile.Close() -+ }() -+ -+ absPath, err := filepath.Abs(desPath) -+ if err != nil { -+ return err -+ } -+ -+ tarReader := tar.NewReader(tarFile) -+ if enableCompression { -+ gzipReader, err := gzip.NewReader(tarFile) -+ if err != nil { -+ return err -+ } -+ defer gzipReader.Close() -+ tarReader = tar.NewReader(gzipReader) -+ } -+ -+ for { -+ tarHeader, err := tarReader.Next() -+ if err == io.EOF { -+ break -+ } -+ if err != nil { -+ return err -+ } -+ -+ fileInfo := tarHeader.FileInfo() -+ fileName := tarHeader.Name -+ if filepath.IsAbs(fileName) { -+ fileName, err = filepath.Rel("/", fileName) -+ if err != nil { -+ return err -+ } -+ } -+ absFileName := filepath.Join(absPath, fileName) -+ -+ if fileInfo.Mode().IsDir() { -+ if err := os.MkdirAll(absFileName, 0755); err != nil { -+ return err -+ } -+ continue -+ } -+ -+ file, err := os.OpenFile(absFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fileInfo.Mode().Perm()) -+ if err != nil { -+ return err -+ } -+ n, cpErr := io.Copy(file, tarReader) -+ if closeErr := file.Close(); closeErr != nil { -+ return err -+ } -+ if cpErr != nil { -+ return cpErr -+ } -+ if n != fileInfo.Size() { -+ return fmt.Errorf("unexpected bytes written: wrote %d, want %d", n, fileInfo.Size()) -+ } -+ } -+ return nil -+} --- -2.39.3 (Apple Git-146) -