github.com/kastenhq/syft@v0.0.0-20230821225854-0710af25cdbe/syft/formats/syftjson/to_format_model.go (about) 1 package syftjson 2 3 import ( 4 "fmt" 5 "sort" 6 "strconv" 7 8 stereoscopeFile "github.com/anchore/stereoscope/pkg/file" 9 "github.com/kastenhq/syft/internal" 10 "github.com/kastenhq/syft/internal/log" 11 "github.com/kastenhq/syft/syft/artifact" 12 "github.com/kastenhq/syft/syft/cpe" 13 "github.com/kastenhq/syft/syft/file" 14 "github.com/kastenhq/syft/syft/formats/syftjson/model" 15 "github.com/kastenhq/syft/syft/internal/sourcemetadata" 16 "github.com/kastenhq/syft/syft/linux" 17 "github.com/kastenhq/syft/syft/pkg" 18 "github.com/kastenhq/syft/syft/sbom" 19 "github.com/kastenhq/syft/syft/source" 20 ) 21 22 // ToFormatModel transforms the sbom import a format-specific model. 23 func ToFormatModel(s sbom.SBOM) model.Document { 24 return model.Document{ 25 Artifacts: toPackageModels(s.Artifacts.Packages), 26 ArtifactRelationships: toRelationshipModel(s.Relationships), 27 Files: toFile(s), 28 Secrets: toSecrets(s.Artifacts.Secrets), 29 Source: toSourceModel(s.Source), 30 Distro: toLinuxReleaser(s.Artifacts.LinuxDistribution), 31 Descriptor: toDescriptor(s.Descriptor), 32 Schema: model.Schema{ 33 Version: internal.JSONSchemaVersion, 34 URL: fmt.Sprintf("https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-%s.json", internal.JSONSchemaVersion), 35 }, 36 } 37 } 38 39 func toLinuxReleaser(d *linux.Release) model.LinuxRelease { 40 if d == nil { 41 return model.LinuxRelease{} 42 } 43 return model.LinuxRelease{ 44 PrettyName: d.PrettyName, 45 Name: d.Name, 46 ID: d.ID, 47 IDLike: d.IDLike, 48 Version: d.Version, 49 VersionID: d.VersionID, 50 VersionCodename: d.VersionCodename, 51 BuildID: d.BuildID, 52 ImageID: d.ImageID, 53 ImageVersion: d.ImageVersion, 54 Variant: d.Variant, 55 VariantID: d.VariantID, 56 HomeURL: d.HomeURL, 57 SupportURL: d.SupportURL, 58 BugReportURL: d.BugReportURL, 59 PrivacyPolicyURL: d.PrivacyPolicyURL, 60 CPEName: d.CPEName, 61 SupportEnd: d.SupportEnd, 62 } 63 } 64 65 func toDescriptor(d sbom.Descriptor) model.Descriptor { 66 return model.Descriptor{ 67 Name: d.Name, 68 Version: d.Version, 69 Configuration: d.Configuration, 70 } 71 } 72 73 func toSecrets(data map[file.Coordinates][]file.SearchResult) []model.Secrets { 74 results := make([]model.Secrets, 0) 75 for coordinates, secrets := range data { 76 results = append(results, model.Secrets{ 77 Location: coordinates, 78 Secrets: secrets, 79 }) 80 } 81 82 // sort by real path then virtual path to ensure the result is stable across multiple runs 83 sort.SliceStable(results, func(i, j int) bool { 84 return results[i].Location.RealPath < results[j].Location.RealPath 85 }) 86 return results 87 } 88 89 func toFile(s sbom.SBOM) []model.File { 90 results := make([]model.File, 0) 91 artifacts := s.Artifacts 92 93 for _, coordinates := range s.AllCoordinates() { 94 var metadata *file.Metadata 95 if metadataForLocation, exists := artifacts.FileMetadata[coordinates]; exists { 96 metadata = &metadataForLocation 97 } 98 99 var digests []file.Digest 100 if digestsForLocation, exists := artifacts.FileDigests[coordinates]; exists { 101 digests = digestsForLocation 102 } 103 104 var contents string 105 if contentsForLocation, exists := artifacts.FileContents[coordinates]; exists { 106 contents = contentsForLocation 107 } 108 109 results = append(results, model.File{ 110 ID: string(coordinates.ID()), 111 Location: coordinates, 112 Metadata: toFileMetadataEntry(coordinates, metadata), 113 Digests: digests, 114 Contents: contents, 115 }) 116 } 117 118 // sort by real path then virtual path to ensure the result is stable across multiple runs 119 sort.SliceStable(results, func(i, j int) bool { 120 return results[i].Location.RealPath < results[j].Location.RealPath 121 }) 122 return results 123 } 124 125 func toFileMetadataEntry(coordinates file.Coordinates, metadata *file.Metadata) *model.FileMetadataEntry { 126 if metadata == nil { 127 return nil 128 } 129 130 var mode int 131 var size int64 132 if metadata != nil && metadata.FileInfo != nil { 133 var err error 134 135 mode, err = strconv.Atoi(fmt.Sprintf("%o", metadata.Mode())) 136 if err != nil { 137 log.Warnf("invalid mode found in file catalog @ location=%+v mode=%q: %+v", coordinates, metadata.Mode, err) 138 mode = 0 139 } 140 141 size = metadata.Size() 142 } 143 144 return &model.FileMetadataEntry{ 145 Mode: mode, 146 Type: toFileType(metadata.Type), 147 LinkDestination: metadata.LinkDestination, 148 UserID: metadata.UserID, 149 GroupID: metadata.GroupID, 150 MIMEType: metadata.MIMEType, 151 Size: size, 152 } 153 } 154 155 func toFileType(ty stereoscopeFile.Type) string { 156 switch ty { 157 case stereoscopeFile.TypeSymLink: 158 return "SymbolicLink" 159 case stereoscopeFile.TypeHardLink: 160 return "HardLink" 161 case stereoscopeFile.TypeDirectory: 162 return "Directory" 163 case stereoscopeFile.TypeSocket: 164 return "Socket" 165 case stereoscopeFile.TypeBlockDevice: 166 return "BlockDevice" 167 case stereoscopeFile.TypeCharacterDevice: 168 return "CharacterDevice" 169 case stereoscopeFile.TypeFIFO: 170 return "FIFONode" 171 case stereoscopeFile.TypeRegular: 172 return "RegularFile" 173 case stereoscopeFile.TypeIrregular: 174 return "IrregularFile" 175 default: 176 return "Unknown" 177 } 178 } 179 180 func toPackageModels(catalog *pkg.Collection) []model.Package { 181 artifacts := make([]model.Package, 0) 182 if catalog == nil { 183 return artifacts 184 } 185 for _, p := range catalog.Sorted() { 186 artifacts = append(artifacts, toPackageModel(p)) 187 } 188 return artifacts 189 } 190 191 func toLicenseModel(pkgLicenses []pkg.License) (modelLicenses []model.License) { 192 for _, l := range pkgLicenses { 193 // guarantee collection 194 locations := make([]file.Location, 0) 195 if v := l.Locations.ToSlice(); v != nil { 196 locations = v 197 } 198 modelLicenses = append(modelLicenses, model.License{ 199 Value: l.Value, 200 SPDXExpression: l.SPDXExpression, 201 Type: l.Type, 202 URLs: l.URLs.ToSlice(), 203 Locations: locations, 204 }) 205 } 206 return 207 } 208 209 // toPackageModel crates a new Package from the given pkg.Package. 210 func toPackageModel(p pkg.Package) model.Package { 211 var cpes = make([]string, len(p.CPEs)) 212 for i, c := range p.CPEs { 213 cpes[i] = cpe.String(c) 214 } 215 216 // we want to make sure all catalogers are 217 // initializing the array; this is a good choke point for this check 218 var licenses = make([]model.License, 0) 219 if !p.Licenses.Empty() { 220 licenses = toLicenseModel(p.Licenses.ToSlice()) 221 } 222 223 return model.Package{ 224 PackageBasicData: model.PackageBasicData{ 225 ID: string(p.ID()), 226 Name: p.Name, 227 Version: p.Version, 228 Type: p.Type, 229 FoundBy: p.FoundBy, 230 Locations: p.Locations.ToSlice(), 231 Licenses: licenses, 232 Language: p.Language, 233 CPEs: cpes, 234 PURL: p.PURL, 235 }, 236 PackageCustomData: model.PackageCustomData{ 237 MetadataType: p.MetadataType, 238 Metadata: p.Metadata, 239 }, 240 } 241 } 242 243 func toRelationshipModel(relationships []artifact.Relationship) []model.Relationship { 244 result := make([]model.Relationship, len(relationships)) 245 for i, r := range relationships { 246 result[i] = model.Relationship{ 247 Parent: string(r.From.ID()), 248 Child: string(r.To.ID()), 249 Type: string(r.Type), 250 Metadata: r.Data, 251 } 252 } 253 sort.Slice(result, func(i, j int) bool { 254 if iParent, jParent := result[i].Parent, result[j].Parent; iParent != jParent { 255 return iParent < jParent 256 } 257 if iChild, jChild := result[i].Child, result[j].Child; iChild != jChild { 258 return iChild < jChild 259 } 260 return result[i].Type < result[j].Type 261 }) 262 return result 263 } 264 265 // toSourceModel creates a new source object to be represented into JSON. 266 func toSourceModel(src source.Description) model.Source { 267 m := model.Source{ 268 ID: src.ID, 269 Name: src.Name, 270 Version: src.Version, 271 Type: sourcemetadata.JSONName(src.Metadata), 272 Metadata: src.Metadata, 273 } 274 275 if metadata, ok := src.Metadata.(source.StereoscopeImageSourceMetadata); ok { 276 // ensure that empty collections are not shown as null 277 if metadata.RepoDigests == nil { 278 metadata.RepoDigests = []string{} 279 } 280 if metadata.Tags == nil { 281 metadata.Tags = []string{} 282 } 283 m.Metadata = metadata 284 } 285 286 return m 287 }