Skip to content

Commit

Permalink
feat(sbom): add support for scanning a sbom attestation (#2652)
Browse files Browse the repository at this point in the history
Co-authored-by: knqyf263 <[email protected]>
  • Loading branch information
otms61 and knqyf263 authored Aug 8, 2022
1 parent 390c256 commit 317a026
Show file tree
Hide file tree
Showing 16 changed files with 300 additions and 8 deletions.
3 changes: 2 additions & 1 deletion docs/docs/attestation/sbom.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ $ trivy image --format spdx-json -o sbom.spdx.json <IMAGE>
$ cosign attest --key /path/to/cosign.key --type spdx --predicate sbom.spdx.json <IMAGE>
# cyclonedx
# The cyclonedx type is supported in Cosign v1.10.0 or later.
$ trivy image --format cyclonedx -o sbom.cdx.json <IMAGE>
$ cosign attest --key /path/to/cosign.key --type https://cyclonedx.org/schema --predicate sbom.cdx.json <IMAGE>
$ cosign attest --key /path/to/cosign.key --type cyclonedx --predicate sbom.cdx.json <IMAGE>
```

## Keyless signing
Expand Down
3 changes: 3 additions & 0 deletions docs/docs/references/cli/sbom.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ Examples:
# Scan CycloneDX and generate a CycloneDX report
$ trivy sbom --format cyclonedx /path/to/report.cdx

# Scan CycloneDX-type attestation and show the result in tables
$ trivy sbom /path/to/report.cdx.intoto.jsonl


Scan Flags
--offline-scan do not issue API requests to identify dependencies
Expand Down
27 changes: 27 additions & 0 deletions docs/docs/sbom/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ $ trivy fs --format cyclonedx --output result.json /app/myproject
Trivy also can take the following SBOM formats as an input and scan for vulnerabilities.

- CycloneDX
- CycloneDX-type attestation

To scan SBOM, you can use the `sbom` subcommand and pass the path to the SBOM.

Expand Down Expand Up @@ -209,5 +210,31 @@ Total: 3 (CRITICAL: 3)
!!! note
CycloneDX XML and SPDX are not supported at the moment.

You can also scan an SBOM attestation.
In the following example, [Cosign][Cosign] can get an attestation and trivy scan it.
To learn more about how to create an SBOM attestation and attach it to an image, see the [SBOM attestation page][sbom_attestation].
```bash
$ cosign verify-attestation --key /path/to/cosign.pub --type cyclonedx <IMAGE> > sbom.cdx.intoto.jsonl
$ trivy sbom ./sbom.cdx.intoto.jsonl

sbom.cdx.intoto.jsonl (alpine 3.7.3)
=========================
Total: 2 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 2)

