github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/format/common/cyclonedxhelpers/decoder_test.go (about) 1 package cyclonedxhelpers 2 3 import ( 4 "fmt" 5 "testing" 6 7 "github.com/CycloneDX/cyclonedx-go" 8 "github.com/stretchr/testify/assert" 9 "github.com/stretchr/testify/require" 10 11 "github.com/anchore/syft/syft/artifact" 12 "github.com/anchore/syft/syft/pkg" 13 "github.com/anchore/syft/syft/sbom" 14 ) 15 16 func Test_decode(t *testing.T) { 17 type expected struct { 18 os string 19 pkg string 20 ver string 21 relation string 22 purl string 23 cpe string 24 } 25 tests := []struct { 26 name string 27 input cyclonedx.BOM 28 expected []expected 29 }{ 30 { 31 name: "basic mapping from cyclonedx", 32 input: cyclonedx.BOM{ 33 Metadata: nil, 34 Components: &[]cyclonedx.Component{ 35 { 36 BOMRef: "p1", 37 Type: cyclonedx.ComponentTypeLibrary, 38 Name: "package-1", 39 Version: "1.0.1", 40 Description: "", 41 Hashes: nil, 42 Licenses: &cyclonedx.Licenses{ 43 { 44 License: &cyclonedx.License{ 45 ID: "MIT", 46 }, 47 }, 48 }, 49 CPE: "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*", 50 PackageURL: "pkg:some/package-1@1.0.1?arch=arm64&upstream=upstream1&distro=alpine-1", 51 ExternalReferences: &[]cyclonedx.ExternalReference{ 52 { 53 URL: "", 54 Comment: "", 55 Hashes: nil, 56 Type: "", 57 }, 58 }, 59 Properties: &[]cyclonedx.Property{ 60 { 61 Name: "foundBy", 62 Value: "the-cataloger-1", 63 }, 64 { 65 Name: "language", 66 Value: "python", 67 }, 68 { 69 Name: "type", 70 Value: "python", 71 }, 72 { 73 Name: "metadataType", 74 Value: "PythonPackageMetadata", 75 }, 76 { 77 Name: "path", 78 Value: "/some/path/pkg1", 79 }, 80 }, 81 Components: nil, 82 Evidence: nil, 83 }, 84 { 85 BOMRef: "p2", 86 Type: cyclonedx.ComponentTypeLibrary, 87 Name: "package-2", 88 Version: "2.0.2", 89 Hashes: nil, 90 Licenses: &cyclonedx.Licenses{ 91 { 92 License: &cyclonedx.License{ 93 ID: "MIT", 94 }, 95 }, 96 }, 97 CPE: "cpe:2.3:*:another:package:2:*:*:*:*:*:*:*", 98 PackageURL: "pkg:apk/alpine/alpine-baselayout@3.2.0-r16?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.14.2", 99 Properties: &[]cyclonedx.Property{ 100 { 101 Name: "foundBy", 102 Value: "apkdb-cataloger", 103 }, 104 { 105 Name: "type", 106 Value: "apk", 107 }, 108 { 109 Name: "metadataType", 110 Value: "ApkMetadata", 111 }, 112 { 113 Name: "path", 114 Value: "/lib/apk/db/installed", 115 }, 116 { 117 Name: "layerID", 118 Value: "sha256:9fb3aa2f8b8023a4bebbf92aa567caf88e38e969ada9f0ac12643b2847391635", 119 }, 120 { 121 Name: "originPackage", 122 Value: "zlib", 123 }, 124 { 125 Name: "size", 126 Value: "51213", 127 }, 128 { 129 Name: "installedSize", 130 Value: "110592", 131 }, 132 { 133 Name: "pullDependencies", 134 Value: "so:libc.musl-x86_64.so.1", 135 }, 136 { 137 Name: "pullChecksum", 138 Value: "Q1uss4DfpvL16Nw2YUTwmzGBABz3Y=", 139 }, 140 { 141 Name: "gitCommitOfApkPort", 142 Value: "d2bfb22c8e8f67ad7d8d02704f35ec4d2a19f9b9", 143 }, 144 }, 145 }, 146 { 147 Type: cyclonedx.ComponentTypeOS, 148 Name: "debian", 149 Version: "1.2.3", 150 Hashes: nil, 151 Licenses: &cyclonedx.Licenses{ 152 { 153 License: &cyclonedx.License{ 154 ID: "MIT", 155 }, 156 }, 157 }, 158 Properties: &[]cyclonedx.Property{ 159 { 160 Name: "prettyName", 161 Value: "debian", 162 }, 163 { 164 Name: "id", 165 Value: "debian", 166 }, 167 { 168 Name: "versionID", 169 Value: "1.2.3", 170 }, 171 }, 172 Components: nil, 173 Evidence: nil, 174 }, 175 }, 176 Dependencies: &[]cyclonedx.Dependency{ 177 { 178 Ref: "p1", 179 Dependencies: &[]string{"p2"}, 180 }, 181 }, 182 }, 183 expected: []expected{ 184 { 185 os: "debian", 186 ver: "1.2.3", 187 }, 188 { 189 pkg: "package-1", 190 ver: "1.0.1", 191 cpe: "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*", 192 purl: "pkg:some/package-1@1.0.1?arch=arm64&upstream=upstream1&distro=alpine-1", 193 }, 194 { 195 pkg: "package-2", 196 ver: "2.0.2", 197 purl: "pkg:apk/alpine/alpine-baselayout@3.2.0-r16?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.14.2", 198 relation: "package-1", 199 }, 200 }, 201 }, 202 } 203 for _, test := range tests { 204 t.Run(test.name, func(t *testing.T) { 205 sbom, err := ToSyftModel(&test.input) 206 assert.NoError(t, err) 207 208 test: 209 for _, e := range test.expected { 210 if e.os != "" { 211 assert.Equal(t, e.os, sbom.Artifacts.LinuxDistribution.ID) 212 assert.Equal(t, e.ver, sbom.Artifacts.LinuxDistribution.VersionID) 213 } 214 if e.pkg != "" { 215 for p := range sbom.Artifacts.Packages.Enumerate() { 216 if e.pkg != p.Name { 217 continue 218 } 219 220 assert.Equal(t, e.ver, p.Version) 221 222 if e.cpe != "" { 223 foundCPE := false 224 for _, c := range p.CPEs { 225 cstr := c.BindToFmtString() 226 if e.cpe == cstr { 227 foundCPE = true 228 break 229 } 230 } 231 if !foundCPE { 232 assert.Fail(t, fmt.Sprintf("CPE not found in package: %s", e.cpe)) 233 } 234 } 235 236 if e.purl != "" { 237 assert.Equal(t, e.purl, p.PURL) 238 } 239 240 if e.relation != "" { 241 foundRelation := false 242 for _, r := range sbom.Relationships { 243 p := sbom.Artifacts.Packages.Package(r.To.ID()) 244 if e.relation == p.Name { 245 foundRelation = true 246 break 247 } 248 } 249 if !foundRelation { 250 assert.Fail(t, fmt.Sprintf("relation not found: %s", e.relation)) 251 } 252 } 253 continue test 254 } 255 assert.Fail(t, fmt.Sprintf("package should be present: %s", e.pkg)) 256 } 257 } 258 }) 259 } 260 } 261 262 func Test_relationshipDirection(t *testing.T) { 263 cyclonedx_bom := cyclonedx.BOM{Metadata: nil, 264 Components: &[]cyclonedx.Component{ 265 { 266 BOMRef: "p1", 267 Type: cyclonedx.ComponentTypeLibrary, 268 Name: "package-1", 269 Version: "1.0.1", 270 PackageURL: "pkg:some/package-1@1.0.1?arch=arm64&upstream=upstream1&distro=alpine-1", 271 }, 272 { 273 BOMRef: "p2", 274 Type: cyclonedx.ComponentTypeLibrary, 275 Name: "package-2", 276 Version: "2.0.2", 277 PackageURL: "pkg:some/package-2@2.0.2?arch=arm64&upstream=upstream1&distro=alpine-1", 278 }, 279 }, 280 Dependencies: &[]cyclonedx.Dependency{ 281 { 282 Ref: "p1", 283 Dependencies: &[]string{"p2"}, 284 }, 285 }} 286 sbom, err := ToSyftModel(&cyclonedx_bom) 287 assert.Nil(t, err) 288 assert.Len(t, sbom.Relationships, 1) 289 relationship := sbom.Relationships[0] 290 291 // check that p2 -- dependency of --> p1 292 // same as p1 -- depends on --> p2 293 assert.Equal(t, artifact.DependencyOfRelationship, relationship.Type) 294 assert.Equal(t, "package-2", packageNameFromIdentifier(sbom, relationship.From)) 295 assert.Equal(t, "package-1", packageNameFromIdentifier(sbom, relationship.To)) 296 } 297 298 func packageNameFromIdentifier(model *sbom.SBOM, identifier artifact.Identifiable) string { 299 return model.Artifacts.Packages.Package(identifier.ID()).Name 300 } 301 302 func Test_missingDataDecode(t *testing.T) { 303 bom := &cyclonedx.BOM{ 304 Metadata: nil, 305 Components: &[]cyclonedx.Component{}, 306 SpecVersion: cyclonedx.SpecVersion1_4, 307 } 308 309 _, err := ToSyftModel(bom) 310 assert.NoError(t, err) 311 312 bom.Metadata = &cyclonedx.Metadata{} 313 314 _, err = ToSyftModel(bom) 315 assert.NoError(t, err) 316 317 pkg := decodeComponent(&cyclonedx.Component{ 318 Licenses: &cyclonedx.Licenses{ 319 { 320 License: nil, 321 }, 322 }, 323 }) 324 assert.Equal(t, pkg.Licenses.Empty(), true) 325 } 326 327 func Test_decodeDependencies(t *testing.T) { 328 c1 := cyclonedx.Component{ 329 Name: "c1", 330 } 331 332 c2 := cyclonedx.Component{ 333 Name: "c2", 334 } 335 336 c3 := cyclonedx.Component{ 337 Name: "c3", 338 } 339 340 for _, c := range []*cyclonedx.Component{&c1, &c2, &c3} { 341 c.BOMRef = c.Name 342 } 343 344 setTypes := func(typ cyclonedx.ComponentType, components ...cyclonedx.Component) *[]cyclonedx.Component { 345 var out []cyclonedx.Component 346 for _, c := range components { 347 c.Type = typ 348 out = append(out, c) 349 } 350 return &out 351 } 352 353 tests := []struct { 354 name string 355 sbom cyclonedx.BOM 356 expected []string 357 }{ 358 { 359 name: "dependencies decoded as dependencyOf relationships", 360 sbom: cyclonedx.BOM{ 361 Components: setTypes(cyclonedx.ComponentTypeLibrary, 362 c1, 363 c2, 364 c3, 365 ), 366 Dependencies: &[]cyclonedx.Dependency{ 367 { 368 Ref: c1.BOMRef, 369 Dependencies: &[]string{ 370 c2.BOMRef, 371 c3.BOMRef, 372 }, 373 }, 374 }, 375 }, 376 expected: []string{c2.Name, c3.Name}, 377 }, 378 { 379 name: "dependencies skipped with unhandled components", 380 sbom: cyclonedx.BOM{ 381 Components: setTypes("", 382 c1, 383 c2, 384 c3, 385 ), 386 Dependencies: &[]cyclonedx.Dependency{ 387 { 388 Ref: c1.BOMRef, 389 Dependencies: &[]string{ 390 c2.BOMRef, 391 c3.BOMRef, 392 }, 393 }, 394 }, 395 }, 396 expected: nil, 397 }, 398 } 399 400 for _, test := range tests { 401 t.Run(test.name, func(t *testing.T) { 402 s, err := ToSyftModel(&test.sbom) 403 require.NoError(t, err) 404 require.NotNil(t, s) 405 406 var deps []string 407 if s != nil { 408 for _, r := range s.Relationships { 409 if r.Type != artifact.DependencyOfRelationship { 410 continue 411 } 412 if p, ok := r.To.(pkg.Package); !ok || p.Name != c1.Name { 413 continue 414 } 415 if p, ok := r.From.(pkg.Package); ok { 416 deps = append(deps, p.Name) 417 } 418 } 419 } 420 require.Equal(t, test.expected, deps) 421 }) 422 } 423 }