github.com/argoproj/argo-cd/v3@v3.2.1/util/io/files/tar_test.go (about) 1 package files_test 2 3 import ( 4 "archive/tar" 5 "compress/gzip" 6 "fmt" 7 "io" 8 "math" 9 "os" 10 "path" 11 "path/filepath" 12 "testing" 13 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 17 "github.com/argoproj/argo-cd/v3/test" 18 "github.com/argoproj/argo-cd/v3/util/io/files" 19 ) 20 21 func TestTgz(t *testing.T) { 22 t.Parallel() 23 24 type fixture struct { 25 file *os.File 26 } 27 setup := func(t *testing.T) *fixture { 28 t.Helper() 29 testDir := getTestDataDir(t) 30 f, err := os.CreateTemp(testDir, "") 31 require.NoError(t, err) 32 return &fixture{ 33 file: f, 34 } 35 } 36 teardown := func(f *fixture) { 37 f.file.Close() 38 os.Remove(f.file.Name()) 39 } 40 prepareRead := func(f *fixture) { 41 _, err := f.file.Seek(0, io.SeekStart) 42 require.NoError(t, err) 43 } 44 45 t.Run("will tgz folder successfully", func(t *testing.T) { 46 // given 47 t.Parallel() 48 exclusions := []string{} 49 f := setup(t) 50 defer teardown(f) 51 52 // when 53 filesWritten, err := files.Tgz(getTestAppDir(t), nil, exclusions, f.file) 54 55 // then 56 assert.Equal(t, 3, filesWritten) 57 require.NoError(t, err) 58 prepareRead(f) 59 files, err := read(f.file) 60 require.NoError(t, err) 61 assert.Len(t, files, 8) 62 assert.Contains(t, files, "README.md") 63 assert.Contains(t, files, "applicationset/latest/kustomization.yaml") 64 assert.Contains(t, files, "applicationset/stable/kustomization.yaml") 65 assert.Contains(t, files, "applicationset/readme-symlink") 66 assert.Equal(t, "../README.md", files["applicationset/readme-symlink"]) 67 }) 68 t.Run("will exclude files from the exclusion list", func(t *testing.T) { 69 // given 70 t.Parallel() 71 exclusions := []string{"README.md"} 72 f := setup(t) 73 defer teardown(f) 74 75 // when 76 filesWritten, err := files.Tgz(getTestAppDir(t), nil, exclusions, f.file) 77 78 // then 79 assert.Equal(t, 2, filesWritten) 80 require.NoError(t, err) 81 prepareRead(f) 82 files, err := read(f.file) 83 require.NoError(t, err) 84 assert.Len(t, files, 7) 85 assert.Contains(t, files, "applicationset/latest/kustomization.yaml") 86 assert.Contains(t, files, "applicationset/stable/kustomization.yaml") 87 }) 88 t.Run("will exclude directories from the exclusion list", func(t *testing.T) { 89 // given 90 t.Parallel() 91 exclusions := []string{"README.md", "applicationset/latest"} 92 f := setup(t) 93 defer teardown(f) 94 95 // when 96 filesWritten, err := files.Tgz(getTestAppDir(t), nil, exclusions, f.file) 97 98 // then 99 assert.Equal(t, 1, filesWritten) 100 require.NoError(t, err) 101 prepareRead(f) 102 files, err := read(f.file) 103 require.NoError(t, err) 104 assert.Len(t, files, 5) 105 assert.Contains(t, files, "applicationset/stable/kustomization.yaml") 106 }) 107 } 108 109 func TestUntgz(t *testing.T) { 110 createTmpDir := func(t *testing.T) string { 111 t.Helper() 112 tmpDir, err := os.MkdirTemp(getTestDataDir(t), "") 113 require.NoErrorf(t, err, "error creating tmpDir: %s", err) 114 return tmpDir 115 } 116 deleteTmpDir := func(t *testing.T, dirname string) { 117 t.Helper() 118 assert.NoError(t, os.RemoveAll(dirname), "error removing tmpDir") 119 } 120 createTgz := func(t *testing.T, fromDir, destDir string) *os.File { 121 t.Helper() 122 f, err := os.CreateTemp(destDir, "") 123 require.NoErrorf(t, err, "error creating tmpFile in %q: %s", destDir, err) 124 _, err = files.Tgz(fromDir, nil, nil, f) 125 require.NoErrorf(t, err, "error during Tgz: %s", err) 126 _, err = f.Seek(0, io.SeekStart) 127 require.NoErrorf(t, err, "seek error: %s", err) 128 return f 129 } 130 readFiles := func(t *testing.T, basedir string) map[string]string { 131 t.Helper() 132 names := make(map[string]string) 133 err := filepath.Walk(basedir, func(path string, info os.FileInfo, err error) error { 134 if err != nil { 135 return err 136 } 137 link := "" 138 if files.IsSymlink(info) { 139 link, err = os.Readlink(path) 140 if err != nil { 141 return err 142 } 143 } 144 relativePath, err := files.RelativePath(path, basedir) 145 require.NoError(t, err) 146 names[relativePath] = link 147 return nil 148 }) 149 require.NoErrorf(t, err, "error reading files: %s", err) 150 return names 151 } 152 t.Run("will untgz successfully", func(t *testing.T) { 153 // given 154 tmpDir := createTmpDir(t) 155 defer deleteTmpDir(t, tmpDir) 156 tgzFile := createTgz(t, getTestAppDir(t), tmpDir) 157 defer tgzFile.Close() 158 159 destDir := filepath.Join(tmpDir, "untgz1") 160 161 // when 162 err := files.Untgz(destDir, tgzFile, math.MaxInt64, false) 163 164 // then 165 require.NoError(t, err) 166 names := readFiles(t, destDir) 167 assert.Len(t, names, 8) 168 assert.Contains(t, names, "README.md") 169 assert.Contains(t, names, "applicationset/latest/kustomization.yaml") 170 assert.Contains(t, names, "applicationset/stable/kustomization.yaml") 171 assert.Contains(t, names, "applicationset/readme-symlink") 172 assert.Equal(t, "../README.md", names["applicationset/readme-symlink"]) 173 }) 174 t.Run("will protect against symlink exploit", func(t *testing.T) { 175 // given 176 tmpDir := createTmpDir(t) 177 defer deleteTmpDir(t, tmpDir) 178 tgzFile := createTgz(t, filepath.Join(getTestDataDir(t), "symlink-exploit"), tmpDir) 179 180 defer tgzFile.Close() 181 182 destDir := filepath.Join(tmpDir, "untgz2") 183 184 // when 185 err := files.Untgz(destDir, tgzFile, math.MaxInt64, false) 186 187 // then 188 assert.ErrorContains(t, err, "illegal filepath in symlink") 189 }) 190 t.Run("will protect against symlink exploit when relativizing symlinks", func(t *testing.T) { 191 // given 192 tmpDir := createTmpDir(t) 193 defer deleteTmpDir(t, tmpDir) 194 tgzFile := createTgz(t, filepath.Join(getTestDataDir(t), "symlink-exploit"), tmpDir) 195 196 defer tgzFile.Close() 197 198 destDir := filepath.Join(tmpDir, "untgz2") 199 200 // when 201 err := files.Untgz(destDir, tgzFile, math.MaxInt64, false) 202 203 // then 204 assert.ErrorContains(t, err, "illegal filepath in symlink") 205 }) 206 207 t.Run("preserves file mode", func(t *testing.T) { 208 // given 209 tmpDir := createTmpDir(t) 210 defer deleteTmpDir(t, tmpDir) 211 tgzFile := createTgz(t, filepath.Join(getTestDataDir(t), "executable"), tmpDir) 212 defer tgzFile.Close() 213 214 destDir := filepath.Join(tmpDir, "untgz1") 215 216 // when 217 err := files.Untgz(destDir, tgzFile, math.MaxInt64, true) 218 require.NoError(t, err) 219 220 // then 221 222 scriptFileInfo, err := os.Stat(path.Join(destDir, "script.sh")) 223 require.NoError(t, err) 224 assert.Equal(t, os.FileMode(0o755), scriptFileInfo.Mode()) 225 }) 226 t.Run("relativizes symlinks", func(t *testing.T) { 227 // given 228 tmpDir := createTmpDir(t) 229 defer deleteTmpDir(t, tmpDir) 230 tgzFile := createTgz(t, getTestAppDir(t), tmpDir) 231 defer tgzFile.Close() 232 233 destDir := filepath.Join(tmpDir, "symlink-relativize") 234 235 // when 236 err := files.Untgz(destDir, tgzFile, math.MaxInt64, false) 237 238 // then 239 require.NoError(t, err) 240 names := readFiles(t, destDir) 241 assert.Equal(t, "../README.md", names["applicationset/readme-symlink"]) 242 }) 243 } 244 245 // read returns a map with the filename as key. In case 246 // the file is a symlink, the value will be populated with 247 // the target file pointed by the symlink. 248 func read(tgz *os.File) (map[string]string, error) { 249 files := make(map[string]string) 250 gzr, err := gzip.NewReader(tgz) 251 if err != nil { 252 return nil, fmt.Errorf("error reading file: %w", err) 253 } 254 defer gzr.Close() 255 256 tr := tar.NewReader(gzr) 257 258 for { 259 header, err := tr.Next() 260 if err != nil { 261 if err == io.EOF { 262 break 263 } 264 return nil, fmt.Errorf("error while iterating on tar reader: %w", err) 265 } 266 if header == nil { 267 continue 268 } 269 files[header.Name] = header.Linkname 270 } 271 return files, nil 272 } 273 274 // getTestAppDir will return the full path of the app dir under 275 // the 'testdata' folder. 276 func getTestAppDir(t *testing.T) string { 277 t.Helper() 278 return filepath.Join(getTestDataDir(t), "app") 279 } 280 281 // getTestDataDir will return the full path of the testdata dir 282 // under the running test folder. 283 func getTestDataDir(t *testing.T) string { 284 t.Helper() 285 return filepath.Join(test.GetTestDir(t), "testdata") 286 }