feat(sbom): add support for scanning a sbom attestation (#2652)

Co-authored-by: knqyf263 <knqyf263@gmail.com>
This commit is contained in:
saso
2022-08-08 22:27:05 +09:00
committed by GitHub
parent 390c256c38
commit 317a026616
16 changed files with 300 additions and 8 deletions

View File

@@ -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> $ cosign attest --key /path/to/cosign.key --type spdx --predicate sbom.spdx.json <IMAGE>
# cyclonedx # cyclonedx
# The cyclonedx type is supported in Cosign v1.10.0 or later.
$ trivy image --format cyclonedx -o sbom.cdx.json <IMAGE> $ 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 ## Keyless signing

View File

@@ -13,6 +13,9 @@ Examples:
# Scan CycloneDX and generate a CycloneDX report # Scan CycloneDX and generate a CycloneDX report
$ trivy sbom --format cyclonedx /path/to/report.cdx $ 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 Scan Flags
--offline-scan do not issue API requests to identify dependencies --offline-scan do not issue API requests to identify dependencies

View File

@@ -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. Trivy also can take the following SBOM formats as an input and scan for vulnerabilities.
- CycloneDX - CycloneDX
- CycloneDX-type attestation
To scan SBOM, you can use the `sbom` subcommand and pass the path to the SBOM. To scan SBOM, you can use the `sbom` subcommand and pass the path to the SBOM.
@@ -209,5 +210,31 @@ Total: 3 (CRITICAL: 3)
!!! note !!! note
CycloneDX XML and SPDX are not supported at the moment. 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 [cyclonedx]: cyclonedx.md
[spdx]: spdx.md [spdx]: spdx.md
[Cosign]: https://github.com/sigstore/cosign
[sbom_attestation]: ../attestation/sbom.md

7
go.mod
View File

@@ -33,6 +33,7 @@ require (
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/google/wire v0.5.0 github.com/google/wire v0.5.0
github.com/hashicorp/go-getter v1.6.2 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-apk-version v0.0.0-20200609155635-041fdbb8563f
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d
github.com/knqyf263/go-rpm-version v0.0.0-20220614171824-631e686d1075 github.com/knqyf263/go-rpm-version v0.0.0-20220614171824-631e686d1075
@@ -46,6 +47,7 @@ require (
github.com/owenrumney/go-sarif/v2 v2.1.2 github.com/owenrumney/go-sarif/v2 v2.1.2
github.com/package-url/packageurl-go v0.1.1-0.20220203205134-d70459300c8a github.com/package-url/packageurl-go v0.1.1-0.20220203205134-d70459300c8a
github.com/samber/lo v1.27.0 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/sosedoff/gitkit v0.3.0
github.com/spf13/cobra v1.5.0 github.com/spf13/cobra v1.5.0
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
@@ -64,7 +66,10 @@ require (
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 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 ( require (
cloud.google.com/go v0.100.2 // indirect cloud.google.com/go v0.100.2 // indirect

7
go.sum
View File

@@ -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/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/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/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-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-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU=
github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=
@@ -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.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= 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 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ=
@@ -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.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.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/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.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 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 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 h1:VI1u+o2KZPZ5AhuPpXY0JBdpQPnkTx6Dd5XJhK/9MYE=
github.com/shogo82148/go-shuffle v0.0.0-20170808115208-59829097ff3b/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc= 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= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=

View File

@@ -41,6 +41,15 @@ func TestCycloneDX(t *testing.T) {
}, },
golden: "testdata/fluentd-multiple-lockfiles-cyclonedx.json.golden", 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 // Set up testing DB

View File

@@ -0,0 +1 @@
{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2N5Y2xvbmVkeC5vcmcvc2NoZW1hIiwic3ViamVjdCI6W3sibmFtZSI6ImdoY3IuaW8vYXF1YXNlY3VyaXR5L3RyaXZ5LXRlc3QtaW1hZ2VzIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjcyYzQyZWQ0OGMzYTJkYjMxYjdkYWZlMTdkMjc1YjYzNDY2NGE3MDhkOTAxZWM5ZmQ1N2IxNTI5MjgwZjAxZmIifX1dLCJwcmVkaWNhdGUiOnsiRGF0YSI6eyJib21Gb3JtYXQiOiJDeWNsb25lRFgiLCJjb21wb25lbnRzIjpbeyJib20tcmVmIjoicGtnOnJwbS9jZW50b3MvYmFzaEA0LjIuNDYtMzEuZWw3P2FyY2g9eDg2XzY0XHUwMDI2ZGlzdHJvPWNlbnRvcy03LjYuMTgxMCIsImxpY2Vuc2VzIjpbeyJleHByZXNzaW9uIjoiR1BMdjMrIn1dLCJuYW1lIjoiYmFzaCIsInByb3BlcnRpZXMiOlt7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6U3JjTmFtZSIsInZhbHVlIjoiYmFzaCJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpTcmNWZXJzaW9uIiwidmFsdWUiOiI0LjIuNDYifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6U3JjUmVsZWFzZSIsInZhbHVlIjoiMzEuZWw3In0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OkxheWVyRGlnZXN0IiwidmFsdWUiOiJzaGEyNTY6YWM5MjA4MjA3YWRhYWMzYTQ4ZTU0YTRkYzZiNDljNjllNzhjMzA3MmQyYjNhZGQ3ZWZkYWJmODE0ZGIyMTMzYiJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpMYXllckRpZmZJRCIsInZhbHVlIjoic2hhMjU2Ojg5MTY5ZDg3ZGJlMmI3MmJhNDJiZmJiMzU3OWM5NTczMjJiYWNhMjhlMDNhMWU1NTgwNzY1NDJhMWMxYjJiNGEifV0sInB1cmwiOiJwa2c6cnBtL2NlbnRvcy9iYXNoQDQuMi40Ni0zMS5lbDc/YXJjaD14ODZfNjRcdTAwMjZkaXN0cm89Y2VudG9zLTcuNi4xODEwIiwidHlwZSI6ImxpYnJhcnkiLCJ2ZXJzaW9uIjoiNC4yLjQ2LTMxLmVsNyJ9LHsiYm9tLXJlZiI6InBrZzpycG0vY2VudG9zL29wZW5zc2wtbGlic0AxOjEuMC4yay0xNi5lbDc/YXJjaD14ODZfNjRcdTAwMjZkaXN0cm89Y2VudG9zLTcuNi4xODEwIiwibGljZW5zZXMiOlt7ImV4cHJlc3Npb24iOiJPcGVuU1NMIn1dLCJuYW1lIjoib3BlbnNzbC1saWJzIiwicHJvcGVydGllcyI6W3sibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpTcmNOYW1lIiwidmFsdWUiOiJvcGVuc3NsIn0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlNyY1ZlcnNpb24iLCJ2YWx1ZSI6IjEuMC4yayJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpTcmNSZWxlYXNlIiwidmFsdWUiOiIxNi5lbDcifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6U3JjRXBvY2giLCJ2YWx1ZSI6IjEifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6TGF5ZXJEaWdlc3QiLCJ2YWx1ZSI6InNoYTI1NjphYzkyMDgyMDdhZGFhYzNhNDhlNTRhNGRjNmI0OWM2OWU3OGMzMDcyZDJiM2FkZDdlZmRhYmY4MTRkYjIxMzNiIn0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OkxheWVyRGlmZklEIiwidmFsdWUiOiJzaGEyNTY6ODkxNjlkODdkYmUyYjcyYmE0MmJmYmIzNTc5Yzk1NzMyMmJhY2EyOGUwM2ExZTU1ODA3NjU0MmExYzFiMmI0YSJ9XSwicHVybCI6InBrZzpycG0vY2VudG9zL29wZW5zc2wtbGlic0AxOjEuMC4yay0xNi5lbDc/YXJjaD14ODZfNjRcdTAwMjZkaXN0cm89Y2VudG9zLTcuNi4xODEwIiwidHlwZSI6ImxpYnJhcnkiLCJ2ZXJzaW9uIjoiMToxLjAuMmstMTYuZWw3In0seyJib20tcmVmIjoiMDE3NWY3MzItZGY5ZC00YmI4LTlmNTYtODcwODk4ZTNmZjg5IiwibmFtZSI6ImNlbnRvcyIsInByb3BlcnRpZXMiOlt7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6VHlwZSIsInZhbHVlIjoiY2VudG9zIn0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OkNsYXNzIiwidmFsdWUiOiJvcy1wa2dzIn1dLCJ0eXBlIjoib3BlcmF0aW5nLXN5c3RlbSIsInZlcnNpb24iOiI3LjYuMTgxMCJ9XSwiZGVwZW5kZW5jaWVzIjpbeyJkZXBlbmRzT24iOlsicGtnOnJwbS9jZW50b3MvYmFzaEA0LjIuNDYtMzEuZWw3P2FyY2g9eDg2XzY0XHUwMDI2ZGlzdHJvPWNlbnRvcy03LjYuMTgxMCIsInBrZzpycG0vY2VudG9zL29wZW5zc2wtbGlic0AxOjEuMC4yay0xNi5lbDc/YXJjaD14ODZfNjRcdTAwMjZkaXN0cm89Y2VudG9zLTcuNi4xODEwIl0sInJlZiI6IjAxNzVmNzMyLWRmOWQtNGJiOC05ZjU2LTg3MDg5OGUzZmY4OSJ9LHsiZGVwZW5kc09uIjpbIjAxNzVmNzMyLWRmOWQtNGJiOC05ZjU2LTg3MDg5OGUzZmY4OSJdLCJyZWYiOiJkMGQ0MWUzMC05NjUwLTQ4OWQtOTQ4ZC00MjVmZjJlZDYzZDIifV0sIm1ldGFkYXRhIjp7ImNvbXBvbmVudCI6eyJib20tcmVmIjoiZDBkNDFlMzAtOTY1MC00ODlkLTk0OGQtNDI1ZmYyZWQ2M2QyIiwibmFtZSI6ImludGVncmF0aW9uL3Rlc3RkYXRhL2ZpeHR1cmVzL2ltYWdlcy9jZW50b3MtNy50YXIuZ3oiLCJwcm9wZXJ0aWVzIjpbeyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlNjaGVtYVZlcnNpb24iLCJ2YWx1ZSI6IjIifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6SW1hZ2VJRCIsInZhbHVlIjoic2hhMjU2OmYxY2I3YzdkNThiNzNlYWM4NTljMzk1ODgyZWVjNDlkNTA2NTEyNDRlMzQyY2Q2YzY4YTVjNzgwOTc4NWY0MjcifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6RGlmZklEIiwidmFsdWUiOiJzaGEyNTY6ODkxNjlkODdkYmUyYjcyYmE0MmJmYmIzNTc5Yzk1NzMyMmJhY2EyOGUwM2ExZTU1ODA3NjU0MmExYzFiMmI0YSJ9XSwidHlwZSI6ImNvbnRhaW5lciJ9LCJ0aW1lc3RhbXAiOiIyMDIyLTA2LTE0VDE1OjA4OjQ4KzAwOjAwIiwidG9vbHMiOlt7Im5hbWUiOiJ0cml2eSIsInZlbmRvciI6ImFxdWFzZWN1cml0eSIsInZlcnNpb24iOiJkZXYifV19LCJzZXJpYWxOdW1iZXIiOiJ1cm46dXVpZDoxNDU1YzAyZC02NGNhLTQ1M2UtYTVkZi1kZGZiNzBhN2M4MDQiLCJzcGVjVmVyc2lvbiI6IjEuNCIsInZlcnNpb24iOjF9LCJUaW1lc3RhbXAiOiIifX0=","signatures":[{"keyid":"","sig":"MEUCIF52Th/Uxp9iGoqyP8ioikcefayjXh/+GhKyhhdczihaAiEAwOedZ0ovOanwY+u9Dl+/bHp8398YcXA2n0zG8Q2gnb0="}]}

View File

@@ -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
}

View File

@@ -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)
})
}
}

