github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/sbom/cyclonedx/unmarshal_test.go (about) 1 package cyclonedx_test 2 3 import ( 4 "encoding/json" 5 "os" 6 "testing" 7 8 "github.com/stretchr/testify/assert" 9 "github.com/stretchr/testify/require" 10 11 ftypes "github.com/devseccon/trivy/pkg/fanal/types" 12 "github.com/devseccon/trivy/pkg/sbom/cyclonedx" 13 "github.com/devseccon/trivy/pkg/types" 14 ) 15 16 func TestUnmarshaler_Unmarshal(t *testing.T) { 17 tests := []struct { 18 name string 19 inputFile string 20 want types.SBOM 21 wantErr string 22 }{ 23 { 24 name: "happy path", 25 inputFile: "testdata/happy/bom.json", 26 want: types.SBOM{ 27 OS: ftypes.OS{ 28 Family: "alpine", 29 Name: "3.16.0", 30 }, 31 Packages: []ftypes.PackageInfo{ 32 { 33 Packages: ftypes.Packages{ 34 { 35 Name: "musl", 36 Version: "1.2.3-r0", 37 SrcName: "musl", 38 SrcVersion: "1.2.3-r0", 39 Licenses: []string{"MIT"}, 40 Ref: "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.0", 41 Layer: ftypes.Layer{ 42 DiffID: "sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3", 43 }, 44 }, 45 }, 46 }, 47 }, 48 Applications: []ftypes.Application{ 49 { 50 Type: ftypes.Composer, 51 FilePath: "app/composer/composer.lock", 52 Libraries: ftypes.Packages{ 53 { 54 Name: "pear/log", 55 Version: "1.13.1", 56 Ref: "pkg:composer/pear/log@1.13.1", 57 Layer: ftypes.Layer{ 58 DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", 59 }, 60 }, 61 { 62 63 Name: "pear/pear_exception", 64 Version: "v1.0.0", 65 Ref: "pkg:composer/pear/pear_exception@v1.0.0", 66 Layer: ftypes.Layer{ 67 DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", 68 }, 69 }, 70 }, 71 }, 72 { 73 Type: ftypes.GoBinary, 74 FilePath: "app/gobinary/gobinary", 75 Libraries: ftypes.Packages{ 76 { 77 Name: "github.com/package-url/packageurl-go", 78 Version: "v0.1.1-0.20220203205134-d70459300c8a", 79 Ref: "pkg:golang/github.com/package-url/packageurl-go@v0.1.1-0.20220203205134-d70459300c8a", 80 Layer: ftypes.Layer{ 81 DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", 82 }, 83 }, 84 }, 85 }, 86 { 87 Type: ftypes.Gradle, 88 FilePath: "app/gradle/target/gradle.lockfile", 89 Libraries: ftypes.Packages{ 90 { 91 Name: "com.example:example", 92 Ref: "pkg:maven/com.example/example@0.0.1", 93 Version: "0.0.1", 94 Layer: ftypes.Layer{ 95 DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", 96 }, 97 }, 98 }, 99 }, 100 { 101 Type: ftypes.Jar, 102 Libraries: ftypes.Packages{ 103 { 104 Name: "org.codehaus.mojo:child-project", 105 Ref: "pkg:maven/org.codehaus.mojo/child-project@1.0?file_path=app%2Fmaven%2Ftarget%2Fchild-project-1.0.jar", 106 Version: "1.0", 107 Layer: ftypes.Layer{ 108 DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", 109 }, 110 FilePath: "app/maven/target/child-project-1.0.jar", 111 }, 112 }, 113 }, 114 { 115 Type: ftypes.NodePkg, 116 FilePath: "", 117 Libraries: ftypes.Packages{ 118 { 119 Name: "bootstrap", 120 Version: "5.0.2", 121 Ref: "pkg:npm/bootstrap@5.0.2?file_path=app%2Fapp%2Fpackage.json", 122 Licenses: []string{"MIT"}, 123 Layer: ftypes.Layer{ 124 DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", 125 }, 126 FilePath: "app/app/package.json", 127 }, 128 }, 129 }, 130 }, 131 }, 132 }, 133 { 134 name: "happy path KBOM", 135 inputFile: "testdata/happy/kbom.json", 136 want: types.SBOM{ 137 OS: ftypes.OS{ 138 Family: "ubuntu", 139 Name: "22.04.2", 140 }, 141 Packages: []ftypes.PackageInfo{ 142 { 143 FilePath: "", 144 }, 145 }, 146 Applications: []ftypes.Application{ 147 { 148 Type: ftypes.GoBinary, 149 Libraries: ftypes.Packages{ 150 { 151 Name: "docker", 152 Version: "24.0.4", 153 Ref: "pkg:golang/docker@24.0.4", 154 }, 155 }, 156 }, 157 { 158 Type: "golang", 159 FilePath: "node-core-components", 160 }, 161 { 162 Type: ftypes.K8sUpstream, 163 Libraries: ftypes.Packages{ 164 { 165 Name: "k8s.io/apiserver", 166 Version: "1.27.4", 167 Ref: "pkg:k8s/k8s.io%2Fapiserver@1.27.4", 168 }, 169 { 170 Name: "k8s.io/controller-manager", 171 Version: "1.27.4", 172 Ref: "pkg:k8s/k8s.io%2Fcontroller-manager@1.27.4", 173 }, 174 { 175 Name: "k8s.io/kube-proxy", 176 Version: "1.27.4", 177 Ref: "pkg:k8s/k8s.io%2Fkube-proxy@1.27.4", 178 }, 179 { 180 Name: "k8s.io/kube-scheduler", 181 Version: "1.27.4", 182 Ref: "pkg:k8s/k8s.io%2Fkube-scheduler@1.27.4", 183 }, 184 { 185 Name: "k8s.io/kubelet", 186 Version: "1.27.4", 187 Ref: "pkg:k8s/k8s.io%2Fkubelet@1.27.4", 188 }, 189 { 190 Name: "k8s.io/kubernetes", 191 Version: "1.27.4", 192 Ref: "pkg:k8s/k8s.io%2Fkubernetes@1.27.4", 193 }, 194 }, 195 }, 196 }, 197 }, 198 }, 199 { 200 name: "happy path with infinity loop", 201 inputFile: "testdata/happy/infinite-loop-bom.json", 202 want: types.SBOM{ 203 OS: ftypes.OS{ 204 Family: "ubuntu", 205 Name: "22.04", 206 }, 207 Packages: []ftypes.PackageInfo{ 208 { 209 Packages: ftypes.Packages{ 210 { 211 ID: "libc6@2.35-0ubuntu3.1", 212 Name: "libc6", 213 Version: "2.35-0ubuntu3.1", 214 SrcName: "glibc", 215 SrcVersion: "2.35", 216 SrcRelease: "0ubuntu3.1", 217 Licenses: []string{ 218 "LGPL-2.1", 219 "GPL-2.0", 220 "GFDL-1.3", 221 }, 222 Ref: "pkg:deb/ubuntu/libc6@2.35-0ubuntu3.1?distro=ubuntu-22.04", 223 Layer: ftypes.Layer{ 224 Digest: "sha256:74ac377868f863e123f24c409f79709f7563fa464557c36a09cf6f85c8b92b7f", 225 DiffID: "sha256:b93c1bd012ab8fda60f5b4f5906bf244586e0e3292d84571d3abb56472248466", 226 }, 227 }, 228 { 229 ID: "libcrypt1@1:4.4.27-1", 230 Name: "libcrypt1", 231 Version: "4.4.27-1", 232 Epoch: 1, 233 SrcName: "libxcrypt", 234 SrcVersion: "4.4.27", 235 SrcRelease: "1", 236 SrcEpoch: 1, 237 Ref: "pkg:deb/ubuntu/libcrypt1@4.4.27-1?epoch=1&distro=ubuntu-22.04", 238 Layer: ftypes.Layer{ 239 Digest: "sha256:74ac377868f863e123f24c409f79709f7563fa464557c36a09cf6f85c8b92b7f", 240 DiffID: "sha256:b93c1bd012ab8fda60f5b4f5906bf244586e0e3292d84571d3abb56472248466", 241 }, 242 }, 243 }, 244 }, 245 }, 246 }, 247 }, 248 { 249 name: "happy path for third party sbom", 250 inputFile: "testdata/happy/third-party-bom.json", 251 want: types.SBOM{ 252 OS: ftypes.OS{ 253 Family: "alpine", 254 Name: "3.16.0", 255 }, 256 Packages: []ftypes.PackageInfo{ 257 { 258 Packages: ftypes.Packages{ 259 { 260 Name: "musl", 261 Version: "1.2.3-r0", 262 SrcName: "musl", 263 SrcVersion: "1.2.3-r0", 264 Licenses: []string{"MIT"}, 265 Ref: "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.0", 266 }, 267 }, 268 }, 269 }, 270 Applications: []ftypes.Application{ 271 { 272 Type: "composer", 273 FilePath: "", 274 Libraries: ftypes.Packages{ 275 { 276 Name: "pear/log", 277 Version: "1.13.1", 278 Ref: "pkg:composer/pear/log@1.13.1", 279 }, 280 { 281 282 Name: "pear/pear_exception", 283 Version: "v1.0.0", 284 Ref: "pkg:composer/pear/pear_exception@v1.0.0", 285 }, 286 }, 287 }, 288 }, 289 }, 290 }, 291 { 292 name: "happy path for third party sbom, no operation-system component", 293 inputFile: "testdata/happy/third-party-bom-no-os.json", 294 want: types.SBOM{ 295 Applications: []ftypes.Application{ 296 { 297 Type: "composer", 298 FilePath: "", 299 Libraries: ftypes.Packages{ 300 { 301 Name: "pear/log", 302 Version: "1.13.1", 303 Ref: "pkg:composer/pear/log@1.13.1", 304 }, 305 }, 306 }, 307 }, 308 }, 309 }, 310 { 311 name: "happy path for unrelated bom", 312 inputFile: "testdata/happy/unrelated-bom.json", 313 want: types.SBOM{ 314 Applications: []ftypes.Application{ 315 { 316 Type: "composer", 317 FilePath: "", 318 Libraries: ftypes.Packages{ 319 { 320 Name: "pear/log", 321 Version: "1.13.1", 322 Ref: "pkg:composer/pear/log@1.13.1", 323 }, 324 { 325 326 Name: "pear/pear_exception", 327 Version: "v1.0.0", 328 Ref: "pkg:composer/pear/pear_exception@v1.0.0", 329 }, 330 }, 331 }, 332 }, 333 }, 334 }, 335 { 336 name: "happy path for independent library bom", 337 inputFile: "testdata/happy/independent-library-bom.json", 338 want: types.SBOM{ 339 Applications: []ftypes.Application{ 340 { 341 Type: "composer", 342 FilePath: "", 343 Libraries: ftypes.Packages{ 344 { 345 Name: "pear/core", 346 Version: "1.13.1", 347 Ref: "pkg:composer/pear/core@1.13.1", 348 }, 349 { 350 Name: "pear/log", 351 Version: "1.13.1", 352 Ref: "pkg:composer/pear/log@1.13.1", 353 }, 354 { 355 356 Name: "pear/pear_exception", 357 Version: "v1.0.0", 358 Ref: "pkg:composer/pear/pear_exception@v1.0.0", 359 }, 360 }, 361 }, 362 }, 363 }, 364 }, 365 { 366 name: "happy path for jar where name is GroupID and ArtifactID", 367 inputFile: "testdata/happy/group-in-name.json", 368 want: types.SBOM{ 369 Applications: []ftypes.Application{ 370 { 371 Type: "jar", 372 Libraries: ftypes.Packages{ 373 { 374 Name: "org.springframework:spring-web", 375 Version: "5.3.22", 376 Ref: "pkg:maven/org.springframework/spring-web@5.3.22?file_path=spring-web-5.3.22.jar", 377 FilePath: "spring-web-5.3.22.jar", 378 }, 379 }, 380 }, 381 }, 382 }, 383 }, 384 { 385 name: "happy path only os component", 386 inputFile: "testdata/happy/os-only-bom.json", 387 want: types.SBOM{ 388 OS: ftypes.OS{ 389 Family: "alpine", 390 Name: "3.16.0", 391 }, 392 Packages: []ftypes.PackageInfo{ 393 {}, 394 }, 395 }, 396 }, 397 { 398 name: "happy path empty component", 399 inputFile: "testdata/happy/empty-bom.json", 400 want: types.SBOM{}, 401 }, 402 { 403 name: "happy path empty metadata component", 404 inputFile: "testdata/happy/empty-metadata-component-bom.json", 405 want: types.SBOM{}, 406 }, 407 { 408 name: "sad path invalid purl", 409 inputFile: "testdata/sad/invalid-purl.json", 410 wantErr: "failed to parse purl", 411 }, 412 } 413 414 for _, tt := range tests { 415 t.Run(tt.name, func(t *testing.T) { 416 f, err := os.Open(tt.inputFile) 417 require.NoError(t, err) 418 defer f.Close() 419 420 var cdx cyclonedx.BOM 421 err = json.NewDecoder(f).Decode(&cdx) 422 if tt.wantErr != "" { 423 require.Error(t, err) 424 assert.Contains(t, err.Error(), tt.wantErr) 425 return 426 } 427 require.NoError(t, err) 428 429 // Not compare the CycloneDX field 430 got := *cdx.SBOM 431 got.CycloneDX = nil 432 433 assert.Equal(t, tt.want, got) 434 }) 435 } 436 }