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  }