View File

@@ -0,0 +1 @@
{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJjb3NpZ24uc2lnc3RvcmUuZGV2L2F0dGVzdGF0aW9uL3YxIiwic3ViamVjdCI6W3sibmFtZSI6ImdoY3IuaW8vYXF1YXNlY3VyaXR5L3RyaXZ5LXRlc3QtaW1hZ2VzIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjcyYzQyZWQ0OGMzYTJkYjMxYjdkYWZlMTdkMjc1YjYzNDY2NGE3MDhkOTAxZWM5ZmQ1N2IxNTI5MjgwZjAxZmIifX1dLCJwcmVkaWNhdGUiOnsiRGF0YSI6ImZvb1xuIiwiVGltZXN0YW1wIjoiMjAyMi0wOC0wM1QxMzowODoyN1oifX0=","signatures":[{"keyid":"","sig":"MEUCIQClJhJ2mS78MWy4L32wxd+8gPXYwpvyn0nmuY9r5t8iiAIgHKKoIJbKAKQ8i/bgN76ocuGhwUMdbgqpgKF0yFfPfGI="}]}

View File

@@ -813,6 +813,9 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
# Scan CycloneDX and generate a CycloneDX report # Scan CycloneDX and generate a CycloneDX report
$ trivy sbom --format cyclonedx /path/to/report.cdx $ 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 { PreRunE: func(cmd *cobra.Command, args []string) error {
if err := sbomFlags.Bind(cmd); err != nil { if err := sbomFlags.Bind(cmd); err != nil {

View File

@@ -11,6 +11,7 @@ import (
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/attestation"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config"
"github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/artifact"
@@ -82,7 +83,7 @@ func (a Artifact) Inspect(_ context.Context) (types.ArtifactReference, error) {
var artifactType types.ArtifactType var artifactType types.ArtifactType
switch format { switch format {
case sbom.FormatCycloneDXJSON, sbom.FormatCycloneDXXML: case sbom.FormatCycloneDXJSON, sbom.FormatCycloneDXXML, sbom.FormatAttestCycloneDXJSON:
artifactType = types.ArtifactCycloneDX artifactType = types.ArtifactCycloneDX
} }
@@ -108,6 +109,16 @@ func (a Artifact) Decode(f io.Reader, format sbom.Format) (sbom.SBOM, error) {
case sbom.FormatCycloneDXJSON: case sbom.FormatCycloneDXJSON:
v = &cyclonedx.CycloneDX{SBOM: &bom} v = &cyclonedx.CycloneDX{SBOM: &bom}
decoder = json.NewDecoder(f) 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: default:
return sbom.SBOM{}, xerrors.Errorf("%s scanning is not yet supported", format) return sbom.SBOM{}, xerrors.Errorf("%s scanning is not yet supported", format)

View File

@@ -128,6 +128,112 @@ func TestArtifact_Inspect(t *testing.T) {
}, },
}, },
}, },
{
name: "happy path for sbom attestation",
filePath: "testdata/sbom.cdx.intoto.jsonl",
putBlobExpectation: cache.ArtifactCachePutBlobExpectation{
Args: cache.ArtifactCachePutBlobArgs{
BlobID: "sha256:21f10e5ab97c37f6c4d6a45815cd5db10e9539d5db8614d3b1d8890111d7a2b8",
BlobInfo: types.BlobInfo{
SchemaVersion: types.BlobJSONSchemaVersion,
OS: &types.OS{
Family: "alpine",
Name: "3.16.0",
},
PackageInfos: []types.PackageInfo{
{
Packages: []types.Package{
{
Name: "musl", Version: "1.2.3-r0", SrcName: "musl", SrcVersion: "1.2.3-r0", Licenses: []string{"MIT"},
Ref: "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.0",
Layer: types.Layer{
DiffID: "sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3",
},
},
},
},
},
Applications: []types.Application{
{
Type: "composer",
FilePath: "app/composer/composer.lock",
Libraries: []types.Package{
{
Name: "pear/log",
Version: "1.13.1",
Ref: "pkg:composer/pear/log@1.13.1",
Layer: types.Layer{
DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1",
},
},
{
Name: "pear/pear_exception",
Version: "v1.0.0",
Ref: "pkg:composer/pear/pear_exception@v1.0.0",
Layer: types.Layer{
DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1",
},
},
},
},
{
Type: "gobinary",
FilePath: "app/gobinary/gobinary",
Libraries: []types.Package{
{
Name: "github.com/package-url/packageurl-go",
Version: "v0.1.1-0.20220203205134-d70459300c8a",
Ref: "pkg:golang/github.com/package-url/packageurl-go@v0.1.1-0.20220203205134-d70459300c8a",
Layer: types.Layer{
DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1",
},
},
},
},
{
Type: "jar",
FilePath: "",
Libraries: []types.Package{
{
Name: "org.codehaus.mojo:child-project",
Ref: "pkg:maven/org.codehaus.mojo/child-project@1.0?file_path=app%2Fmaven%2Ftarget%2Fchild-project-1.0.jar",
Version: "1.0",
Layer: types.Layer{
DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1",
},
},
},
},
{
Type: "node-pkg",
FilePath: "",
Libraries: []types.Package{
{
Name: "bootstrap",
Version: "5.0.2",
Ref: "pkg:npm/bootstrap@5.0.2?file_path=app%2Fapp%2Fpackage.json",
Licenses: []string{"MIT"},
Layer: types.Layer{
DiffID: "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1",
},
},
},
},
},
},
},
Returns: cache.ArtifactCachePutBlobReturns{},
},
want: types.ArtifactReference{
Name: "testdata/sbom.cdx.intoto.jsonl",
Type: types.ArtifactCycloneDX,
ID: "sha256:21f10e5ab97c37f6c4d6a45815cd5db10e9539d5db8614d3b1d8890111d7a2b8",
BlobIDs: []string{
"sha256:21f10e5ab97c37f6c4d6a45815cd5db10e9539d5db8614d3b1d8890111d7a2b8",
},
},
},
{ {
name: "sad path with no such directory", name: "sad path with no such directory",
filePath: "./testdata/unknown.json", filePath: "./testdata/unknown.json",

File diff suppressed because one or more lines are too long

View File

@@ -6,6 +6,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/aquasecurity/trivy/pkg/log"
cdx "github.com/CycloneDX/cyclonedx-go" cdx "github.com/CycloneDX/cyclonedx-go"
"github.com/samber/lo" "github.com/samber/lo"
"golang.org/x/xerrors" "golang.org/x/xerrors"
@@ -23,6 +25,7 @@ type CycloneDX struct {
} }
func (c *CycloneDX) UnmarshalJSON(b []byte) error { func (c *CycloneDX) UnmarshalJSON(b []byte) error {
log.Logger.Debug("Unmarshaling CycloneDX JSON...")
if c.SBOM == nil { if c.SBOM == nil {
c.SBOM = &sbom.SBOM{} c.SBOM = &sbom.SBOM{}
} }

View File

@@ -6,8 +6,10 @@ import (
"io" "io"
"strings" "strings"
"github.com/in-toto/in-toto-golang/in_toto"
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/attestation"
"github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/types"
) )
@@ -26,6 +28,7 @@ const (
FormatCycloneDXXML Format = "cyclonedx-xml" FormatCycloneDXXML Format = "cyclonedx-xml"
FormatSPDXJSON Format = "spdx-json" FormatSPDXJSON Format = "spdx-json"
FormatSPDXXML Format = "spdx-xml" FormatSPDXXML Format = "spdx-xml"
FormatAttestCycloneDXJSON Format = "attest-cyclonedx-json"
FormatUnknown Format = "unknown" FormatUnknown Format = "unknown"
) )
@@ -57,7 +60,19 @@ func DetectFormat(r io.ReadSeeker) (Format, error) {
} }
} }
if _, err := r.Seek(0, io.SeekStart); err != nil {
return FormatUnknown, xerrors.Errorf("seek error: %w", err)
}
// TODO: implement SPDX // TODO: implement SPDX
// Try in-toto attestation
var s attestation.Statement
if err := json.NewDecoder(r).Decode(&s); err == nil {
if s.PredicateType == in_toto.PredicateCycloneDX {
return FormatAttestCycloneDXJSON, nil
}
}
return FormatUnknown, nil return FormatUnknown, nil
} }