┌────────────┬────────────────┬──────────┬───────────────────┬───────────────┬──────────────────────────────────────────────────────────┐
│ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │ Title │
├────────────┼────────────────┼──────────┼───────────────────┼───────────────┼──────────────────────────────────────────────────────────┤
│ musl │ CVE-2019-14697 │ CRITICAL │ 1.1.18-r3 │ 1.1.18-r4 │ musl libc through 1.1.23 has an x87 floating-point stack │
│ │ │ │ │ │ adjustment im ...... │
│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2019-14697 │
├────────────┤ │ │ │ │ │
│ musl-utils │ │ │ │ │ │
│ │ │ │ │ │ │
│ │ │ │ │ │ │
└────────────┴────────────────┴──────────┴───────────────────┴───────────────┴──────────────────────────────────────────────────────────┘
```

[cyclonedx]: cyclonedx.md
[spdx]: spdx.md
[Cosign]: https://github.com/sigstore/cosign
[sbom_attestation]: ../attestation/sbom.md
7 changes: 6 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ require (
github.com/google/uuid v1.3.0
github.com/google/wire v0.5.0
github.com/hashicorp/go-getter v1.6.2
github.com/in-toto/in-toto-golang v0.3.4-0.20220709202702-fa494aaa0add
github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d
github.com/knqyf263/go-rpm-version v0.0.0-20220614171824-631e686d1075
Expand All @@ -46,6 +47,7 @@ require (
github.com/owenrumney/go-sarif/v2 v2.1.2
github.com/package-url/packageurl-go v0.1.1-0.20220203205134-d70459300c8a
github.com/samber/lo v1.27.0
github.com/secure-systems-lab/go-securesystemslib v0.4.0
github.com/sosedoff/gitkit v0.3.0
github.com/spf13/cobra v1.5.0
github.com/spf13/pflag v1.0.5
Expand All @@ -64,7 +66,10 @@ require (
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
)

require github.com/emicklei/go-restful/v3 v3.8.0 // indirect
require (
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
github.com/shibumi/go-pathspec v1.3.0 // indirect
)

require (
cloud.google.com/go v0.100.2 // indirect
Expand Down
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:z
github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE=
github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU=
github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=
Expand Down Expand Up @@ -922,6 +923,8 @@ github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/in-toto/in-toto-golang v0.3.4-0.20220709202702-fa494aaa0add h1:DAh7mHiRT7wc6kKepYdCpH16ElPciMPQWJaJ7H3l/ng=
github.com/in-toto/in-toto-golang v0.3.4-0.20220709202702-fa494aaa0add/go.mod h1:DQI8vlV6h6qSY/tCOoYKtxjWrkyiNpJ3WTV/WoBllmQ=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ=
Expand Down Expand Up @@ -1355,9 +1358,13 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
github.com/secure-systems-lab/go-securesystemslib v0.4.0 h1:b23VGrQhTA8cN2CbBw7/FulN9fTtqYUdS5+Oxzt+DUE=
github.com/secure-systems-lab/go-securesystemslib v0.4.0/go.mod h1:FGBZgq2tXWICsxWQW1msNf49F0Pf2Op5Htayx335Qbs=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI=
github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE=
github.com/shogo82148/go-shuffle v0.0.0-20170808115208-59829097ff3b h1:VI1u+o2KZPZ5AhuPpXY0JBdpQPnkTx6Dd5XJhK/9MYE=
github.com/shogo82148/go-shuffle v0.0.0-20170808115208-59829097ff3b/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
Expand Down
9 changes: 9 additions & 0 deletions integration/sbom_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ func TestCycloneDX(t *testing.T) {
},
golden: "testdata/fluentd-multiple-lockfiles-cyclonedx.json.golden",
},
{
name: "centos7-bom in in-toto attestation",
args: args{
input: "testdata/fixtures/sbom/centos-7-cyclonedx.intoto.jsonl",
format: "cyclonedx",
artifactType: "cyclonedx",
},
golden: "testdata/centos-7-cyclonedx.json.golden",
},
}

// Set up testing DB
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2N5Y2xvbmVkeC5vcmcvc2NoZW1hIiwic3ViamVjdCI6W3sibmFtZSI6ImdoY3IuaW8vYXF1YXNlY3VyaXR5L3RyaXZ5LXRlc3QtaW1hZ2VzIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjcyYzQyZWQ0OGMzYTJkYjMxYjdkYWZlMTdkMjc1YjYzNDY2NGE3MDhkOTAxZWM5ZmQ1N2IxNTI5MjgwZjAxZmIifX1dLCJwcmVkaWNhdGUiOnsiRGF0YSI6eyJib21Gb3JtYXQiOiJDeWNsb25lRFgiLCJjb21wb25lbnRzIjpbeyJib20tcmVmIjoicGtnOnJwbS9jZW50b3MvYmFzaEA0LjIuNDYtMzEuZWw3P2FyY2g9eDg2XzY0XHUwMDI2ZGlzdHJvPWNlbnRvcy03LjYuMTgxMCIsImxpY2Vuc2VzIjpbeyJleHByZXNzaW9uIjoiR1BMdjMrIn1dLCJuYW1lIjoiYmFzaCIsInByb3BlcnRpZXMiOlt7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6U3JjTmFtZSIsInZhbHVlIjoiYmFzaCJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpTcmNWZXJzaW9uIiwidmFsdWUiOiI0LjIuNDYifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6U3JjUmVsZWFzZSIsInZhbHVlIjoiMzEuZWw3In0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OkxheWVyRGlnZXN0IiwidmFsdWUiOiJzaGEyNTY6YWM5MjA4MjA3YWRhYWMzYTQ4ZTU0YTRkYzZiNDljNjllNzhjMzA3MmQyYjNhZGQ3ZWZkYWJmODE0ZGIyMTMzYiJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpMYXllckRpZmZJRCIsInZhbHVlIjoic2hhMjU2Ojg5MTY5ZDg3ZGJlMmI3MmJhNDJiZmJiMzU3OWM5NTczMjJiYWNhMjhlMDNhMWU1NTgwNzY1NDJhMWMxYjJiNGEifV0sInB1cmwiOiJwa2c6cnBtL2NlbnRvcy9iYXNoQDQuMi40Ni0zMS5lbDc/YXJjaD14ODZfNjRcdTAwMjZkaXN0cm89Y2VudG9zLTcuNi4xODEwIiwidHlwZSI6ImxpYnJhcnkiLCJ2ZXJzaW9uIjoiNC4yLjQ2LTMxLmVsNyJ9LHsiYm9tLXJlZiI6InBrZzpycG0vY2VudG9zL29wZW5zc2wtbGlic0AxOjEuMC4yay0xNi5lbDc/YXJjaD14ODZfNjRcdTAwMjZkaXN0cm89Y2VudG9zLTcuNi4xODEwIiwibGljZW5zZXMiOlt7ImV4cHJlc3Npb24iOiJPcGVuU1NMIn1dLCJuYW1lIjoib3BlbnNzbC1saWJzIiwicHJvcGVydGllcyI6W3sibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpTcmNOYW1lIiwidmFsdWUiOiJvcGVuc3NsIn0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlNyY1ZlcnNpb24iLCJ2YWx1ZSI6IjEuMC4yayJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpTcmNSZWxlYXNlIiwidmFsdWUiOiIxNi5lbDcifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6U3JjRXBvY2giLCJ2YWx1ZSI6IjEifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6TGF5ZXJEaWdlc3QiLCJ2YWx1ZSI6InNoYTI1NjphYzkyMDgyMDdhZGFhYzNhNDhlNTRhNGRjNmI0OWM2OWU3OGMzMDcyZDJiM2FkZDdlZmRhYmY4MTRkYjIxMzNiIn0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OkxheWVyRGlmZklEIiwidmFsdWUiOiJzaGEyNTY6ODkxNjlkODdkYmUyYjcyYmE0MmJmYmIzNTc5Yzk1NzMyMmJhY2EyOGUwM2ExZTU1ODA3NjU0MmExYzFiMmI0YSJ9XSwicHVybCI6InBrZzpycG0vY2VudG9zL29wZW5zc2wtbGlic0AxOjEuMC4yay0xNi5lbDc/YXJjaD14ODZfNjRcdTAwMjZkaXN0cm89Y2VudG9zLTcuNi4xODEwIiwidHlwZSI6ImxpYnJhcnkiLCJ2ZXJzaW9uIjoiMToxLjAuMmstMTYuZWw3In0seyJib20tcmVmIjoiMDE3NWY3MzItZGY5ZC00YmI4LTlmNTYtODcwODk4ZTNmZjg5IiwibmFtZSI6ImNlbnRvcyIsInByb3BlcnRpZXMiOlt7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6VHlwZSIsInZhbHVlIjoiY2VudG9zIn0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OkNsYXNzIiwidmFsdWUiOiJvcy1wa2dzIn1dLCJ0eXBlIjoib3BlcmF0aW5nLXN5c3RlbSIsInZlcnNpb24iOiI3LjYuMTgxMCJ9XSwiZGVwZW5kZW5jaWVzIjpbeyJkZXBlbmRzT24iOlsicGtnOnJwbS9jZW50b3MvYmFzaEA0LjIuNDYtMzEuZWw3P2FyY2g9eDg2XzY0XHUwMDI2ZGlzdHJvPWNlbnRvcy03LjYuMTgxMCIsInBrZzpycG0vY2VudG9zL29wZW5zc2wtbGlic0AxOjEuMC4yay0xNi5lbDc/YXJjaD14ODZfNjRcdTAwMjZkaXN0cm89Y2VudG9zLTcuNi4xODEwIl0sInJlZiI6IjAxNzVmNzMyLWRmOWQtNGJiOC05ZjU2LTg3MDg5OGUzZmY4OSJ9LHsiZGVwZW5kc09uIjpbIjAxNzVmNzMyLWRmOWQtNGJiOC05ZjU2LTg3MDg5OGUzZmY4OSJdLCJyZWYiOiJkMGQ0MWUzMC05NjUwLTQ4OWQtOTQ4ZC00MjVmZjJlZDYzZDIifV0sIm1ldGFkYXRhIjp7ImNvbXBvbmVudCI6eyJib20tcmVmIjoiZDBkNDFlMzAtOTY1MC00ODlkLTk0OGQtNDI1ZmYyZWQ2M2QyIiwibmFtZSI6ImludGVncmF0aW9uL3Rlc3RkYXRhL2ZpeHR1cmVzL2ltYWdlcy9jZW50b3MtNy50YXIuZ3oiLCJwcm9wZXJ0aWVzIjpbeyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlNjaGVtYVZlcnNpb24iLCJ2YWx1ZSI6IjIifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6SW1hZ2VJRCIsInZhbHVlIjoic2hhMjU2OmYxY2I3YzdkNThiNzNlYWM4NTljMzk1ODgyZWVjNDlkNTA2NTEyNDRlMzQyY2Q2YzY4YTVjNzgwOTc4NWY0MjcifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6RGlmZklEIiwidmFsdWUiOiJzaGEyNTY6ODkxNjlkODdkYmUyYjcyYmE0MmJmYmIzNTc5Yzk1NzMyMmJhY2EyOGUwM2ExZTU1ODA3NjU0MmExYzFiMmI0YSJ9XSwidHlwZSI6ImNvbnRhaW5lciJ9LCJ0aW1lc3RhbXAiOiIyMDIyLTA2LTE0VDE1OjA4OjQ4KzAwOjAwIiwidG9vbHMiOlt7Im5hbWUiOiJ0cml2eSIsInZlbmRvciI6ImFxdWFzZWN1cml0eSIsInZlcnNpb24iOiJkZXYifV19LCJzZXJpYWxOdW1iZXIiOiJ1cm46dXVpZDoxNDU1YzAyZC02NGNhLTQ1M2UtYTVkZi1kZGZiNzBhN2M4MDQiLCJzcGVjVmVyc2lvbiI6IjEuNCIsInZlcnNpb24iOjF9LCJUaW1lc3RhbXAiOiIifX0=","signatures":[{"keyid":"","sig":"MEUCIF52Th/Uxp9iGoqyP8ioikcefayjXh/+GhKyhhdczihaAiEAwOedZ0ovOanwY+u9Dl+/bHp8398YcXA2n0zG8Q2gnb0="}]}
44 changes: 44 additions & 0 deletions pkg/attestation/attestation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package attestation

import (
"bytes"
"encoding/base64"
"encoding/json"

"github.com/in-toto/in-toto-golang/in_toto"
"github.com/secure-systems-lab/go-securesystemslib/dsse"
"golang.org/x/xerrors"
)

// CosignPredicate specifies the format of the Custom Predicate.
// Cosign uses this structure when creating an SBOM attestation.
// cf. https://github.com/sigstore/cosign/blob/e0547cff64f98585a837a524ff77ff6b47ff5609/pkg/cosign/attestation/attestation.go#L39-L43
type CosignPredicate struct {
Data interface{}
}

// Statement holds in-toto statement headers and the predicate.
type Statement in_toto.Statement

func (s *Statement) UnmarshalJSON(b []byte) error {
var envelope dsse.Envelope
err := json.NewDecoder(bytes.NewReader(b)).Decode(&envelope)
if err != nil {
return xerrors.Errorf("failed to decode as a dsse envelope: %w", err)
}
if envelope.PayloadType != in_toto.PayloadType {
return xerrors.Errorf("invalid attestation payload type: %s", envelope.PayloadType)
}

decoded, err := base64.StdEncoding.DecodeString(envelope.Payload)
if err != nil {
return xerrors.Errorf("failed to decode attestation payload: %w", err)
}

statement := (*in_toto.Statement)(s)
if err = json.NewDecoder(bytes.NewReader(decoded)).Decode(statement); err != nil {
return xerrors.Errorf("failed to decode attestation payload as in-toto statement: %w", err)
}

return nil
}
55 changes: 55 additions & 0 deletions pkg/attestation/attestation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package attestation_test

import (
"encoding/json"
"os"
"testing"

"github.com/in-toto/in-toto-golang/in_toto"
slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
"github.com/stretchr/testify/require"

"github.com/aquasecurity/trivy/pkg/attestation"
)

func TestStatement_UnmarshalJSON(t *testing.T) {
tests := []struct {
name string
inputFile string
want attestation.Statement
}{
{
name: "happy path",
inputFile: "testdata/attestation.json",
want: attestation.Statement{
StatementHeader: in_toto.StatementHeader{
Type: "https://in-toto.io/Statement/v0.1",
PredicateType: "cosign.sigstore.dev/attestation/v1",
Subject: []in_toto.Subject{
{
Name: "ghcr.io/aquasecurity/trivy-test-images",
Digest: slsa.DigestSet{
"sha256": "72c42ed48c3a2db31b7dafe17d275b634664a708d901ec9fd57b1529280f01fb",
},
},
},
},
Predicate: &attestation.CosignPredicate{
Data: "foo\n",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f, err := os.Open(tt.inputFile)
require.NoError(t, err)
defer f.Close()

got := attestation.Statement{Predicate: &attestation.CosignPredicate{}}
err = json.NewDecoder(f).Decode(&got)
require.NoError(t, err)
require.Equal(t, tt.want, got)
})
}
}
1 change: 1 addition & 0 deletions pkg/attestation/testdata/attestation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJjb3NpZ24uc2lnc3RvcmUuZGV2L2F0dGVzdGF0aW9uL3YxIiwic3ViamVjdCI6W3sibmFtZSI6ImdoY3IuaW8vYXF1YXNlY3VyaXR5L3RyaXZ5LXRlc3QtaW1hZ2VzIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjcyYzQyZWQ0OGMzYTJkYjMxYjdkYWZlMTdkMjc1YjYzNDY2NGE3MDhkOTAxZWM5ZmQ1N2IxNTI5MjgwZjAxZmIifX1dLCJwcmVkaWNhdGUiOnsiRGF0YSI6ImZvb1xuIiwiVGltZXN0YW1wIjoiMjAyMi0wOC0wM1QxMzowODoyN1oifX0=","signatures":[{"keyid":"","sig":"MEUCIQClJhJ2mS78MWy4L32wxd+8gPXYwpvyn0nmuY9r5t8iiAIgHKKoIJbKAKQ8i/bgN76ocuGhwUMdbgqpgKF0yFfPfGI="}]}
3 changes: 3 additions & 0 deletions pkg/commands/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,9 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
# Scan CycloneDX and generate a CycloneDX report
$ trivy sbom --format cyclonedx /path/to/report.cdx
# Scan CycloneDX-type attestation and show the result in tables
$ trivy sbom /path/to/report.cdx.intoto.jsonl
`,
PreRunE: func(cmd *cobra.Command, args []string) error {
if err := sbomFlags.Bind(cmd); err != nil {
Expand Down
13 changes: 12 additions & 1 deletion pkg/fanal/artifact/sbom/sbom.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
digest "github.com/opencontainers/go-digest"
"golang.org/x/xerrors"

"github.com/aquasecurity/trivy/pkg/attestation"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer/config"
"github.com/aquasecurity/trivy/pkg/fanal/artifact"
Expand Down Expand Up @@ -82,7 +83,7 @@ func (a Artifact) Inspect(_ context.Context) (types.ArtifactReference, error) {

var artifactType types.ArtifactType
switch format {
case sbom.FormatCycloneDXJSON, sbom.FormatCycloneDXXML:
case sbom.FormatCycloneDXJSON, sbom.FormatCycloneDXXML, sbom.FormatAttestCycloneDXJSON:
artifactType = types.ArtifactCycloneDX
}

Expand All @@ -108,6 +109,16 @@ func (a Artifact) Decode(f io.Reader, format sbom.Format) (sbom.SBOM, error) {
case sbom.FormatCycloneDXJSON:
v = &cyclonedx.CycloneDX{SBOM: &bom}
decoder = json.NewDecoder(f)
case sbom.FormatAttestCycloneDXJSON:
// in-toto attestation
// => cosign predicate
// => CycloneDX JSON
v = &attestation.Statement{
Predicate: &attestation.CosignPredicate{
Data: &cyclonedx.CycloneDX{SBOM: &bom},
},
}
decoder = json.NewDecoder(f)
default:
return sbom.SBOM{}, xerrors.Errorf("%s scanning is not yet supported", format)

Expand Down
Loading

0 comments on commit 317a026

Please sign in to comment.