github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/pkg/packager/sources/tarball.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: 2021-Present The Jackal Authors 3 4 // Package sources contains core implementations of the PackageSource interface. 5 package sources 6 7 import ( 8 "archive/tar" 9 "errors" 10 "fmt" 11 "io" 12 "os" 13 "path/filepath" 14 15 "github.com/Racer159/jackal/src/pkg/layout" 16 "github.com/Racer159/jackal/src/pkg/message" 17 "github.com/Racer159/jackal/src/pkg/packager/filters" 18 "github.com/Racer159/jackal/src/pkg/zoci" 19 "github.com/Racer159/jackal/src/types" 20 "github.com/defenseunicorns/pkg/helpers" 21 "github.com/mholt/archiver/v3" 22 ) 23 24 var ( 25 // verify that TarballSource implements PackageSource 26 _ PackageSource = (*TarballSource)(nil) 27 ) 28 29 // TarballSource is a package source for tarballs. 30 type TarballSource struct { 31 *types.JackalPackageOptions 32 } 33 34 // LoadPackage loads a package from a tarball. 35 func (s *TarballSource) LoadPackage(dst *layout.PackagePaths, filter filters.ComponentFilterStrategy, unarchiveAll bool) (pkg types.JackalPackage, warnings []string, err error) { 36 spinner := message.NewProgressSpinner("Loading package from %q", s.PackageSource) 37 defer spinner.Stop() 38 39 if s.Shasum != "" { 40 if err := helpers.SHAsMatch(s.PackageSource, s.Shasum); err != nil { 41 return pkg, nil, err 42 } 43 } 44 45 pathsExtracted := []string{} 46 47 err = archiver.Walk(s.PackageSource, func(f archiver.File) error { 48 if f.IsDir() { 49 return nil 50 } 51 header, ok := f.Header.(*tar.Header) 52 if !ok { 53 return fmt.Errorf("expected header to be *tar.Header but was %T", f.Header) 54 } 55 path := header.Name 56 57 dir := filepath.Dir(path) 58 if dir != "." { 59 if err := os.MkdirAll(filepath.Join(dst.Base, dir), helpers.ReadExecuteAllWriteUser); err != nil { 60 return err 61 } 62 } 63 64 dstPath := filepath.Join(dst.Base, path) 65 pathsExtracted = append(pathsExtracted, path) 66 dst, err := os.Create(dstPath) 67 if err != nil { 68 return err 69 } 70 defer dst.Close() 71 72 _, err = io.Copy(dst, f) 73 if err != nil { 74 return err 75 } 76 77 return nil 78 }) 79 if err != nil { 80 return pkg, nil, err 81 } 82 83 dst.SetFromPaths(pathsExtracted) 84 85 pkg, warnings, err = dst.ReadJackalYAML() 86 if err != nil { 87 return pkg, nil, err 88 } 89 pkg.Components, err = filter.Apply(pkg) 90 if err != nil { 91 return pkg, nil, err 92 } 93 94 if err := dst.MigrateLegacy(); err != nil { 95 return pkg, nil, err 96 } 97 98 if !dst.IsLegacyLayout() { 99 spinner := message.NewProgressSpinner("Validating full package checksums") 100 defer spinner.Stop() 101 102 if err := ValidatePackageIntegrity(dst, pkg.Metadata.AggregateChecksum, false); err != nil { 103 return pkg, nil, err 104 } 105 106 spinner.Success() 107 108 if err := ValidatePackageSignature(dst, s.PublicKeyPath); err != nil { 109 return pkg, nil, err 110 } 111 } 112 113 if unarchiveAll { 114 for _, component := range pkg.Components { 115 if err := dst.Components.Unarchive(component); err != nil { 116 if layout.IsNotLoaded(err) { 117 _, err := dst.Components.Create(component) 118 if err != nil { 119 return pkg, nil, err 120 } 121 } else { 122 return pkg, nil, err 123 } 124 } 125 } 126 127 if dst.SBOMs.Path != "" { 128 if err := dst.SBOMs.Unarchive(); err != nil { 129 return pkg, nil, err 130 } 131 } 132 } 133 134 spinner.Success() 135 136 return pkg, warnings, nil 137 } 138 139 // LoadPackageMetadata loads a package's metadata from a tarball. 140 func (s *TarballSource) LoadPackageMetadata(dst *layout.PackagePaths, wantSBOM bool, skipValidation bool) (pkg types.JackalPackage, warnings []string, err error) { 141 if s.Shasum != "" { 142 if err := helpers.SHAsMatch(s.PackageSource, s.Shasum); err != nil { 143 return pkg, nil, err 144 } 145 } 146 147 toExtract := zoci.PackageAlwaysPull 148 if wantSBOM { 149 toExtract = append(toExtract, layout.SBOMTar) 150 } 151 pathsExtracted := []string{} 152 153 for _, rel := range toExtract { 154 if err := archiver.Extract(s.PackageSource, rel, dst.Base); err != nil { 155 return pkg, nil, err 156 } 157 // archiver.Extract will not return an error if the file does not exist, so we must manually check 158 if !helpers.InvalidPath(filepath.Join(dst.Base, rel)) { 159 pathsExtracted = append(pathsExtracted, rel) 160 } 161 } 162 163 dst.SetFromPaths(pathsExtracted) 164 165 pkg, warnings, err = dst.ReadJackalYAML() 166 if err != nil { 167 return pkg, nil, err 168 } 169 170 if err := dst.MigrateLegacy(); err != nil { 171 return pkg, nil, err 172 } 173 174 if !dst.IsLegacyLayout() { 175 if wantSBOM { 176 spinner := message.NewProgressSpinner("Validating SBOM checksums") 177 defer spinner.Stop() 178 179 if err := ValidatePackageIntegrity(dst, pkg.Metadata.AggregateChecksum, true); err != nil { 180 return pkg, nil, err 181 } 182 183 spinner.Success() 184 } 185 186 if err := ValidatePackageSignature(dst, s.PublicKeyPath); err != nil { 187 if errors.Is(err, ErrPkgSigButNoKey) && skipValidation { 188 message.Warn("The package was signed but no public key was provided, skipping signature validation") 189 } else { 190 return pkg, nil, err 191 } 192 } 193 } 194 195 if wantSBOM { 196 if err := dst.SBOMs.Unarchive(); err != nil { 197 return pkg, nil, err 198 } 199 } 200 201 return pkg, warnings, nil 202 } 203 204 // Collect for the TarballSource is essentially an `mv` 205 func (s *TarballSource) Collect(dir string) (string, error) { 206 dst := filepath.Join(dir, filepath.Base(s.PackageSource)) 207 return dst, os.Rename(s.PackageSource, dst) 208 }