github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/format/syftjson/to_syft_model.go (about) 1 package syftjson 2 3 import ( 4 "fmt" 5 "os" 6 "path" 7 "strconv" 8 "strings" 9 10 "github.com/google/go-cmp/cmp" 11 12 stereoscopeFile "github.com/anchore/stereoscope/pkg/file" 13 "github.com/anchore/syft/syft/artifact" 14 "github.com/anchore/syft/syft/cpe" 15 "github.com/anchore/syft/syft/file" 16 "github.com/anchore/syft/syft/format/syftjson/model" 17 "github.com/anchore/syft/syft/linux" 18 "github.com/anchore/syft/syft/pkg" 19 "github.com/anchore/syft/syft/sbom" 20 "github.com/anchore/syft/syft/source" 21 "github.com/lineaje-labs/syft/internal/log" 22 ) 23 24 func toSyftModel(doc model.Document) *sbom.SBOM { 25 idAliases := make(map[string]string) 26 27 catalog := toSyftCatalog(doc.Artifacts, idAliases) 28 29 fileArtifacts := toSyftFiles(doc.Files) 30 31 return &sbom.SBOM{ 32 Artifacts: sbom.Artifacts{ 33 Packages: catalog, 34 FileMetadata: fileArtifacts.FileMetadata, 35 FileDigests: fileArtifacts.FileDigests, 36 FileContents: fileArtifacts.FileContents, 37 FileLicenses: fileArtifacts.FileLicenses, 38 LinuxDistribution: toSyftLinuxRelease(doc.Distro), 39 }, 40 Source: *toSyftSourceData(doc.Source), 41 Descriptor: toSyftDescriptor(doc.Descriptor), 42 Relationships: warnConversionErrors(toSyftRelationships(&doc, catalog, doc.ArtifactRelationships, idAliases)), 43 } 44 } 45 46 func warnConversionErrors[T any](converted []T, errors []error) []T { 47 errorMessages := deduplicateErrors(errors) 48 for _, msg := range errorMessages { 49 log.Warn(msg) 50 } 51 return converted 52 } 53 54 func deduplicateErrors(errors []error) []string { 55 errorCounts := make(map[string]int) 56 var errorMessages []string 57 for _, e := range errors { 58 errorCounts[e.Error()] = errorCounts[e.Error()] + 1 59 } 60 for msg, count := range errorCounts { 61 errorMessages = append(errorMessages, fmt.Sprintf("%q occurred %d time(s)", msg, count)) 62 } 63 return errorMessages 64 } 65 66 func toSyftFiles(files []model.File) sbom.Artifacts { 67 ret := sbom.Artifacts{ 68 FileMetadata: make(map[file.Coordinates]file.Metadata), 69 FileDigests: make(map[file.Coordinates][]file.Digest), 70 FileContents: make(map[file.Coordinates]string), 71 FileLicenses: make(map[file.Coordinates][]file.License), 72 } 73 74 for _, f := range files { 75 coord := f.Location 76 if f.Metadata != nil { 77 mode, err := strconv.ParseInt(strconv.Itoa(f.Metadata.Mode), 8, 64) 78 if err != nil { 79 log.Warnf("invalid mode found in file catalog @ location=%+v mode=%q: %+v", coord, f.Metadata.Mode, err) 80 mode = 0 81 } 82 83 fm := os.FileMode(mode) 84 85 ret.FileMetadata[coord] = file.Metadata{ 86 FileInfo: stereoscopeFile.ManualInfo{ 87 NameValue: path.Base(coord.RealPath), 88 SizeValue: f.Metadata.Size, 89 ModeValue: fm, 90 }, 91 Path: coord.RealPath, 92 LinkDestination: f.Metadata.LinkDestination, 93 UserID: f.Metadata.UserID, 94 GroupID: f.Metadata.GroupID, 95 Type: toSyftFileType(f.Metadata.Type), 96 MIMEType: f.Metadata.MIMEType, 97 } 98 } 99 100 for _, d := range f.Digests { 101 ret.FileDigests[coord] = append(ret.FileDigests[coord], file.Digest{ 102 Algorithm: d.Algorithm, 103 Value: d.Value, 104 }) 105 } 106 107 if f.Contents != "" { 108 ret.FileContents[coord] = f.Contents 109 } 110 111 for _, l := range f.Licenses { 112 var evidence *file.LicenseEvidence 113 if e := l.Evidence; e != nil { 114 evidence = &file.LicenseEvidence{ 115 Confidence: e.Confidence, 116 Offset: e.Offset, 117 Extent: e.Extent, 118 } 119 } 120 ret.FileLicenses[coord] = append(ret.FileLicenses[coord], file.License{ 121 Value: l.Value, 122 SPDXExpression: l.SPDXExpression, 123 Type: l.Type, 124 LicenseEvidence: evidence, 125 }) 126 } 127 } 128 129 return ret 130 } 131 132 func toSyftLicenses(m []model.License) (p []pkg.License) { 133 for _, l := range m { 134 p = append(p, pkg.License{ 135 Value: l.Value, 136 SPDXExpression: l.SPDXExpression, 137 Type: l.Type, 138 URLs: l.URLs, 139 Locations: file.NewLocationSet(l.Locations...), 140 }) 141 } 142 return 143 } 144 145 func toSyftFileType(ty string) stereoscopeFile.Type { 146 switch ty { 147 case "SymbolicLink": 148 return stereoscopeFile.TypeSymLink 149 case "HardLink": 150 return stereoscopeFile.TypeHardLink 151 case "Directory": 152 return stereoscopeFile.TypeDirectory 153 case "Socket": 154 return stereoscopeFile.TypeSocket 155 case "BlockDevice": 156 return stereoscopeFile.TypeBlockDevice 157 case "CharacterDevice": 158 return stereoscopeFile.TypeCharacterDevice 159 case "FIFONode": 160 return stereoscopeFile.TypeFIFO 161 case "RegularFile": 162 return stereoscopeFile.TypeRegular 163 case "IrregularFile": 164 return stereoscopeFile.TypeIrregular 165 default: 166 return stereoscopeFile.TypeIrregular 167 } 168 } 169 170 func toSyftLinuxRelease(d model.LinuxRelease) *linux.Release { 171 if cmp.Equal(d, model.LinuxRelease{}) { 172 return nil 173 } 174 return &linux.Release{ 175 PrettyName: d.PrettyName, 176 Name: d.Name, 177 ID: d.ID, 178 IDLike: d.IDLike, 179 Version: d.Version, 180 VersionID: d.VersionID, 181 VersionCodename: d.VersionCodename, 182 BuildID: d.BuildID, 183 ImageID: d.ImageID, 184 ImageVersion: d.ImageVersion, 185 Variant: d.Variant, 186 VariantID: d.VariantID, 187 HomeURL: d.HomeURL, 188 SupportURL: d.SupportURL, 189 BugReportURL: d.BugReportURL, 190 PrivacyPolicyURL: d.PrivacyPolicyURL, 191 CPEName: d.CPEName, 192 SupportEnd: d.SupportEnd, 193 } 194 } 195 196 func toSyftRelationships( 197 doc *model.Document, catalog *pkg.Collection, relationships []model.Relationship, idAliases map[string]string, 198 ) ([]artifact.Relationship, []error) { 199 idMap := make(map[string]interface{}) 200 201 for _, p := range catalog.Sorted() { 202 idMap[string(p.ID())] = p 203 locations := p.Locations.ToSlice() 204 for _, l := range locations { 205 idMap[string(l.Coordinates.ID())] = l.Coordinates 206 } 207 } 208 209 // set source metadata in identifier map 210 idMap[doc.Source.ID] = toSyftSource(doc.Source) 211 212 for _, f := range doc.Files { 213 idMap[f.ID] = f.Location 214 } 215 216 var out []artifact.Relationship 217 var conversionErrors []error 218 for _, r := range relationships { 219 syftRelationship, err := toSyftRelationship(idMap, r, idAliases) 220 if err != nil { 221 conversionErrors = append(conversionErrors, err) 222 } 223 if syftRelationship != nil { 224 out = append(out, *syftRelationship) 225 } 226 } 227 228 return out, conversionErrors 229 } 230 231 func toSyftSource(s model.Source) source.Source { 232 description := toSyftSourceData(s) 233 if description == nil { 234 return nil 235 } 236 return source.FromDescription(*description) 237 } 238 239 func toSyftRelationship( 240 idMap map[string]interface{}, relationship model.Relationship, idAliases map[string]string, 241 ) (*artifact.Relationship, error) { 242 id := func(id string) string { 243 aliased, ok := idAliases[id] 244 if ok { 245 return aliased 246 } 247 return id 248 } 249 250 from, ok := idMap[id(relationship.Parent)].(artifact.Identifiable) 251 if !ok { 252 return nil, fmt.Errorf("relationship mapping from key %s is not a valid artifact.Identifiable type: %+v", relationship.Parent, idMap[relationship.Parent]) 253 } 254 255 to, ok := idMap[id(relationship.Child)].(artifact.Identifiable) 256 if !ok { 257 return nil, fmt.Errorf("relationship mapping to key %s is not a valid artifact.Identifiable type: %+v", relationship.Child, idMap[relationship.Child]) 258 } 259 260 typ := artifact.RelationshipType(relationship.Type) 261 262 switch typ { 263 case artifact.OwnershipByFileOverlapRelationship, artifact.ContainsRelationship, artifact.DependencyOfRelationship, artifact.EvidentByRelationship: 264 default: 265 if !strings.Contains(string(typ), "dependency-of") { 266 return nil, fmt.Errorf("unknown relationship type: %s", string(typ)) 267 } 268 // lets try to stay as compatible as possible with similar relationship types without dropping the relationship 269 log.Warnf("assuming %q for relationship type %q", artifact.DependencyOfRelationship, typ) 270 typ = artifact.DependencyOfRelationship 271 } 272 return &artifact.Relationship{ 273 From: from, 274 To: to, 275 Type: typ, 276 Data: relationship.Metadata, 277 }, nil 278 } 279 280 func toSyftDescriptor(d model.Descriptor) sbom.Descriptor { 281 return sbom.Descriptor{ 282 Name: d.Name, 283 Version: d.Version, 284 Configuration: d.Configuration, 285 } 286 } 287 288 func toSyftSourceData(s model.Source) *source.Description { 289 return &source.Description{ 290 ID: s.ID, 291 Name: s.Name, 292 Version: s.Version, 293 Metadata: s.Metadata, 294 } 295 } 296 297 func toSyftCatalog(pkgs []model.Package, idAliases map[string]string) *pkg.Collection { 298 catalog := pkg.NewCollection() 299 for _, p := range pkgs { 300 catalog.Add(toSyftPackage(p, idAliases)) 301 } 302 return catalog 303 } 304 305 func toSyftPackage(p model.Package, idAliases map[string]string) pkg.Package { 306 var cpes []cpe.CPE 307 for _, c := range p.CPEs { 308 value, err := cpe.New(c) 309 if err != nil { 310 log.Warnf("excluding invalid CPE %q: %v", c, err) 311 continue 312 } 313 314 cpes = append(cpes, value) 315 } 316 317 out := pkg.Package{ 318 Name: p.Name, 319 Version: p.Version, 320 FoundBy: p.FoundBy, 321 Locations: file.NewLocationSet(p.Locations...), 322 Licenses: pkg.NewLicenseSet(toSyftLicenses(p.Licenses)...), 323 Language: p.Language, 324 Type: p.Type, 325 CPEs: cpes, 326 PURL: p.PURL, 327 Metadata: p.Metadata, 328 } 329 330 // we don't know if this package ID is truly unique, however, we need to trust the user input in case there are 331 // external references to it. That is, we can't derive our own ID (using pkg.SetID()) since consumers won't 332 // be able to historically interact with data that references the IDs from the original SBOM document being decoded now. 333 out.OverrideID(artifact.ID(p.ID)) 334 335 // this alias mapping is currently defunct, but could be useful in the future. 336 id := string(out.ID()) 337 if id != p.ID { 338 idAliases[p.ID] = id 339 } 340 341 return out 342 }