diff --git a/docs/docs/attestation/sbom.md b/docs/docs/attestation/sbom.md index 45bd750940c5..8c5c4abad21e 100644 --- a/docs/docs/attestation/sbom.md +++ b/docs/docs/attestation/sbom.md @@ -31,8 +31,9 @@ $ trivy image --format spdx-json -o sbom.spdx.json $ cosign attest --key /path/to/cosign.key --type spdx --predicate sbom.spdx.json # cyclonedx +# The cyclonedx type is supported in Cosign v1.10.0 or later. $ trivy image --format cyclonedx -o sbom.cdx.json -$ cosign attest --key /path/to/cosign.key --type https://cyclonedx.org/schema --predicate sbom.cdx.json +$ cosign attest --key /path/to/cosign.key --type cyclonedx --predicate sbom.cdx.json ``` ## Keyless signing diff --git a/docs/docs/references/cli/sbom.md b/docs/docs/references/cli/sbom.md index 622a86dc3982..c943dc6335ed 100644 --- a/docs/docs/references/cli/sbom.md +++ b/docs/docs/references/cli/sbom.md @@ -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 diff --git a/docs/docs/sbom/index.md b/docs/docs/sbom/index.md index caa51538aaef..94f8af8f5ad7 100644 --- a/docs/docs/sbom/index.md +++ b/docs/docs/sbom/index.md @@ -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. @@ -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 > 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 \ No newline at end of file diff --git a/go.mod b/go.mod index ccbde1a24052..61cbc9752518 100644 --- a/go.mod +++ b/go.mod @@ -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 @@ -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 @@ -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 diff --git a/go.sum b/go.sum index 00f81933e7d1..e1bd3d6b69f0 100644 --- a/go.sum +++ b/go.sum @@ -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= @@ -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= @@ -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= diff --git a/integration/sbom_test.go b/integration/sbom_test.go index 431514a189be..1f44310ca223 100644 --- a/integration/sbom_test.go +++ b/integration/sbom_test.go @@ -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 diff --git a/integration/testdata/fixtures/sbom/centos-7-cyclonedx.intoto.jsonl b/integration/testdata/fixtures/sbom/centos-7-cyclonedx.intoto.jsonl new file mode 100644 index 000000000000..9574507800c8 --- /dev/null +++ b/integration/testdata/fixtures/sbom/centos-7-cyclonedx.intoto.jsonl @@ -0,0 +1 @@ +{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2N5Y2xvbmVkeC5vcmcvc2NoZW1hIiwic3ViamVjdCI6W3sibmFtZSI6ImdoY3IuaW8vYXF1YXNlY3VyaXR5L3RyaXZ5LXRlc3QtaW1hZ2VzIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjcyYzQyZWQ0OGMzYTJkYjMxYjdkYWZlMTdkMjc1YjYzNDY2NGE3MDhkOTAxZWM5ZmQ1N2IxNTI5MjgwZjAxZmIifX1dLCJwcmVkaWNhdGUiOnsiRGF0YSI6eyJib21Gb3JtYXQiOiJDeWNsb25lRFgiLCJjb21wb25lbnRzIjpbeyJib20tcmVmIjoicGtnOnJwbS9jZW50b3MvYmFzaEA0LjIuNDYtMzEuZWw3P2FyY2g9eDg2XzY0XHUwMDI2ZGlzdHJvPWNlbnRvcy03LjYuMTgxMCIsImxpY2Vuc2VzIjpbeyJleHByZXNzaW9uIjoiR1BMdjMrIn1dLCJuYW1lIjoiYmFzaCIsInByb3BlcnRpZXMiOlt7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6U3JjTmFtZSIsInZhbHVlIjoiYmFzaCJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpTcmNWZXJzaW9uIiwidmFsdWUiOiI0LjIuNDYifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6U3JjUmVsZWFzZSIsInZhbHVlIjoiMzEuZWw3In0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OkxheWVyRGlnZXN0IiwidmFsdWUiOiJzaGEyNTY6YWM5MjA4MjA3YWRhYWMzYTQ4ZTU0YTRkYzZiNDljNjllNzhjMzA3MmQyYjNhZGQ3ZWZkYWJmODE0ZGIyMTMzYiJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpMYXllckRpZmZJRCIsInZhbHVlIjoic2hhMjU2Ojg5MTY5ZDg3ZGJlMmI3MmJhNDJiZmJiMzU3OWM5NTczMjJiYWNhMjhlMDNhMWU1NTgwNzY1NDJhMWMxYjJiNGEifV0sInB1cmwiOiJwa2c6cnBtL2NlbnRvcy9iYXNoQDQuMi40Ni0zMS5lbDc/YXJjaD14ODZfNjRcdTAwMjZkaXN0cm89Y2VudG9zLTcuNi4xODEwIiwidHlwZSI6ImxpYnJhcnkiLCJ2ZXJzaW9uIjoiNC4yLjQ2LTMxLmVsNyJ9LHsiYm9tLXJlZiI6InBrZzpycG0vY2VudG9zL29wZW5zc2wtbGlic0AxOjEuMC4yay0xNi5lbDc/YXJjaD14ODZfNjRcdTAwMjZkaXN0cm89Y2VudG9zLTcuNi4xODEwIiwibGljZW5zZXMiOlt7ImV4cHJlc3Npb24iOiJPcGVuU1NMIn1dLCJuYW1lIjoib3BlbnNzbC1saWJzIiwicHJvcGVydGllcyI6W3sibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpTcmNOYW1lIiwidmFsdWUiOiJvcGVuc3NsIn0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlNyY1ZlcnNpb24iLCJ2YWx1ZSI6IjEuMC4yayJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpTcmNSZWxlYXNlIiwidmFsdWUiOiIxNi5lbDcifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6U3JjRXBvY2giLCJ2YWx1ZSI6IjEifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6TGF5ZXJEaWdlc3QiLCJ2YWx1ZSI6InNoYTI1NjphYzkyMDgyMDdhZGFhYzNhNDhlNTRhNGRjNmI0OWM2OWU3OGMzMDcyZDJiM2FkZDdlZmRhYmY4MTRkYjIxMzNiIn0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OkxheWVyRGlmZklEIiwidmFsdWUiOiJzaGEyNTY6ODkxNjlkODdkYmUyYjcyYmE0MmJmYmIzNTc5Yzk1NzMyMmJhY2EyOGUwM2ExZTU1ODA3NjU0MmExYzFiMmI0YSJ9XSwicHVybCI6InBrZzpycG0vY2VudG9zL29wZW5zc2wtbGlic0AxOjEuMC4yay0xNi5lbDc/YXJjaD14ODZfNjRcdTAwMjZkaXN0cm89Y2VudG9zLTcuNi4xODEwIiwidHlwZSI6ImxpYnJhcnkiLCJ2ZXJzaW9uIjoiMToxLjAuMmstMTYuZWw3In0seyJib20tcmVmIjoiMDE3NWY3MzItZGY5ZC00YmI4LTlmNTYtODcwODk4ZTNmZjg5IiwibmFtZSI6ImNlbnRvcyIsInByb3BlcnRpZXMiOlt7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6VHlwZSIsInZhbHVlIjoiY2VudG9zIn0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OkNsYXNzIiwidmFsdWUiOiJvcy1wa2dzIn1dLCJ0eXBlIjoib3BlcmF0aW5nLXN5c3RlbSIsInZlcnNpb24iOiI3LjYuMTgxMCJ9XSwiZGVwZW5kZW5jaWVzIjpbeyJkZXBlbmRzT24iOlsicGtnOnJwbS9jZW50b3MvYmFzaEA0LjIuNDYtMzEuZWw3P2FyY2g9eDg2XzY0XHUwMDI2ZGlzdHJvPWNlbnRvcy03LjYuMTgxMCIsInBrZzpycG0vY2VudG9zL29wZW5zc2wtbGlic0AxOjEuMC4yay0xNi5lbDc/YXJjaD14ODZfNjRcdTAwMjZkaXN0cm89Y2VudG9zLTcuNi4xODEwIl0sInJlZiI6IjAxNzVmNzMyLWRmOWQtNGJiOC05ZjU2LTg3MDg5OGUzZmY4OSJ9LHsiZGVwZW5kc09uIjpbIjAxNzVmNzMyLWRmOWQtNGJiOC05ZjU2LTg3MDg5OGUzZmY4OSJdLCJyZWYiOiJkMGQ0MWUzMC05NjUwLTQ4OWQtOTQ4ZC00MjVmZjJlZDYzZDIifV0sIm1ldGFkYXRhIjp7ImNvbXBvbmVudCI6eyJib20tcmVmIjoiZDBkNDFlMzAtOTY1MC00ODlkLTk0OGQtNDI1ZmYyZWQ2M2QyIiwibmFtZSI6ImludGVncmF0aW9uL3Rlc3RkYXRhL2ZpeHR1cmVzL2ltYWdlcy9jZW50b3MtNy50YXIuZ3oiLCJwcm9wZXJ0aWVzIjpbeyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlNjaGVtYVZlcnNpb24iLCJ2YWx1ZSI6IjIifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6SW1hZ2VJRCIsInZhbHVlIjoic2hhMjU2OmYxY2I3YzdkNThiNzNlYWM4NTljMzk1ODgyZWVjNDlkNTA2NTEyNDRlMzQyY2Q2YzY4YTVjNzgwOTc4NWY0MjcifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6RGlmZklEIiwidmFsdWUiOiJzaGEyNTY6ODkxNjlkODdkYmUyYjcyYmE0MmJmYmIzNTc5Yzk1NzMyMmJhY2EyOGUwM2ExZTU1ODA3NjU0MmExYzFiMmI0YSJ9XSwidHlwZSI6ImNvbnRhaW5lciJ9LCJ0aW1lc3RhbXAiOiIyMDIyLTA2LTE0VDE1OjA4OjQ4KzAwOjAwIiwidG9vbHMiOlt7Im5hbWUiOiJ0cml2eSIsInZlbmRvciI6ImFxdWFzZWN1cml0eSIsInZlcnNpb24iOiJkZXYifV19LCJzZXJpYWxOdW1iZXIiOiJ1cm46dXVpZDoxNDU1YzAyZC02NGNhLTQ1M2UtYTVkZi1kZGZiNzBhN2M4MDQiLCJzcGVjVmVyc2lvbiI6IjEuNCIsInZlcnNpb24iOjF9LCJUaW1lc3RhbXAiOiIifX0=","signatures":[{"keyid":"","sig":"MEUCIF52Th/Uxp9iGoqyP8ioikcefayjXh/+GhKyhhdczihaAiEAwOedZ0ovOanwY+u9Dl+/bHp8398YcXA2n0zG8Q2gnb0="}]} diff --git a/pkg/attestation/attestation.go b/pkg/attestation/attestation.go new file mode 100644 index 000000000000..8f4cb8ca54aa --- /dev/null +++ b/pkg/attestation/attestation.go @@ -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 +} diff --git a/pkg/attestation/attestation_test.go b/pkg/attestation/attestation_test.go new file mode 100644 index 000000000000..92aff5d1399c --- /dev/null +++ b/pkg/attestation/attestation_test.go @@ -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) + }) + } +} diff --git a/pkg/attestation/testdata/attestation.json b/pkg/attestation/testdata/attestation.json new file mode 100644 index 000000000000..2e9dbf326a24 --- /dev/null +++ b/pkg/attestation/testdata/attestation.json @@ -0,0 +1 @@ +{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJjb3NpZ24uc2lnc3RvcmUuZGV2L2F0dGVzdGF0aW9uL3YxIiwic3ViamVjdCI6W3sibmFtZSI6ImdoY3IuaW8vYXF1YXNlY3VyaXR5L3RyaXZ5LXRlc3QtaW1hZ2VzIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjcyYzQyZWQ0OGMzYTJkYjMxYjdkYWZlMTdkMjc1YjYzNDY2NGE3MDhkOTAxZWM5ZmQ1N2IxNTI5MjgwZjAxZmIifX1dLCJwcmVkaWNhdGUiOnsiRGF0YSI6ImZvb1xuIiwiVGltZXN0YW1wIjoiMjAyMi0wOC0wM1QxMzowODoyN1oifX0=","signatures":[{"keyid":"","sig":"MEUCIQClJhJ2mS78MWy4L32wxd+8gPXYwpvyn0nmuY9r5t8iiAIgHKKoIJbKAKQ8i/bgN76ocuGhwUMdbgqpgKF0yFfPfGI="}]} diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 08a28014cc35..89251033faf2 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -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 { diff --git a/pkg/fanal/artifact/sbom/sbom.go b/pkg/fanal/artifact/sbom/sbom.go index c0a418a76fbe..d20615d5eb98 100644 --- a/pkg/fanal/artifact/sbom/sbom.go +++ b/pkg/fanal/artifact/sbom/sbom.go @@ -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" @@ -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 } @@ -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) diff --git a/pkg/fanal/artifact/sbom/sbom_test.go b/pkg/fanal/artifact/sbom/sbom_test.go index a6a338d9da99..cde6a6a64848 100644 --- a/pkg/fanal/artifact/sbom/sbom_test.go +++ b/pkg/fanal/artifact/sbom/sbom_test.go @@ -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", filePath: "./testdata/unknown.json", diff --git a/pkg/fanal/artifact/sbom/testdata/sbom.cdx.intoto.jsonl b/pkg/fanal/artifact/sbom/testdata/sbom.cdx.intoto.jsonl new file mode 100644 index 000000000000..9682dc616c58 --- /dev/null +++ b/pkg/fanal/artifact/sbom/testdata/sbom.cdx.intoto.jsonl @@ -0,0 +1 @@ +{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2N5Y2xvbmVkeC5vcmcvc2NoZW1hIiwic3ViamVjdCI6W3sibmFtZSI6ImluZGV4LmRvY2tlci5pby9vdG1zNjEvaGVsbG8tMSIsImRpZ2VzdCI6eyJzaGEyNTYiOiIyMGQzZjY5M2RjZmZhNDRkNmIyNGVhZTg4NzgzMzI0ZDI1Y2MxMzJjMjIwODlmNzBlNGZiZmI4NTg2MjViMDYyIn19XSwicHJlZGljYXRlIjp7IkRhdGEiOnsiYm9tRm9ybWF0IjoiQ3ljbG9uZURYIiwiY29tcG9uZW50cyI6W3siYm9tLXJlZiI6InBrZzphcGsvYWxwaW5lL211c2xAMS4yLjMtcjA/ZGlzdHJvPTMuMTYuMCIsImxpY2Vuc2VzIjpbeyJleHByZXNzaW9uIjoiTUlUIn1dLCJuYW1lIjoibXVzbCIsInByb3BlcnRpZXMiOlt7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6U3JjTmFtZSIsInZhbHVlIjoibXVzbCJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpTcmNWZXJzaW9uIiwidmFsdWUiOiIxLjIuMy1yMCJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpMYXllckRpZmZJRCIsInZhbHVlIjoic2hhMjU2OmRkNTY1ZmY4NTBlNzAwMzM1NmUyYjI1Mjc1OGY5YmRjMWZmMjgwM2Y2MWU5OTVlMjRjNzg0NGY2Mjk3ZjhmYzMifV0sInB1cmwiOiJwa2c6YXBrL2FscGluZS9tdXNsQDEuMi4zLXIwP2Rpc3Rybz0zLjE2LjAiLCJ0eXBlIjoibGlicmFyeSIsInZlcnNpb24iOiIxLjIuMy1yMCJ9LHsiYm9tLXJlZiI6IjYwZTlmNTdiLWQ0YTYtNGY3MS1hZDE0LTA4OTNhYzYwOTE4MiIsIm5hbWUiOiJhbHBpbmUiLCJwcm9wZXJ0aWVzIjpbeyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlR5cGUiLCJ2YWx1ZSI6ImFscGluZSJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpDbGFzcyIsInZhbHVlIjoib3MtcGtncyJ9XSwidHlwZSI6Im9wZXJhdGluZy1zeXN0ZW0iLCJ2ZXJzaW9uIjoiMy4xNi4wIn0seyJib20tcmVmIjoicGtnOm1hdmVuL29yZy5jb2RlaGF1cy5tb2pvL2NoaWxkLXByb2plY3RAMS4wP2ZpbGVfcGF0aD1hcHAlMkZtYXZlbiUyRnRhcmdldCUyRmNoaWxkLXByb2plY3QtMS4wLmphciIsIm5hbWUiOiJvcmcuY29kZWhhdXMubW9qbzpjaGlsZC1wcm9qZWN0IiwicHJvcGVydGllcyI6W3sibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpGaWxlUGF0aCIsInZhbHVlIjoiYXBwL21hdmVuL3RhcmdldC9jaGlsZC1wcm9qZWN0LTEuMC5qYXIifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6TGF5ZXJEaWZmSUQiLCJ2YWx1ZSI6InNoYTI1NjozYzc5ZTgzMmIxYjQ4OTFhMWNiNGEzMjZlZjg1MjRlMGJkMTRhMjUzNzE1MGFjMGUyMDNhNTY3NzE3NmMxY2ExIn0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlR5cGUiLCJ2YWx1ZSI6ImphciJ9XSwicHVybCI6InBrZzptYXZlbi9vcmcuY29kZWhhdXMubW9qby9jaGlsZC1wcm9qZWN0QDEuMCIsInR5cGUiOiJsaWJyYXJ5IiwidmVyc2lvbiI6IjEuMCJ9LHsiYm9tLXJlZiI6InBrZzpucG0vYm9vdHN0cmFwQDUuMC4yP2ZpbGVfcGF0aD1hcHAlMkZhcHAlMkZwYWNrYWdlLmpzb24iLCJsaWNlbnNlcyI6W3siZXhwcmVzc2lvbiI6Ik1JVCJ9XSwibmFtZSI6ImJvb3RzdHJhcCIsInByb3BlcnRpZXMiOlt7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6RmlsZVBhdGgiLCJ2YWx1ZSI6ImFwcC9hcHAvcGFja2FnZS5qc29uIn0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OkxheWVyRGlmZklEIiwidmFsdWUiOiJzaGEyNTY6M2M3OWU4MzJiMWI0ODkxYTFjYjRhMzI2ZWY4NTI0ZTBiZDE0YTI1MzcxNTBhYzBlMjAzYTU2NzcxNzZjMWNhMSJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpUeXBlIiwidmFsdWUiOiJub2RlLXBrZyJ9XSwicHVybCI6InBrZzpucG0vYm9vdHN0cmFwQDUuMC4yIiwidHlwZSI6ImxpYnJhcnkiLCJ2ZXJzaW9uIjoiNS4wLjIifSx7ImJvbS1yZWYiOiJwa2c6Y29tcG9zZXIvcGVhci9sb2dAMS4xMy4xIiwibmFtZSI6InBlYXIvbG9nIiwicHJvcGVydGllcyI6W3sibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpMYXllckRpZmZJRCIsInZhbHVlIjoic2hhMjU2OjNjNzllODMyYjFiNDg5MWExY2I0YTMyNmVmODUyNGUwYmQxNGEyNTM3MTUwYWMwZTIwM2E1Njc3MTc2YzFjYTEifV0sInB1cmwiOiJwa2c6Y29tcG9zZXIvcGVhci9sb2dAMS4xMy4xIiwidHlwZSI6ImxpYnJhcnkiLCJ2ZXJzaW9uIjoiMS4xMy4xIn0seyJib20tcmVmIjoicGtnOmNvbXBvc2VyL3BlYXIvcGVhcl9leGNlcHRpb25AdjEuMC4wIiwibmFtZSI6InBlYXIvcGVhcl9leGNlcHRpb24iLCJwcm9wZXJ0aWVzIjpbeyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OkxheWVyRGlmZklEIiwidmFsdWUiOiJzaGEyNTY6M2M3OWU4MzJiMWI0ODkxYTFjYjRhMzI2ZWY4NTI0ZTBiZDE0YTI1MzcxNTBhYzBlMjAzYTU2NzcxNzZjMWNhMSJ9XSwicHVybCI6InBrZzpjb21wb3Nlci9wZWFyL3BlYXJfZXhjZXB0aW9uQHYxLjAuMCIsInR5cGUiOiJsaWJyYXJ5IiwidmVyc2lvbiI6InYxLjAuMCJ9LHsiYm9tLXJlZiI6IjEwMDkyNWZmLTdjMGEtNDcwZi1hNzI1LThmYjk3M2I0MGU3YiIsIm5hbWUiOiJhcHAvY29tcG9zZXIvY29tcG9zZXIubG9jayIsInByb3BlcnRpZXMiOlt7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6VHlwZSIsInZhbHVlIjoiY29tcG9zZXIifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6Q2xhc3MiLCJ2YWx1ZSI6ImxhbmctcGtncyJ9XSwidHlwZSI6ImFwcGxpY2F0aW9uIn0seyJib20tcmVmIjoicGtnOmdvbGFuZy9naXRodWIuY29tL3BhY2thZ2UtdXJsL3BhY2thZ2V1cmwtZ29AdjAuMS4xLTAuMjAyMjAyMDMyMDUxMzQtZDcwNDU5MzAwYzhhIiwibmFtZSI6ImdpdGh1Yi5jb20vcGFja2FnZS11cmwvcGFja2FnZXVybC1nbyIsInByb3BlcnRpZXMiOlt7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6TGF5ZXJEaWZmSUQiLCJ2YWx1ZSI6InNoYTI1NjozYzc5ZTgzMmIxYjQ4OTFhMWNiNGEzMjZlZjg1MjRlMGJkMTRhMjUzNzE1MGFjMGUyMDNhNTY3NzE3NmMxY2ExIn1dLCJwdXJsIjoicGtnOmdvbGFuZy9naXRodWIuY29tL3BhY2thZ2UtdXJsL3BhY2thZ2V1cmwtZ29AdjAuMS4xLTAuMjAyMjAyMDMyMDUxMzQtZDcwNDU5MzAwYzhhIiwidHlwZSI6ImxpYnJhcnkiLCJ2ZXJzaW9uIjoidjAuMS4xLTAuMjAyMjAyMDMyMDUxMzQtZDcwNDU5MzAwYzhhIn0seyJib20tcmVmIjoiMWExMTFlNmItYTY4Mi00NzBlLThiMGUtYWFhNDlkOTNjZDM5IiwibmFtZSI6ImFwcC9nb2JpbmFyeS9nb2JpbmFyeSIsInByb3BlcnRpZXMiOlt7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6VHlwZSIsInZhbHVlIjoiZ29iaW5hcnkifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6Q2xhc3MiLCJ2YWx1ZSI6ImxhbmctcGtncyJ9XSwidHlwZSI6ImFwcGxpY2F0aW9uIn1dLCJkZXBlbmRlbmNpZXMiOlt7ImRlcGVuZHNPbiI6WyJwa2c6YXBrL2FscGluZS9tdXNsQDEuMi4zLXIwP2Rpc3Rybz0zLjE2LjAiXSwicmVmIjoiNjBlOWY1N2ItZDRhNi00ZjcxLWFkMTQtMDg5M2FjNjA5MTgyIn0seyJkZXBlbmRzT24iOlsicGtnOmNvbXBvc2VyL3BlYXIvbG9nQDEuMTMuMSIsInBrZzpjb21wb3Nlci9wZWFyL3BlYXJfZXhjZXB0aW9uQHYxLjAuMCJdLCJyZWYiOiIxMDA5MjVmZi03YzBhLTQ3MGYtYTcyNS04ZmI5NzNiNDBlN2IifSx7ImRlcGVuZHNPbiI6WyJwa2c6Z29sYW5nL2dpdGh1Yi5jb20vcGFja2FnZS11cmwvcGFja2FnZXVybC1nb0B2MC4xLjEtMC4yMDIyMDIwMzIwNTEzNC1kNzA0NTkzMDBjOGEiXSwicmVmIjoiMWExMTFlNmItYTY4Mi00NzBlLThiMGUtYWFhNDlkOTNjZDM5In0seyJkZXBlbmRzT24iOlsiNjBlOWY1N2ItZDRhNi00ZjcxLWFkMTQtMDg5M2FjNjA5MTgyIiwicGtnOm1hdmVuL29yZy5jb2RlaGF1cy5tb2pvL2NoaWxkLXByb2plY3RAMS4wP2ZpbGVfcGF0aD1hcHAlMkZtYXZlbiUyRnRhcmdldCUyRmNoaWxkLXByb2plY3QtMS4wLmphciIsInBrZzpucG0vYm9vdHN0cmFwQDUuMC4yP2ZpbGVfcGF0aD1hcHAlMkZhcHAlMkZwYWNrYWdlLmpzb24iLCIxMDA5MjVmZi03YzBhLTQ3MGYtYTcyNS04ZmI5NzNiNDBlN2IiLCIxYTExMWU2Yi1hNjgyLTQ3MGUtOGIwZS1hYWE0OWQ5M2NkMzkiXSwicmVmIjoiMGY1ODVkNjQtNDgxNS00YjcyLTkyYzUtOTdkYWUxOTFmYTRhIn1dLCJtZXRhZGF0YSI6eyJjb21wb25lbnQiOnsiYm9tLXJlZiI6IjBmNTg1ZDY0LTQ4MTUtNGI3Mi05MmM1LTk3ZGFlMTkxZmE0YSIsIm5hbWUiOiJtYXZlbi10ZXN0LXByb2plY3QiLCJwcm9wZXJ0aWVzIjpbeyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlNjaGVtYVZlcnNpb24iLCJ2YWx1ZSI6IjIifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6SW1hZ2VJRCIsInZhbHVlIjoic2hhMjU2OjQ5MTkzYTIzMTBkYmFkNGMwMjM4MmRhODdhYzYyNGE4MGE5MjM4N2E0Zjc1MzYyMzVmOWJhNTkwZTViY2Q3YjUifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6RGlmZklEIiwidmFsdWUiOiJzaGEyNTY6ZGQ1NjVmZjg1MGU3MDAzMzU2ZTJiMjUyNzU4ZjliZGMxZmYyODAzZjYxZTk5NWUyNGM3ODQ0ZjYyOTdmOGZjMyJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpEaWZmSUQiLCJ2YWx1ZSI6InNoYTI1NjozYzc5ZTgzMmIxYjQ4OTFhMWNiNGEzMjZlZjg1MjRlMGJkMTRhMjUzNzE1MGFjMGUyMDNhNTY3NzE3NmMxY2ExIn0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlJlcG9UYWciLCJ2YWx1ZSI6Im1hdmVuLXRlc3QtcHJvamVjdDpsYXRlc3QifV0sInR5cGUiOiJjb250YWluZXIifSwidGltZXN0YW1wIjoiMjAyMi0wNS0yOFQxMDoyMDowMy43OTUyN1oiLCJ0b29scyI6W3sibmFtZSI6InRyaXZ5IiwidmVuZG9yIjoiYXF1YXNlY3VyaXR5IiwidmVyc2lvbiI6ImRldiJ9XX0sInNlcmlhbE51bWJlciI6InVybjp1dWlkOmM5ODZiYTk0LWUzN2QtNDljOC05ZTMwLTk2ZGFjY2QwNDE1YiIsInNwZWNWZXJzaW9uIjoiMS40IiwidmVyc2lvbiI6MSwidnVsbmVyYWJpbGl0aWVzIjpbXX0sIlRpbWVzdGFtcCI6IiJ9fQ==","signatures":[{"keyid":"","sig":"MEUCIEgi2douvietdOgymv2Ox8TAXaELf9eGx1n+WJsjQiO8AiEAhblfDCTvMDhjjk4aFSUSff+0A9yGVG2fkWW26BPhhqI="}]} diff --git a/pkg/sbom/cyclonedx/unmarshal.go b/pkg/sbom/cyclonedx/unmarshal.go index 2a8d282d1011..6f7a2f69d40e 100644 --- a/pkg/sbom/cyclonedx/unmarshal.go +++ b/pkg/sbom/cyclonedx/unmarshal.go @@ -6,6 +6,8 @@ import ( "strconv" "strings" + "github.com/aquasecurity/trivy/pkg/log" + cdx "github.com/CycloneDX/cyclonedx-go" "github.com/samber/lo" "golang.org/x/xerrors" @@ -23,6 +25,7 @@ type CycloneDX struct { } func (c *CycloneDX) UnmarshalJSON(b []byte) error { + log.Logger.Debug("Unmarshaling CycloneDX JSON...") if c.SBOM == nil { c.SBOM = &sbom.SBOM{} } diff --git a/pkg/sbom/sbom.go b/pkg/sbom/sbom.go index dfe3c99fb74f..04699c27ef00 100644 --- a/pkg/sbom/sbom.go +++ b/pkg/sbom/sbom.go @@ -6,8 +6,10 @@ import ( "io" "strings" + "github.com/in-toto/in-toto-golang/in_toto" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/attestation" "github.com/aquasecurity/trivy/pkg/fanal/types" ) @@ -22,11 +24,12 @@ type SBOM struct { type Format string const ( - FormatCycloneDXJSON Format = "cyclonedx-json" - FormatCycloneDXXML Format = "cyclonedx-xml" - FormatSPDXJSON Format = "spdx-json" - FormatSPDXXML Format = "spdx-xml" - FormatUnknown Format = "unknown" + FormatCycloneDXJSON Format = "cyclonedx-json" + FormatCycloneDXXML Format = "cyclonedx-xml" + FormatSPDXJSON Format = "spdx-json" + FormatSPDXXML Format = "spdx-xml" + FormatAttestCycloneDXJSON Format = "attest-cyclonedx-json" + FormatUnknown Format = "unknown" ) func DetectFormat(r io.ReadSeeker) (Format, error) { @@ -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 + // 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 }