From ca91044a8c40633943c8220e056381e6d3a8eb80 Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Fri, 2 Dec 2022 02:47:10 +0100 Subject: [PATCH] imagetools inspect: handle provenance and sboms Signed-off-by: CrazyMax --- docs/reference/buildx_imagetools_inspect.md | 231 ++-- go.mod | 6 + go.sum | 9 + util/imagetools/inspect.go | 10 - util/imagetools/printers.go | 290 +++-- .../containerd/reference/docker/reference.go | 799 ++++++++++++ .../docker/go-imageinspect/.dockerignore | 1 + .../docker/go-imageinspect/.gitignore | 1 + .../docker/go-imageinspect/.golangci.yml | 34 + .../docker/go-imageinspect/Dockerfile | 92 ++ .../github.com/docker/go-imageinspect/LICENSE | 202 +++ .../docker/go-imageinspect/README.md | 11 + .../docker/go-imageinspect/buildinfo.go | 95 ++ .../docker/go-imageinspect/config.go | 42 + .../docker/go-imageinspect/docker-bake.hcl | 60 + .../github.com/docker/go-imageinspect/load.go | 308 +++++ .../docker/go-imageinspect/provenance.go | 29 + .../github.com/docker/go-imageinspect/sbom.go | 203 +++ .../docker/go-imageinspect/types.go | 125 ++ .../github.com/in-toto/in-toto-golang/LICENSE | 13 + .../in-toto-golang/in_toto/certconstraint.go | 156 +++ .../in-toto/in-toto-golang/in_toto/hashlib.go | 30 + .../in-toto/in-toto-golang/in_toto/keylib.go | 669 ++++++++++ .../in-toto/in-toto-golang/in_toto/match.go | 228 ++++ .../in-toto/in-toto-golang/in_toto/model.go | 1056 ++++++++++++++++ .../in-toto/in-toto-golang/in_toto/rulelib.go | 131 ++ .../in-toto/in-toto-golang/in_toto/runlib.go | 400 ++++++ .../slsa_provenance/v0.2/provenance.go | 66 + .../in-toto/in-toto-golang/in_toto/util.go | 147 +++ .../in-toto-golang/in_toto/util_unix.go | 14 + .../in-toto-golang/in_toto/util_windows.go | 25 + .../in-toto-golang/in_toto/verifylib.go | 1091 +++++++++++++++++ .../go-securesystemslib/LICENSE | 21 + .../cjson/canonicaljson.go | 145 +++ .../go-securesystemslib/dsse/sign.go | 197 +++ .../go-securesystemslib/dsse/verify.go | 146 +++ .../github.com/shibumi/go-pathspec/.gitignore | 26 + .../github.com/shibumi/go-pathspec/GO-LICENSE | 27 + vendor/github.com/shibumi/go-pathspec/LICENSE | 201 +++ .../github.com/shibumi/go-pathspec/README.md | 45 + .../shibumi/go-pathspec/gitignore.go | 299 +++++ .../github.com/spdx/tools-golang/LICENSE.code | 550 +++++++++ .../github.com/spdx/tools-golang/LICENSE.docs | 399 ++++++ .../tools-golang/jsonloader/jsonloader.go | 24 + .../jsonloader/parser2v2/parse_annotations.go | 48 + .../parser2v2/parse_creation_info.go | 122 ++ .../jsonloader/parser2v2/parse_files.go | 122 ++ .../parser2v2/parse_other_license.go | 45 + .../jsonloader/parser2v2/parse_package.go | 211 ++++ .../parser2v2/parse_relationship.go | 51 + .../jsonloader/parser2v2/parse_reviews.go | 45 + .../jsonloader/parser2v2/parse_snippets.go | 89 ++ .../jsonloader/parser2v2/parser.go | 132 ++ .../jsonloader/parser2v2/types.go | 9 + .../tools-golang/jsonloader/parser2v2/util.go | 115 ++ .../spdx/tools-golang/spdx/annotation.go | 55 + .../spdx/tools-golang/spdx/checksum.go | 26 + .../spdx/tools-golang/spdx/creation_info.go | 147 +++ .../spdx/tools-golang/spdx/document.go | 32 + .../github.com/spdx/tools-golang/spdx/file.go | 177 +++ .../spdx/tools-golang/spdx/identifier.go | 71 ++ .../spdx/tools-golang/spdx/other_license.go | 61 + .../spdx/tools-golang/spdx/package.go | 272 ++++ .../spdx/tools-golang/spdx/relationship.go | 39 + .../spdx/tools-golang/spdx/review.go | 47 + .../spdx/tools-golang/spdx/snippet.go | 99 ++ vendor/modules.txt | 21 + 67 files changed, 10455 insertions(+), 235 deletions(-) create mode 100644 vendor/github.com/containerd/containerd/reference/docker/reference.go create mode 100644 vendor/github.com/docker/go-imageinspect/.dockerignore create mode 100644 vendor/github.com/docker/go-imageinspect/.gitignore create mode 100644 vendor/github.com/docker/go-imageinspect/.golangci.yml create mode 100644 vendor/github.com/docker/go-imageinspect/Dockerfile create mode 100644 vendor/github.com/docker/go-imageinspect/LICENSE create mode 100644 vendor/github.com/docker/go-imageinspect/README.md create mode 100644 vendor/github.com/docker/go-imageinspect/buildinfo.go create mode 100644 vendor/github.com/docker/go-imageinspect/config.go create mode 100644 vendor/github.com/docker/go-imageinspect/docker-bake.hcl create mode 100644 vendor/github.com/docker/go-imageinspect/load.go create mode 100644 vendor/github.com/docker/go-imageinspect/provenance.go create mode 100644 vendor/github.com/docker/go-imageinspect/sbom.go create mode 100644 vendor/github.com/docker/go-imageinspect/types.go create mode 100644 vendor/github.com/in-toto/in-toto-golang/LICENSE create mode 100644 vendor/github.com/in-toto/in-toto-golang/in_toto/certconstraint.go create mode 100644 vendor/github.com/in-toto/in-toto-golang/in_toto/hashlib.go create mode 100644 vendor/github.com/in-toto/in-toto-golang/in_toto/keylib.go create mode 100644 vendor/github.com/in-toto/in-toto-golang/in_toto/match.go create mode 100644 vendor/github.com/in-toto/in-toto-golang/in_toto/model.go create mode 100644 vendor/github.com/in-toto/in-toto-golang/in_toto/rulelib.go create mode 100644 vendor/github.com/in-toto/in-toto-golang/in_toto/runlib.go create mode 100644 vendor/github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2/provenance.go create mode 100644 vendor/github.com/in-toto/in-toto-golang/in_toto/util.go create mode 100644 vendor/github.com/in-toto/in-toto-golang/in_toto/util_unix.go create mode 100644 vendor/github.com/in-toto/in-toto-golang/in_toto/util_windows.go create mode 100644 vendor/github.com/in-toto/in-toto-golang/in_toto/verifylib.go create mode 100644 vendor/github.com/secure-systems-lab/go-securesystemslib/LICENSE create mode 100644 vendor/github.com/secure-systems-lab/go-securesystemslib/cjson/canonicaljson.go create mode 100644 vendor/github.com/secure-systems-lab/go-securesystemslib/dsse/sign.go create mode 100644 vendor/github.com/secure-systems-lab/go-securesystemslib/dsse/verify.go create mode 100644 vendor/github.com/shibumi/go-pathspec/.gitignore create mode 100644 vendor/github.com/shibumi/go-pathspec/GO-LICENSE create mode 100644 vendor/github.com/shibumi/go-pathspec/LICENSE create mode 100644 vendor/github.com/shibumi/go-pathspec/README.md create mode 100644 vendor/github.com/shibumi/go-pathspec/gitignore.go create mode 100644 vendor/github.com/spdx/tools-golang/LICENSE.code create mode 100644 vendor/github.com/spdx/tools-golang/LICENSE.docs create mode 100644 vendor/github.com/spdx/tools-golang/jsonloader/jsonloader.go create mode 100644 vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_annotations.go create mode 100644 vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_creation_info.go create mode 100644 vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_files.go create mode 100644 vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_other_license.go create mode 100644 vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_package.go create mode 100644 vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_relationship.go create mode 100644 vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_reviews.go create mode 100644 vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_snippets.go create mode 100644 vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parser.go create mode 100644 vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/types.go create mode 100644 vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/util.go create mode 100644 vendor/github.com/spdx/tools-golang/spdx/annotation.go create mode 100644 vendor/github.com/spdx/tools-golang/spdx/checksum.go create mode 100644 vendor/github.com/spdx/tools-golang/spdx/creation_info.go create mode 100644 vendor/github.com/spdx/tools-golang/spdx/document.go create mode 100644 vendor/github.com/spdx/tools-golang/spdx/file.go create mode 100644 vendor/github.com/spdx/tools-golang/spdx/identifier.go create mode 100644 vendor/github.com/spdx/tools-golang/spdx/other_license.go create mode 100644 vendor/github.com/spdx/tools-golang/spdx/package.go create mode 100644 vendor/github.com/spdx/tools-golang/spdx/relationship.go create mode 100644 vendor/github.com/spdx/tools-golang/spdx/review.go create mode 100644 vendor/github.com/spdx/tools-golang/spdx/snippet.go diff --git a/docs/reference/buildx_imagetools_inspect.md b/docs/reference/buildx_imagetools_inspect.md index 82555b4933db..8934cf2e9b99 100644 --- a/docs/reference/buildx_imagetools_inspect.md +++ b/docs/reference/buildx_imagetools_inspect.md @@ -72,7 +72,8 @@ unset. Following fields are available: * `.Name`: provides the reference of the image * `.Manifest`: provides the manifest or manifest list * `.Image`: provides the image config -* `.BuildInfo`: provides [build info from image config](https://github.com/moby/buildkit/blob/master/docs/build-repro.md#image-config) +* `.Provenance`: provides provenance or [build info from image config](https://github.com/moby/buildkit/blob/master/docs/build-repro.md#image-config) +* `.SBOM`: provides SBOM #### `.Name` @@ -122,18 +123,17 @@ Manifests: Platform: linux/riscv64 ``` -#### `.BuildInfo` +#### `.Provenance` ```console -$ docker buildx imagetools inspect crazymax/buildx:buildinfo --format "{{.BuildInfo}}" +$ docker buildx imagetools inspect crazymax/buildx:buildinfo --format "{{.Provenance}}" Name: docker.io/crazymax/buildx:buildinfo -Frontend: dockerfile.v0 -Attrs: - filename: Dockerfile - source: docker/dockerfile-upstream:master-labs - build-arg:bar: foo - build-arg:foo: bar -Sources: +BuildSource: +BuildDefinition: Dockerfile +BuildParameters: + bar: foo + foo: bar +Materials: Type: docker-image Ref: docker.io/docker/buildx-bin:0.6.1@sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0 Pin: sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0 @@ -155,6 +155,73 @@ Sources: Pin: sha256:419455202b0ef97e480d7f8199b26a721a417818bc0e2d106975f74323f25e6c ``` +#### `.SBOM` + +```console +$ docker buildx imagetools inspect crazymax/buildkit:attest --format "{{.SBOM}}" +Name: docker.io/crazymax/buildkit:attest +AlpinePackages: + Name: alpine-baselayout + Version: 3.4.0-r0 + + Name: alpine-baselayout-data + Version: 3.4.0-r0 + + Name: alpine-keys + Version: 2.4-r1 + + Name: apk-tools + Version: 2.12.10-r1 + + Name: brotli-libs + Version: 1.0.9-r9 + + Name: busybox + Version: 1.35.0-r29 + + Name: busybox-binsh + Version: 1.35.0-r29 + + Name: ca-certificates + Version: 20220614-r2 + + Name: ca-certificates-bundle + Version: 20220614-r2 + + Name: curl + Version: 7.86.0-r1 + + Name: libc-utils + Version: 0.7.2-r3 + + Name: libcrypto3 + Version: 3.0.7-r0 + + Name: libcurl + Version: 7.86.0-r1 + + Name: libssl3 + Version: 3.0.7-r0 + + Name: musl + Version: 1.2.3-r4 + + Name: musl-utils + Version: 1.2.3-r4 + + Name: nghttp2-libs + Version: 1.51.0-r0 + + Name: scanelf + Version: 1.3.5-r1 + + Name: ssl_client + Version: 1.35.0-r29 + + Name: zlib + Version: 1.2.13-r0 +``` + #### JSON output A `json` go template func is also available if you want to render fields as @@ -178,12 +245,12 @@ $ docker buildx imagetools inspect moby/buildkit:master --format "{{json .Manife { "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", - "digest": "sha256:79d97f205e2799d99a3a8ae2a1ef17acb331e11784262c3faada847dc6972c52", + "digest": "sha256:eef5f92f1e942856995ae4714b85a58277b2a7fcc3bcb62ea2f0d38e0f5e88de", "size": 2010, "manifests": [ { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "digest": "sha256:bd1e78f06de26610fadf4eb9d04b1a45a545799d6342701726e952cc0c11c912", + "digest": "sha256:f9f41c85124686c2afe330a985066748a91d7a5d505777fe274df804ab5e077e", "size": 1158, "platform": { "architecture": "amd64", @@ -192,7 +259,7 @@ $ docker buildx imagetools inspect moby/buildkit:master --format "{{json .Manife }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "digest": "sha256:d37dcced63ec0965824fca644f0ac9efad8569434ec15b4c83adfcb3dcfc743b", + "digest": "sha256:82097c2be19c617aafb3c3e43c88548738d4b2bf3db5c36666283a918b390266", "size": 1158, "platform": { "architecture": "arm", @@ -202,7 +269,7 @@ $ docker buildx imagetools inspect moby/buildkit:master --format "{{json .Manife }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "digest": "sha256:ce142eb2255e6af46f2809e159fd03081697c7605a3de03b9cbe9a52ddb244bf", + "digest": "sha256:b6b91e6c823d7220ded7d3b688e571ba800b13d91bbc904c1d8053593e3ee42c", "size": 1158, "platform": { "architecture": "arm64", @@ -211,7 +278,7 @@ $ docker buildx imagetools inspect moby/buildkit:master --format "{{json .Manife }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "digest": "sha256:f59bfb5062fff76ce464bfa4e25ebaaaac887d6818238e119d68613c456d360c", + "digest": "sha256:797061bcc16778de048b96f769c018ec24da221088050bbe926ea3b8d51d77e8", "size": 1158, "platform": { "architecture": "s390x", @@ -220,7 +287,7 @@ $ docker buildx imagetools inspect moby/buildkit:master --format "{{json .Manife }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "digest": "sha256:cc96426e0c50a78105d5637d31356db5dd6ec594f21b24276e534a32da09645c", + "digest": "sha256:b93d3a84d18c4d0b8c279e77343d854d9b5177df7ea55cf468d461aa2523364e", "size": 1159, "platform": { "architecture": "ppc64le", @@ -229,7 +296,7 @@ $ docker buildx imagetools inspect moby/buildkit:master --format "{{json .Manife }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "digest": "sha256:39f9c1e2878e6c333acb23187d6b205ce82ed934c60da326cb2c698192631478", + "digest": "sha256:d5c950dd1b270d437c838187112a0cb44c9258248d7a3a8bcb42fae8f717dc01", "size": 1158, "platform": { "architecture": "riscv64", @@ -241,42 +308,20 @@ $ docker buildx imagetools inspect moby/buildkit:master --format "{{json .Manife ``` ```console -$ docker buildx imagetools inspect crazymax/buildx:buildinfo --format "{{json .BuildInfo}}" +$ docker buildx imagetools inspect crazymax/buildkit:attest --format "{{json .Provenance}}" ``` ```json { - "frontend": "dockerfile.v0", - "attrs": { - "build-arg:bar": "foo", - "build-arg:foo": "bar", - "filename": "Dockerfile", - "source": "crazymax/dockerfile:buildattrs" - }, - "sources": [ - { - "type": "docker-image", - "ref": "docker.io/docker/buildx-bin:0.6.1@sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0", - "pin": "sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0" - }, - { - "type": "docker-image", - "ref": "docker.io/library/alpine:3.13@sha256:026f721af4cf2843e07bba648e158fb35ecc876d822130633cc49f707f0fc88c", - "pin": "sha256:026f721af4cf2843e07bba648e158fb35ecc876d822130633cc49f707f0fc88c" - }, - { - "type": "docker-image", - "ref": "docker.io/moby/buildkit:v0.9.0@sha256:8dc668e7f66db1c044aadbed306020743516a94848793e0f81f94a087ee78cab", - "pin": "sha256:8dc668e7f66db1c044aadbed306020743516a94848793e0f81f94a087ee78cab" - }, + "Materials": [ { - "type": "docker-image", - "ref": "docker.io/tonistiigi/xx@sha256:21a61be4744f6531cb5f33b0e6f40ede41fa3a1b8c82d5946178f80cc84bfc04", - "pin": "sha256:21a61be4744f6531cb5f33b0e6f40ede41fa3a1b8c82d5946178f80cc84bfc04" + "Type": "docker-image", + "Ref": "docker.io/docker/buildkit-syft-scanner:stable-1", + "Pin": "sha256:b45f1d207e16c3a3a5a10b254ad8ad358d01f7ea090d382b95c6b2ee2b3ef765" }, { - "type": "http", - "ref": "https://raw.githubusercontent.com/moby/moby/master/README.md", - "pin": "sha256:419455202b0ef97e480d7f8199b26a721a417818bc0e2d106975f74323f25e6c" + "Type": "docker-image", + "Ref": "docker.io/library/alpine:latest", + "Pin": "sha256:8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4" } ] } @@ -412,39 +457,37 @@ $ docker buildx imagetools inspect crazymax/buildx:buildinfo --format "{{json .} } ] }, - "buildinfo": { - "frontend": "dockerfile.v0", - "attrs": { - "build-arg:bar": "foo", - "build-arg:foo": "bar", - "filename": "Dockerfile", - "source": "docker/dockerfile-upstream:master-labs" + "provenance": { + "BuildDefinition": "Dockerfile", + "BuildParameters": { + "bar": "foo", + "foo": "bar" }, - "sources": [ + "Materials": [ { - "type": "docker-image", - "ref": "docker.io/docker/buildx-bin:0.6.1@sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0", - "pin": "sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0" + "Type": "docker-image", + "Ref": "docker.io/docker/buildx-bin:0.6.1@sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0", + "Pin": "sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0" }, { - "type": "docker-image", - "ref": "docker.io/library/alpine:3.13", - "pin": "sha256:026f721af4cf2843e07bba648e158fb35ecc876d822130633cc49f707f0fc88c" + "Type": "docker-image", + "Ref": "docker.io/library/alpine:3.13", + "Pin": "sha256:026f721af4cf2843e07bba648e158fb35ecc876d822130633cc49f707f0fc88c" }, { - "type": "docker-image", - "ref": "docker.io/moby/buildkit:v0.9.0", - "pin": "sha256:8dc668e7f66db1c044aadbed306020743516a94848793e0f81f94a087ee78cab" + "Type": "docker-image", + "Ref": "docker.io/moby/buildkit:v0.9.0", + "Pin": "sha256:8dc668e7f66db1c044aadbed306020743516a94848793e0f81f94a087ee78cab" }, { - "type": "docker-image", - "ref": "docker.io/tonistiigi/xx@sha256:21a61be4744f6531cb5f33b0e6f40ede41fa3a1b8c82d5946178f80cc84bfc04", - "pin": "sha256:21a61be4744f6531cb5f33b0e6f40ede41fa3a1b8c82d5946178f80cc84bfc04" + "Type": "docker-image", + "Ref": "docker.io/tonistiigi/xx@sha256:21a61be4744f6531cb5f33b0e6f40ede41fa3a1b8c82d5946178f80cc84bfc04", + "Pin": "sha256:21a61be4744f6531cb5f33b0e6f40ede41fa3a1b8c82d5946178f80cc84bfc04" }, { - "type": "http", - "ref": "https://raw.githubusercontent.com/moby/moby/master/README.md", - "pin": "sha256:419455202b0ef97e480d7f8199b26a721a417818bc0e2d106975f74323f25e6c" + "Type": "http", + "Ref": "https://raw.githubusercontent.com/moby/moby/master/README.md", + "Pin": "sha256:419455202b0ef97e480d7f8199b26a721a417818bc0e2d106975f74323f25e6c" } ] } @@ -453,16 +496,16 @@ $ docker buildx imagetools inspect crazymax/buildx:buildinfo --format "{{json .} #### Multi-platform -Multi-platform images are supported for `.Image` and `.BuildInfo` fields. If -you want to pick up a specific platform, you can specify it using the `index` -go template function: +Multi-platform images are supported for `.Image`, `.Provenance` and `.SBOM` +fields. If you want to pick up a specific platform, you can specify it using +the `index` go template function: ```console $ docker buildx imagetools inspect --format '{{json (index .Image "linux/s390x")}}' moby/buildkit:master ``` ```json { - "created": "2022-02-25T17:13:27.89891722Z", + "created": "2022-11-30T17:42:26.414957336Z", "architecture": "s390x", "os": "linux", "config": { @@ -481,8 +524,8 @@ $ docker buildx imagetools inspect --format '{{json (index .Image "linux/s390x") "diff_ids": [ "sha256:41048e32d0684349141cf05f629c5fc3c5915d1f3426b66dbb8953a540e01e1e", "sha256:2651209b9208fff6c053bc3c17353cb07874e50f1a9bc96d6afd03aef63de76a", - "sha256:6741ed7e73039d853fa8902246a4c7e8bf9dd09652fd1b08251bc5f9e8876a7f", - "sha256:92ac046adeeb65c86ae3f0b458dee04ad4a462e417661c04d77642c66494f69b" + "sha256:88577322e65f094ce8ac27435880f1a8a9baadb569258026bb141770451bafcb", + "sha256:de8f9a790e4ed10ff1f1f8ea923c9da4f97246a7e200add2dc6650eba3f10a20" ] }, "history": [ @@ -501,23 +544,23 @@ $ docker buildx imagetools inspect --format '{{json (index .Image "linux/s390x") "comment": "buildkit.dockerfile.v0" }, { - "created": "2022-02-24T00:34:00.924540012Z", + "created": "2022-08-25T00:39:25.652811078Z", "created_by": "COPY examples/buildctl-daemonless/buildctl-daemonless.sh /usr/bin/ # buildkit", "comment": "buildkit.dockerfile.v0" }, { - "created": "2022-02-25T17:13:27.89891722Z", + "created": "2022-11-30T17:42:26.414957336Z", "created_by": "VOLUME [/var/lib/buildkit]", "comment": "buildkit.dockerfile.v0", "empty_layer": true }, { - "created": "2022-02-25T17:13:27.89891722Z", + "created": "2022-11-30T17:42:26.414957336Z", "created_by": "COPY / /usr/bin/ # buildkit", "comment": "buildkit.dockerfile.v0" }, { - "created": "2022-02-25T17:13:27.89891722Z", + "created": "2022-11-30T17:42:26.414957336Z", "created_by": "ENTRYPOINT [\"buildkitd\"]", "comment": "buildkit.dockerfile.v0", "empty_layer": true @@ -541,24 +584,24 @@ $ docker buildx imagetools inspect --raw crazymax/loop | jq "schemaVersion": 2, "config": { "mediaType": "application/vnd.docker.container.image.v1+json", - "digest": "sha256:7ace7d324e79b360b2db8b820d83081863d96d22e734cdf297a8e7fd83f6ceb3", - "size": 2298 + "digest": "sha256:a98999183d2c7a8845f6d56496e51099ce6e4359ee7255504174b05430c4b78b", + "size": 2762 }, "layers": [ { "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", - "digest": "sha256:5843afab387455b37944e709ee8c78d7520df80f8d01cf7f861aae63beeddb6b", - "size": 2811478 + "digest": "sha256:8663204ce13b2961da55026a2034abb9e5afaaccf6a9cfb44ad71406dcd07c7b", + "size": 2818370 }, { "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", - "digest": "sha256:726d3732a87e1c430d67e8969de6b222a889d45e045ebae1a008a37ba38f3b1f", - "size": 1776812 + "digest": "sha256:f0868a92f8e1e5018ed4e60eb845ed4ff0e2229897f4105e5a4735c1d6fd874f", + "size": 1821402 }, { "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", - "digest": "sha256:5d7cf9b33148a8f220c84f27dd2cfae46aca019a3ea3fbf7274f6d6dbfae8f3b", - "size": 382855 + "digest": "sha256:d010066dbdfcf7c12fca30cd4b567aa7218eb6762ab53169d043655b7a8d7f2e", + "size": 404457 } ] } @@ -574,7 +617,7 @@ $ docker buildx imagetools inspect --raw moby/buildkit:master | jq "manifests": [ { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "digest": "sha256:667d28c9fb33820ce686887a717a148e89fa77f9097f9352996bbcce99d352b1", + "digest": "sha256:f9f41c85124686c2afe330a985066748a91d7a5d505777fe274df804ab5e077e", "size": 1158, "platform": { "architecture": "amd64", @@ -583,7 +626,7 @@ $ docker buildx imagetools inspect --raw moby/buildkit:master | jq }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "digest": "sha256:71789527b64ab3d7b3de01d364b449cd7f7a3da758218fbf73b9c9aae05a6775", + "digest": "sha256:82097c2be19c617aafb3c3e43c88548738d4b2bf3db5c36666283a918b390266", "size": 1158, "platform": { "architecture": "arm", @@ -593,7 +636,7 @@ $ docker buildx imagetools inspect --raw moby/buildkit:master | jq }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "digest": "sha256:fb64667e1ce6ab0d05478f3a8402af07b27737598dcf9a510fb1d792b13a66be", + "digest": "sha256:b6b91e6c823d7220ded7d3b688e571ba800b13d91bbc904c1d8053593e3ee42c", "size": 1158, "platform": { "architecture": "arm64", @@ -602,7 +645,7 @@ $ docker buildx imagetools inspect --raw moby/buildkit:master | jq }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "digest": "sha256:1c3ddf95a0788e23f72f25800c05abc4458946685e2b66788c3d978cde6da92b", + "digest": "sha256:797061bcc16778de048b96f769c018ec24da221088050bbe926ea3b8d51d77e8", "size": 1158, "platform": { "architecture": "s390x", @@ -611,7 +654,7 @@ $ docker buildx imagetools inspect --raw moby/buildkit:master | jq }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "digest": "sha256:05bcde6d460a284e5bc88026cd070277e8380355de3126cbc8fe8a452708c6b1", + "digest": "sha256:b93d3a84d18c4d0b8c279e77343d854d9b5177df7ea55cf468d461aa2523364e", "size": 1159, "platform": { "architecture": "ppc64le", @@ -620,7 +663,7 @@ $ docker buildx imagetools inspect --raw moby/buildkit:master | jq }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "digest": "sha256:c04c57765304ab84f4f9807fff3e11605c3a60e16435c734b02c723680f6bd6e", + "digest": "sha256:d5c950dd1b270d437c838187112a0cb44c9258248d7a3a8bcb42fae8f717dc01", "size": 1158, "platform": { "architecture": "riscv64", diff --git a/go.mod b/go.mod index 30f0e5f75d51..79af33f7fdb8 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/docker/cli-docs-tool v0.5.0 github.com/docker/distribution v2.8.1+incompatible github.com/docker/docker v20.10.18+incompatible // v22.06.x - see "replace" for the actual version + github.com/docker/go-imageinspect v0.0.0-00010101000000-000000000000 github.com/docker/go-units v0.5.0 github.com/gofrs/flock v0.7.3 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 @@ -102,6 +103,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect github.com/imdario/mergo v0.3.13 // indirect + github.com/in-toto/in-toto-golang v0.3.4-0.20220709202702-fa494aaa0add // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/jinzhu/gorm v1.9.2 // indirect github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a // indirect @@ -129,6 +131,9 @@ require ( github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect github.com/rogpeppe/go-internal v1.8.1 // indirect + github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect + github.com/shibumi/go-pathspec v1.3.0 // indirect + github.com/spdx/tools-golang v0.3.0 // indirect github.com/spf13/viper v1.14.0 // indirect github.com/theupdateframework/notary v0.6.1 // indirect github.com/tonistiigi/fsutil v0.0.0-20220930225714-4638ad635be5 // indirect @@ -173,6 +178,7 @@ replace ( github.com/aws/aws-sdk-go-v2/config => github.com/aws/aws-sdk-go-v2/config v1.15.5 // same as buildkit github.com/docker/cli => github.com/docker/cli v20.10.3-0.20220803220330-418ca3b4d46f+incompatible // master (v22.06-dev) github.com/docker/docker => github.com/docker/docker v20.10.3-0.20221006005007-99aa9bb766b5+incompatible // 22.06 branch (v22.06-dev) + github.com/docker/go-imageinspect => github.com/crazy-max/go-imageinspect v0.0.0-20221202013537-618679aa0cae k8s.io/api => k8s.io/api v0.22.4 k8s.io/apimachinery => k8s.io/apimachinery v0.22.4 k8s.io/client-go => k8s.io/client-go v0.22.4 diff --git a/go.sum b/go.sum index 5c8737dd2777..3484f394ac48 100644 --- a/go.sum +++ b/go.sum @@ -135,6 +135,7 @@ github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XP github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/compose-spec/compose-go v1.6.0 h1:7Ol/UULMUtbPmB0EYrETASRoum821JpOh/XaEf+hN+Q= github.com/compose-spec/compose-go v1.6.0/go.mod h1:os+Ulh2jlZxY1XT1hbciERadjSUU/BtZ6+gcN7vD7J0= github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= @@ -155,6 +156,8 @@ github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcD github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/crazy-max/go-imageinspect v0.0.0-20221202013537-618679aa0cae h1:y8M8HTAsztBgqsEBHaMVAB9fe/+F3H0U8OjcFgIszDA= +github.com/crazy-max/go-imageinspect v0.0.0-20221202013537-618679aa0cae/go.mod h1:+9rqlV1TOfcDYUig6IKnx4cWgiv7xU40STy1OqEgbok= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -348,6 +351,7 @@ github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ 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.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jinzhu/gorm v1.9.2 h1:lCvgEaqe/HVE+tjAR2mt4HbbHAZsQOv3XAZiEZV37iw= @@ -505,11 +509,13 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 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 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002 h1:ka9QPuQg2u4LGipiZGsgkg3rJCo4iIUCy75FddM0GRQ= github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc= github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= +github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -518,6 +524,9 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM= +github.com/spdx/tools-golang v0.3.0 h1:rtm+DHk3aAt74Fh0Wgucb4pCxjXV8SqHCPEb2iBd30k= +github.com/spdx/tools-golang v0.3.0/go.mod h1:RO4Y3IFROJnz+43JKm1YOrbtgQNljW4gAPpA/sY2eqo= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= diff --git a/util/imagetools/inspect.go b/util/imagetools/inspect.go index 88e96788c712..78cd760218f4 100644 --- a/util/imagetools/inspect.go +++ b/util/imagetools/inspect.go @@ -15,9 +15,7 @@ import ( clitypes "github.com/docker/cli/cli/config/types" "github.com/docker/distribution/reference" "github.com/moby/buildkit/util/contentutil" - "github.com/moby/buildkit/util/imageutil" "github.com/moby/buildkit/util/tracing" - "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" ) @@ -162,11 +160,3 @@ func RegistryAuthForRef(ref string, a Auth) (string, error) { } return base64.URLEncoding.EncodeToString(buf), nil } - -func (r *Resolver) ImageConfig(ctx context.Context, in string, platform *ocispec.Platform) (digest.Digest, []byte, error) { - in, _, err := r.Resolve(ctx, in) - if err != nil { - return "", nil, err - } - return imageutil.Config(ctx, in, r.resolver(), r.buffer, nil, platform) -} diff --git a/util/imagetools/printers.go b/util/imagetools/printers.go index 371607010f8a..07a1962d782a 100644 --- a/util/imagetools/printers.go +++ b/util/imagetools/printers.go @@ -8,18 +8,15 @@ import ( "os" "sort" "strings" - "sync" "text/tabwriter" "text/template" "github.com/containerd/containerd/images" "github.com/containerd/containerd/platforms" "github.com/docker/distribution/reference" - binfotypes "github.com/moby/buildkit/util/buildinfo/types" - "github.com/moby/buildkit/util/imageutil" + "github.com/docker/go-imageinspect" "github.com/opencontainers/go-digest" ocispecs "github.com/opencontainers/image-spec/specs-go/v1" - "golang.org/x/sync/errgroup" ) const defaultPfx = " " @@ -31,16 +28,29 @@ type Printer struct { name string format string - raw []byte - ref reference.Named - manifest ocispecs.Descriptor - index ocispecs.Index - platforms []ocispecs.Platform + insres *imageinspect.Result + raw []byte + ref reference.Named + manifest ocispecs.Descriptor + index ocispecs.Index } func NewPrinter(ctx context.Context, opt Opt, name string, format string) (*Printer, error) { resolver := New(opt) + iinspect, err := imageinspect.NewLoader(imageinspect.Opt{ + // TODO: set cache dir (maybe ~/.docker/buildx/cache) + Resolver: resolver.resolver(), + }) + if err != nil { + return nil, err + } + + insres, err := iinspect.Load(ctx, name) + if err != nil { + return nil, err + } + ref, err := parseRef(name) if err != nil { return nil, err @@ -56,28 +66,16 @@ func NewPrinter(ctx context.Context, opt Opt, name string, format string) (*Prin return nil, err } - var pforms []ocispecs.Platform - switch manifest.MediaType { - case images.MediaTypeDockerSchema2ManifestList, ocispecs.MediaTypeImageIndex: - for _, m := range index.Manifests { - if m.Platform != nil { - pforms = append(pforms, *m.Platform) - } - } - default: - pforms = append(pforms, platforms.DefaultSpec()) - } - return &Printer{ - ctx: ctx, - resolver: resolver, - name: name, - format: format, - raw: dt, - ref: ref, - manifest: manifest, - index: index, - platforms: pforms, + ctx: ctx, + resolver: resolver, + name: name, + format: format, + insres: insres, + raw: dt, + ref: ref, + manifest: manifest, + index: index, }, nil } @@ -112,38 +110,9 @@ func (p *Printer) Print(raw bool, out io.Writer) error { return err } - imageconfigs := make(map[string]*ocispecs.Image) - imageconfigsMutex := sync.Mutex{} - buildinfos := make(map[string]*binfotypes.BuildInfo) - buildinfosMutex := sync.Mutex{} - - eg, _ := errgroup.WithContext(p.ctx) - for _, platform := range p.platforms { - func(platform ocispecs.Platform) { - eg.Go(func() error { - img, dtic, err := p.getImageConfig(&platform) - if err != nil { - return err - } else if img != nil { - imageconfigsMutex.Lock() - imageconfigs[platforms.Format(platform)] = img - imageconfigsMutex.Unlock() - } - if bi, err := imageutil.BuildInfo(dtic); err != nil { - return err - } else if bi != nil { - buildinfosMutex.Lock() - buildinfos[platforms.Format(platform)] = bi - buildinfosMutex.Unlock() - } - return nil - }) - }(platform) - } - if err := eg.Wait(); err != nil { - return err - } - + imageconfigs := p.insres.Configs() + provenances := p.insres.Provenances() + sboms := p.insres.SBOMs() format := tpl.Root.String() var manifest interface{} @@ -170,10 +139,11 @@ func (p *Printer) Print(raw bool, out io.Writer) error { switch { // TODO: print formatted config - case strings.HasPrefix(format, "{{.Manifest"), strings.HasPrefix(format, "{{.BuildInfo"): + case strings.HasPrefix(format, "{{.Manifest"), strings.HasPrefix(format, "{{.BuildInfo"), strings.HasPrefix(format, "{{.SBOM"), strings.HasPrefix(format, "{{.Provenance"): w := tabwriter.NewWriter(out, 0, 0, 1, ' ', 0) _, _ = fmt.Fprintf(w, "Name:\t%s\n", p.ref.String()) - if strings.HasPrefix(format, "{{.Manifest") { + switch { + case strings.HasPrefix(format, "{{.Manifest"): _, _ = fmt.Fprintf(w, "MediaType:\t%s\n", p.manifest.MediaType) _, _ = fmt.Fprintf(w, "Digest:\t%s\n", p.manifest.Digest) _ = w.Flush() @@ -181,42 +151,53 @@ func (p *Printer) Print(raw bool, out io.Writer) error { case images.MediaTypeDockerSchema2ManifestList, ocispecs.MediaTypeImageIndex: _ = p.printManifestList(out) } - } else if strings.HasPrefix(format, "{{.BuildInfo") { + case strings.HasPrefix(format, "{{.BuildInfo"), strings.HasPrefix(format, "{{.Provenance"): + _ = w.Flush() + _ = p.printProvenances(provenances, out) + case strings.HasPrefix(format, "{{.SBOM"): _ = w.Flush() - _ = p.printBuildInfos(buildinfos, out) + _ = p.printSBOMs(sboms, out) } default: - if len(p.platforms) > 1 { + if len(p.insres.Platforms) > 1 { return tpl.Execute(out, struct { - Name string `json:"name,omitempty"` - Manifest interface{} `json:"manifest,omitempty"` - Image map[string]*ocispecs.Image `json:"image,omitempty"` - BuildInfo map[string]*binfotypes.BuildInfo `json:"buildinfo,omitempty"` + Name string `json:"name,omitempty"` + Manifest interface{} `json:"manifest,omitempty"` + Image map[string]*imageinspect.Config `json:"image,omitempty"` + Provenance map[string]*imageinspect.Provenance `json:"provenance,omitempty"` + SBOM map[string]*imageinspect.SBOM `json:"sbom,omitempty"` }{ - Name: p.name, - Manifest: manifest, - Image: imageconfigs, - BuildInfo: buildinfos, + Name: p.name, + Manifest: manifest, + Image: imageconfigs, + Provenance: provenances, + SBOM: sboms, }) } - var ic *ocispecs.Image + var imageconfig *imageinspect.Config for _, v := range imageconfigs { - ic = v + imageconfig = v } - var bi *binfotypes.BuildInfo - for _, v := range buildinfos { - bi = v + var provenance *imageinspect.Provenance + for _, v := range provenances { + provenance = v + } + var sbom *imageinspect.SBOM + for _, v := range sboms { + sbom = v } return tpl.Execute(out, struct { - Name string `json:"name,omitempty"` - Manifest interface{} `json:"manifest,omitempty"` - Image *ocispecs.Image `json:"image,omitempty"` - BuildInfo *binfotypes.BuildInfo `json:"buildinfo,omitempty"` + Name string `json:"name,omitempty"` + Manifest interface{} `json:"manifest,omitempty"` + Image *imageinspect.Config `json:"image,omitempty"` + Provenance *imageinspect.Provenance `json:"provenance,omitempty"` + SBOM *imageinspect.SBOM `json:"sbom,omitempty"` }{ - Name: p.name, - Manifest: manifest, - Image: ic, - BuildInfo: bi, + Name: p.name, + Manifest: manifest, + Image: imageconfig, + Provenance: provenance, + SBOM: sbom, }) } @@ -265,47 +246,49 @@ func (p *Printer) printManifestList(out io.Writer) error { return w.Flush() } -func (p *Printer) printBuildInfos(bis map[string]*binfotypes.BuildInfo, out io.Writer) error { - if len(bis) == 0 { +func (p *Printer) printProvenances(provenances map[string]*imageinspect.Provenance, out io.Writer) error { + if len(provenances) == 0 { return nil - } else if len(bis) == 1 { - for _, bi := range bis { - return p.printBuildInfo(bi, "", out) + } else if len(provenances) == 1 { + for _, pr := range provenances { + return p.printProvenance(pr, "", out) } } var pkeys []string - for _, pform := range p.platforms { - pkeys = append(pkeys, platforms.Format(pform)) + for _, pform := range p.insres.Platforms { + pkeys = append(pkeys, pform) } sort.Strings(pkeys) for _, platform := range pkeys { - bi := bis[platform] - w := tabwriter.NewWriter(out, 0, 0, 1, ' ', 0) - _, _ = fmt.Fprintf(w, "\t\nPlatform:\t%s\t\n", platform) - _ = w.Flush() - if err := p.printBuildInfo(bi, "", out); err != nil { - return err + if pr, ok := provenances[platform]; ok { + w := tabwriter.NewWriter(out, 0, 0, 1, ' ', 0) + _, _ = fmt.Fprintf(w, "\t\nPlatform:\t%s\t\n", platform) + _ = w.Flush() + if err := p.printProvenance(pr, "", out); err != nil { + return err + } } } return nil } -func (p *Printer) printBuildInfo(bi *binfotypes.BuildInfo, pfx string, out io.Writer) error { +func (p *Printer) printProvenance(pr *imageinspect.Provenance, pfx string, out io.Writer) error { w := tabwriter.NewWriter(out, 0, 0, 1, ' ', 0) - _, _ = fmt.Fprintf(w, "%sFrontend:\t%s\n", pfx, bi.Frontend) + _, _ = fmt.Fprintf(w, "%sBuildSource:\t%s\n", pfx, pr.BuildSource) + _, _ = fmt.Fprintf(w, "%sBuildDefinition:\t%s\n", pfx, pr.BuildDefinition) - if len(bi.Attrs) > 0 { - _, _ = fmt.Fprintf(w, "%sAttrs:\t\n", pfx) + if len(pr.BuildParameters) > 0 { + _, _ = fmt.Fprintf(w, "%sBuildParameters:\t\n", pfx) _ = w.Flush() - for k, v := range bi.Attrs { - _, _ = fmt.Fprintf(w, "%s%s:\t%s\n", pfx+defaultPfx, k, *v) + for k, v := range pr.BuildParameters { + _, _ = fmt.Fprintf(w, "%s%s:\t%s\n", pfx+defaultPfx, k, v) } } - if len(bi.Sources) > 0 { - _, _ = fmt.Fprintf(w, "%sSources:\t\n", pfx) + if len(pr.Materials) > 0 { + _, _ = fmt.Fprintf(w, "%sMaterials:\t\n", pfx) _ = w.Flush() - for i, v := range bi.Sources { + for i, v := range pr.Materials { if i != 0 { _, _ = fmt.Fprintf(w, "\t\n") } @@ -315,32 +298,77 @@ func (p *Printer) printBuildInfo(bi *binfotypes.BuildInfo, pfx string, out io.Wr } } - if len(bi.Deps) > 0 { - _, _ = fmt.Fprintf(w, "%sDeps:\t\n", pfx) - _ = w.Flush() - firstPass := true - for k, v := range bi.Deps { - if !firstPass { - _, _ = fmt.Fprintf(w, "\t\n") - } - _, _ = fmt.Fprintf(w, "%sName:\t%s\n", pfx+defaultPfx, k) + // TODO: deps not yet implemented with provenance + //if len(pr.Deps) > 0 { + // _, _ = fmt.Fprintf(w, "%sDeps:\t\n", pfx) + // _ = w.Flush() + // firstPass := true + // for k, v := range pr.Deps { + // if !firstPass { + // _, _ = fmt.Fprintf(w, "\t\n") + // } + // _, _ = fmt.Fprintf(w, "%sName:\t%s\n", pfx+defaultPfx, k) + // _ = w.Flush() + // _ = p.printProvenance(&v, pfx+defaultPfx, out) + // firstPass = false + // } + //} + + return w.Flush() +} + +func (p *Printer) printSBOMs(sboms map[string]*imageinspect.SBOM, out io.Writer) error { + if len(sboms) == 0 { + return nil + } else if len(sboms) == 1 { + for _, sb := range sboms { + return p.printSBOM(sb, "", out) + } + } + var pkeys []string + for _, pform := range p.insres.Platforms { + pkeys = append(pkeys, pform) + } + sort.Strings(pkeys) + for _, platform := range pkeys { + if sb, ok := sboms[platform]; ok { + w := tabwriter.NewWriter(out, 0, 0, 1, ' ', 0) + _, _ = fmt.Fprintf(w, "\t\nPlatform:\t%s\t\n", platform) _ = w.Flush() - _ = p.printBuildInfo(&v, pfx+defaultPfx, out) - firstPass = false + if err := p.printSBOM(sb, "", out); err != nil { + return err + } } } - - return w.Flush() + return nil } -func (p *Printer) getImageConfig(platform *ocispecs.Platform) (*ocispecs.Image, []byte, error) { - _, dtic, err := p.resolver.ImageConfig(p.ctx, p.name, platform) - if err != nil { - return nil, nil, err +func (p *Printer) printSBOM(sb *imageinspect.SBOM, pfx string, out io.Writer) error { + w := tabwriter.NewWriter(out, 0, 0, 1, ' ', 0) + + if len(sb.AlpinePackages) > 0 { + _, _ = fmt.Fprintf(w, "%sAlpinePackages:\t\n", pfx) + _ = w.Flush() + for i, v := range sb.AlpinePackages { + if i != 0 { + _, _ = fmt.Fprintf(w, "\t\n") + } + _, _ = fmt.Fprintf(w, "%sName:\t%s\n", pfx+defaultPfx, v.Name) + _, _ = fmt.Fprintf(w, "%sVersion:\t%s\n", pfx+defaultPfx, v.Version) + } } - var img *ocispecs.Image - if err = json.Unmarshal(dtic, &img); err != nil { - return nil, nil, err + + if len(sb.UnknownPackages) > 0 { + _, _ = fmt.Fprintf(w, "%sUnknownPackages:\t\n", pfx) + _ = w.Flush() + for i, v := range sb.UnknownPackages { + if i != 0 { + _, _ = fmt.Fprintf(w, "\t\n") + } + _, _ = fmt.Fprintf(w, "%sName:\t%s\n", pfx+defaultPfx, v.Name) + _, _ = fmt.Fprintf(w, "%sVersion:\t%s\n", pfx+defaultPfx, v.Version) + } } - return img, dtic, nil + + return w.Flush() } diff --git a/vendor/github.com/containerd/containerd/reference/docker/reference.go b/vendor/github.com/containerd/containerd/reference/docker/reference.go new file mode 100644 index 000000000000..25436b645550 --- /dev/null +++ b/vendor/github.com/containerd/containerd/reference/docker/reference.go @@ -0,0 +1,799 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// Package docker provides a general type to represent any way of referencing images within the registry. +// Its main purpose is to abstract tags and digests (content-addressable hash). +// +// Grammar +// +// reference := name [ ":" tag ] [ "@" digest ] +// name := [domain '/'] path-component ['/' path-component]* +// domain := domain-component ['.' domain-component]* [':' port-number] +// domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/ +// port-number := /[0-9]+/ +// path-component := alpha-numeric [separator alpha-numeric]* +// alpha-numeric := /[a-z0-9]+/ +// separator := /[_.]|__|[-]*/ +// +// tag := /[\w][\w.-]{0,127}/ +// +// digest := digest-algorithm ":" digest-hex +// digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]* +// digest-algorithm-separator := /[+.-_]/ +// digest-algorithm-component := /[A-Za-z][A-Za-z0-9]*/ +// digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value +// +// identifier := /[a-f0-9]{64}/ +// short-identifier := /[a-f0-9]{6,64}/ +package docker + +import ( + "errors" + "fmt" + "path" + "regexp" + "strings" + + "github.com/opencontainers/go-digest" +) + +const ( + // NameTotalLengthMax is the maximum total number of characters in a repository name. + NameTotalLengthMax = 255 +) + +var ( + // ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference. + ErrReferenceInvalidFormat = errors.New("invalid reference format") + + // ErrTagInvalidFormat represents an error while trying to parse a string as a tag. + ErrTagInvalidFormat = errors.New("invalid tag format") + + // ErrDigestInvalidFormat represents an error while trying to parse a string as a tag. + ErrDigestInvalidFormat = errors.New("invalid digest format") + + // ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters. + ErrNameContainsUppercase = errors.New("repository name must be lowercase") + + // ErrNameEmpty is returned for empty, invalid repository names. + ErrNameEmpty = errors.New("repository name must have at least one component") + + // ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax. + ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax) + + // ErrNameNotCanonical is returned when a name is not canonical. + ErrNameNotCanonical = errors.New("repository name must be canonical") +) + +// Reference is an opaque object reference identifier that may include +// modifiers such as a hostname, name, tag, and digest. +type Reference interface { + // String returns the full reference + String() string +} + +// Field provides a wrapper type for resolving correct reference types when +// working with encoding. +type Field struct { + reference Reference +} + +// AsField wraps a reference in a Field for encoding. +func AsField(reference Reference) Field { + return Field{reference} +} + +// Reference unwraps the reference type from the field to +// return the Reference object. This object should be +// of the appropriate type to further check for different +// reference types. +func (f Field) Reference() Reference { + return f.reference +} + +// MarshalText serializes the field to byte text which +// is the string of the reference. +func (f Field) MarshalText() (p []byte, err error) { + return []byte(f.reference.String()), nil +} + +// UnmarshalText parses text bytes by invoking the +// reference parser to ensure the appropriately +// typed reference object is wrapped by field. +func (f *Field) UnmarshalText(p []byte) error { + r, err := Parse(string(p)) + if err != nil { + return err + } + + f.reference = r + return nil +} + +// Named is an object with a full name +type Named interface { + Reference + Name() string +} + +// Tagged is an object which has a tag +type Tagged interface { + Reference + Tag() string +} + +// NamedTagged is an object including a name and tag. +type NamedTagged interface { + Named + Tag() string +} + +// Digested is an object which has a digest +// in which it can be referenced by +type Digested interface { + Reference + Digest() digest.Digest +} + +// Canonical reference is an object with a fully unique +// name including a name with domain and digest +type Canonical interface { + Named + Digest() digest.Digest +} + +// namedRepository is a reference to a repository with a name. +// A namedRepository has both domain and path components. +type namedRepository interface { + Named + Domain() string + Path() string +} + +// Domain returns the domain part of the Named reference +func Domain(named Named) string { + if r, ok := named.(namedRepository); ok { + return r.Domain() + } + domain, _ := splitDomain(named.Name()) + return domain +} + +// Path returns the name without the domain part of the Named reference +func Path(named Named) (name string) { + if r, ok := named.(namedRepository); ok { + return r.Path() + } + _, path := splitDomain(named.Name()) + return path +} + +func splitDomain(name string) (string, string) { + match := anchoredNameRegexp.FindStringSubmatch(name) + if len(match) != 3 { + return "", name + } + return match[1], match[2] +} + +// SplitHostname splits a named reference into a +// hostname and name string. If no valid hostname is +// found, the hostname is empty and the full value +// is returned as name +// DEPRECATED: Use Domain or Path +func SplitHostname(named Named) (string, string) { + if r, ok := named.(namedRepository); ok { + return r.Domain(), r.Path() + } + return splitDomain(named.Name()) +} + +// Parse parses s and returns a syntactically valid Reference. +// If an error was encountered it is returned, along with a nil Reference. +// NOTE: Parse will not handle short digests. +func Parse(s string) (Reference, error) { + matches := ReferenceRegexp.FindStringSubmatch(s) + if matches == nil { + if s == "" { + return nil, ErrNameEmpty + } + if ReferenceRegexp.FindStringSubmatch(strings.ToLower(s)) != nil { + return nil, ErrNameContainsUppercase + } + return nil, ErrReferenceInvalidFormat + } + + if len(matches[1]) > NameTotalLengthMax { + return nil, ErrNameTooLong + } + + var repo repository + + nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1]) + if len(nameMatch) == 3 { + repo.domain = nameMatch[1] + repo.path = nameMatch[2] + } else { + repo.domain = "" + repo.path = matches[1] + } + + ref := reference{ + namedRepository: repo, + tag: matches[2], + } + if matches[3] != "" { + var err error + ref.digest, err = digest.Parse(matches[3]) + if err != nil { + return nil, err + } + } + + r := getBestReferenceType(ref) + if r == nil { + return nil, ErrNameEmpty + } + + return r, nil +} + +// ParseNamed parses s and returns a syntactically valid reference implementing +// the Named interface. The reference must have a name and be in the canonical +// form, otherwise an error is returned. +// If an error was encountered it is returned, along with a nil Reference. +// NOTE: ParseNamed will not handle short digests. +func ParseNamed(s string) (Named, error) { + named, err := ParseNormalizedNamed(s) + if err != nil { + return nil, err + } + if named.String() != s { + return nil, ErrNameNotCanonical + } + return named, nil +} + +// WithName returns a named object representing the given string. If the input +// is invalid ErrReferenceInvalidFormat will be returned. +func WithName(name string) (Named, error) { + if len(name) > NameTotalLengthMax { + return nil, ErrNameTooLong + } + + match := anchoredNameRegexp.FindStringSubmatch(name) + if match == nil || len(match) != 3 { + return nil, ErrReferenceInvalidFormat + } + return repository{ + domain: match[1], + path: match[2], + }, nil +} + +// WithTag combines the name from "name" and the tag from "tag" to form a +// reference incorporating both the name and the tag. +func WithTag(name Named, tag string) (NamedTagged, error) { + if !anchoredTagRegexp.MatchString(tag) { + return nil, ErrTagInvalidFormat + } + var repo repository + if r, ok := name.(namedRepository); ok { + repo.domain = r.Domain() + repo.path = r.Path() + } else { + repo.path = name.Name() + } + if canonical, ok := name.(Canonical); ok { + return reference{ + namedRepository: repo, + tag: tag, + digest: canonical.Digest(), + }, nil + } + return taggedReference{ + namedRepository: repo, + tag: tag, + }, nil +} + +// WithDigest combines the name from "name" and the digest from "digest" to form +// a reference incorporating both the name and the digest. +func WithDigest(name Named, digest digest.Digest) (Canonical, error) { + if !anchoredDigestRegexp.MatchString(digest.String()) { + return nil, ErrDigestInvalidFormat + } + var repo repository + if r, ok := name.(namedRepository); ok { + repo.domain = r.Domain() + repo.path = r.Path() + } else { + repo.path = name.Name() + } + if tagged, ok := name.(Tagged); ok { + return reference{ + namedRepository: repo, + tag: tagged.Tag(), + digest: digest, + }, nil + } + return canonicalReference{ + namedRepository: repo, + digest: digest, + }, nil +} + +// TrimNamed removes any tag or digest from the named reference. +func TrimNamed(ref Named) Named { + repo := repository{} + if r, ok := ref.(namedRepository); ok { + repo.domain, repo.path = r.Domain(), r.Path() + } else { + repo.domain, repo.path = splitDomain(ref.Name()) + } + return repo +} + +func getBestReferenceType(ref reference) Reference { + if ref.Name() == "" { + // Allow digest only references + if ref.digest != "" { + return digestReference(ref.digest) + } + return nil + } + if ref.tag == "" { + if ref.digest != "" { + return canonicalReference{ + namedRepository: ref.namedRepository, + digest: ref.digest, + } + } + return ref.namedRepository + } + if ref.digest == "" { + return taggedReference{ + namedRepository: ref.namedRepository, + tag: ref.tag, + } + } + + return ref +} + +type reference struct { + namedRepository + tag string + digest digest.Digest +} + +func (r reference) String() string { + return r.Name() + ":" + r.tag + "@" + r.digest.String() +} + +func (r reference) Tag() string { + return r.tag +} + +func (r reference) Digest() digest.Digest { + return r.digest +} + +type repository struct { + domain string + path string +} + +func (r repository) String() string { + return r.Name() +} + +func (r repository) Name() string { + if r.domain == "" { + return r.path + } + return r.domain + "/" + r.path +} + +func (r repository) Domain() string { + return r.domain +} + +func (r repository) Path() string { + return r.path +} + +type digestReference digest.Digest + +func (d digestReference) String() string { + return digest.Digest(d).String() +} + +func (d digestReference) Digest() digest.Digest { + return digest.Digest(d) +} + +type taggedReference struct { + namedRepository + tag string +} + +func (t taggedReference) String() string { + return t.Name() + ":" + t.tag +} + +func (t taggedReference) Tag() string { + return t.tag +} + +type canonicalReference struct { + namedRepository + digest digest.Digest +} + +func (c canonicalReference) String() string { + return c.Name() + "@" + c.digest.String() +} + +func (c canonicalReference) Digest() digest.Digest { + return c.digest +} + +var ( + // alphaNumericRegexp defines the alpha numeric atom, typically a + // component of names. This only allows lower case characters and digits. + alphaNumericRegexp = match(`[a-z0-9]+`) + + // separatorRegexp defines the separators allowed to be embedded in name + // components. This allow one period, one or two underscore and multiple + // dashes. + separatorRegexp = match(`(?:[._]|__|[-]*)`) + + // nameComponentRegexp restricts registry path component names to start + // with at least one letter or number, with following parts able to be + // separated by one period, one or two underscore and multiple dashes. + nameComponentRegexp = expression( + alphaNumericRegexp, + optional(repeated(separatorRegexp, alphaNumericRegexp))) + + // domainComponentRegexp restricts the registry domain component of a + // repository name to start with a component as defined by DomainRegexp + // and followed by an optional port. + domainComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`) + + // DomainRegexp defines the structure of potential domain components + // that may be part of image names. This is purposely a subset of what is + // allowed by DNS to ensure backwards compatibility with Docker image + // names. + DomainRegexp = expression( + domainComponentRegexp, + optional(repeated(literal(`.`), domainComponentRegexp)), + optional(literal(`:`), match(`[0-9]+`))) + + // TagRegexp matches valid tag names. From docker/docker:graph/tags.go. + TagRegexp = match(`[\w][\w.-]{0,127}`) + + // anchoredTagRegexp matches valid tag names, anchored at the start and + // end of the matched string. + anchoredTagRegexp = anchored(TagRegexp) + + // DigestRegexp matches valid digests. + DigestRegexp = match(`[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`) + + // anchoredDigestRegexp matches valid digests, anchored at the start and + // end of the matched string. + anchoredDigestRegexp = anchored(DigestRegexp) + + // NameRegexp is the format for the name component of references. The + // regexp has capturing groups for the domain and name part omitting + // the separating forward slash from either. + NameRegexp = expression( + optional(DomainRegexp, literal(`/`)), + nameComponentRegexp, + optional(repeated(literal(`/`), nameComponentRegexp))) + + // anchoredNameRegexp is used to parse a name value, capturing the + // domain and trailing components. + anchoredNameRegexp = anchored( + optional(capture(DomainRegexp), literal(`/`)), + capture(nameComponentRegexp, + optional(repeated(literal(`/`), nameComponentRegexp)))) + + // ReferenceRegexp is the full supported format of a reference. The regexp + // is anchored and has capturing groups for name, tag, and digest + // components. + ReferenceRegexp = anchored(capture(NameRegexp), + optional(literal(":"), capture(TagRegexp)), + optional(literal("@"), capture(DigestRegexp))) + + // IdentifierRegexp is the format for string identifier used as a + // content addressable identifier using sha256. These identifiers + // are like digests without the algorithm, since sha256 is used. + IdentifierRegexp = match(`([a-f0-9]{64})`) + + // ShortIdentifierRegexp is the format used to represent a prefix + // of an identifier. A prefix may be used to match a sha256 identifier + // within a list of trusted identifiers. + ShortIdentifierRegexp = match(`([a-f0-9]{6,64})`) + + // anchoredIdentifierRegexp is used to check or match an + // identifier value, anchored at start and end of string. + anchoredIdentifierRegexp = anchored(IdentifierRegexp) +) + +// match compiles the string to a regular expression. +var match = regexp.MustCompile + +// literal compiles s into a literal regular expression, escaping any regexp +// reserved characters. +func literal(s string) *regexp.Regexp { + re := match(regexp.QuoteMeta(s)) + + if _, complete := re.LiteralPrefix(); !complete { + panic("must be a literal") + } + + return re +} + +// expression defines a full expression, where each regular expression must +// follow the previous. +func expression(res ...*regexp.Regexp) *regexp.Regexp { + var s string + for _, re := range res { + s += re.String() + } + + return match(s) +} + +// optional wraps the expression in a non-capturing group and makes the +// production optional. +func optional(res ...*regexp.Regexp) *regexp.Regexp { + return match(group(expression(res...)).String() + `?`) +} + +// repeated wraps the regexp in a non-capturing group to get one or more +// matches. +func repeated(res ...*regexp.Regexp) *regexp.Regexp { + return match(group(expression(res...)).String() + `+`) +} + +// group wraps the regexp in a non-capturing group. +func group(res ...*regexp.Regexp) *regexp.Regexp { + return match(`(?:` + expression(res...).String() + `)`) +} + +// capture wraps the expression in a capturing group. +func capture(res ...*regexp.Regexp) *regexp.Regexp { + return match(`(` + expression(res...).String() + `)`) +} + +// anchored anchors the regular expression by adding start and end delimiters. +func anchored(res ...*regexp.Regexp) *regexp.Regexp { + return match(`^` + expression(res...).String() + `$`) +} + +var ( + legacyDefaultDomain = "index.docker.io" + defaultDomain = "docker.io" + officialRepoName = "library" + defaultTag = "latest" +) + +// normalizedNamed represents a name which has been +// normalized and has a familiar form. A familiar name +// is what is used in Docker UI. An example normalized +// name is "docker.io/library/ubuntu" and corresponding +// familiar name of "ubuntu". +type normalizedNamed interface { + Named + Familiar() Named +} + +// ParseNormalizedNamed parses a string into a named reference +// transforming a familiar name from Docker UI to a fully +// qualified reference. If the value may be an identifier +// use ParseAnyReference. +func ParseNormalizedNamed(s string) (Named, error) { + if ok := anchoredIdentifierRegexp.MatchString(s); ok { + return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s) + } + domain, remainder := splitDockerDomain(s) + var remoteName string + if tagSep := strings.IndexRune(remainder, ':'); tagSep > -1 { + remoteName = remainder[:tagSep] + } else { + remoteName = remainder + } + if strings.ToLower(remoteName) != remoteName { + return nil, errors.New("invalid reference format: repository name must be lowercase") + } + + ref, err := Parse(domain + "/" + remainder) + if err != nil { + return nil, err + } + named, isNamed := ref.(Named) + if !isNamed { + return nil, fmt.Errorf("reference %s has no name", ref.String()) + } + return named, nil +} + +// ParseDockerRef normalizes the image reference following the docker convention. This is added +// mainly for backward compatibility. +// The reference returned can only be either tagged or digested. For reference contains both tag +// and digest, the function returns digested reference, e.g. docker.io/library/busybox:latest@ +// sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa will be returned as +// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa. +func ParseDockerRef(ref string) (Named, error) { + named, err := ParseNormalizedNamed(ref) + if err != nil { + return nil, err + } + if _, ok := named.(NamedTagged); ok { + if canonical, ok := named.(Canonical); ok { + // The reference is both tagged and digested, only + // return digested. + newNamed, err := WithName(canonical.Name()) + if err != nil { + return nil, err + } + newCanonical, err := WithDigest(newNamed, canonical.Digest()) + if err != nil { + return nil, err + } + return newCanonical, nil + } + } + return TagNameOnly(named), nil +} + +// splitDockerDomain splits a repository name to domain and remotename string. +// If no valid domain is found, the default domain is used. Repository name +// needs to be already validated before. +func splitDockerDomain(name string) (domain, remainder string) { + i := strings.IndexRune(name, '/') + if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") { + domain, remainder = defaultDomain, name + } else { + domain, remainder = name[:i], name[i+1:] + } + if domain == legacyDefaultDomain { + domain = defaultDomain + } + if domain == defaultDomain && !strings.ContainsRune(remainder, '/') { + remainder = officialRepoName + "/" + remainder + } + return +} + +// familiarizeName returns a shortened version of the name familiar +// to to the Docker UI. Familiar names have the default domain +// "docker.io" and "library/" repository prefix removed. +// For example, "docker.io/library/redis" will have the familiar +// name "redis" and "docker.io/dmcgowan/myapp" will be "dmcgowan/myapp". +// Returns a familiarized named only reference. +func familiarizeName(named namedRepository) repository { + repo := repository{ + domain: named.Domain(), + path: named.Path(), + } + + if repo.domain == defaultDomain { + repo.domain = "" + // Handle official repositories which have the pattern "library/" + if split := strings.Split(repo.path, "/"); len(split) == 2 && split[0] == officialRepoName { + repo.path = split[1] + } + } + return repo +} + +func (r reference) Familiar() Named { + return reference{ + namedRepository: familiarizeName(r.namedRepository), + tag: r.tag, + digest: r.digest, + } +} + +func (r repository) Familiar() Named { + return familiarizeName(r) +} + +func (t taggedReference) Familiar() Named { + return taggedReference{ + namedRepository: familiarizeName(t.namedRepository), + tag: t.tag, + } +} + +func (c canonicalReference) Familiar() Named { + return canonicalReference{ + namedRepository: familiarizeName(c.namedRepository), + digest: c.digest, + } +} + +// TagNameOnly adds the default tag "latest" to a reference if it only has +// a repo name. +func TagNameOnly(ref Named) Named { + if IsNameOnly(ref) { + namedTagged, err := WithTag(ref, defaultTag) + if err != nil { + // Default tag must be valid, to create a NamedTagged + // type with non-validated input the WithTag function + // should be used instead + panic(err) + } + return namedTagged + } + return ref +} + +// ParseAnyReference parses a reference string as a possible identifier, +// full digest, or familiar name. +func ParseAnyReference(ref string) (Reference, error) { + if ok := anchoredIdentifierRegexp.MatchString(ref); ok { + return digestReference("sha256:" + ref), nil + } + if dgst, err := digest.Parse(ref); err == nil { + return digestReference(dgst), nil + } + + return ParseNormalizedNamed(ref) +} + +// IsNameOnly returns true if reference only contains a repo name. +func IsNameOnly(ref Named) bool { + if _, ok := ref.(NamedTagged); ok { + return false + } + if _, ok := ref.(Canonical); ok { + return false + } + return true +} + +// FamiliarName returns the familiar name string +// for the given named, familiarizing if needed. +func FamiliarName(ref Named) string { + if nn, ok := ref.(normalizedNamed); ok { + return nn.Familiar().Name() + } + return ref.Name() +} + +// FamiliarString returns the familiar string representation +// for the given reference, familiarizing if needed. +func FamiliarString(ref Reference) string { + if nn, ok := ref.(normalizedNamed); ok { + return nn.Familiar().String() + } + return ref.String() +} + +// FamiliarMatch reports whether ref matches the specified pattern. +// See https://godoc.org/path#Match for supported patterns. +func FamiliarMatch(pattern string, ref Reference) (bool, error) { + matched, err := path.Match(pattern, FamiliarString(ref)) + if namedRef, isNamed := ref.(Named); isNamed && !matched { + matched, _ = path.Match(pattern, FamiliarName(namedRef)) + } + return matched, err +} diff --git a/vendor/github.com/docker/go-imageinspect/.dockerignore b/vendor/github.com/docker/go-imageinspect/.dockerignore new file mode 100644 index 000000000000..e660fd93d319 --- /dev/null +++ b/vendor/github.com/docker/go-imageinspect/.dockerignore @@ -0,0 +1 @@ +bin/ diff --git a/vendor/github.com/docker/go-imageinspect/.gitignore b/vendor/github.com/docker/go-imageinspect/.gitignore new file mode 100644 index 000000000000..e660fd93d319 --- /dev/null +++ b/vendor/github.com/docker/go-imageinspect/.gitignore @@ -0,0 +1 @@ +bin/ diff --git a/vendor/github.com/docker/go-imageinspect/.golangci.yml b/vendor/github.com/docker/go-imageinspect/.golangci.yml new file mode 100644 index 000000000000..4eaf96fc93c1 --- /dev/null +++ b/vendor/github.com/docker/go-imageinspect/.golangci.yml @@ -0,0 +1,34 @@ +run: + timeout: 10m + +linters: + enable: + - gofmt + - govet + - deadcode + - depguard + - goimports + - ineffassign + - misspell + - unused + - varcheck + - revive + - staticcheck + - typecheck + #- structcheck # FIXME: structcheck is disabled because of generics: https://github.com/golangci/golangci-lint/issues/2649 + disable-all: true + +linters-settings: + depguard: + list-type: blacklist + include-go-root: true + packages: + # The io/ioutil package has been deprecated. + # https://go.dev/doc/go1.16#ioutil + - io/ioutil + +issues: + exclude-rules: + - linters: + - revive + text: "stutters" diff --git a/vendor/github.com/docker/go-imageinspect/Dockerfile b/vendor/github.com/docker/go-imageinspect/Dockerfile new file mode 100644 index 000000000000..78e79298ad55 --- /dev/null +++ b/vendor/github.com/docker/go-imageinspect/Dockerfile @@ -0,0 +1,92 @@ +# syntax=docker/dockerfile:1 + +# Copyright 2022 go-imageinspect authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +ARG GO_VERSION="1.19" +ARG GOLANGCI_LINT_VERSION="v1.48" +ARG ADDLICENSE_VERSION="v1.0.0" + +ARG LICENSE_ARGS="-c go-imageinspect -l apache" +ARG LICENSE_FILES=".*\(Dockerfile\|\.go\|\.hcl\|\.sh\)" + +FROM golangci/golangci-lint:${GOLANGCI_LINT_VERSION}-alpine AS golangci-lint +FROM ghcr.io/google/addlicense:${ADDLICENSE_VERSION} AS addlicense + +FROM golang:${GO_VERSION}-alpine AS base +RUN apk add --no-cache cpio findutils git linux-headers +ENV CGO_ENABLED=0 +WORKDIR /src + +FROM base AS build-base +COPY go.* . +RUN --mount=type=cache,target=/go/pkg/mod \ + --mount=type=cache,target=/root/.cache/go-build \ + go mod download + +FROM base AS vendored +RUN --mount=type=bind,target=.,rw \ + --mount=type=cache,target=/go/pkg/mod \ + go mod tidy && mkdir /out && cp go.mod go.sum /out + +FROM scratch AS vendor-update +COPY --from=vendored /out / + +FROM vendored AS vendor-validate +RUN --mount=type=bind,target=.,rw <&2 'ERROR: Vendor result differs. Please vendor your package with "docker buildx bake vendor"' + echo "$diff" + exit 1 +fi +EOT + +FROM build-base AS lint +RUN --mount=type=bind,target=. \ + --mount=type=cache,target=/root/.cache \ + --mount=type=cache,target=/go/pkg/mod \ + --mount=from=golangci-lint,source=/usr/bin/golangci-lint,target=/usr/bin/golangci-lint \ + golangci-lint run ./... + +FROM base AS license-set +ARG LICENSE_ARGS +ARG LICENSE_FILES +RUN --mount=type=bind,target=.,rw \ + --mount=from=addlicense,source=/app/addlicense,target=/usr/bin/addlicense \ + find . -regex "${LICENSE_FILES}" | xargs addlicense ${LICENSE_ARGS} \ + && mkdir /out \ + && find . -regex "${LICENSE_FILES}" | cpio -pdm /out + +FROM scratch AS license-update +COPY --from=license-set /out / + +FROM base AS license-validate +ARG LICENSE_ARGS +ARG LICENSE_FILES +RUN --mount=type=bind,target=. \ + --mount=from=addlicense,source=/app/addlicense,target=/usr/bin/addlicense \ + find . -regex "${LICENSE_FILES}" | xargs addlicense -check ${LICENSE_ARGS} + +FROM build-base AS test +RUN --mount=type=bind,target=. \ + --mount=type=cache,target=/root/.cache \ + --mount=type=cache,target=/go/pkg/mod \ + go test -v -coverprofile=/tmp/coverage.txt -covermode=atomic ./... + +FROM scratch AS test-coverage +COPY --from=test /tmp/coverage.txt /coverage.txt diff --git a/vendor/github.com/docker/go-imageinspect/LICENSE b/vendor/github.com/docker/go-imageinspect/LICENSE new file mode 100644 index 000000000000..d64569567334 --- /dev/null +++ b/vendor/github.com/docker/go-imageinspect/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/docker/go-imageinspect/README.md b/vendor/github.com/docker/go-imageinspect/README.md new file mode 100644 index 000000000000..a8c5bc253247 --- /dev/null +++ b/vendor/github.com/docker/go-imageinspect/README.md @@ -0,0 +1,11 @@ +[![CI Status](https://img.shields.io/github/workflow/status/docker/go-imageinspect/ci?label=ci&logo=github&style=flat-square)](https://github.com/docker/go-imageinspect/actions?query=workflow%3Aci) + +## About + +Go library for accessing container images with their associated objects, typed +metadata and verified signatures. + +## Contributing + +Want to contribute? Awesome! You can find information about contributing to +this project in the [CONTRIBUTING.md](/.github/CONTRIBUTING.md) diff --git a/vendor/github.com/docker/go-imageinspect/buildinfo.go b/vendor/github.com/docker/go-imageinspect/buildinfo.go new file mode 100644 index 000000000000..0878e3f10958 --- /dev/null +++ b/vendor/github.com/docker/go-imageinspect/buildinfo.go @@ -0,0 +1,95 @@ +// Copyright 2022 go-imageinspect authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package imageinspect + +import ( + "context" + "encoding/base64" + "encoding/json" + "strings" + + "github.com/containerd/containerd/content" + "github.com/containerd/containerd/remotes" + binfotypes "github.com/moby/buildkit/util/buildinfo/types" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" +) + +func (l *Loader) scanBuildInfo(ctx context.Context, fetcher remotes.Fetcher, desc ocispec.Descriptor, img *Image) error { + _, err := remotes.FetchHandler(l.cache, fetcher)(ctx, desc) + if err != nil { + return err + } + dt, err := content.ReadBlob(ctx, l.cache, desc) + if err != nil { + return err + } + + var cfg binfotypes.ImageConfig + if err := json.Unmarshal(dt, &cfg); err != nil { + return err + } + + if cfg.BuildInfo == "" { + return nil + } + + dt, err = base64.StdEncoding.DecodeString(cfg.BuildInfo) + if err != nil { + return errors.Wrapf(err, "failed to decode buildinfo base64") + } + + var bi binfotypes.BuildInfo + if err := json.Unmarshal(dt, &bi); err != nil { + return errors.Wrapf(err, "failed to decode buildinfo") + } + + p := img.Provenance + if p == nil { + p = &Provenance{} + img.Provenance = p + } + + if context := bi.Attrs["context"]; context != nil { + p.BuildSource = *context + } + + if fn := bi.Attrs["filename"]; fn != nil { + p.BuildDefinition = *fn + } + + for key, val := range bi.Attrs { + if val == nil || !strings.HasPrefix(key, "build-arg:") { + continue + } + if p.BuildParameters == nil { + p.BuildParameters = make(map[string]string) + } + p.BuildParameters[strings.TrimPrefix(key, "build-arg:")] = *val + } + + p.Materials = make([]Material, len(bi.Sources)) + + for i, src := range bi.Sources { + // TODO: mark base image + p.Materials[i] = Material{ + Type: string(src.Type), + Ref: src.Ref, + Alias: src.Alias, + Pin: src.Pin, + } + } + return nil +} diff --git a/vendor/github.com/docker/go-imageinspect/config.go b/vendor/github.com/docker/go-imageinspect/config.go new file mode 100644 index 000000000000..8e048920067d --- /dev/null +++ b/vendor/github.com/docker/go-imageinspect/config.go @@ -0,0 +1,42 @@ +// Copyright 2022 go-imageinspect authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package imageinspect + +import ( + "context" + "encoding/json" + + "github.com/containerd/containerd/content" + "github.com/containerd/containerd/remotes" + ocispecs "github.com/opencontainers/image-spec/specs-go/v1" +) + +type Config struct { + ocispecs.Image +} + +func (l *Loader) scanConfig(ctx context.Context, fetcher remotes.Fetcher, desc ocispecs.Descriptor, img *Image) error { + _, err := remotes.FetchHandler(l.cache, fetcher)(ctx, desc) + if err != nil { + return err + } + + dt, err := content.ReadBlob(ctx, l.cache, desc) + if err != nil { + return err + } + + return json.Unmarshal(dt, &img.Config) +} diff --git a/vendor/github.com/docker/go-imageinspect/docker-bake.hcl b/vendor/github.com/docker/go-imageinspect/docker-bake.hcl new file mode 100644 index 000000000000..67bd4e952fa6 --- /dev/null +++ b/vendor/github.com/docker/go-imageinspect/docker-bake.hcl @@ -0,0 +1,60 @@ +// Copyright 2022 go-imageinspect authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +# Defines the output folder +variable "DESTDIR" { + default = "" +} +function "bindir" { + params = [defaultdir] + result = DESTDIR != "" ? DESTDIR : "./bin/${defaultdir}" +} + +group "default" { + targets = ["test"] +} + +group "validate" { + targets = ["lint", "vendor-validate", "license-validate"] +} + +target "lint" { + target = "lint" + output = ["type=cacheonly"] +} + +target "vendor-validate" { + target = "vendor-validate" + output = ["type=cacheonly"] +} + +target "vendor-update" { + target = "vendor-update" + output = ["."] +} + +target "test" { + target = "test-coverage" + output = [bindir("coverage")] +} + +target "license-validate" { + target = "license-validate" + output = ["type=cacheonly"] +} + +target "license-update" { + target = "license-update" + output = ["."] +} diff --git a/vendor/github.com/docker/go-imageinspect/load.go b/vendor/github.com/docker/go-imageinspect/load.go new file mode 100644 index 000000000000..620a810f6396 --- /dev/null +++ b/vendor/github.com/docker/go-imageinspect/load.go @@ -0,0 +1,308 @@ +// Copyright 2022 go-imageinspect authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package imageinspect + +import ( + "context" + "encoding/json" + "path/filepath" + "sort" + "sync" + + "github.com/containerd/containerd/content" + "github.com/containerd/containerd/content/local" + "github.com/containerd/containerd/images" + "github.com/containerd/containerd/platforms" + distref "github.com/containerd/containerd/reference/docker" + "github.com/containerd/containerd/remotes" + "github.com/moby/buildkit/util/contentutil" + "github.com/pkg/errors" + "golang.org/x/sync/errgroup" + + "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" +) + +const ( + AnnotationReference = "vnd.docker.reference.digest" + AnnotationImageTitle = "org.opencontainers.image.title" +) + +type ContentCache interface { + content.Provider + content.Ingester +} + +type Opt struct { + Resolver remotes.Resolver + CacheDir string +} + +type Loader struct { + opt *Opt + + cache ContentCache +} + +type manifest struct { + desc ocispec.Descriptor + manifest ocispec.Manifest +} + +type index struct { + desc ocispec.Descriptor + index ocispec.Index +} + +type result struct { + mu sync.Mutex + indexes map[digest.Digest]index + manifests map[digest.Digest]manifest + images map[string]digest.Digest + refs map[digest.Digest][]digest.Digest +} + +func newResult() *result { + return &result{ + indexes: make(map[digest.Digest]index), + manifests: make(map[digest.Digest]manifest), + images: make(map[string]digest.Digest), + refs: make(map[digest.Digest][]digest.Digest), + } +} + +func NewLoader(opt Opt) (*Loader, error) { + l := &Loader{ + opt: &opt, + } + + if opt.CacheDir != "" { + store, err := local.NewStore(filepath.Join(opt.CacheDir, "content")) + if err != nil { + return nil, err + } + l.cache = store + } else { + l.cache = contentutil.NewBuffer() + } + + return l, nil +} + +func (l *Loader) Load(ctx context.Context, ref string) (*Result, error) { + named, err := parseReference(ref) + if err != nil { + return nil, err + } + + _, desc, err := l.opt.Resolver.Resolve(ctx, named.String()) + if err != nil { + return nil, err + } + + canonical, err := distref.WithDigest(named, desc.Digest) + if err != nil { + return nil, err + } + + fetcher, err := l.opt.Resolver.Fetcher(ctx, canonical.String()) + if err != nil { + return nil, err + } + + r := newResult() + + if err := l.fetch(ctx, fetcher, desc, r); err != nil { + return nil, err + } + + rr := &Result{ + Images: make(map[string]Image), + } + + rr.Digest = desc.Digest + + if _, ok := r.manifests[desc.Digest]; ok { + rr.ResultType = Manifest + } else if _, ok := r.indexes[desc.Digest]; ok { + rr.ResultType = Index + } else { + rr.ResultType = Unknown + } + + for platform, dgst := range r.images { + rr.Platforms = append(rr.Platforms, platform) + + mfst, ok := r.manifests[dgst] + if !ok { + return nil, errors.Errorf("image %s not found", platform) + } + + var img Image + + var size int64 + + for _, layer := range mfst.manifest.Layers { + size += layer.Size + } + + img.Size = size + img.Platform = platform + + annotations := make(map[string]string, len(mfst.manifest.Annotations)+len(mfst.desc.Annotations)) + for k, v := range mfst.desc.Annotations { + annotations[k] = v + } + for k, v := range mfst.manifest.Annotations { + annotations[k] = v + } + + if title, ok := annotations[AnnotationImageTitle]; ok { + img.Title = title + } + + if err := l.scanConfig(ctx, fetcher, mfst.manifest.Config, &img); err != nil { + return nil, err + } + + refs, ok := r.refs[dgst] + if ok { + if err := l.scanSBOM(ctx, fetcher, r, dgst, refs, &img); err != nil { + return nil, err // TODO: these errors should likely be stored in the result + } + } + + if err := l.scanBuildInfo(ctx, fetcher, mfst.manifest.Config, &img); err != nil { + return nil, err + } + + rr.Images[platform] = img + + } + + sort.Strings(rr.Platforms) + + return rr, nil +} + +func (l *Loader) fetch(ctx context.Context, fetcher remotes.Fetcher, desc ocispec.Descriptor, r *result) error { + _, err := remotes.FetchHandler(l.cache, fetcher)(ctx, desc) + if err != nil { + return err + } + + switch desc.MediaType { + case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest: + var mfst ocispec.Manifest + dt, err := content.ReadBlob(ctx, l.cache, desc) + if err != nil { + return err + } + if err := json.Unmarshal(dt, &mfst); err != nil { + return err + } + r.mu.Lock() + r.manifests[desc.Digest] = manifest{ + desc: desc, + manifest: mfst, + } + r.mu.Unlock() + + ref, ok := desc.Annotations[AnnotationReference] + if ok { + refdgst, err := digest.Parse(ref) + if err != nil { + return err + } + r.mu.Lock() + r.refs[refdgst] = append(r.refs[refdgst], desc.Digest) + r.mu.Unlock() + } else { + p := desc.Platform + if p == nil { + p, err = l.readPlatformFromConfig(ctx, fetcher, mfst.Config) + if err != nil { + return err + } + } + r.mu.Lock() + r.images[platforms.Format(platforms.Normalize(*p))] = desc.Digest + r.mu.Unlock() + } + + case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex: + var idx ocispec.Index + dt, err := content.ReadBlob(ctx, l.cache, desc) + if err != nil { + return err + } + + if err := json.Unmarshal(dt, &idx); err != nil { + return err + } + + r.mu.Lock() + r.indexes[desc.Digest] = index{ + desc: desc, + index: idx, + } + r.mu.Unlock() + + eg, ctx := errgroup.WithContext(ctx) + for _, d := range idx.Manifests { + d := d + eg.Go(func() error { + return l.fetch(ctx, fetcher, d, r) + }) + } + + if err := eg.Wait(); err != nil { + return err + } + default: + } + return nil +} + +func (l *Loader) readPlatformFromConfig(ctx context.Context, fetcher remotes.Fetcher, desc ocispec.Descriptor) (*ocispec.Platform, error) { + _, err := remotes.FetchHandler(l.cache, fetcher)(ctx, desc) + if err != nil { + return nil, err + } + + dt, err := content.ReadBlob(ctx, l.cache, desc) + if err != nil { + return nil, err + } + + var config ocispec.Image + if err := json.Unmarshal(dt, &config); err != nil { + return nil, err + } + + return &ocispec.Platform{ + OS: config.OS, + Architecture: config.Architecture, + Variant: config.Variant, + }, nil +} + +func parseReference(ref string) (distref.Named, error) { + named, err := distref.ParseNormalizedNamed(ref) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse %q", ref) + } + return distref.TagNameOnly(named), nil +} diff --git a/vendor/github.com/docker/go-imageinspect/provenance.go b/vendor/github.com/docker/go-imageinspect/provenance.go new file mode 100644 index 000000000000..4ff5c33db830 --- /dev/null +++ b/vendor/github.com/docker/go-imageinspect/provenance.go @@ -0,0 +1,29 @@ +// Copyright 2022 go-imageinspect authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package imageinspect + +type Provenance struct { // TODO: this is only a stub, to be refactored later + BuildSource string `json:",omitempty"` + BuildDefinition string `json:",omitempty"` + BuildParameters map[string]string `json:",omitempty"` + Materials []Material +} + +type Material struct { + Type string `json:",omitempty"` + Ref string `json:",omitempty"` + Alias string `json:",omitempty"` + Pin string `json:",omitempty"` +} diff --git a/vendor/github.com/docker/go-imageinspect/sbom.go b/vendor/github.com/docker/go-imageinspect/sbom.go new file mode 100644 index 000000000000..14bc9ac29195 --- /dev/null +++ b/vendor/github.com/docker/go-imageinspect/sbom.go @@ -0,0 +1,203 @@ +// Copyright 2022 go-imageinspect authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package imageinspect + +import ( + "bytes" + "context" + "encoding/json" + "sort" + "strings" + + "github.com/containerd/containerd/content" + "github.com/containerd/containerd/remotes" + intoto "github.com/in-toto/in-toto-golang/in_toto" + "github.com/opencontainers/go-digest" + "github.com/pkg/errors" + "github.com/spdx/tools-golang/jsonloader" + "github.com/spdx/tools-golang/spdx" +) + +type SBOM struct { + AlpinePackages []Package `json:",omitempty"` + UnknownPackages []Package `json:",omitempty"` +} + +type pkgType int + +const ( + pkgTypeUnknown pkgType = iota + pkgTypeAlpine +) + +type Package struct { + Name string + Version string + Description string + Creator PackageCreator + DownloadURL string + HomepageURL string + License []string + Files []string + + CPEs []string +} + +type PackageCreator struct { + Name string // TODO: split name and e-mail + Org string `json:",omitempty"` +} + +type spdxStatement struct { + intoto.StatementHeader + Predicate json.RawMessage `json:"predicate"` +} + +func (l *Loader) scanSBOM(ctx context.Context, fetcher remotes.Fetcher, r *result, subject digest.Digest, refs []digest.Digest, img *Image) error { + ctx = remotes.WithMediaTypeKeyPrefix(ctx, "application/vnd.in-toto+json", "intoto") + + for _, dgst := range refs { + mfst, ok := r.manifests[dgst] + if !ok { + return errors.Errorf("referenced image %s not found", dgst) + } + + for _, layer := range mfst.manifest.Layers { + if layer.MediaType == "application/vnd.in-toto+json" && layer.Annotations["in-toto.io/predicate-type"] == "https://spdx.dev/Document" { + var stmt spdxStatement + _, err := remotes.FetchHandler(l.cache, fetcher)(ctx, layer) + if err != nil { + return err + } + dt, err := content.ReadBlob(ctx, l.cache, layer) + if err != nil { + return err + } + if err := json.Unmarshal(dt, &stmt); err != nil { + return err + } + + if stmt.PredicateType != "https://spdx.dev/Document" { + return errors.Errorf("unexpected predicate type %s", stmt.PredicateType) + } + + subjectValidated := false + for _, s := range stmt.Subject { + for alg, hash := range s.Digest { + if alg+":"+hash == subject.String() { + subjectValidated = true + break + } + } + } + + if !subjectValidated { + return errors.Errorf("unable to validate subject %s, expected %s", stmt.Subject, subject.String()) + } + + doc, err := decodeSPDX(stmt.Predicate) + if err != nil { + return err + } + addSPDX(img, doc) + } + } + } + + normalizeSBOM(img.SBOM) + + return nil +} + +func addSPDX(img *Image, doc *spdx.Document2_2) { + sbom := img.SBOM + if sbom == nil { + sbom = &SBOM{} + } + + for _, p := range doc.Packages { + var files []string + for _, f := range p.Files { + if f == nil { + // HACK: the SPDX parser is broken with multiple files in hasFiles + continue + } + files = append(files, f.FileName) + } + + pkg := Package{ + Name: p.PackageName, + Version: p.PackageVersion, + Creator: PackageCreator{Name: p.PackageOriginatorPerson, Org: p.PackageOriginatorOrganization}, + Description: p.PackageDescription, + HomepageURL: p.PackageHomePage, + DownloadURL: p.PackageDownloadLocation, + License: strings.Split(p.PackageLicenseConcluded, " AND "), + Files: files, + } + + typ := pkgTypeUnknown + for _, ref := range p.PackageExternalReferences { + if ref.Category == "PACKAGE_MANAGER" && ref.RefType == "purl" { + if strings.HasPrefix(ref.Locator, "pkg:alpine/") { + typ = pkgTypeAlpine + } + } + if ref.Category == "SECURITY" && ref.RefType == "cpe23Type" { + pkg.CPEs = append(pkg.CPEs, ref.Locator) + } + } + + switch typ { + case pkgTypeAlpine: + sbom.AlpinePackages = append(sbom.AlpinePackages, pkg) + default: + sbom.UnknownPackages = append(sbom.UnknownPackages, pkg) + } + } + img.SBOM = sbom +} + +func normalizeSBOM(sbom *SBOM) { + if sbom == nil { + return + } + + for _, pkgs := range [][]Package{sbom.AlpinePackages, sbom.UnknownPackages} { + // TODO: remote duplicates + sort.Slice(pkgs, func(i, j int) bool { + if pkgs[i].Name == pkgs[j].Name { + return pkgs[i].Version < pkgs[j].Version + } + return pkgs[i].Name < pkgs[j].Name + }) + } +} + +func decodeSPDX(dt []byte) (s *spdx.Document2_2, err error) { + defer func() { + // The spdx tools JSON parser is reported to be panicking sometimes + if v := recover(); v != nil { + s = nil + err = errors.Errorf("an error occurred during SPDX JSON document parsing: %+v", v) + } + }() + + doc, err := jsonloader.Load2_2(bytes.NewReader(dt)) + if err != nil { + return nil, errors.Wrap(err, "unable to decode spdx") + } + return doc, nil +} diff --git a/vendor/github.com/docker/go-imageinspect/types.go b/vendor/github.com/docker/go-imageinspect/types.go new file mode 100644 index 000000000000..939d00d9e6cb --- /dev/null +++ b/vendor/github.com/docker/go-imageinspect/types.go @@ -0,0 +1,125 @@ +// Copyright 2022 go-imageinspect authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package imageinspect + +import ( + "github.com/opencontainers/go-digest" +) + +type ResultType string + +const ( + Manifest ResultType = "manifest" + Index ResultType = "index" + Unknown ResultType = "unknown" +) + +type Result struct { + Digest digest.Digest + ResultType ResultType + Platforms []string + Images map[string]Image + + // Signature summary +} + +type Identity struct { + PublicKey string + // ... +} + +type Signature struct { + Verified bool + Identity Identity +} + +type Image struct { + Title string + Platform string + Author string + Vendor string + URL string + Source string + Revision string + Documentation string + ShortDescription string + Description string + License string + Size int64 + + Signatures []Signature + Config *Config `json:",omitempty"` + SBOM *SBOM `json:",omitempty"` + Provenance *Provenance `json:",omitempty"` + + // Build logs + // Hub identity +} + +func (r *Result) Signatures() map[string][]Signature { + if len(r.Images) == 0 { + return nil + } + res := make(map[string][]Signature) + for p, img := range r.Images { + if len(img.Signatures) == 0 { + continue + } + res[p] = img.Signatures + } + return res +} + +func (r *Result) Configs() map[string]*Config { + if len(r.Images) == 0 { + return nil + } + res := make(map[string]*Config) + for p, img := range r.Images { + if img.SBOM == nil { + continue + } + res[p] = img.Config + } + return res +} + +func (r *Result) Provenances() map[string]*Provenance { + if len(r.Images) == 0 { + return nil + } + res := make(map[string]*Provenance) + for p, img := range r.Images { + if img.Provenance == nil { + continue + } + res[p] = img.Provenance + } + return res +} + +func (r *Result) SBOMs() map[string]*SBOM { + if len(r.Images) == 0 { + return nil + } + res := make(map[string]*SBOM) + for p, img := range r.Images { + if img.SBOM == nil { + continue + } + res[p] = img.SBOM + } + return res +} diff --git a/vendor/github.com/in-toto/in-toto-golang/LICENSE b/vendor/github.com/in-toto/in-toto-golang/LICENSE new file mode 100644 index 000000000000..963ee949e8e1 --- /dev/null +++ b/vendor/github.com/in-toto/in-toto-golang/LICENSE @@ -0,0 +1,13 @@ +Copyright 2018 New York University + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/github.com/in-toto/in-toto-golang/in_toto/certconstraint.go b/vendor/github.com/in-toto/in-toto-golang/in_toto/certconstraint.go new file mode 100644 index 000000000000..9b1de12b182d --- /dev/null +++ b/vendor/github.com/in-toto/in-toto-golang/in_toto/certconstraint.go @@ -0,0 +1,156 @@ +package in_toto + +import ( + "crypto/x509" + "fmt" + "net/url" +) + +const ( + AllowAllConstraint = "*" +) + +// CertificateConstraint defines the attributes a certificate must have to act as a functionary. +// A wildcard `*` allows any value in the specified attribute, where as an empty array or value +// asserts that the certificate must have nothing for that attribute. A certificate must have +// every value defined in a constraint to match. +type CertificateConstraint struct { + CommonName string `json:"common_name"` + DNSNames []string `json:"dns_names"` + Emails []string `json:"emails"` + Organizations []string `json:"organizations"` + Roots []string `json:"roots"` + URIs []string `json:"uris"` +} + +// checkResult is a data structure used to hold +// certificate constraint errors +type checkResult struct { + errors []error +} + +// newCheckResult initializes a new checkResult +func newCheckResult() *checkResult { + return &checkResult{ + errors: make([]error, 0), + } +} + +// evaluate runs a constraint check on a certificate +func (cr *checkResult) evaluate(cert *x509.Certificate, constraintCheck func(*x509.Certificate) error) *checkResult { + err := constraintCheck(cert) + if err != nil { + cr.errors = append(cr.errors, err) + } + return cr +} + +// error reduces all of the errors into one error with a +// combined error message. If there are no errors, nil +// will be returned. +func (cr *checkResult) error() error { + if len(cr.errors) == 0 { + return nil + } + return fmt.Errorf("cert failed constraints check: %+q", cr.errors) +} + +// Check tests the provided certificate against the constraint. An error is returned if the certificate +// fails any of the constraints. nil is returned if the certificate passes all of the constraints. +func (cc CertificateConstraint) Check(cert *x509.Certificate, rootCAIDs []string, rootCertPool, intermediateCertPool *x509.CertPool) error { + return newCheckResult(). + evaluate(cert, cc.checkCommonName). + evaluate(cert, cc.checkDNSNames). + evaluate(cert, cc.checkEmails). + evaluate(cert, cc.checkOrganizations). + evaluate(cert, cc.checkRoots(rootCAIDs, rootCertPool, intermediateCertPool)). + evaluate(cert, cc.checkURIs). + error() +} + +// checkCommonName verifies that the certificate's common name matches the constraint. +func (cc CertificateConstraint) checkCommonName(cert *x509.Certificate) error { + return checkCertConstraint("common name", []string{cc.CommonName}, []string{cert.Subject.CommonName}) +} + +// checkDNSNames verifies that the certificate's dns names matches the constraint. +func (cc CertificateConstraint) checkDNSNames(cert *x509.Certificate) error { + return checkCertConstraint("dns name", cc.DNSNames, cert.DNSNames) +} + +// checkEmails verifies that the certificate's emails matches the constraint. +func (cc CertificateConstraint) checkEmails(cert *x509.Certificate) error { + return checkCertConstraint("email", cc.Emails, cert.EmailAddresses) +} + +// checkOrganizations verifies that the certificate's organizations matches the constraint. +func (cc CertificateConstraint) checkOrganizations(cert *x509.Certificate) error { + return checkCertConstraint("organization", cc.Organizations, cert.Subject.Organization) +} + +// checkRoots verifies that the certificate's roots matches the constraint. +// The certificates trust chain must also be verified. +func (cc CertificateConstraint) checkRoots(rootCAIDs []string, rootCertPool, intermediateCertPool *x509.CertPool) func(*x509.Certificate) error { + return func(cert *x509.Certificate) error { + _, err := VerifyCertificateTrust(cert, rootCertPool, intermediateCertPool) + if err != nil { + return fmt.Errorf("failed to verify roots: %w", err) + } + return checkCertConstraint("root", cc.Roots, rootCAIDs) + } +} + +// checkURIs verifies that the certificate's URIs matches the constraint. +func (cc CertificateConstraint) checkURIs(cert *x509.Certificate) error { + return checkCertConstraint("uri", cc.URIs, urisToStrings(cert.URIs)) +} + +// urisToStrings is a helper that converts a list of URL objects to the string that represents them +func urisToStrings(uris []*url.URL) []string { + res := make([]string, 0, len(uris)) + for _, uri := range uris { + res = append(res, uri.String()) + } + + return res +} + +// checkCertConstraint tests that the provided test values match the allowed values of the constraint. +// All allowed values must be met one-to-one to be considered a successful match. +func checkCertConstraint(attributeName string, constraints, values []string) error { + // If the only constraint is to allow all, the check succeeds + if len(constraints) == 1 && constraints[0] == AllowAllConstraint { + return nil + } + + if len(constraints) == 1 && constraints[0] == "" { + constraints = []string{} + } + + if len(values) == 1 && values[0] == "" { + values = []string{} + } + + // If no constraints are specified, but the certificate has values for the attribute, then the check fails + if len(constraints) == 0 && len(values) > 0 { + return fmt.Errorf("not expecting any %s(s), but cert has %d %s(s)", attributeName, len(values), attributeName) + } + + unmet := NewSet(constraints...) + for _, v := range values { + // if the cert has a value we didn't expect, fail early + if !unmet.Has(v) { + return fmt.Errorf("cert has an unexpected %s %s given constraints %+q", attributeName, v, constraints) + } + + // consider the constraint met + unmet.Remove(v) + } + + // if we have any unmet left after going through each test value, fail. + if len(unmet) > 0 { + return fmt.Errorf("cert with %s(s) %+q did not pass all constraints %+q", attributeName, values, constraints) + } + + return nil +} diff --git a/vendor/github.com/in-toto/in-toto-golang/in_toto/hashlib.go b/vendor/github.com/in-toto/in-toto-golang/in_toto/hashlib.go new file mode 100644 index 000000000000..bdfc65d69f99 --- /dev/null +++ b/vendor/github.com/in-toto/in-toto-golang/in_toto/hashlib.go @@ -0,0 +1,30 @@ +package in_toto + +import ( + "crypto/sha256" + "crypto/sha512" + "hash" +) + +/* +getHashMapping returns a mapping from hash algorithm to supported hash +interface. +*/ +func getHashMapping() map[string]func() hash.Hash { + return map[string]func() hash.Hash{ + "sha256": sha256.New, + "sha512": sha512.New, + "sha384": sha512.New384, + } +} + +/* +hashToHex calculates the hash over data based on hash algorithm h. +*/ +func hashToHex(h hash.Hash, data []byte) []byte { + h.Write(data) + // We need to use h.Sum(nil) here, because otherwise hash.Sum() appends + // the hash to the passed data. So instead of having only the hash + // we would get: "dataHASH" + return h.Sum(nil) +} diff --git a/vendor/github.com/in-toto/in-toto-golang/in_toto/keylib.go b/vendor/github.com/in-toto/in-toto-golang/in_toto/keylib.go new file mode 100644 index 000000000000..8811d32d9332 --- /dev/null +++ b/vendor/github.com/in-toto/in-toto-golang/in_toto/keylib.go @@ -0,0 +1,669 @@ +package in_toto + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/hex" + "encoding/pem" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "strings" + + "github.com/secure-systems-lab/go-securesystemslib/cjson" +) + +// ErrFailedPEMParsing gets returned when PKCS1, PKCS8 or PKIX key parsing fails +var ErrFailedPEMParsing = errors.New("failed parsing the PEM block: unsupported PEM type") + +// ErrNoPEMBlock gets triggered when there is no PEM block in the provided file +var ErrNoPEMBlock = errors.New("failed to decode the data as PEM block (are you sure this is a pem file?)") + +// ErrUnsupportedKeyType is returned when we are dealing with a key type different to ed25519 or RSA +var ErrUnsupportedKeyType = errors.New("unsupported key type") + +// ErrInvalidSignature is returned when the signature is invalid +var ErrInvalidSignature = errors.New("invalid signature") + +// ErrInvalidKey is returned when a given key is none of RSA, ECDSA or ED25519 +var ErrInvalidKey = errors.New("invalid key") + +const ( + rsaKeyType string = "rsa" + ecdsaKeyType string = "ecdsa" + ed25519KeyType string = "ed25519" + rsassapsssha256Scheme string = "rsassa-pss-sha256" + ecdsaSha2nistp224 string = "ecdsa-sha2-nistp224" + ecdsaSha2nistp256 string = "ecdsa-sha2-nistp256" + ecdsaSha2nistp384 string = "ecdsa-sha2-nistp384" + ecdsaSha2nistp521 string = "ecdsa-sha2-nistp521" + ed25519Scheme string = "ed25519" + pemPublicKey string = "PUBLIC KEY" + pemPrivateKey string = "PRIVATE KEY" + pemRSAPrivateKey string = "RSA PRIVATE KEY" +) + +/* +getSupportedKeyIDHashAlgorithms returns a string slice of supported +KeyIDHashAlgorithms. We need to use this function instead of a constant, +because Go does not support global constant slices. +*/ +func getSupportedKeyIDHashAlgorithms() Set { + return NewSet("sha256", "sha512") +} + +/* +getSupportedRSASchemes returns a string slice of supported RSA Key schemes. +We need to use this function instead of a constant because Go does not support +global constant slices. +*/ +func getSupportedRSASchemes() []string { + return []string{rsassapsssha256Scheme} +} + +/* +getSupportedEcdsaSchemes returns a string slice of supported ecdsa Key schemes. +We need to use this function instead of a constant because Go does not support +global constant slices. +*/ +func getSupportedEcdsaSchemes() []string { + return []string{ecdsaSha2nistp224, ecdsaSha2nistp256, ecdsaSha2nistp384, ecdsaSha2nistp521} +} + +/* +getSupportedEd25519Schemes returns a string slice of supported ed25519 Key +schemes. We need to use this function instead of a constant because Go does +not support global constant slices. +*/ +func getSupportedEd25519Schemes() []string { + return []string{ed25519Scheme} +} + +/* +generateKeyID creates a partial key map and generates the key ID +based on the created partial key map via the SHA256 method. +The resulting keyID will be directly saved in the corresponding key object. +On success generateKeyID will return nil, in case of errors while encoding +there will be an error. +*/ +func (k *Key) generateKeyID() error { + // Create partial key map used to create the keyid + // Unfortunately, we can't use the Key object because this also carries + // yet unwanted fields, such as KeyID and KeyVal.Private and therefore + // produces a different hash. We generate the keyID exactly as we do in + // the securesystemslib to keep interoperability between other in-toto + // implementations. + var keyToBeHashed = map[string]interface{}{ + "keytype": k.KeyType, + "scheme": k.Scheme, + "keyid_hash_algorithms": k.KeyIDHashAlgorithms, + "keyval": map[string]string{ + "public": k.KeyVal.Public, + }, + } + keyCanonical, err := cjson.EncodeCanonical(keyToBeHashed) + if err != nil { + return err + } + // calculate sha256 and return string representation of keyID + keyHashed := sha256.Sum256(keyCanonical) + k.KeyID = fmt.Sprintf("%x", keyHashed) + err = validateKey(*k) + if err != nil { + return err + } + return nil +} + +/* +generatePEMBlock creates a PEM block from scratch via the keyBytes and the pemType. +If successful it returns a PEM block as []byte slice. This function should always +succeed, if keyBytes is empty the PEM block will have an empty byte block. +Therefore only header and footer will exist. +*/ +func generatePEMBlock(keyBytes []byte, pemType string) []byte { + // construct PEM block + pemBlock := &pem.Block{ + Type: pemType, + Headers: nil, + Bytes: keyBytes, + } + return pem.EncodeToMemory(pemBlock) +} + +/* +setKeyComponents sets all components in our key object. +Furthermore it makes sure to remove any trailing and leading whitespaces or newlines. +We treat key types differently for interoperability reasons to the in-toto python +implementation and the securesystemslib. +*/ +func (k *Key) setKeyComponents(pubKeyBytes []byte, privateKeyBytes []byte, keyType string, scheme string, KeyIDHashAlgorithms []string) error { + // assume we have a privateKey if the key size is bigger than 0 + + switch keyType { + case rsaKeyType: + if len(privateKeyBytes) > 0 { + k.KeyVal = KeyVal{ + Private: strings.TrimSpace(string(generatePEMBlock(privateKeyBytes, pemRSAPrivateKey))), + Public: strings.TrimSpace(string(generatePEMBlock(pubKeyBytes, pemPublicKey))), + } + } else { + k.KeyVal = KeyVal{ + Public: strings.TrimSpace(string(generatePEMBlock(pubKeyBytes, pemPublicKey))), + } + } + case ecdsaKeyType: + if len(privateKeyBytes) > 0 { + k.KeyVal = KeyVal{ + Private: strings.TrimSpace(string(generatePEMBlock(privateKeyBytes, pemPrivateKey))), + Public: strings.TrimSpace(string(generatePEMBlock(pubKeyBytes, pemPublicKey))), + } + } else { + k.KeyVal = KeyVal{ + Public: strings.TrimSpace(string(generatePEMBlock(pubKeyBytes, pemPublicKey))), + } + } + case ed25519KeyType: + if len(privateKeyBytes) > 0 { + k.KeyVal = KeyVal{ + Private: strings.TrimSpace(hex.EncodeToString(privateKeyBytes)), + Public: strings.TrimSpace(hex.EncodeToString(pubKeyBytes)), + } + } else { + k.KeyVal = KeyVal{ + Public: strings.TrimSpace(hex.EncodeToString(pubKeyBytes)), + } + } + default: + return fmt.Errorf("%w: %s", ErrUnsupportedKeyType, keyType) + } + k.KeyType = keyType + k.Scheme = scheme + k.KeyIDHashAlgorithms = KeyIDHashAlgorithms + if err := k.generateKeyID(); err != nil { + return err + } + return nil +} + +/* +parseKey tries to parse a PEM []byte slice. Using the following standards +in the given order: + + * PKCS8 + * PKCS1 + * PKIX + +On success it returns the parsed key and nil. +On failure it returns nil and the error ErrFailedPEMParsing +*/ +func parseKey(data []byte) (interface{}, error) { + key, err := x509.ParsePKCS8PrivateKey(data) + if err == nil { + return key, nil + } + key, err = x509.ParsePKCS1PrivateKey(data) + if err == nil { + return key, nil + } + key, err = x509.ParsePKIXPublicKey(data) + if err == nil { + return key, nil + } + key, err = x509.ParseCertificate(data) + if err == nil { + return key, nil + } + key, err = x509.ParseECPrivateKey(data) + if err == nil { + return key, nil + } + return nil, ErrFailedPEMParsing +} + +/* +decodeAndParse receives potential PEM bytes decodes them via pem.Decode +and pushes them to parseKey. If any error occurs during this process, +the function will return nil and an error (either ErrFailedPEMParsing +or ErrNoPEMBlock). On success it will return the decoded pemData, the +key object interface and nil as error. We need the decoded pemData, +because LoadKey relies on decoded pemData for operating system +interoperability. +*/ +func decodeAndParse(pemBytes []byte) (*pem.Block, interface{}, error) { + // pem.Decode returns the parsed pem block and a rest. + // The rest is everything, that could not be parsed as PEM block. + // Therefore we can drop this via using the blank identifier "_" + data, _ := pem.Decode(pemBytes) + if data == nil { + return nil, nil, ErrNoPEMBlock + } + + // Try to load private key, if this fails try to load + // key as public key + key, err := parseKey(data.Bytes) + if err != nil { + return nil, nil, err + } + return data, key, nil +} + +/* +LoadKey loads the key file at specified file path into the key object. +It automatically derives the PEM type and the key type. +Right now the following PEM types are supported: + + * PKCS1 for private keys + * PKCS8 for private keys + * PKIX for public keys + +The following key types are supported and will be automatically assigned to +the key type field: + + * ed25519 + * rsa + * ecdsa + +The following schemes are supported: + + * ed25519 -> ed25519 + * rsa -> rsassa-pss-sha256 + * ecdsa -> ecdsa-sha256-nistp256 + +Note that, this behavior is consistent with the securesystemslib, except for +ecdsa. We do not use the scheme string as key type in in-toto-golang. +Instead we are going with a ecdsa/ecdsa-sha2-nistp256 pair. + +On success it will return nil. The following errors can happen: + + * path not found or not readable + * no PEM block in the loaded file + * no valid PKCS8/PKCS1 private key or PKIX public key + * errors while marshalling + * unsupported key types +*/ +func (k *Key) LoadKey(path string, scheme string, KeyIDHashAlgorithms []string) error { + pemFile, err := os.Open(path) + if err != nil { + return err + } + defer pemFile.Close() + + err = k.LoadKeyReader(pemFile, scheme, KeyIDHashAlgorithms) + if err != nil { + return err + } + + return pemFile.Close() +} + +func (k *Key) LoadKeyDefaults(path string) error { + pemFile, err := os.Open(path) + if err != nil { + return err + } + defer pemFile.Close() + + err = k.LoadKeyReaderDefaults(pemFile) + if err != nil { + return err + } + + return pemFile.Close() +} + +// LoadKeyReader loads the key from a supplied reader. The logic matches LoadKey otherwise. +func (k *Key) LoadKeyReader(r io.Reader, scheme string, KeyIDHashAlgorithms []string) error { + if r == nil { + return ErrNoPEMBlock + } + // Read key bytes + pemBytes, err := ioutil.ReadAll(r) + if err != nil { + return err + } + // decodeAndParse returns the pemData for later use + // and a parsed key object (for operations on that key, like extracting the public Key) + pemData, key, err := decodeAndParse(pemBytes) + if err != nil { + return err + } + + return k.loadKey(key, pemData, scheme, KeyIDHashAlgorithms) +} + +func (k *Key) LoadKeyReaderDefaults(r io.Reader) error { + if r == nil { + return ErrNoPEMBlock + } + // Read key bytes + pemBytes, err := ioutil.ReadAll(r) + if err != nil { + return err + } + // decodeAndParse returns the pemData for later use + // and a parsed key object (for operations on that key, like extracting the public Key) + pemData, key, err := decodeAndParse(pemBytes) + if err != nil { + return err + } + + scheme, keyIDHashAlgorithms, err := getDefaultKeyScheme(key) + if err != nil { + return err + } + + return k.loadKey(key, pemData, scheme, keyIDHashAlgorithms) +} + +func getDefaultKeyScheme(key interface{}) (scheme string, keyIDHashAlgorithms []string, err error) { + keyIDHashAlgorithms = []string{"sha256", "sha512"} + + switch key.(type) { + case *rsa.PublicKey, *rsa.PrivateKey: + scheme = rsassapsssha256Scheme + case ed25519.PrivateKey, ed25519.PublicKey: + scheme = ed25519Scheme + case *ecdsa.PrivateKey, *ecdsa.PublicKey: + scheme = ecdsaSha2nistp256 + case *x509.Certificate: + return getDefaultKeyScheme(key.(*x509.Certificate).PublicKey) + default: + err = ErrUnsupportedKeyType + } + + return scheme, keyIDHashAlgorithms, err +} + +func (k *Key) loadKey(key interface{}, pemData *pem.Block, scheme string, keyIDHashAlgorithms []string) error { + + switch key.(type) { + case *rsa.PublicKey: + pubKeyBytes, err := x509.MarshalPKIXPublicKey(key.(*rsa.PublicKey)) + if err != nil { + return err + } + if err := k.setKeyComponents(pubKeyBytes, []byte{}, rsaKeyType, scheme, keyIDHashAlgorithms); err != nil { + return err + } + case *rsa.PrivateKey: + // Note: RSA Public Keys will get stored as X.509 SubjectPublicKeyInfo (RFC5280) + // This behavior is consistent to the securesystemslib + pubKeyBytes, err := x509.MarshalPKIXPublicKey(key.(*rsa.PrivateKey).Public()) + if err != nil { + return err + } + if err := k.setKeyComponents(pubKeyBytes, pemData.Bytes, rsaKeyType, scheme, keyIDHashAlgorithms); err != nil { + return err + } + case ed25519.PublicKey: + if err := k.setKeyComponents(key.(ed25519.PublicKey), []byte{}, ed25519KeyType, scheme, keyIDHashAlgorithms); err != nil { + return err + } + case ed25519.PrivateKey: + pubKeyBytes := key.(ed25519.PrivateKey).Public() + if err := k.setKeyComponents(pubKeyBytes.(ed25519.PublicKey), key.(ed25519.PrivateKey), ed25519KeyType, scheme, keyIDHashAlgorithms); err != nil { + return err + } + case *ecdsa.PrivateKey: + pubKeyBytes, err := x509.MarshalPKIXPublicKey(key.(*ecdsa.PrivateKey).Public()) + if err != nil { + return err + } + if err := k.setKeyComponents(pubKeyBytes, pemData.Bytes, ecdsaKeyType, scheme, keyIDHashAlgorithms); err != nil { + return err + } + case *ecdsa.PublicKey: + pubKeyBytes, err := x509.MarshalPKIXPublicKey(key.(*ecdsa.PublicKey)) + if err != nil { + return err + } + if err := k.setKeyComponents(pubKeyBytes, []byte{}, ecdsaKeyType, scheme, keyIDHashAlgorithms); err != nil { + return err + } + case *x509.Certificate: + err := k.loadKey(key.(*x509.Certificate).PublicKey, pemData, scheme, keyIDHashAlgorithms) + if err != nil { + return err + } + + k.KeyVal.Certificate = string(pem.EncodeToMemory(pemData)) + + default: + // We should never get here, because we implement all from Go supported Key Types + return errors.New("unexpected Error in LoadKey function") + } + + return nil +} + +/* +GenerateSignature will automatically detect the key type and sign the signable data +with the provided key. If everything goes right GenerateSignature will return +a for the key valid signature and err=nil. If something goes wrong it will +return a not initialized signature and an error. Possible errors are: + + * ErrNoPEMBlock + * ErrUnsupportedKeyType + +Currently supported is only one scheme per key. + +Note that in-toto-golang has different requirements to an ecdsa key. +In in-toto-golang we use the string 'ecdsa' as string for the key type. +In the key scheme we use: ecdsa-sha2-nistp256. +*/ +func GenerateSignature(signable []byte, key Key) (Signature, error) { + err := validateKey(key) + if err != nil { + return Signature{}, err + } + var signature Signature + var signatureBuffer []byte + hashMapping := getHashMapping() + // The following switch block is needed for keeping interoperability + // with the securesystemslib and the python implementation + // in which we are storing RSA keys in PEM format, but ed25519 keys hex encoded. + switch key.KeyType { + case rsaKeyType: + // We do not need the pemData here, so we can throw it away via '_' + _, parsedKey, err := decodeAndParse([]byte(key.KeyVal.Private)) + if err != nil { + return Signature{}, err + } + parsedKey, ok := parsedKey.(*rsa.PrivateKey) + if !ok { + return Signature{}, ErrKeyKeyTypeMismatch + } + switch key.Scheme { + case rsassapsssha256Scheme: + hashed := hashToHex(hashMapping["sha256"](), signable) + // We use rand.Reader as secure random source for rsa.SignPSS() + signatureBuffer, err = rsa.SignPSS(rand.Reader, parsedKey.(*rsa.PrivateKey), crypto.SHA256, hashed, + &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}) + if err != nil { + return signature, err + } + default: + // supported key schemes will get checked in validateKey + panic("unexpected Error in GenerateSignature function") + } + case ecdsaKeyType: + // We do not need the pemData here, so we can throw it away via '_' + _, parsedKey, err := decodeAndParse([]byte(key.KeyVal.Private)) + if err != nil { + return Signature{}, err + } + parsedKey, ok := parsedKey.(*ecdsa.PrivateKey) + if !ok { + return Signature{}, ErrKeyKeyTypeMismatch + } + curveSize := parsedKey.(*ecdsa.PrivateKey).Curve.Params().BitSize + var hashed []byte + if err := matchEcdsaScheme(curveSize, key.Scheme); err != nil { + return Signature{}, ErrCurveSizeSchemeMismatch + } + // implement https://tools.ietf.org/html/rfc5656#section-6.2.1 + // We determine the curve size and choose the correct hashing + // method based on the curveSize + switch { + case curveSize <= 256: + hashed = hashToHex(hashMapping["sha256"](), signable) + case 256 < curveSize && curveSize <= 384: + hashed = hashToHex(hashMapping["sha384"](), signable) + case curveSize > 384: + hashed = hashToHex(hashMapping["sha512"](), signable) + default: + panic("unexpected Error in GenerateSignature function") + } + // Generate the ecdsa signature on the same way, as we do in the securesystemslib + // We are marshalling the ecdsaSignature struct as ASN.1 INTEGER SEQUENCES + // into an ASN.1 Object. + signatureBuffer, err = ecdsa.SignASN1(rand.Reader, parsedKey.(*ecdsa.PrivateKey), hashed[:]) + if err != nil { + return signature, err + } + case ed25519KeyType: + // We do not need a scheme switch here, because ed25519 + // only consist of sha256 and curve25519. + privateHex, err := hex.DecodeString(key.KeyVal.Private) + if err != nil { + return signature, ErrInvalidHexString + } + // Note: We can directly use the key for signing and do not + // need to use ed25519.NewKeyFromSeed(). + signatureBuffer = ed25519.Sign(privateHex, signable) + default: + // We should never get here, because we call validateKey in the first + // line of the function. + panic("unexpected Error in GenerateSignature function") + } + signature.Sig = hex.EncodeToString(signatureBuffer) + signature.KeyID = key.KeyID + signature.Certificate = key.KeyVal.Certificate + return signature, nil +} + +/* +VerifySignature will verify unverified byte data via a passed key and signature. +Supported key types are: + + * rsa + * ed25519 + * ecdsa + +When encountering an RSA key, VerifySignature will decode the PEM block in the key +and will call rsa.VerifyPSS() for verifying the RSA signature. +When encountering an ed25519 key, VerifySignature will decode the hex string encoded +public key and will use ed25519.Verify() for verifying the ed25519 signature. +When the given key is an ecdsa key, VerifySignature will unmarshall the ASN1 object +and will use the retrieved ecdsa components 'r' and 's' for verifying the signature. +On success it will return nil. In case of an unsupported key type or any other error +it will return an error. + +Note that in-toto-golang has different requirements to an ecdsa key. +In in-toto-golang we use the string 'ecdsa' as string for the key type. +In the key scheme we use: ecdsa-sha2-nistp256. +*/ +func VerifySignature(key Key, sig Signature, unverified []byte) error { + err := validateKey(key) + if err != nil { + return err + } + sigBytes, err := hex.DecodeString(sig.Sig) + if err != nil { + return err + } + hashMapping := getHashMapping() + switch key.KeyType { + case rsaKeyType: + // We do not need the pemData here, so we can throw it away via '_' + _, parsedKey, err := decodeAndParse([]byte(key.KeyVal.Public)) + if err != nil { + return err + } + parsedKey, ok := parsedKey.(*rsa.PublicKey) + if !ok { + return ErrKeyKeyTypeMismatch + } + switch key.Scheme { + case rsassapsssha256Scheme: + hashed := hashToHex(hashMapping["sha256"](), unverified) + err = rsa.VerifyPSS(parsedKey.(*rsa.PublicKey), crypto.SHA256, hashed, sigBytes, &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}) + if err != nil { + return fmt.Errorf("%w: %s", ErrInvalidSignature, err) + } + default: + // supported key schemes will get checked in validateKey + panic("unexpected Error in VerifySignature function") + } + case ecdsaKeyType: + // We do not need the pemData here, so we can throw it away via '_' + _, parsedKey, err := decodeAndParse([]byte(key.KeyVal.Public)) + if err != nil { + return err + } + parsedKey, ok := parsedKey.(*ecdsa.PublicKey) + if !ok { + return ErrKeyKeyTypeMismatch + } + curveSize := parsedKey.(*ecdsa.PublicKey).Curve.Params().BitSize + var hashed []byte + if err := matchEcdsaScheme(curveSize, key.Scheme); err != nil { + return ErrCurveSizeSchemeMismatch + } + // implement https://tools.ietf.org/html/rfc5656#section-6.2.1 + // We determine the curve size and choose the correct hashing + // method based on the curveSize + switch { + case curveSize <= 256: + hashed = hashToHex(hashMapping["sha256"](), unverified) + case 256 < curveSize && curveSize <= 384: + hashed = hashToHex(hashMapping["sha384"](), unverified) + case curveSize > 384: + hashed = hashToHex(hashMapping["sha512"](), unverified) + default: + panic("unexpected Error in VerifySignature function") + } + if ok := ecdsa.VerifyASN1(parsedKey.(*ecdsa.PublicKey), hashed[:], sigBytes); !ok { + return ErrInvalidSignature + } + case ed25519KeyType: + // We do not need a scheme switch here, because ed25519 + // only consist of sha256 and curve25519. + pubHex, err := hex.DecodeString(key.KeyVal.Public) + if err != nil { + return ErrInvalidHexString + } + if ok := ed25519.Verify(pubHex, unverified, sigBytes); !ok { + return fmt.Errorf("%w: ed25519", ErrInvalidSignature) + } + default: + // We should never get here, because we call validateKey in the first + // line of the function. + panic("unexpected Error in VerifySignature function") + } + return nil +} + +/* +VerifyCertificateTrust verifies that the certificate has a chain of trust +to a root in rootCertPool, possibly using any intermediates in +intermediateCertPool */ +func VerifyCertificateTrust(cert *x509.Certificate, rootCertPool, intermediateCertPool *x509.CertPool) ([][]*x509.Certificate, error) { + verifyOptions := x509.VerifyOptions{ + Roots: rootCertPool, + Intermediates: intermediateCertPool, + } + chains, err := cert.Verify(verifyOptions) + if len(chains) == 0 || err != nil { + return nil, fmt.Errorf("cert cannot be verified by provided roots and intermediates") + } + return chains, nil +} diff --git a/vendor/github.com/in-toto/in-toto-golang/in_toto/match.go b/vendor/github.com/in-toto/in-toto-golang/in_toto/match.go new file mode 100644 index 000000000000..71e8d4322d08 --- /dev/null +++ b/vendor/github.com/in-toto/in-toto-golang/in_toto/match.go @@ -0,0 +1,228 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found at https://golang.org/LICENSE. + +// this is a modified version of path.Match that removes handling of path separators + +package in_toto + +import ( + "errors" + "unicode/utf8" +) + +// errBadPattern indicates a pattern was malformed. +var errBadPattern = errors.New("syntax error in pattern") + +// match reports whether name matches the shell pattern. +// The pattern syntax is: +// +// pattern: +// { term } +// term: +// '*' matches any sequence of non-/ characters +// '?' matches any single non-/ character +// '[' [ '^' ] { character-range } ']' +// character class (must be non-empty) +// c matches character c (c != '*', '?', '\\', '[') +// '\\' c matches character c +// +// character-range: +// c matches character c (c != '\\', '-', ']') +// '\\' c matches character c +// lo '-' hi matches character c for lo <= c <= hi +// +// Match requires pattern to match all of name, not just a substring. +// The only possible returned error is ErrBadPattern, when pattern +// is malformed. +// +func match(pattern, name string) (matched bool, err error) { +Pattern: + for len(pattern) > 0 { + var star bool + var chunk string + star, chunk, pattern = scanChunk(pattern) + if star && chunk == "" { + // Trailing * matches everything + return true, nil + } + // Look for match at current position. + t, ok, err := matchChunk(chunk, name) + // if we're the last chunk, make sure we've exhausted the name + // otherwise we'll give a false result even if we could still match + // using the star + if ok && (len(t) == 0 || len(pattern) > 0) { + name = t + continue + } + if err != nil { + return false, err + } + if star { + // Look for match skipping i+1 bytes. + for i := 0; i < len(name); i++ { + t, ok, err := matchChunk(chunk, name[i+1:]) + if ok { + // if we're the last chunk, make sure we exhausted the name + if len(pattern) == 0 && len(t) > 0 { + continue + } + name = t + continue Pattern + } + if err != nil { + return false, err + } + } + } + // Before returning false with no error, + // check that the remainder of the pattern is syntactically valid. + for len(pattern) > 0 { + _, chunk, pattern = scanChunk(pattern) + if _, _, err := matchChunk(chunk, ""); err != nil { + return false, err + } + } + return false, nil + } + return len(name) == 0, nil +} + +// scanChunk gets the next segment of pattern, which is a non-star string +// possibly preceded by a star. +func scanChunk(pattern string) (star bool, chunk, rest string) { + for len(pattern) > 0 && pattern[0] == '*' { + pattern = pattern[1:] + star = true + } + inrange := false + var i int +Scan: + for i = 0; i < len(pattern); i++ { + switch pattern[i] { + case '\\': + // error check handled in matchChunk: bad pattern. + if i+1 < len(pattern) { + i++ + } + case '[': + inrange = true + case ']': + inrange = false + case '*': + if !inrange { + break Scan + } + } + } + return star, pattern[0:i], pattern[i:] +} + +// matchChunk checks whether chunk matches the beginning of s. +// If so, it returns the remainder of s (after the match). +// Chunk is all single-character operators: literals, char classes, and ?. +func matchChunk(chunk, s string) (rest string, ok bool, err error) { + // failed records whether the match has failed. + // After the match fails, the loop continues on processing chunk, + // checking that the pattern is well-formed but no longer reading s. + failed := false + for len(chunk) > 0 { + if !failed && len(s) == 0 { + failed = true + } + switch chunk[0] { + case '[': + // character class + var r rune + if !failed { + var n int + r, n = utf8.DecodeRuneInString(s) + s = s[n:] + } + chunk = chunk[1:] + // possibly negated + negated := false + if len(chunk) > 0 && chunk[0] == '^' { + negated = true + chunk = chunk[1:] + } + // parse all ranges + match := false + nrange := 0 + for { + if len(chunk) > 0 && chunk[0] == ']' && nrange > 0 { + chunk = chunk[1:] + break + } + var lo, hi rune + if lo, chunk, err = getEsc(chunk); err != nil { + return "", false, err + } + hi = lo + if chunk[0] == '-' { + if hi, chunk, err = getEsc(chunk[1:]); err != nil { + return "", false, err + } + } + if lo <= r && r <= hi { + match = true + } + nrange++ + } + if match == negated { + failed = true + } + + case '?': + if !failed { + _, n := utf8.DecodeRuneInString(s) + s = s[n:] + } + chunk = chunk[1:] + + case '\\': + chunk = chunk[1:] + if len(chunk) == 0 { + return "", false, errBadPattern + } + fallthrough + + default: + if !failed { + if chunk[0] != s[0] { + failed = true + } + s = s[1:] + } + chunk = chunk[1:] + } + } + if failed { + return "", false, nil + } + return s, true, nil +} + +// getEsc gets a possibly-escaped character from chunk, for a character class. +func getEsc(chunk string) (r rune, nchunk string, err error) { + if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' { + err = errBadPattern + return + } + if chunk[0] == '\\' { + chunk = chunk[1:] + if len(chunk) == 0 { + err = errBadPattern + return + } + } + r, n := utf8.DecodeRuneInString(chunk) + if r == utf8.RuneError && n == 1 { + err = errBadPattern + } + nchunk = chunk[n:] + if len(nchunk) == 0 { + err = errBadPattern + } + return +} diff --git a/vendor/github.com/in-toto/in-toto-golang/in_toto/model.go b/vendor/github.com/in-toto/in-toto-golang/in_toto/model.go new file mode 100644 index 000000000000..25e12c033e69 --- /dev/null +++ b/vendor/github.com/in-toto/in-toto-golang/in_toto/model.go @@ -0,0 +1,1056 @@ +package in_toto + +import ( + "crypto/ecdsa" + "crypto/rsa" + "crypto/x509" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + "reflect" + "regexp" + "strconv" + "strings" + "time" + + slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" + + "github.com/secure-systems-lab/go-securesystemslib/cjson" + "github.com/secure-systems-lab/go-securesystemslib/dsse" +) + +/* +KeyVal contains the actual values of a key, as opposed to key metadata such as +a key identifier or key type. For RSA keys, the key value is a pair of public +and private keys in PEM format stored as strings. For public keys the Private +field may be an empty string. +*/ +type KeyVal struct { + Private string `json:"private"` + Public string `json:"public"` + Certificate string `json:"certificate,omitempty"` +} + +/* +Key represents a generic in-toto key that contains key metadata, such as an +identifier, supported hash algorithms to create the identifier, the key type +and the supported signature scheme, and the actual key value. +*/ +type Key struct { + KeyID string `json:"keyid"` + KeyIDHashAlgorithms []string `json:"keyid_hash_algorithms"` + KeyType string `json:"keytype"` + KeyVal KeyVal `json:"keyval"` + Scheme string `json:"scheme"` +} + +// PayloadType is the payload type used for links and layouts. +const PayloadType = "application/vnd.in-toto+json" + +// ErrEmptyKeyField will be thrown if a field in our Key struct is empty. +var ErrEmptyKeyField = errors.New("empty field in key") + +// ErrInvalidHexString will be thrown, if a string doesn't match a hex string. +var ErrInvalidHexString = errors.New("invalid hex string") + +// ErrSchemeKeyTypeMismatch will be thrown, if the given scheme and key type are not supported together. +var ErrSchemeKeyTypeMismatch = errors.New("the scheme and key type are not supported together") + +// ErrUnsupportedKeyIDHashAlgorithms will be thrown, if the specified KeyIDHashAlgorithms is not supported. +var ErrUnsupportedKeyIDHashAlgorithms = errors.New("the given keyID hash algorithm is not supported") + +// ErrKeyKeyTypeMismatch will be thrown, if the specified keyType does not match the key +var ErrKeyKeyTypeMismatch = errors.New("the given key does not match its key type") + +// ErrNoPublicKey gets returned when the private key value is not empty. +var ErrNoPublicKey = errors.New("the given key is not a public key") + +// ErrCurveSizeSchemeMismatch gets returned, when the scheme and curve size are incompatible +// for example: curve size = "521" and scheme = "ecdsa-sha2-nistp224" +var ErrCurveSizeSchemeMismatch = errors.New("the scheme does not match the curve size") + +const ( + // StatementInTotoV01 is the statement type for the generalized link format + // containing statements. This is constant for all predicate types. + StatementInTotoV01 = "https://in-toto.io/Statement/v0.1" + // PredicateSPDX represents a SBOM using the SPDX standard. + // The SPDX mandates 'spdxVersion' field, so predicate type can omit + // version. + PredicateSPDX = "https://spdx.dev/Document" + // PredicateCycloneDX represents a CycloneDX SBOM + PredicateCycloneDX = "https://cyclonedx.org/schema" + // PredicateLinkV1 represents an in-toto 0.9 link. + PredicateLinkV1 = "https://in-toto.io/Link/v1" +) + +// ErrInvalidPayloadType indicates that the envelope used an unkown payload type +var ErrInvalidPayloadType = errors.New("unknown payload type") + +/* +matchEcdsaScheme checks if the scheme suffix, matches the ecdsa key +curve size. We do not need a full regex match here, because +our validateKey functions are already checking for a valid scheme string. +*/ +func matchEcdsaScheme(curveSize int, scheme string) error { + if !strings.HasSuffix(scheme, strconv.Itoa(curveSize)) { + return ErrCurveSizeSchemeMismatch + } + return nil +} + +/* +validateHexString is used to validate that a string passed to it contains +only valid hexadecimal characters. +*/ +func validateHexString(str string) error { + formatCheck, _ := regexp.MatchString("^[a-fA-F0-9]+$", str) + if !formatCheck { + return fmt.Errorf("%w: %s", ErrInvalidHexString, str) + } + return nil +} + +/* +validateKeyVal validates the KeyVal struct. In case of an ed25519 key, +it will check for a hex string for private and public key. In any other +case, validateKeyVal will try to decode the PEM block. If this succeeds, +we have a valid PEM block in our KeyVal struct. On success it will return nil +on failure it will return the corresponding error. This can be either +an ErrInvalidHexString, an ErrNoPEMBlock or an ErrUnsupportedKeyType +if the KeyType is unknown. +*/ +func validateKeyVal(key Key) error { + switch key.KeyType { + case ed25519KeyType: + // We cannot use matchPublicKeyKeyType or matchPrivateKeyKeyType here, + // because we retrieve the key not from PEM. Hence we are dealing with + // plain ed25519 key bytes. These bytes can't be typechecked like in the + // matchKeyKeytype functions. + err := validateHexString(key.KeyVal.Public) + if err != nil { + return err + } + if key.KeyVal.Private != "" { + err := validateHexString(key.KeyVal.Private) + if err != nil { + return err + } + } + case rsaKeyType, ecdsaKeyType: + // We do not need the pemData here, so we can throw it away via '_' + _, parsedKey, err := decodeAndParse([]byte(key.KeyVal.Public)) + if err != nil { + return err + } + err = matchPublicKeyKeyType(parsedKey, key.KeyType) + if err != nil { + return err + } + if key.KeyVal.Private != "" { + // We do not need the pemData here, so we can throw it away via '_' + _, parsedKey, err := decodeAndParse([]byte(key.KeyVal.Private)) + if err != nil { + return err + } + err = matchPrivateKeyKeyType(parsedKey, key.KeyType) + if err != nil { + return err + } + } + default: + return ErrUnsupportedKeyType + } + return nil +} + +/* +matchPublicKeyKeyType validates an interface if it can be asserted to a +the RSA or ECDSA public key type. We can only check RSA and ECDSA this way, +because we are storing them in PEM format. Ed25519 keys are stored as plain +ed25519 keys encoded as hex strings, thus we have no metadata for them. +This function will return nil on success. If the key type does not match +it will return an ErrKeyKeyTypeMismatch. +*/ +func matchPublicKeyKeyType(key interface{}, keyType string) error { + switch key.(type) { + case *rsa.PublicKey: + if keyType != rsaKeyType { + return ErrKeyKeyTypeMismatch + } + case *ecdsa.PublicKey: + if keyType != ecdsaKeyType { + return ErrKeyKeyTypeMismatch + } + default: + return ErrInvalidKey + } + return nil +} + +/* +matchPrivateKeyKeyType validates an interface if it can be asserted to a +the RSA or ECDSA private key type. We can only check RSA and ECDSA this way, +because we are storing them in PEM format. Ed25519 keys are stored as plain +ed25519 keys encoded as hex strings, thus we have no metadata for them. +This function will return nil on success. If the key type does not match +it will return an ErrKeyKeyTypeMismatch. +*/ +func matchPrivateKeyKeyType(key interface{}, keyType string) error { + // we can only check RSA and ECDSA this way, because we are storing them in PEM + // format. ed25519 keys are stored as plain ed25519 keys encoded as hex strings + // so we have no metadata for them. + switch key.(type) { + case *rsa.PrivateKey: + if keyType != rsaKeyType { + return ErrKeyKeyTypeMismatch + } + case *ecdsa.PrivateKey: + if keyType != ecdsaKeyType { + return ErrKeyKeyTypeMismatch + } + default: + return ErrInvalidKey + } + return nil +} + +/* +matchKeyTypeScheme checks if the specified scheme matches our specified +keyType. If the keyType is not supported it will return an +ErrUnsupportedKeyType. If the keyType and scheme do not match it will return +an ErrSchemeKeyTypeMismatch. If the specified keyType and scheme are +compatible matchKeyTypeScheme will return nil. +*/ +func matchKeyTypeScheme(key Key) error { + switch key.KeyType { + case rsaKeyType: + for _, scheme := range getSupportedRSASchemes() { + if key.Scheme == scheme { + return nil + } + } + case ed25519KeyType: + for _, scheme := range getSupportedEd25519Schemes() { + if key.Scheme == scheme { + return nil + } + } + case ecdsaKeyType: + for _, scheme := range getSupportedEcdsaSchemes() { + if key.Scheme == scheme { + return nil + } + } + default: + return fmt.Errorf("%w: %s", ErrUnsupportedKeyType, key.KeyType) + } + return ErrSchemeKeyTypeMismatch +} + +/* +validateKey checks the outer key object (everything, except the KeyVal struct). +It verifies the keyID for being a hex string and checks for empty fields. +On success it will return nil, on error it will return the corresponding error. +Either: ErrEmptyKeyField or ErrInvalidHexString. +*/ +func validateKey(key Key) error { + err := validateHexString(key.KeyID) + if err != nil { + return err + } + // This probably can be done more elegant with reflection + // but we care about performance, do we?! + if key.KeyType == "" { + return fmt.Errorf("%w: keytype", ErrEmptyKeyField) + } + if key.KeyVal.Public == "" && key.KeyVal.Certificate == "" { + return fmt.Errorf("%w: keyval.public and keyval.certificate cannot both be blank", ErrEmptyKeyField) + } + if key.Scheme == "" { + return fmt.Errorf("%w: scheme", ErrEmptyKeyField) + } + err = matchKeyTypeScheme(key) + if err != nil { + return err + } + // only check for supported KeyIDHashAlgorithms, if the variable has been set + if key.KeyIDHashAlgorithms != nil { + supportedKeyIDHashAlgorithms := getSupportedKeyIDHashAlgorithms() + if !supportedKeyIDHashAlgorithms.IsSubSet(NewSet(key.KeyIDHashAlgorithms...)) { + return fmt.Errorf("%w: %#v, supported are: %#v", ErrUnsupportedKeyIDHashAlgorithms, key.KeyIDHashAlgorithms, getSupportedKeyIDHashAlgorithms()) + } + } + return nil +} + +/* +validatePublicKey is a wrapper around validateKey. It test if the private key +value in the key is empty and then validates the key via calling validateKey. +On success it will return nil, on error it will return an ErrNoPublicKey error. +*/ +func validatePublicKey(key Key) error { + if key.KeyVal.Private != "" { + return ErrNoPublicKey + } + err := validateKey(key) + if err != nil { + return err + } + return nil +} + +/* +Signature represents a generic in-toto signature that contains the identifier +of the Key, which was used to create the signature and the signature data. The +used signature scheme is found in the corresponding Key. +*/ +type Signature struct { + KeyID string `json:"keyid"` + Sig string `json:"sig"` + Certificate string `json:"cert,omitempty"` +} + +// GetCertificate returns the parsed x509 certificate attached to the signature, +// if it exists. +func (sig Signature) GetCertificate() (Key, error) { + key := Key{} + if len(sig.Certificate) == 0 { + return key, errors.New("Signature has empty Certificate") + } + + err := key.LoadKeyReaderDefaults(strings.NewReader(sig.Certificate)) + return key, err +} + +/* +validateSignature is a function used to check if a passed signature is valid, +by inspecting the key ID and the signature itself. +*/ +func validateSignature(signature Signature) error { + if err := validateHexString(signature.KeyID); err != nil { + return err + } + if err := validateHexString(signature.Sig); err != nil { + return err + } + return nil +} + +/* +validateSliceOfSignatures is a helper function used to validate multiple +signatures stored in a slice. +*/ +func validateSliceOfSignatures(slice []Signature) error { + for _, signature := range slice { + if err := validateSignature(signature); err != nil { + return err + } + } + return nil +} + +/* +Link represents the evidence of a supply chain step performed by a functionary. +It should be contained in a generic Metablock object, which provides +functionality for signing and signature verification, and reading from and +writing to disk. +*/ +type Link struct { + Type string `json:"_type"` + Name string `json:"name"` + Materials map[string]interface{} `json:"materials"` + Products map[string]interface{} `json:"products"` + ByProducts map[string]interface{} `json:"byproducts"` + Command []string `json:"command"` + Environment map[string]interface{} `json:"environment"` +} + +/* +validateArtifacts is a general function used to validate products and materials. +*/ +func validateArtifacts(artifacts map[string]interface{}) error { + for artifactName, artifact := range artifacts { + artifactValue := reflect.ValueOf(artifact).MapRange() + for artifactValue.Next() { + value := artifactValue.Value().Interface().(string) + hashType := artifactValue.Key().Interface().(string) + if err := validateHexString(value); err != nil { + return fmt.Errorf("in artifact '%s', %s hash value: %s", + artifactName, hashType, err.Error()) + } + } + } + return nil +} + +/* +validateLink is a function used to ensure that a passed item of type Link +matches the necessary format. +*/ +func validateLink(link Link) error { + if link.Type != "link" { + return fmt.Errorf("invalid type for link '%s': should be 'link'", + link.Name) + } + + if err := validateArtifacts(link.Materials); err != nil { + return fmt.Errorf("in materials of link '%s': %s", link.Name, + err.Error()) + } + + if err := validateArtifacts(link.Products); err != nil { + return fmt.Errorf("in products of link '%s': %s", link.Name, + err.Error()) + } + + return nil +} + +/* +LinkNameFormat represents a format string used to create the filename for a +signed Link (wrapped in a Metablock). It consists of the name of the link and +the first 8 characters of the signing key id. E.g.: + fmt.Sprintf(LinkNameFormat, "package", + "2f89b9272acfc8f4a0a0f094d789fdb0ba798b0fe41f2f5f417c12f0085ff498") + // returns "package.2f89b9272.link" +*/ +const LinkNameFormat = "%s.%.8s.link" +const PreliminaryLinkNameFormat = ".%s.%.8s.link-unfinished" + +/* +LinkNameFormatShort is for links that are not signed, e.g.: + fmt.Sprintf(LinkNameFormatShort, "unsigned") + // returns "unsigned.link" +*/ +const LinkNameFormatShort = "%s.link" +const LinkGlobFormat = "%s.????????.link" + +/* +SublayoutLinkDirFormat represents the format of the name of the directory for +sublayout links during the verification workflow. +*/ +const SublayoutLinkDirFormat = "%s.%.8s" + +/* +SupplyChainItem summarizes common fields of the two available supply chain +item types, Inspection and Step. +*/ +type SupplyChainItem struct { + Name string `json:"name"` + ExpectedMaterials [][]string `json:"expected_materials"` + ExpectedProducts [][]string `json:"expected_products"` +} + +/* +validateArtifactRule calls UnpackRule to validate that the passed rule conforms +with any of the available rule formats. +*/ +func validateArtifactRule(rule []string) error { + if _, err := UnpackRule(rule); err != nil { + return err + } + return nil +} + +/* +validateSliceOfArtifactRules iterates over passed rules to validate them. +*/ +func validateSliceOfArtifactRules(rules [][]string) error { + for _, rule := range rules { + if err := validateArtifactRule(rule); err != nil { + return err + } + } + return nil +} + +/* +validateSupplyChainItem is used to validate the common elements found in both +steps and inspections. Here, the function primarily ensures that the name of +a supply chain item isn't empty. +*/ +func validateSupplyChainItem(item SupplyChainItem) error { + if item.Name == "" { + return fmt.Errorf("name cannot be empty") + } + + if err := validateSliceOfArtifactRules(item.ExpectedMaterials); err != nil { + return fmt.Errorf("invalid material rule: %s", err) + } + if err := validateSliceOfArtifactRules(item.ExpectedProducts); err != nil { + return fmt.Errorf("invalid product rule: %s", err) + } + return nil +} + +/* +Inspection represents an in-toto supply chain inspection, whose command in the +Run field is executed during final product verification, generating unsigned +link metadata. Materials and products used/produced by the inspection are +constrained by the artifact rules in the inspection's ExpectedMaterials and +ExpectedProducts fields. +*/ +type Inspection struct { + Type string `json:"_type"` + Run []string `json:"run"` + SupplyChainItem +} + +/* +validateInspection ensures that a passed inspection is valid and matches the +necessary format of an inspection. +*/ +func validateInspection(inspection Inspection) error { + if err := validateSupplyChainItem(inspection.SupplyChainItem); err != nil { + return fmt.Errorf("inspection %s", err.Error()) + } + if inspection.Type != "inspection" { + return fmt.Errorf("invalid Type value for inspection '%s': should be "+ + "'inspection'", inspection.SupplyChainItem.Name) + } + return nil +} + +/* +Step represents an in-toto step of the supply chain performed by a functionary. +During final product verification in-toto looks for corresponding Link +metadata, which is used as signed evidence that the step was performed +according to the supply chain definition. Materials and products used/produced +by the step are constrained by the artifact rules in the step's +ExpectedMaterials and ExpectedProducts fields. +*/ +type Step struct { + Type string `json:"_type"` + PubKeys []string `json:"pubkeys"` + CertificateConstraints []CertificateConstraint `json:"cert_constraints,omitempty"` + ExpectedCommand []string `json:"expected_command"` + Threshold int `json:"threshold"` + SupplyChainItem +} + +// CheckCertConstraints returns true if the provided certificate matches at least one +// of the constraints for this step. +func (s Step) CheckCertConstraints(key Key, rootCAIDs []string, rootCertPool, intermediateCertPool *x509.CertPool) error { + if len(s.CertificateConstraints) == 0 { + return fmt.Errorf("no constraints found") + } + + _, possibleCert, err := decodeAndParse([]byte(key.KeyVal.Certificate)) + if err != nil { + return err + } + + cert, ok := possibleCert.(*x509.Certificate) + if !ok { + return fmt.Errorf("not a valid certificate") + } + + for _, constraint := range s.CertificateConstraints { + err = constraint.Check(cert, rootCAIDs, rootCertPool, intermediateCertPool) + if err == nil { + return nil + } + } + if err != nil { + return err + } + + // this should not be reachable since there is at least one constraint, and the for loop only saw err != nil + return fmt.Errorf("unknown certificate constraint error") +} + +/* +validateStep ensures that a passed step is valid and matches the +necessary format of an step. +*/ +func validateStep(step Step) error { + if err := validateSupplyChainItem(step.SupplyChainItem); err != nil { + return fmt.Errorf("step %s", err.Error()) + } + if step.Type != "step" { + return fmt.Errorf("invalid Type value for step '%s': should be 'step'", + step.SupplyChainItem.Name) + } + for _, keyID := range step.PubKeys { + if err := validateHexString(keyID); err != nil { + return err + } + } + return nil +} + +/* +ISO8601DateSchema defines the format string of a timestamp following the +ISO 8601 standard. +*/ +const ISO8601DateSchema = "2006-01-02T15:04:05Z" + +/* +Layout represents the definition of a software supply chain. It lists the +sequence of steps required in the software supply chain and the functionaries +authorized to perform these steps. Functionaries are identified by their +public keys. In addition, the layout may list a sequence of inspections that +are executed during in-toto supply chain verification. A layout should be +contained in a generic Metablock object, which provides functionality for +signing and signature verification, and reading from and writing to disk. +*/ +type Layout struct { + Type string `json:"_type"` + Steps []Step `json:"steps"` + Inspect []Inspection `json:"inspect"` + Keys map[string]Key `json:"keys"` + RootCas map[string]Key `json:"rootcas,omitempty"` + IntermediateCas map[string]Key `json:"intermediatecas,omitempty"` + Expires string `json:"expires"` + Readme string `json:"readme"` +} + +// Go does not allow to pass `[]T` (slice with certain type) to a function +// that accepts `[]interface{}` (slice with generic type) +// We have to manually create the interface slice first, see +// https://golang.org/doc/faq#convert_slice_of_interface +// TODO: Is there a better way to do polymorphism for steps and inspections? +func (l *Layout) stepsAsInterfaceSlice() []interface{} { + stepsI := make([]interface{}, len(l.Steps)) + for i, v := range l.Steps { + stepsI[i] = v + } + return stepsI +} +func (l *Layout) inspectAsInterfaceSlice() []interface{} { + inspectionsI := make([]interface{}, len(l.Inspect)) + for i, v := range l.Inspect { + inspectionsI[i] = v + } + return inspectionsI +} + +// RootCAIDs returns a slice of all of the Root CA IDs +func (l *Layout) RootCAIDs() []string { + rootCAIDs := make([]string, 0, len(l.RootCas)) + for rootCAID := range l.RootCas { + rootCAIDs = append(rootCAIDs, rootCAID) + } + return rootCAIDs +} + +func validateLayoutKeys(keys map[string]Key) error { + for keyID, key := range keys { + if key.KeyID != keyID { + return fmt.Errorf("invalid key found") + } + err := validatePublicKey(key) + if err != nil { + return err + } + } + + return nil +} + +/* +validateLayout is a function used to ensure that a passed item of type Layout +matches the necessary format. +*/ +func validateLayout(layout Layout) error { + if layout.Type != "layout" { + return fmt.Errorf("invalid Type value for layout: should be 'layout'") + } + + if _, err := time.Parse(ISO8601DateSchema, layout.Expires); err != nil { + return fmt.Errorf("expiry time parsed incorrectly - date either" + + " invalid or of incorrect format") + } + + if err := validateLayoutKeys(layout.Keys); err != nil { + return err + } + + if err := validateLayoutKeys(layout.RootCas); err != nil { + return err + } + + if err := validateLayoutKeys(layout.IntermediateCas); err != nil { + return err + } + + var namesSeen = make(map[string]bool) + for _, step := range layout.Steps { + if namesSeen[step.Name] { + return fmt.Errorf("non unique step or inspection name found") + } + + namesSeen[step.Name] = true + + if err := validateStep(step); err != nil { + return err + } + } + for _, inspection := range layout.Inspect { + if namesSeen[inspection.Name] { + return fmt.Errorf("non unique step or inspection name found") + } + + namesSeen[inspection.Name] = true + } + return nil +} + +/* +Metablock is a generic container for signable in-toto objects such as Layout +or Link. It has two fields, one that contains the signable object and one that +contains corresponding signatures. Metablock also provides functionality for +signing and signature verification, and reading from and writing to disk. +*/ +type Metablock struct { + // NOTE: Whenever we want to access an attribute of `Signed` we have to + // perform type assertion, e.g. `metablock.Signed.(Layout).Keys` + // Maybe there is a better way to store either Layouts or Links in `Signed`? + // The notary folks seem to have separate container structs: + // https://github.com/theupdateframework/notary/blob/master/tuf/data/root.go#L10-L14 + // https://github.com/theupdateframework/notary/blob/master/tuf/data/targets.go#L13-L17 + // I implemented it this way, because there will be several functions that + // receive or return a Metablock, where the type of Signed has to be inferred + // on runtime, e.g. when iterating over links for a layout, and a link can + // turn out to be a layout (sublayout) + Signed interface{} `json:"signed"` + Signatures []Signature `json:"signatures"` +} + +type jsonField struct { + name string + omitempty bool +} + +/* +checkRequiredJSONFields checks that the passed map (obj) has keys for each of +the json tags in the passed struct type (typ), and returns an error otherwise. +Any json tags that contain the "omitempty" option be allowed to be optional. +*/ +func checkRequiredJSONFields(obj map[string]interface{}, + typ reflect.Type) error { + + // Create list of json tags, e.g. `json:"_type"` + attributeCount := typ.NumField() + allFields := make([]jsonField, 0) + for i := 0; i < attributeCount; i++ { + fieldStr := typ.Field(i).Tag.Get("json") + field := jsonField{ + name: fieldStr, + omitempty: false, + } + + if idx := strings.Index(fieldStr, ","); idx != -1 { + field.name = fieldStr[:idx] + field.omitempty = strings.Contains(fieldStr[idx+1:], "omitempty") + } + + allFields = append(allFields, field) + } + + // Assert that there's a key in the passed map for each tag + for _, field := range allFields { + if _, ok := obj[field.name]; !ok && !field.omitempty { + return fmt.Errorf("required field %s missing", field.name) + } + } + return nil +} + +/* +Load parses JSON formatted metadata at the passed path into the Metablock +object on which it was called. It returns an error if it cannot parse +a valid JSON formatted Metablock that contains a Link or Layout. +*/ +func (mb *Metablock) Load(path string) error { + // Open file and close before returning + jsonFile, err := os.Open(path) + if err != nil { + return err + } + defer jsonFile.Close() + + // Read entire file + jsonBytes, err := ioutil.ReadAll(jsonFile) + if err != nil { + return err + } + + // Unmarshal JSON into a map of raw messages (signed and signatures) + // We can't fully unmarshal immediately, because we need to inspect the + // type (link or layout) to decide which data structure to use + var rawMb map[string]*json.RawMessage + if err := json.Unmarshal(jsonBytes, &rawMb); err != nil { + return err + } + + // Error out on missing `signed` or `signatures` field or if + // one of them has a `null` value, which would lead to a nil pointer + // dereference in Unmarshal below. + if rawMb["signed"] == nil || rawMb["signatures"] == nil { + return fmt.Errorf("in-toto metadata requires 'signed' and" + + " 'signatures' parts") + } + + // Fully unmarshal signatures part + if err := json.Unmarshal(*rawMb["signatures"], &mb.Signatures); err != nil { + return err + } + + // Temporarily copy signed to opaque map to inspect the `_type` of signed + // and create link or layout accordingly + var signed map[string]interface{} + if err := json.Unmarshal(*rawMb["signed"], &signed); err != nil { + return err + } + + if signed["_type"] == "link" { + var link Link + if err := checkRequiredJSONFields(signed, reflect.TypeOf(link)); err != nil { + return err + } + + data, err := rawMb["signed"].MarshalJSON() + if err != nil { + return err + } + decoder := json.NewDecoder(strings.NewReader(string(data))) + decoder.DisallowUnknownFields() + if err := decoder.Decode(&link); err != nil { + return err + } + mb.Signed = link + + } else if signed["_type"] == "layout" { + var layout Layout + if err := checkRequiredJSONFields(signed, reflect.TypeOf(layout)); err != nil { + return err + } + + data, err := rawMb["signed"].MarshalJSON() + if err != nil { + return err + } + decoder := json.NewDecoder(strings.NewReader(string(data))) + decoder.DisallowUnknownFields() + if err := decoder.Decode(&layout); err != nil { + return err + } + + mb.Signed = layout + + } else { + return fmt.Errorf("the '_type' field of the 'signed' part of in-toto" + + " metadata must be one of 'link' or 'layout'") + } + + return jsonFile.Close() +} + +/* +Dump JSON serializes and writes the Metablock on which it was called to the +passed path. It returns an error if JSON serialization or writing fails. +*/ +func (mb *Metablock) Dump(path string) error { + // JSON encode Metablock formatted with newlines and indentation + // TODO: parametrize format + jsonBytes, err := json.MarshalIndent(mb, "", " ") + if err != nil { + return err + } + + // Write JSON bytes to the passed path with permissions (-rw-r--r--) + err = ioutil.WriteFile(path, jsonBytes, 0644) + if err != nil { + return err + } + + return nil +} + +/* +GetSignableRepresentation returns the canonical JSON representation of the +Signed field of the Metablock on which it was called. If canonicalization +fails the first return value is nil and the second return value is the error. +*/ +func (mb *Metablock) GetSignableRepresentation() ([]byte, error) { + return cjson.EncodeCanonical(mb.Signed) +} + +/* +VerifySignature verifies the first signature, corresponding to the passed Key, +that it finds in the Signatures field of the Metablock on which it was called. +It returns an error if Signatures does not contain a Signature corresponding to +the passed Key, the object in Signed cannot be canonicalized, or the Signature +is invalid. +*/ +func (mb *Metablock) VerifySignature(key Key) error { + sig, err := mb.GetSignatureForKeyID(key.KeyID) + if err != nil { + return err + } + + dataCanonical, err := mb.GetSignableRepresentation() + if err != nil { + return err + } + + if err := VerifySignature(key, sig, dataCanonical); err != nil { + return err + } + return nil +} + +// GetSignatureForKeyID returns the signature that was created by the provided keyID, if it exists. +func (mb *Metablock) GetSignatureForKeyID(keyID string) (Signature, error) { + for _, s := range mb.Signatures { + if s.KeyID == keyID { + return s, nil + } + } + + return Signature{}, fmt.Errorf("no signature found for key '%s'", keyID) +} + +/* +ValidateMetablock ensures that a passed Metablock object is valid. It indirectly +validates the Link or Layout that the Metablock object contains. +*/ +func ValidateMetablock(mb Metablock) error { + switch mbSignedType := mb.Signed.(type) { + case Layout: + if err := validateLayout(mb.Signed.(Layout)); err != nil { + return err + } + case Link: + if err := validateLink(mb.Signed.(Link)); err != nil { + return err + } + default: + return fmt.Errorf("unknown type '%s', should be 'layout' or 'link'", + mbSignedType) + } + + if err := validateSliceOfSignatures(mb.Signatures); err != nil { + return err + } + + return nil +} + +/* +Sign creates a signature over the signed portion of the metablock using the Key +object provided. It then appends the resulting signature to the signatures +field as provided. It returns an error if the Signed object cannot be +canonicalized, or if the key is invalid or not supported. +*/ +func (mb *Metablock) Sign(key Key) error { + + dataCanonical, err := mb.GetSignableRepresentation() + if err != nil { + return err + } + + newSignature, err := GenerateSignature(dataCanonical, key) + if err != nil { + return err + } + + mb.Signatures = append(mb.Signatures, newSignature) + return nil +} + +// Subject describes the set of software artifacts the statement applies to. +type Subject struct { + Name string `json:"name"` + Digest slsa.DigestSet `json:"digest"` +} + +// StatementHeader defines the common fields for all statements +type StatementHeader struct { + Type string `json:"_type"` + PredicateType string `json:"predicateType"` + Subject []Subject `json:"subject"` +} + +/* +Statement binds the attestation to a particular subject and identifies the +of the predicate. This struct represents a generic statement. +*/ +type Statement struct { + StatementHeader + // Predicate contains type speficic metadata. + Predicate interface{} `json:"predicate"` +} + +// ProvenanceStatement is the definition for an entire provenance statement. +type ProvenanceStatement struct { + StatementHeader + Predicate slsa.ProvenancePredicate `json:"predicate"` +} + +// LinkStatement is the definition for an entire link statement. +type LinkStatement struct { + StatementHeader + Predicate Link `json:"predicate"` +} + +/* +SPDXStatement is the definition for an entire SPDX statement. +This is currently not implemented. Some tooling exists here: +https://github.com/spdx/tools-golang, but this software is still in +early state. +This struct is the same as the generic Statement struct but is added for +completeness +*/ +type SPDXStatement struct { + StatementHeader + Predicate interface{} `json:"predicate"` +} + +/* +CycloneDXStatement defines a cyclonedx sbom in the predicate. It is not +currently serialized just as its SPDX counterpart. It is an empty +interface, like the generic Statement. +*/ +type CycloneDXStatement struct { + StatementHeader + Predicate interface{} `json:"predicate"` +} + +/* +DSSESigner provides signature generation and validation based on the SSL +Signing Spec: https://github.com/secure-systems-lab/signing-spec +as describe by: https://github.com/MarkLodato/ITE/tree/media-type/ITE/5 +It wraps the generic SSL envelope signer and enforces the correct payload +type both during signature generation and validation. +*/ +type DSSESigner struct { + signer *dsse.EnvelopeSigner +} + +func NewDSSESigner(p ...dsse.SignVerifier) (*DSSESigner, error) { + es, err := dsse.NewEnvelopeSigner(p...) + if err != nil { + return nil, err + } + + return &DSSESigner{ + signer: es, + }, nil +} + +func (s *DSSESigner) SignPayload(body []byte) (*dsse.Envelope, error) { + return s.signer.SignPayload(PayloadType, body) +} + +func (s *DSSESigner) Verify(e *dsse.Envelope) error { + if e.PayloadType != PayloadType { + return ErrInvalidPayloadType + } + + _, err := s.signer.Verify(e) + return err +} diff --git a/vendor/github.com/in-toto/in-toto-golang/in_toto/rulelib.go b/vendor/github.com/in-toto/in-toto-golang/in_toto/rulelib.go new file mode 100644 index 000000000000..1bba77c39e50 --- /dev/null +++ b/vendor/github.com/in-toto/in-toto-golang/in_toto/rulelib.go @@ -0,0 +1,131 @@ +package in_toto + +import ( + "fmt" + "strings" +) + +// An error message issued in UnpackRule if it receives a malformed rule. +var errorMsg = "Wrong rule format, available formats are:\n" + + "\tMATCH [IN ] WITH (MATERIALS|PRODUCTS)" + + " [IN ] FROM ,\n" + + "\tCREATE ,\n" + + "\tDELETE ,\n" + + "\tMODIFY ,\n" + + "\tALLOW ,\n" + + "\tDISALLOW ,\n" + + "\tREQUIRE \n\n" + +/* +UnpackRule parses the passed rule and extracts and returns the information +required for rule processing. It can be used to verify if a rule has a valid +format. Available rule formats are: + + MATCH [IN ] WITH (MATERIALS|PRODUCTS) + [IN ] FROM , + CREATE , + DELETE , + MODIFY , + ALLOW , + DISALLOW + +Rule tokens are normalized to lower case before returning. The returned map +has the following format: + + { + "type": "match" | "create" | "delete" |"modify" | "allow" | "disallow" + "pattern": "", + "srcPrefix": "", // MATCH rule only + "dstPrefix": "", // MATCH rule only + "dstType": "materials" | "products">, // MATCH rule only + "dstName": "", // Match rule only + } + +If the rule does not match any of the available formats the first return value +is nil and the second return value is the error. +*/ +func UnpackRule(rule []string) (map[string]string, error) { + // Cache rule len + ruleLen := len(rule) + + // Create all lower rule copy to case-insensitively parse out tokens whose + // position we don't know yet. We keep the original rule to retain the + // non-token elements' case. + ruleLower := make([]string, ruleLen) + for i, val := range rule { + ruleLower[i] = strings.ToLower(val) + } + + switch ruleLower[0] { + case "create", "modify", "delete", "allow", "disallow", "require": + if ruleLen != 2 { + return nil, + fmt.Errorf("%s Got:\n\t %s", errorMsg, rule) + } + + return map[string]string{ + "type": ruleLower[0], + "pattern": rule[1], + }, nil + + case "match": + var srcPrefix string + var dstType string + var dstPrefix string + var dstName string + + // MATCH IN WITH (MATERIALS|PRODUCTS) \ + // IN FROM + if ruleLen == 10 && ruleLower[2] == "in" && + ruleLower[4] == "with" && ruleLower[6] == "in" && + ruleLower[8] == "from" { + srcPrefix = rule[3] + dstType = ruleLower[5] + dstPrefix = rule[7] + dstName = rule[9] + // MATCH IN WITH (MATERIALS|PRODUCTS) \ + // FROM + } else if ruleLen == 8 && ruleLower[2] == "in" && + ruleLower[4] == "with" && ruleLower[6] == "from" { + srcPrefix = rule[3] + dstType = ruleLower[5] + dstPrefix = "" + dstName = rule[7] + + // MATCH WITH (MATERIALS|PRODUCTS) IN + // FROM + } else if ruleLen == 8 && ruleLower[2] == "with" && + ruleLower[4] == "in" && ruleLower[6] == "from" { + srcPrefix = "" + dstType = ruleLower[3] + dstPrefix = rule[5] + dstName = rule[7] + + // MATCH WITH (MATERIALS|PRODUCTS) FROM + } else if ruleLen == 6 && ruleLower[2] == "with" && + ruleLower[4] == "from" { + srcPrefix = "" + dstType = ruleLower[3] + dstPrefix = "" + dstName = rule[5] + + } else { + return nil, + fmt.Errorf("%s Got:\n\t %s", errorMsg, rule) + + } + + return map[string]string{ + "type": ruleLower[0], + "pattern": rule[1], + "srcPrefix": srcPrefix, + "dstPrefix": dstPrefix, + "dstType": dstType, + "dstName": dstName, + }, nil + + default: + return nil, + fmt.Errorf("%s Got:\n\t %s", errorMsg, rule) + } +} diff --git a/vendor/github.com/in-toto/in-toto-golang/in_toto/runlib.go b/vendor/github.com/in-toto/in-toto-golang/in_toto/runlib.go new file mode 100644 index 000000000000..80eef3d75da5 --- /dev/null +++ b/vendor/github.com/in-toto/in-toto-golang/in_toto/runlib.go @@ -0,0 +1,400 @@ +package in_toto + +import ( + "bytes" + "errors" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "reflect" + "strings" + "syscall" + + "github.com/shibumi/go-pathspec" +) + +// ErrSymCycle signals a detected symlink cycle in our RecordArtifacts() function. +var ErrSymCycle = errors.New("symlink cycle detected") + +// ErrUnsupportedHashAlgorithm signals a missing hash mapping in getHashMapping +var ErrUnsupportedHashAlgorithm = errors.New("unsupported hash algorithm detected") + +// visitedSymlinks is a hashset that contains all paths that we have visited. +var visitedSymlinks Set + +/* +RecordArtifact reads and hashes the contents of the file at the passed path +using sha256 and returns a map in the following format: + + { + "": { + "sha256": + } + } + +If reading the file fails, the first return value is nil and the second return +value is the error. +NOTE: For cross-platform consistency Windows-style line separators (CRLF) are +normalized to Unix-style line separators (LF) before hashing file contents. +*/ +func RecordArtifact(path string, hashAlgorithms []string, lineNormalization bool) (map[string]interface{}, error) { + supportedHashMappings := getHashMapping() + // Read file from passed path + contents, err := ioutil.ReadFile(path) + hashedContentsMap := make(map[string]interface{}) + if err != nil { + return nil, err + } + + if lineNormalization { + // "Normalize" file contents. We convert all line separators to '\n' + // for keeping operating system independence + contents = bytes.ReplaceAll(contents, []byte("\r\n"), []byte("\n")) + contents = bytes.ReplaceAll(contents, []byte("\r"), []byte("\n")) + } + + // Create a map of all the hashes present in the hash_func list + for _, element := range hashAlgorithms { + if _, ok := supportedHashMappings[element]; !ok { + return nil, fmt.Errorf("%w: %s", ErrUnsupportedHashAlgorithm, element) + } + h := supportedHashMappings[element] + result := fmt.Sprintf("%x", hashToHex(h(), contents)) + hashedContentsMap[element] = result + } + + // Return it in a format that is conformant with link metadata artifacts + return hashedContentsMap, nil +} + +/* +RecordArtifacts is a wrapper around recordArtifacts. +RecordArtifacts initializes a set for storing visited symlinks, +calls recordArtifacts and deletes the set if no longer needed. +recordArtifacts walks through the passed slice of paths, traversing +subdirectories, and calls RecordArtifact for each file. It returns a map in +the following format: + + { + "": { + "sha256": + }, + "": { + "sha256": + }, + ... + } + +If recording an artifact fails the first return value is nil and the second +return value is the error. +*/ +func RecordArtifacts(paths []string, hashAlgorithms []string, gitignorePatterns []string, lStripPaths []string, lineNormalization bool) (evalArtifacts map[string]interface{}, err error) { + // Make sure to initialize a fresh hashset for every RecordArtifacts call + visitedSymlinks = NewSet() + evalArtifacts, err = recordArtifacts(paths, hashAlgorithms, gitignorePatterns, lStripPaths, lineNormalization) + // pass result and error through + return evalArtifacts, err +} + +/* +recordArtifacts walks through the passed slice of paths, traversing +subdirectories, and calls RecordArtifact for each file. It returns a map in +the following format: + + { + "": { + "sha256": + }, + "": { + "sha256": + }, + ... + } + +If recording an artifact fails the first return value is nil and the second +return value is the error. +*/ +func recordArtifacts(paths []string, hashAlgorithms []string, gitignorePatterns []string, lStripPaths []string, lineNormalization bool) (map[string]interface{}, error) { + artifacts := make(map[string]interface{}) + for _, path := range paths { + err := filepath.Walk(path, + func(path string, info os.FileInfo, err error) error { + // Abort if Walk function has a problem, + // e.g. path does not exist + if err != nil { + return err + } + // We need to call pathspec.GitIgnore inside of our filepath.Walk, because otherwise + // we will not catch all paths. Just imagine a path like "." and a pattern like "*.pub". + // If we would call pathspec outside of the filepath.Walk this would not match. + ignore, err := pathspec.GitIgnore(gitignorePatterns, path) + if err != nil { + return err + } + if ignore { + return nil + } + // Don't hash directories + if info.IsDir() { + return nil + } + + // check for symlink and evaluate the last element in a symlink + // chain via filepath.EvalSymlinks. We use EvalSymlinks here, + // because with os.Readlink() we would just read the next + // element in a possible symlink chain. This would mean more + // iterations. infoMode()&os.ModeSymlink uses the file + // type bitmask to check for a symlink. + if info.Mode()&os.ModeSymlink == os.ModeSymlink { + // return with error if we detect a symlink cycle + if ok := visitedSymlinks.Has(path); ok { + // this error will get passed through + // to RecordArtifacts() + return ErrSymCycle + } + evalSym, err := filepath.EvalSymlinks(path) + if err != nil { + return err + } + // add symlink to visitedSymlinks set + // this way, we know which link we have visited already + // if we visit a symlink twice, we have detected a symlink cycle + visitedSymlinks.Add(path) + // We recursively call RecordArtifacts() to follow + // the new path. + evalArtifacts, evalErr := recordArtifacts([]string{evalSym}, hashAlgorithms, gitignorePatterns, lStripPaths, lineNormalization) + if evalErr != nil { + return evalErr + } + for key, value := range evalArtifacts { + artifacts[key] = value + } + return nil + } + artifact, err := RecordArtifact(path, hashAlgorithms, lineNormalization) + // Abort if artifact can't be recorded, e.g. + // due to file permissions + if err != nil { + return err + } + + for _, strip := range lStripPaths { + if strings.HasPrefix(path, strip) { + path = strings.TrimPrefix(path, strip) + break + } + } + // Check if path is unique + _, existingPath := artifacts[path] + if existingPath { + return fmt.Errorf("left stripping has resulted in non unique dictionary key: %s", path) + } + artifacts[path] = artifact + return nil + }) + + if err != nil { + return nil, err + } + } + + return artifacts, nil +} + +/* +waitErrToExitCode converts an error returned by Cmd.wait() to an exit code. It +returns -1 if no exit code can be inferred. +*/ +func waitErrToExitCode(err error) int { + // If there's no exit code, we return -1 + retVal := -1 + + // See https://stackoverflow.com/questions/10385551/get-exit-code-go + if err != nil { + if exiterr, ok := err.(*exec.ExitError); ok { + // The program has exited with an exit code != 0 + // This works on both Unix and Windows. Although package + // syscall is generally platform dependent, WaitStatus is + // defined for both Unix and Windows and in both cases has + // an ExitStatus() method with the same signature. + if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { + retVal = status.ExitStatus() + } + } + } else { + retVal = 0 + } + + return retVal +} + +/* +RunCommand executes the passed command in a subprocess. The first element of +cmdArgs is used as executable and the rest as command arguments. It captures +and returns stdout, stderr and exit code. The format of the returned map is: + + { + "return-value": , + "stdout": "", + "stderr": "" + } + +If the command cannot be executed or no pipes for stdout or stderr can be +created the first return value is nil and the second return value is the error. +NOTE: Since stdout and stderr are captured, they cannot be seen during the +command execution. +*/ +func RunCommand(cmdArgs []string, runDir string) (map[string]interface{}, error) { + + cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...) + + if runDir != "" { + cmd.Dir = runDir + } + + stderrPipe, err := cmd.StderrPipe() + if err != nil { + return nil, err + } + stdoutPipe, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + + if err := cmd.Start(); err != nil { + return nil, err + } + + // TODO: duplicate stdout, stderr + stdout, _ := ioutil.ReadAll(stdoutPipe) + stderr, _ := ioutil.ReadAll(stderrPipe) + + retVal := waitErrToExitCode(cmd.Wait()) + + return map[string]interface{}{ + "return-value": float64(retVal), + "stdout": string(stdout), + "stderr": string(stderr), + }, nil +} + +/* +InTotoRun executes commands, e.g. for software supply chain steps or +inspections of an in-toto layout, and creates and returns corresponding link +metadata. Link metadata contains recorded products at the passed productPaths +and materials at the passed materialPaths. The returned link is wrapped in a +Metablock object. If command execution or artifact recording fails the first +return value is an empty Metablock and the second return value is the error. +*/ +func InTotoRun(name string, runDir string, materialPaths []string, productPaths []string, + cmdArgs []string, key Key, hashAlgorithms []string, gitignorePatterns []string, + lStripPaths []string, lineNormalization bool) (Metablock, error) { + var linkMb Metablock + + materials, err := RecordArtifacts(materialPaths, hashAlgorithms, gitignorePatterns, lStripPaths, lineNormalization) + if err != nil { + return linkMb, err + } + + byProducts, err := RunCommand(cmdArgs, runDir) + if err != nil { + return linkMb, err + } + + products, err := RecordArtifacts(productPaths, hashAlgorithms, gitignorePatterns, lStripPaths, lineNormalization) + if err != nil { + return linkMb, err + } + + linkMb.Signed = Link{ + Type: "link", + Name: name, + Materials: materials, + Products: products, + ByProducts: byProducts, + Command: cmdArgs, + Environment: map[string]interface{}{}, + } + + linkMb.Signatures = []Signature{} + // We use a new feature from Go1.13 here, to check the key struct. + // IsZero() will return True, if the key hasn't been initialized + + // with other values than the default ones. + if !reflect.ValueOf(key).IsZero() { + if err := linkMb.Sign(key); err != nil { + return linkMb, err + } + } + + return linkMb, nil +} + +/* +InTotoRecordStart begins the creation of a link metablock file in two steps, +in order to provide evidence for supply chain steps that cannot be carries out +by a single command. InTotoRecordStart collects the hashes of the materials +before any commands are run, signs the unfinished link, and returns the link. +*/ +func InTotoRecordStart(name string, materialPaths []string, key Key, hashAlgorithms, gitignorePatterns []string, lStripPaths []string, lineNormalization bool) (Metablock, error) { + var linkMb Metablock + materials, err := RecordArtifacts(materialPaths, hashAlgorithms, gitignorePatterns, lStripPaths, lineNormalization) + if err != nil { + return linkMb, err + } + + linkMb.Signed = Link{ + Type: "link", + Name: name, + Materials: materials, + Products: map[string]interface{}{}, + ByProducts: map[string]interface{}{}, + Command: []string{}, + Environment: map[string]interface{}{}, + } + + if !reflect.ValueOf(key).IsZero() { + if err := linkMb.Sign(key); err != nil { + return linkMb, err + } + } + + return linkMb, nil +} + +/* +InTotoRecordStop ends the creation of a metatadata link file created by +InTotoRecordStart. InTotoRecordStop takes in a signed unfinished link metablock +created by InTotoRecordStart and records the hashes of any products creted by +commands run between InTotoRecordStart and InTotoRecordStop. The resultant +finished link metablock is then signed by the provided key and returned. +*/ +func InTotoRecordStop(prelimLinkMb Metablock, productPaths []string, key Key, hashAlgorithms, gitignorePatterns []string, lStripPaths []string, lineNormalization bool) (Metablock, error) { + var linkMb Metablock + if err := prelimLinkMb.VerifySignature(key); err != nil { + return linkMb, err + } + + link, ok := prelimLinkMb.Signed.(Link) + if !ok { + return linkMb, errors.New("invalid metadata block") + } + + products, err := RecordArtifacts(productPaths, hashAlgorithms, gitignorePatterns, lStripPaths, lineNormalization) + if err != nil { + return linkMb, err + } + + link.Products = products + linkMb.Signed = link + + if !reflect.ValueOf(key).IsZero() { + if err := linkMb.Sign(key); err != nil { + return linkMb, err + } + } + + return linkMb, nil +} diff --git a/vendor/github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2/provenance.go b/vendor/github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2/provenance.go new file mode 100644 index 000000000000..1cecf3a4989f --- /dev/null +++ b/vendor/github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2/provenance.go @@ -0,0 +1,66 @@ +package v02 + +import "time" + +const ( + // PredicateSLSAProvenance represents a build provenance for an artifact. + PredicateSLSAProvenance = "https://slsa.dev/provenance/v0.2" +) + +// ProvenancePredicate is the provenance predicate definition. +type ProvenancePredicate struct { + Builder ProvenanceBuilder `json:"builder"` + BuildType string `json:"buildType"` + Invocation ProvenanceInvocation `json:"invocation,omitempty"` + BuildConfig interface{} `json:"buildConfig,omitempty"` + Metadata *ProvenanceMetadata `json:"metadata,omitempty"` + Materials []ProvenanceMaterial `json:"materials,omitempty"` +} + +// ProvenanceBuilder idenfifies the entity that executed the build steps. +type ProvenanceBuilder struct { + ID string `json:"id"` +} + +// ProvenanceInvocation identifies the event that kicked off the build. +type ProvenanceInvocation struct { + ConfigSource ConfigSource `json:"configSource,omitempty"` + Parameters interface{} `json:"parameters,omitempty"` + Environment interface{} `json:"environment,omitempty"` +} + +type ConfigSource struct { + URI string `json:"uri,omitempty"` + Digest DigestSet `json:"digest,omitempty"` + EntryPoint string `json:"entryPoint,omitempty"` +} + +// ProvenanceMetadata contains metadata for the built artifact. +type ProvenanceMetadata struct { + BuildInvocationID string `json:"buildInvocationID,omitempty"` + // Use pointer to make sure that the abscense of a time is not + // encoded as the Epoch time. + BuildStartedOn *time.Time `json:"buildStartedOn,omitempty"` + BuildFinishedOn *time.Time `json:"buildFinishedOn,omitempty"` + Completeness ProvenanceComplete `json:"completeness"` + Reproducible bool `json:"reproducible"` +} + +// ProvenanceMaterial defines the materials used to build an artifact. +type ProvenanceMaterial struct { + URI string `json:"uri,omitempty"` + Digest DigestSet `json:"digest,omitempty"` +} + +// ProvenanceComplete indicates wheter the claims in build/recipe are complete. +// For in depth information refer to the specifictaion: +// https://github.com/in-toto/attestation/blob/v0.1.0/spec/predicates/provenance.md +type ProvenanceComplete struct { + Parameters bool `json:"parameters"` + Environment bool `json:"environment"` + Materials bool `json:"materials"` +} + +// DigestSet contains a set of digests. It is represented as a map from +// algorithm name to lowercase hex-encoded value. +type DigestSet map[string]string diff --git a/vendor/github.com/in-toto/in-toto-golang/in_toto/util.go b/vendor/github.com/in-toto/in-toto-golang/in_toto/util.go new file mode 100644 index 000000000000..59cba86eb52c --- /dev/null +++ b/vendor/github.com/in-toto/in-toto-golang/in_toto/util.go @@ -0,0 +1,147 @@ +package in_toto + +import ( + "fmt" +) + +/* +Set represents a data structure for set operations. See `NewSet` for how to +create a Set, and available Set receivers for useful set operations. + +Under the hood Set aliases map[string]struct{}, where the map keys are the set +elements and the map values are a memory-efficient way of storing the keys. +*/ +type Set map[string]struct{} + +/* +NewSet creates a new Set, assigns it the optionally passed variadic string +elements, and returns it. +*/ +func NewSet(elems ...string) Set { + var s Set = make(map[string]struct{}) + for _, elem := range elems { + s.Add(elem) + } + return s +} + +/* +Has returns True if the passed string is member of the set on which it was +called and False otherwise. +*/ +func (s Set) Has(elem string) bool { + _, ok := s[elem] + return ok +} + +/* +Add adds the passed string to the set on which it was called, if the string is +not a member of the set. +*/ +func (s Set) Add(elem string) { + s[elem] = struct{}{} +} + +/* +Remove removes the passed string from the set on which was is called, if the +string is a member of the set. +*/ +func (s Set) Remove(elem string) { + delete(s, elem) +} + +/* +Intersection creates and returns a new Set with the elements of the set on +which it was called that are also in the passed set. +*/ +func (s Set) Intersection(s2 Set) Set { + res := NewSet() + for elem := range s { + if !s2.Has(elem) { + continue + } + res.Add(elem) + } + return res +} + +/* +Difference creates and returns a new Set with the elements of the set on +which it was called that are not in the passed set. +*/ +func (s Set) Difference(s2 Set) Set { + res := NewSet() + for elem := range s { + if s2.Has(elem) { + continue + } + res.Add(elem) + } + return res +} + +/* +Filter creates and returns a new Set with the elements of the set on which it +was called that match the passed pattern. A matching error is treated like a +non-match plus a warning is printed. +*/ +func (s Set) Filter(pattern string) Set { + res := NewSet() + for elem := range s { + matched, err := match(pattern, elem) + if err != nil { + fmt.Printf("WARNING: %s, pattern was '%s'\n", err, pattern) + continue + } + if !matched { + continue + } + res.Add(elem) + } + return res +} + +/* +Slice creates and returns an unordered string slice with the elements of the +set on which it was called. +*/ +func (s Set) Slice() []string { + var res []string + res = make([]string, 0, len(s)) + for elem := range s { + res = append(res, elem) + } + return res +} + +/* +InterfaceKeyStrings returns string keys of passed interface{} map in an +unordered string slice. +*/ +func InterfaceKeyStrings(m map[string]interface{}) []string { + res := make([]string, len(m)) + i := 0 + for k := range m { + res[i] = k + i++ + } + return res +} + +/* +IsSubSet checks if the parameter subset is a +subset of the superset s. +*/ +func (s Set) IsSubSet(subset Set) bool { + if len(subset) > len(s) { + return false + } + for key := range subset { + if s.Has(key) { + continue + } else { + return false + } + } + return true +} diff --git a/vendor/github.com/in-toto/in-toto-golang/in_toto/util_unix.go b/vendor/github.com/in-toto/in-toto-golang/in_toto/util_unix.go new file mode 100644 index 000000000000..f555f79a528d --- /dev/null +++ b/vendor/github.com/in-toto/in-toto-golang/in_toto/util_unix.go @@ -0,0 +1,14 @@ +//go:build linux || darwin || !windows +// +build linux darwin !windows + +package in_toto + +import "golang.org/x/sys/unix" + +func isWritable(path string) error { + err := unix.Access(path, unix.W_OK) + if err != nil { + return err + } + return nil +} diff --git a/vendor/github.com/in-toto/in-toto-golang/in_toto/util_windows.go b/vendor/github.com/in-toto/in-toto-golang/in_toto/util_windows.go new file mode 100644 index 000000000000..8552f0345d04 --- /dev/null +++ b/vendor/github.com/in-toto/in-toto-golang/in_toto/util_windows.go @@ -0,0 +1,25 @@ +package in_toto + +import ( + "errors" + "os" +) + +func isWritable(path string) error { + // get fileInfo + info, err := os.Stat(path) + if err != nil { + return err + } + + // check if path is a directory + if !info.IsDir() { + return errors.New("not a directory") + } + + // Check if the user bit is enabled in file permission + if info.Mode().Perm()&(1<<(uint(7))) == 0 { + return errors.New("not writable") + } + return nil +} diff --git a/vendor/github.com/in-toto/in-toto-golang/in_toto/verifylib.go b/vendor/github.com/in-toto/in-toto-golang/in_toto/verifylib.go new file mode 100644 index 000000000000..2302040f4600 --- /dev/null +++ b/vendor/github.com/in-toto/in-toto-golang/in_toto/verifylib.go @@ -0,0 +1,1091 @@ +/* +Package in_toto implements types and routines to verify a software supply chain +according to the in-toto specification. +See https://github.com/in-toto/docs/blob/master/in-toto-spec.md +*/ +package in_toto + +import ( + "crypto/x509" + "errors" + "fmt" + "io" + "os" + "path" + osPath "path" + "path/filepath" + "reflect" + "regexp" + "strings" + "time" +) + +// ErrInspectionRunDirIsSymlink gets thrown if the runDir is a symlink +var ErrInspectionRunDirIsSymlink = errors.New("runDir is a symlink. This is a security risk") + +/* +RunInspections iteratively executes the command in the Run field of all +inspections of the passed layout, creating unsigned link metadata that records +all files found in the current working directory as materials (before command +execution) and products (after command execution). A map with inspection names +as keys and Metablocks containing the generated link metadata as values is +returned. The format is: + + { + : Metablock, + : Metablock, + ... + } + +If executing the inspection command fails, or if the executed command has a +non-zero exit code, the first return value is an empty Metablock map and the +second return value is the error. +*/ +func RunInspections(layout Layout, runDir string, lineNormalization bool) (map[string]Metablock, error) { + inspectionMetadata := make(map[string]Metablock) + + for _, inspection := range layout.Inspect { + + paths := []string{"."} + if runDir != "" { + paths = []string{runDir} + } + + linkMb, err := InTotoRun(inspection.Name, runDir, paths, paths, + inspection.Run, Key{}, []string{"sha256"}, nil, nil, lineNormalization) + + if err != nil { + return nil, err + } + + retVal := linkMb.Signed.(Link).ByProducts["return-value"] + if retVal != float64(0) { + return nil, fmt.Errorf("inspection command '%s' of inspection '%s'"+ + " returned a non-zero value: %d", inspection.Run, inspection.Name, + retVal) + } + + // Dump inspection link to cwd using the short link name format + linkName := fmt.Sprintf(LinkNameFormatShort, inspection.Name) + if err := linkMb.Dump(linkName); err != nil { + fmt.Printf("JSON serialization or writing failed: %s", err) + } + + inspectionMetadata[inspection.Name] = linkMb + } + return inspectionMetadata, nil +} + +// verifyMatchRule is a helper function to process artifact rules of +// type MATCH. See VerifyArtifacts for more details. +func verifyMatchRule(ruleData map[string]string, + srcArtifacts map[string]interface{}, srcArtifactQueue Set, + itemsMetadata map[string]Metablock) Set { + consumed := NewSet() + // Get destination link metadata + dstLinkMb, exists := itemsMetadata[ruleData["dstName"]] + if !exists { + // Destination link does not exist, rule can't consume any + // artifacts + return consumed + } + + // Get artifacts from destination link metadata + var dstArtifacts map[string]interface{} + switch ruleData["dstType"] { + case "materials": + dstArtifacts = dstLinkMb.Signed.(Link).Materials + case "products": + dstArtifacts = dstLinkMb.Signed.(Link).Products + } + + // cleanup paths in pattern and artifact maps + if ruleData["pattern"] != "" { + ruleData["pattern"] = path.Clean(ruleData["pattern"]) + } + for k := range srcArtifacts { + if path.Clean(k) != k { + srcArtifacts[path.Clean(k)] = srcArtifacts[k] + delete(srcArtifacts, k) + } + } + for k := range dstArtifacts { + if path.Clean(k) != k { + dstArtifacts[path.Clean(k)] = dstArtifacts[k] + delete(dstArtifacts, k) + } + } + + // Normalize optional source and destination prefixes, i.e. if + // there is a prefix, then add a trailing slash if not there yet + for _, prefix := range []string{"srcPrefix", "dstPrefix"} { + if ruleData[prefix] != "" { + ruleData[prefix] = path.Clean(ruleData[prefix]) + if !strings.HasSuffix(ruleData[prefix], "/") { + ruleData[prefix] += "/" + } + } + } + // Iterate over queue and mark consumed artifacts + for srcPath := range srcArtifactQueue { + // Remove optional source prefix from source artifact path + // Noop if prefix is empty, or artifact does not have it + srcBasePath := strings.TrimPrefix(srcPath, ruleData["srcPrefix"]) + + // Ignore artifacts not matched by rule pattern + matched, err := match(ruleData["pattern"], srcBasePath) + if err != nil || !matched { + continue + } + + // Construct corresponding destination artifact path, i.e. + // an optional destination prefix plus the source base path + dstPath := path.Clean(osPath.Join(ruleData["dstPrefix"], srcBasePath)) + + // Try to find the corresponding destination artifact + dstArtifact, exists := dstArtifacts[dstPath] + // Ignore artifacts without corresponding destination artifact + if !exists { + continue + } + + // Ignore artifact pairs with no matching hashes + if !reflect.DeepEqual(srcArtifacts[srcPath], dstArtifact) { + continue + } + + // Only if a source and destination artifact pair was found and + // their hashes are equal, will we mark the source artifact as + // successfully consumed, i.e. it will be removed from the queue + consumed.Add(srcPath) + } + return consumed +} + +/* +VerifyArtifacts iteratively applies the material and product rules of the +passed items (step or inspection) to enforce and authorize artifacts (materials +or products) reported by the corresponding link and to guarantee that +artifacts are linked together across links. In the beginning all artifacts are +placed in a queue according to their type. If an artifact gets consumed by a +rule it is removed from the queue. An artifact can only be consumed once in +the course of processing the set of rules in ExpectedMaterials or +ExpectedProducts. + +Rules of type MATCH, ALLOW, CREATE, DELETE, MODIFY and DISALLOW are supported. + +All rules except for DISALLOW consume queued artifacts on success, and +leave the queue unchanged on failure. Hence, it is left to a terminal +DISALLOW rule to fail overall verification, if artifacts are left in the queue +that should have been consumed by preceding rules. +*/ +func VerifyArtifacts(items []interface{}, + itemsMetadata map[string]Metablock) error { + // Verify artifact rules for each item in the layout + for _, itemI := range items { + // The layout item (interface) must be a Link or an Inspection we are only + // interested in the name and the expected materials and products + var itemName string + var expectedMaterials [][]string + var expectedProducts [][]string + + switch item := itemI.(type) { + case Step: + itemName = item.Name + expectedMaterials = item.ExpectedMaterials + expectedProducts = item.ExpectedProducts + + case Inspection: + itemName = item.Name + expectedMaterials = item.ExpectedMaterials + expectedProducts = item.ExpectedProducts + + default: // Something wrong + return fmt.Errorf("VerifyArtifacts received an item of invalid type,"+ + " elements of passed slice 'items' must be one of 'Step' or"+ + " 'Inspection', got: '%s'", reflect.TypeOf(item)) + } + + // Use the item's name to extract the corresponding link + srcLinkMb, exists := itemsMetadata[itemName] + if !exists { + return fmt.Errorf("VerifyArtifacts could not find metadata"+ + " for item '%s', got: '%s'", itemName, itemsMetadata) + } + + // Create shortcuts to materials and products (including hashes) reported + // by the item's link, required to verify "match" rules + materials := srcLinkMb.Signed.(Link).Materials + products := srcLinkMb.Signed.(Link).Products + + // All other rules only require the material or product paths (without + // hashes). We extract them from the corresponding maps and store them as + // sets for convenience in further processing + materialPaths := NewSet() + for _, p := range InterfaceKeyStrings(materials) { + materialPaths.Add(path.Clean(p)) + } + productPaths := NewSet() + for _, p := range InterfaceKeyStrings(products) { + productPaths.Add(path.Clean(p)) + } + + // For `create`, `delete` and `modify` rules we prepare sets of artifacts + // (without hashes) that were created, deleted or modified in the current + // step or inspection + created := productPaths.Difference(materialPaths) + deleted := materialPaths.Difference(productPaths) + remained := materialPaths.Intersection(productPaths) + modified := NewSet() + for name := range remained { + if !reflect.DeepEqual(materials[name], products[name]) { + modified.Add(name) + } + } + + // For each item we have to run rule verification, once per artifact type. + // Here we prepare the corresponding data for each round. + verificationDataList := []map[string]interface{}{ + { + "srcType": "materials", + "rules": expectedMaterials, + "artifacts": materials, + "artifactPaths": materialPaths, + }, + { + "srcType": "products", + "rules": expectedProducts, + "artifacts": products, + "artifactPaths": productPaths, + }, + } + // TODO: Add logging library (see in-toto/in-toto-golang#4) + // fmt.Printf("Verifying %s '%s' ", reflect.TypeOf(itemI), itemName) + + // Process all material rules using the corresponding materials and all + // product rules using the corresponding products + for _, verificationData := range verificationDataList { + // TODO: Add logging library (see in-toto/in-toto-golang#4) + // fmt.Printf("%s...\n", verificationData["srcType"]) + + rules := verificationData["rules"].([][]string) + artifacts := verificationData["artifacts"].(map[string]interface{}) + + // Use artifacts (without hashes) as base queue. Each rule only operates + // on artifacts in that queue. If a rule consumes an artifact (i.e. can + // be applied successfully), the artifact is removed from the queue. By + // applying a DISALLOW rule eventually, verification may return an error, + // if the rule matches any artifacts in the queue that should have been + // consumed earlier. + queue := verificationData["artifactPaths"].(Set) + + // TODO: Add logging library (see in-toto/in-toto-golang#4) + // fmt.Printf("Initial state\nMaterials: %s\nProducts: %s\nQueue: %s\n\n", + // materialPaths.Slice(), productPaths.Slice(), queue.Slice()) + + // Verify rules sequentially + for _, rule := range rules { + // Parse rule and error out if it is malformed + // NOTE: the rule format should have been validated before + ruleData, err := UnpackRule(rule) + if err != nil { + return err + } + + // Apply rule pattern to filter queued artifacts that are up for rule + // specific consumption + filtered := queue.Filter(path.Clean(ruleData["pattern"])) + + var consumed Set + switch ruleData["type"] { + case "match": + // Note: here we need to perform more elaborate filtering + consumed = verifyMatchRule(ruleData, artifacts, queue, itemsMetadata) + + case "allow": + // Consumes all filtered artifacts + consumed = filtered + + case "create": + // Consumes filtered artifacts that were created + consumed = filtered.Intersection(created) + + case "delete": + // Consumes filtered artifacts that were deleted + consumed = filtered.Intersection(deleted) + + case "modify": + // Consumes filtered artifacts that were modified + consumed = filtered.Intersection(modified) + + case "disallow": + // Does not consume but errors out if artifacts were filtered + if len(filtered) > 0 { + return fmt.Errorf("artifact verification failed for %s '%s',"+ + " %s %s disallowed by rule %s", + reflect.TypeOf(itemI).Name(), itemName, + verificationData["srcType"], filtered.Slice(), rule) + } + case "require": + // REQUIRE is somewhat of a weird animal that does not use + // patterns bur rather single filenames (for now). + if !queue.Has(ruleData["pattern"]) { + return fmt.Errorf("artifact verification failed for %s in REQUIRE '%s',"+ + " because %s is not in %s", verificationData["srcType"], + ruleData["pattern"], ruleData["pattern"], queue.Slice()) + } + } + // Update queue by removing consumed artifacts + queue = queue.Difference(consumed) + // TODO: Add logging library (see in-toto/in-toto-golang#4) + // fmt.Printf("Rule: %s\nQueue: %s\n\n", rule, queue.Slice()) + } + } + } + return nil +} + +/* +ReduceStepsMetadata merges for each step of the passed Layout all the passed +per-functionary links into a single link, asserting that the reported Materials +and Products are equal across links for a given step. This function may be +used at a time during the overall verification, where link threshold's have +been verified and subsequent verification only needs one exemplary link per +step. The function returns a map with one Metablock (link) per step: + + { + : Metablock, + : Metablock, + ... + } + +If links corresponding to the same step report different Materials or different +Products, the first return value is an empty Metablock map and the second +return value is the error. +*/ +func ReduceStepsMetadata(layout Layout, + stepsMetadata map[string]map[string]Metablock) (map[string]Metablock, + error) { + stepsMetadataReduced := make(map[string]Metablock) + + for _, step := range layout.Steps { + linksPerStep, ok := stepsMetadata[step.Name] + // We should never get here, layout verification must fail earlier + if !ok || len(linksPerStep) < 1 { + panic("Could not reduce metadata for step '" + step.Name + + "', no link metadata found.") + } + + // Get the first link (could be any link) for the current step, which will + // serve as reference link for below comparisons + var referenceKeyID string + var referenceLinkMb Metablock + for keyID, linkMb := range linksPerStep { + referenceLinkMb = linkMb + referenceKeyID = keyID + break + } + + // Only one link, nothing to reduce, take the reference link + if len(linksPerStep) == 1 { + stepsMetadataReduced[step.Name] = referenceLinkMb + + // Multiple links, reduce but first check + } else { + // Artifact maps must be equal for each type among all links + // TODO: What should we do if there are more links, than the + // threshold requires, but not all of them are equal? Right now we would + // also error. + for keyID, linkMb := range linksPerStep { + if !reflect.DeepEqual(linkMb.Signed.(Link).Materials, + referenceLinkMb.Signed.(Link).Materials) || + !reflect.DeepEqual(linkMb.Signed.(Link).Products, + referenceLinkMb.Signed.(Link).Products) { + return nil, fmt.Errorf("link '%s' and '%s' have different"+ + " artifacts", + fmt.Sprintf(LinkNameFormat, step.Name, referenceKeyID), + fmt.Sprintf(LinkNameFormat, step.Name, keyID)) + } + } + // We haven't errored out, so we can reduce (i.e take the reference link) + stepsMetadataReduced[step.Name] = referenceLinkMb + } + } + return stepsMetadataReduced, nil +} + +/* +VerifyStepCommandAlignment (soft) verifies that for each step of the passed +layout the command executed, as per the passed link, matches the expected +command, as per the layout. Soft verification means that, in case a command +does not align, a warning is issued. +*/ +func VerifyStepCommandAlignment(layout Layout, + stepsMetadata map[string]map[string]Metablock) { + for _, step := range layout.Steps { + linksPerStep, ok := stepsMetadata[step.Name] + // We should never get here, layout verification must fail earlier + if !ok || len(linksPerStep) < 1 { + panic("Could not verify command alignment for step '" + step.Name + + "', no link metadata found.") + } + + for signerKeyID, linkMb := range linksPerStep { + expectedCommandS := strings.Join(step.ExpectedCommand, " ") + executedCommandS := strings.Join(linkMb.Signed.(Link).Command, " ") + + if expectedCommandS != executedCommandS { + linkName := fmt.Sprintf(LinkNameFormat, step.Name, signerKeyID) + fmt.Printf("WARNING: Expected command for step '%s' (%s) and command"+ + " reported by '%s' (%s) differ.\n", + step.Name, expectedCommandS, linkName, executedCommandS) + } + } + } +} + +/* +LoadLayoutCertificates loads the root and intermediate CAs from the layout if in the layout. +This will be used to check signatures that were used to sign links but not configured +in the PubKeys section of the step. No configured CAs means we don't want to allow this. +Returned CertPools will be empty in this case. +*/ +func LoadLayoutCertificates(layout Layout, intermediatePems [][]byte) (*x509.CertPool, *x509.CertPool, error) { + rootPool := x509.NewCertPool() + for _, certPem := range layout.RootCas { + ok := rootPool.AppendCertsFromPEM([]byte(certPem.KeyVal.Certificate)) + if !ok { + return nil, nil, fmt.Errorf("failed to load root certificates for layout") + } + } + + intermediatePool := x509.NewCertPool() + for _, intermediatePem := range layout.IntermediateCas { + ok := intermediatePool.AppendCertsFromPEM([]byte(intermediatePem.KeyVal.Certificate)) + if !ok { + return nil, nil, fmt.Errorf("failed to load intermediate certificates for layout") + } + } + + for _, intermediatePem := range intermediatePems { + ok := intermediatePool.AppendCertsFromPEM(intermediatePem) + if !ok { + return nil, nil, fmt.Errorf("failed to load provided intermediate certificates") + } + } + + return rootPool, intermediatePool, nil +} + +/* +VerifyLinkSignatureThesholds verifies that for each step of the passed layout, +there are at least Threshold links, validly signed by different authorized +functionaries. The returned map of link metadata per steps contains only +links with valid signatures from distinct functionaries and has the format: + + { + : { + : Metablock, + : Metablock, + ... + }, + : { + : Metablock, + : Metablock, + ... + } + ... + } + +If for any step of the layout there are not enough links available, the first +return value is an empty map of Metablock maps and the second return value is +the error. +*/ +func VerifyLinkSignatureThesholds(layout Layout, + stepsMetadata map[string]map[string]Metablock, rootCertPool, intermediateCertPool *x509.CertPool) ( + map[string]map[string]Metablock, error) { + // This will stores links with valid signature from an authorized functionary + // for all steps + stepsMetadataVerified := make(map[string]map[string]Metablock) + + // Try to find enough (>= threshold) links each with a valid signature from + // distinct authorized functionaries for each step + for _, step := range layout.Steps { + var stepErr error + + // This will store links with valid signature from an authorized + // functionary for the given step + linksPerStepVerified := make(map[string]Metablock) + + // Check if there are any links at all for the given step + linksPerStep, ok := stepsMetadata[step.Name] + if !ok || len(linksPerStep) < 1 { + stepErr = fmt.Errorf("no links found") + } + + // For each link corresponding to a step, check that the signer key was + // authorized, the layout contains a verification key and the signature + // verification passes. Only good links are stored, to verify thresholds + // below. + isAuthorizedSignature := false + for signerKeyID, linkMb := range linksPerStep { + for _, authorizedKeyID := range step.PubKeys { + if signerKeyID == authorizedKeyID { + if verifierKey, ok := layout.Keys[authorizedKeyID]; ok { + if err := linkMb.VerifySignature(verifierKey); err == nil { + linksPerStepVerified[signerKeyID] = linkMb + isAuthorizedSignature = true + break + } + } + } + } + + // If the signer's key wasn't in our step's pubkeys array, check the cert pool to + // see if the key is known to us. + if !isAuthorizedSignature { + sig, err := linkMb.GetSignatureForKeyID(signerKeyID) + if err != nil { + stepErr = err + continue + } + + cert, err := sig.GetCertificate() + if err != nil { + stepErr = err + continue + } + + // test certificate against the step's constraints to make sure it's a valid functionary + err = step.CheckCertConstraints(cert, layout.RootCAIDs(), rootCertPool, intermediateCertPool) + if err != nil { + stepErr = err + continue + } + + err = linkMb.VerifySignature(cert) + if err != nil { + stepErr = err + continue + } + + linksPerStepVerified[signerKeyID] = linkMb + } + } + + // Store all good links for a step + stepsMetadataVerified[step.Name] = linksPerStepVerified + + if len(linksPerStepVerified) < step.Threshold { + linksPerStep := stepsMetadata[step.Name] + return nil, fmt.Errorf("step '%s' requires '%d' link metadata file(s)."+ + " '%d' out of '%d' available link(s) have a valid signature from an"+ + " authorized signer: %v", step.Name, step.Threshold, + len(linksPerStepVerified), len(linksPerStep), stepErr) + } + } + return stepsMetadataVerified, nil +} + +/* +LoadLinksForLayout loads for every Step of the passed Layout a Metablock +containing the corresponding Link. A base path to a directory that contains +the links may be passed using linkDir. Link file names are constructed, +using LinkNameFormat together with the corresponding step name and authorized +functionary key ids. A map of link metadata is returned and has the following +format: + + { + : { + : Metablock, + : Metablock, + ... + }, + : { + : Metablock, + : Metablock, + ... + } + ... + } + +If a link cannot be loaded at a constructed link name or is invalid, it is +ignored. Only a preliminary threshold check is performed, that is, if there +aren't at least Threshold links for any given step, the first return value +is an empty map of Metablock maps and the second return value is the error. +*/ +func LoadLinksForLayout(layout Layout, linkDir string) (map[string]map[string]Metablock, error) { + stepsMetadata := make(map[string]map[string]Metablock) + + for _, step := range layout.Steps { + linksPerStep := make(map[string]Metablock) + // Since we can verify against certificates belonging to a CA, we need to + // load any possible links + linkFiles, err := filepath.Glob(osPath.Join(linkDir, fmt.Sprintf(LinkGlobFormat, step.Name))) + if err != nil { + return nil, err + } + + for _, linkPath := range linkFiles { + var linkMb Metablock + if err := linkMb.Load(linkPath); err != nil { + continue + } + + // To get the full key from the metadata's signatures, we have to check + // for one with the same short id... + signerShortKeyID := strings.TrimSuffix(strings.TrimPrefix(filepath.Base(linkPath), step.Name+"."), ".link") + for _, sig := range linkMb.Signatures { + if strings.HasPrefix(sig.KeyID, signerShortKeyID) { + linksPerStep[sig.KeyID] = linkMb + break + } + } + } + + if len(linksPerStep) < step.Threshold { + return nil, fmt.Errorf("step '%s' requires '%d' link metadata file(s),"+ + " found '%d'", step.Name, step.Threshold, len(linksPerStep)) + } + + stepsMetadata[step.Name] = linksPerStep + } + + return stepsMetadata, nil +} + +/* +VerifyLayoutExpiration verifies that the passed Layout has not expired. It +returns an error if the (zulu) date in the Expires field is in the past. +*/ +func VerifyLayoutExpiration(layout Layout) error { + expires, err := time.Parse(ISO8601DateSchema, layout.Expires) + if err != nil { + return err + } + // Uses timezone of expires, i.e. UTC + if time.Until(expires) < 0 { + return fmt.Errorf("layout has expired on '%s'", expires) + } + return nil +} + +/* +VerifyLayoutSignatures verifies for each key in the passed key map the +corresponding signature of the Layout in the passed Metablock's Signed field. +Signatures and keys are associated by key id. If the key map is empty, or the +Metablock's Signature field does not have a signature for one or more of the +passed keys, or a matching signature is invalid, an error is returned. +*/ +func VerifyLayoutSignatures(layoutMb Metablock, + layoutKeys map[string]Key) error { + if len(layoutKeys) < 1 { + return fmt.Errorf("layout verification requires at least one key") + } + + for _, key := range layoutKeys { + if err := layoutMb.VerifySignature(key); err != nil { + return err + } + } + return nil +} + +/* +GetSummaryLink merges the materials of the first step (as mentioned in the +layout) and the products of the last step and returns a new link. This link +reports the materials and products and summarizes the overall software supply +chain. +NOTE: The assumption is that the steps mentioned in the layout are to be +performed sequentially. So, the first step mentioned in the layout denotes what +comes into the supply chain and the last step denotes what goes out. +*/ +func GetSummaryLink(layout Layout, stepsMetadataReduced map[string]Metablock, + stepName string) (Metablock, error) { + var summaryLink Link + var result Metablock + if len(layout.Steps) > 0 { + firstStepLink := stepsMetadataReduced[layout.Steps[0].Name] + lastStepLink := stepsMetadataReduced[layout.Steps[len(layout.Steps)-1].Name] + + summaryLink.Materials = firstStepLink.Signed.(Link).Materials + summaryLink.Name = stepName + summaryLink.Type = firstStepLink.Signed.(Link).Type + + summaryLink.Products = lastStepLink.Signed.(Link).Products + summaryLink.ByProducts = lastStepLink.Signed.(Link).ByProducts + // Using the last command of the sublayout as the command + // of the summary link can be misleading. Is it necessary to + // include all the commands executed as part of sublayout? + summaryLink.Command = lastStepLink.Signed.(Link).Command + } + + result.Signed = summaryLink + + return result, nil +} + +/* +VerifySublayouts checks if any step in the supply chain is a sublayout, and if +so, recursively resolves it and replaces it with a summary link summarizing the +steps carried out in the sublayout. +*/ +func VerifySublayouts(layout Layout, + stepsMetadataVerified map[string]map[string]Metablock, + superLayoutLinkPath string, intermediatePems [][]byte, lineNormalization bool) (map[string]map[string]Metablock, error) { + for stepName, linkData := range stepsMetadataVerified { + for keyID, metadata := range linkData { + if _, ok := metadata.Signed.(Layout); ok { + layoutKeys := make(map[string]Key) + layoutKeys[keyID] = layout.Keys[keyID] + + sublayoutLinkDir := fmt.Sprintf(SublayoutLinkDirFormat, + stepName, keyID) + sublayoutLinkPath := filepath.Join(superLayoutLinkPath, + sublayoutLinkDir) + summaryLink, err := InTotoVerify(metadata, layoutKeys, + sublayoutLinkPath, stepName, make(map[string]string), intermediatePems, lineNormalization) + if err != nil { + return nil, err + } + linkData[keyID] = summaryLink + } + + } + } + return stepsMetadataVerified, nil +} + +// TODO: find a better way than two helper functions for the replacer op + +func substituteParamatersInSlice(replacer *strings.Replacer, slice []string) []string { + newSlice := make([]string, 0) + for _, item := range slice { + newSlice = append(newSlice, replacer.Replace(item)) + } + return newSlice +} + +func substituteParametersInSliceOfSlices(replacer *strings.Replacer, + slice [][]string) [][]string { + newSlice := make([][]string, 0) + for _, item := range slice { + newSlice = append(newSlice, substituteParamatersInSlice(replacer, + item)) + } + return newSlice +} + +/* +SubstituteParameters performs parameter substitution in steps and inspections +in the following fields: +- Expected Materials and Expected Products of both +- Run of inspections +- Expected Command of steps +The substitution marker is '{}' and the keyword within the braces is replaced +by a value found in the substitution map passed, parameterDictionary. The +layout with parameters substituted is returned to the calling function. +*/ +func SubstituteParameters(layout Layout, + parameterDictionary map[string]string) (Layout, error) { + + if len(parameterDictionary) == 0 { + return layout, nil + } + + parameters := make([]string, 0) + + re := regexp.MustCompile("^[a-zA-Z0-9_-]+$") + + for parameter, value := range parameterDictionary { + parameterFormatCheck := re.MatchString(parameter) + if !parameterFormatCheck { + return layout, fmt.Errorf("invalid format for parameter") + } + + parameters = append(parameters, "{"+parameter+"}") + parameters = append(parameters, value) + } + + replacer := strings.NewReplacer(parameters...) + + for i := range layout.Steps { + layout.Steps[i].ExpectedMaterials = substituteParametersInSliceOfSlices( + replacer, layout.Steps[i].ExpectedMaterials) + layout.Steps[i].ExpectedProducts = substituteParametersInSliceOfSlices( + replacer, layout.Steps[i].ExpectedProducts) + layout.Steps[i].ExpectedCommand = substituteParamatersInSlice(replacer, + layout.Steps[i].ExpectedCommand) + } + + for i := range layout.Inspect { + layout.Inspect[i].ExpectedMaterials = + substituteParametersInSliceOfSlices(replacer, + layout.Inspect[i].ExpectedMaterials) + layout.Inspect[i].ExpectedProducts = + substituteParametersInSliceOfSlices(replacer, + layout.Inspect[i].ExpectedProducts) + layout.Inspect[i].Run = substituteParamatersInSlice(replacer, + layout.Inspect[i].Run) + } + + return layout, nil +} + +/* +InTotoVerify can be used to verify an entire software supply chain according to +the in-toto specification. It requires the metadata of the root layout, a map +that contains public keys to verify the root layout signatures, a path to a +directory from where it can load link metadata files, which are treated as +signed evidence for the steps defined in the layout, a step name, and a +paramater dictionary used for parameter substitution. The step name only +matters for sublayouts, where it's important to associate the summary of that +step with a unique name. The verification routine is as follows: + +1. Verify layout signature(s) using passed key(s) +2. Verify layout expiration date +3. Substitute parameters in layout +4. Load link metadata files for steps of layout +5. Verify signatures and signature thresholds for steps of layout +6. Verify sublayouts recursively +7. Verify command alignment for steps of layout (only warns) +8. Verify artifact rules for steps of layout +9. Execute inspection commands (generates link metadata for each inspection) +10. Verify artifact rules for inspections of layout + +InTotoVerify returns a summary link wrapped in a Metablock object and an error +value. If any of the verification routines fail, verification is aborted and +error is returned. In such an instance, the first value remains an empty +Metablock object. + +NOTE: Artifact rules of type "create", "modify" +and "delete" are currently not supported. +*/ +func InTotoVerify(layoutMb Metablock, layoutKeys map[string]Key, + linkDir string, stepName string, parameterDictionary map[string]string, intermediatePems [][]byte, lineNormalization bool) ( + Metablock, error) { + + var summaryLink Metablock + var err error + + // Verify root signatures + if err := VerifyLayoutSignatures(layoutMb, layoutKeys); err != nil { + return summaryLink, err + } + + // Extract the layout from its Metablock container (for further processing) + layout := layoutMb.Signed.(Layout) + + // Verify layout expiration + if err := VerifyLayoutExpiration(layout); err != nil { + return summaryLink, err + } + + // Substitute parameters in layout + layout, err = SubstituteParameters(layout, parameterDictionary) + if err != nil { + return summaryLink, err + } + + rootCertPool, intermediateCertPool, err := LoadLayoutCertificates(layout, intermediatePems) + if err != nil { + return summaryLink, err + } + + // Load links for layout + stepsMetadata, err := LoadLinksForLayout(layout, linkDir) + if err != nil { + return summaryLink, err + } + + // Verify link signatures + stepsMetadataVerified, err := VerifyLinkSignatureThesholds(layout, + stepsMetadata, rootCertPool, intermediateCertPool) + if err != nil { + return summaryLink, err + } + + // Verify and resolve sublayouts + stepsSublayoutVerified, err := VerifySublayouts(layout, + stepsMetadataVerified, linkDir, intermediatePems, lineNormalization) + if err != nil { + return summaryLink, err + } + + // Verify command alignment (WARNING only) + VerifyStepCommandAlignment(layout, stepsSublayoutVerified) + + // Given that signature thresholds have been checked above and the rest of + // the relevant link properties, i.e. materials and products, have to be + // exactly equal, we can reduce the map of steps metadata. However, we error + // if the relevant properties are not equal among links of a step. + stepsMetadataReduced, err := ReduceStepsMetadata(layout, + stepsSublayoutVerified) + if err != nil { + return summaryLink, err + } + + // Verify artifact rules + if err = VerifyArtifacts(layout.stepsAsInterfaceSlice(), + stepsMetadataReduced); err != nil { + return summaryLink, err + } + + inspectionMetadata, err := RunInspections(layout, "", lineNormalization) + if err != nil { + return summaryLink, err + } + + // Add steps metadata to inspection metadata, because inspection artifact + // rules may also refer to artifacts reported by step links + for k, v := range stepsMetadataReduced { + inspectionMetadata[k] = v + } + + if err = VerifyArtifacts(layout.inspectAsInterfaceSlice(), + inspectionMetadata); err != nil { + return summaryLink, err + } + + summaryLink, err = GetSummaryLink(layout, stepsMetadataReduced, stepName) + if err != nil { + return summaryLink, err + } + + return summaryLink, nil +} + +/* +InTotoVerifyWithDirectory provides the same functionality as IntotoVerify, but +adds the possibility to select a local directory from where the inspections are run. +*/ +func InTotoVerifyWithDirectory(layoutMb Metablock, layoutKeys map[string]Key, + linkDir string, runDir string, stepName string, parameterDictionary map[string]string, intermediatePems [][]byte, lineNormalization bool) ( + Metablock, error) { + + var summaryLink Metablock + var err error + + // runDir sanity checks + // check if path exists + info, err := os.Stat(runDir) + if err != nil { + return Metablock{}, err + } + + // check if runDir is a symlink + if info.Mode()&os.ModeSymlink == os.ModeSymlink { + return Metablock{}, ErrInspectionRunDirIsSymlink + } + + // check if runDir is writable and a directory + err = isWritable(runDir) + if err != nil { + return Metablock{}, err + } + + // check if runDir is empty (we do not want to overwrite files) + // We abuse File.Readdirnames for this action. + f, err := os.Open(runDir) + if err != nil { + return Metablock{}, err + } + defer f.Close() + // We use Readdirnames(1) for performance reasons, one child node + // is enough to proof that the directory is not empty + _, err = f.Readdirnames(1) + // if io.EOF gets returned as error the directory is empty + if err == io.EOF { + return Metablock{}, err + } + err = f.Close() + if err != nil { + return Metablock{}, err + } + + // Verify root signatures + if err := VerifyLayoutSignatures(layoutMb, layoutKeys); err != nil { + return summaryLink, err + } + + // Extract the layout from its Metablock container (for further processing) + layout := layoutMb.Signed.(Layout) + + // Verify layout expiration + if err := VerifyLayoutExpiration(layout); err != nil { + return summaryLink, err + } + + // Substitute parameters in layout + layout, err = SubstituteParameters(layout, parameterDictionary) + if err != nil { + return summaryLink, err + } + + rootCertPool, intermediateCertPool, err := LoadLayoutCertificates(layout, intermediatePems) + if err != nil { + return summaryLink, err + } + + // Load links for layout + stepsMetadata, err := LoadLinksForLayout(layout, linkDir) + if err != nil { + return summaryLink, err + } + + // Verify link signatures + stepsMetadataVerified, err := VerifyLinkSignatureThesholds(layout, + stepsMetadata, rootCertPool, intermediateCertPool) + if err != nil { + return summaryLink, err + } + + // Verify and resolve sublayouts + stepsSublayoutVerified, err := VerifySublayouts(layout, + stepsMetadataVerified, linkDir, intermediatePems, lineNormalization) + if err != nil { + return summaryLink, err + } + + // Verify command alignment (WARNING only) + VerifyStepCommandAlignment(layout, stepsSublayoutVerified) + + // Given that signature thresholds have been checked above and the rest of + // the relevant link properties, i.e. materials and products, have to be + // exactly equal, we can reduce the map of steps metadata. However, we error + // if the relevant properties are not equal among links of a step. + stepsMetadataReduced, err := ReduceStepsMetadata(layout, + stepsSublayoutVerified) + if err != nil { + return summaryLink, err + } + + // Verify artifact rules + if err = VerifyArtifacts(layout.stepsAsInterfaceSlice(), + stepsMetadataReduced); err != nil { + return summaryLink, err + } + + inspectionMetadata, err := RunInspections(layout, runDir, lineNormalization) + if err != nil { + return summaryLink, err + } + + // Add steps metadata to inspection metadata, because inspection artifact + // rules may also refer to artifacts reported by step links + for k, v := range stepsMetadataReduced { + inspectionMetadata[k] = v + } + + if err = VerifyArtifacts(layout.inspectAsInterfaceSlice(), + inspectionMetadata); err != nil { + return summaryLink, err + } + + summaryLink, err = GetSummaryLink(layout, stepsMetadataReduced, stepName) + if err != nil { + return summaryLink, err + } + + return summaryLink, nil +} diff --git a/vendor/github.com/secure-systems-lab/go-securesystemslib/LICENSE b/vendor/github.com/secure-systems-lab/go-securesystemslib/LICENSE new file mode 100644 index 000000000000..e51324f9b5b4 --- /dev/null +++ b/vendor/github.com/secure-systems-lab/go-securesystemslib/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2021 NYU Secure Systems Lab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/secure-systems-lab/go-securesystemslib/cjson/canonicaljson.go b/vendor/github.com/secure-systems-lab/go-securesystemslib/cjson/canonicaljson.go new file mode 100644 index 000000000000..fb1d5918b282 --- /dev/null +++ b/vendor/github.com/secure-systems-lab/go-securesystemslib/cjson/canonicaljson.go @@ -0,0 +1,145 @@ +package cjson + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "reflect" + "regexp" + "sort" +) + +/* +encodeCanonicalString is a helper function to canonicalize the passed string +according to the OLPC canonical JSON specification for strings (see +http://wiki.laptop.org/go/Canonical_JSON). String canonicalization consists of +escaping backslashes ("\") and double quotes (") and wrapping the resulting +string in double quotes ("). +*/ +func encodeCanonicalString(s string) string { + re := regexp.MustCompile(`([\"\\])`) + return fmt.Sprintf("\"%s\"", re.ReplaceAllString(s, "\\$1")) +} + +/* +encodeCanonical is a helper function to recursively canonicalize the passed +object according to the OLPC canonical JSON specification (see +http://wiki.laptop.org/go/Canonical_JSON) and write it to the passed +*bytes.Buffer. If canonicalization fails it returns an error. +*/ +func encodeCanonical(obj interface{}, result *bytes.Buffer) (err error) { + // Since this function is called recursively, we use panic if an error occurs + // and recover in a deferred function, which is always called before + // returning. There we set the error that is returned eventually. + defer func() { + if r := recover(); r != nil { + err = errors.New(r.(string)) + } + }() + + switch objAsserted := obj.(type) { + case string: + result.WriteString(encodeCanonicalString(objAsserted)) + + case bool: + if objAsserted { + result.WriteString("true") + } else { + result.WriteString("false") + } + + // The wrapping `EncodeCanonical` function decodes the passed json data with + // `decoder.UseNumber` so that any numeric value is stored as `json.Number` + // (instead of the default `float64`). This allows us to assert that it is a + // non-floating point number, which are the only numbers allowed by the used + // canonicalization specification. + case json.Number: + if _, err := objAsserted.Int64(); err != nil { + panic(fmt.Sprintf("Can't canonicalize floating point number '%s'", + objAsserted)) + } + result.WriteString(objAsserted.String()) + + case nil: + result.WriteString("null") + + // Canonicalize slice + case []interface{}: + result.WriteString("[") + for i, val := range objAsserted { + if err := encodeCanonical(val, result); err != nil { + return err + } + if i < (len(objAsserted) - 1) { + result.WriteString(",") + } + } + result.WriteString("]") + + case map[string]interface{}: + result.WriteString("{") + + // Make a list of keys + var mapKeys []string + for key := range objAsserted { + mapKeys = append(mapKeys, key) + } + // Sort keys + sort.Strings(mapKeys) + + // Canonicalize map + for i, key := range mapKeys { + // Note: `key` must be a `string` (see `case map[string]interface{}`) and + // canonicalization of strings cannot err out (see `case string`), thus + // no error handling is needed here. + encodeCanonical(key, result) + + result.WriteString(":") + if err := encodeCanonical(objAsserted[key], result); err != nil { + return err + } + if i < (len(mapKeys) - 1) { + result.WriteString(",") + } + i++ + } + result.WriteString("}") + + default: + // We recover in a deferred function defined above + panic(fmt.Sprintf("Can't canonicalize '%s' of type '%s'", + objAsserted, reflect.TypeOf(objAsserted))) + } + return nil +} + +/* +EncodeCanonical JSON canonicalizes the passed object and returns it as a byte +slice. It uses the OLPC canonical JSON specification (see +http://wiki.laptop.org/go/Canonical_JSON). If canonicalization fails the byte +slice is nil and the second return value contains the error. +*/ +func EncodeCanonical(obj interface{}) ([]byte, error) { + // FIXME: Terrible hack to turn the passed struct into a map, converting + // the struct's variable names to the json key names defined in the struct + data, err := json.Marshal(obj) + if err != nil { + return nil, err + } + var jsonMap interface{} + + dec := json.NewDecoder(bytes.NewReader(data)) + dec.UseNumber() + if err := dec.Decode(&jsonMap); err != nil { + return nil, err + } + + // Create a buffer and write the canonicalized JSON bytes to it + var result bytes.Buffer + if err := encodeCanonical(jsonMap, &result); err != nil { + return nil, err + } + + return result.Bytes(), nil +} diff --git a/vendor/github.com/secure-systems-lab/go-securesystemslib/dsse/sign.go b/vendor/github.com/secure-systems-lab/go-securesystemslib/dsse/sign.go new file mode 100644 index 000000000000..3dc05a4294e1 --- /dev/null +++ b/vendor/github.com/secure-systems-lab/go-securesystemslib/dsse/sign.go @@ -0,0 +1,197 @@ +/* +Package dsse implements the Dead Simple Signing Envelope (DSSE) +https://github.com/secure-systems-lab/dsse +*/ +package dsse + +import ( + "encoding/base64" + "errors" + "fmt" +) + +// ErrUnknownKey indicates that the implementation does not recognize the +// key. +var ErrUnknownKey = errors.New("unknown key") + +// ErrNoSignature indicates that an envelope did not contain any signatures. +var ErrNoSignature = errors.New("no signature found") + +// ErrNoSigners indicates that no signer was provided. +var ErrNoSigners = errors.New("no signers provided") + +/* +Envelope captures an envelope as described by the Secure Systems Lab +Signing Specification. See here: +https://github.com/secure-systems-lab/signing-spec/blob/master/envelope.md +*/ +type Envelope struct { + PayloadType string `json:"payloadType"` + Payload string `json:"payload"` + Signatures []Signature `json:"signatures"` +} + +/* +DecodeB64Payload returns the serialized body, decoded +from the envelope's payload field. A flexible +decoder is used, first trying standard base64, then +URL-encoded base64. +*/ +func (e *Envelope) DecodeB64Payload() ([]byte, error) { + return b64Decode(e.Payload) +} + +/* +Signature represents a generic in-toto signature that contains the identifier +of the key which was used to create the signature. +The used signature scheme has to be agreed upon by the signer and verifer +out of band. +The signature is a base64 encoding of the raw bytes from the signature +algorithm. +*/ +type Signature struct { + KeyID string `json:"keyid"` + Sig string `json:"sig"` +} + +/* +PAE implementes the DSSE Pre-Authentic Encoding +https://github.com/secure-systems-lab/dsse/blob/master/protocol.md#signature-definition +*/ +func PAE(payloadType string, payload []byte) []byte { + return []byte(fmt.Sprintf("DSSEv1 %d %s %d %s", + len(payloadType), payloadType, + len(payload), payload)) +} + +/* +Signer defines the interface for an abstract signing algorithm. +The Signer interface is used to inject signature algorithm implementations +into the EnevelopeSigner. This decoupling allows for any signing algorithm +and key management system can be used. +The full message is provided as the parameter. If the signature algorithm +depends on hashing of the message prior to signature calculation, the +implementor of this interface must perform such hashing. +The function must return raw bytes representing the calculated signature +using the current algorithm, and the key used (if applicable). +For an example see EcdsaSigner in sign_test.go. +*/ +type Signer interface { + Sign(data []byte) ([]byte, error) + KeyID() (string, error) +} + +// SignVerifer provides both the signing and verification interface. +type SignVerifier interface { + Signer + Verifier +} + +// EnvelopeSigner creates signed Envelopes. +type EnvelopeSigner struct { + providers []SignVerifier + ev *EnvelopeVerifier +} + +/* +NewEnvelopeSigner creates an EnvelopeSigner that uses 1+ Signer +algorithms to sign the data. +Creates a verifier with threshold=1, at least one of the providers must validate signitures successfully. +*/ +func NewEnvelopeSigner(p ...SignVerifier) (*EnvelopeSigner, error) { + return NewMultiEnvelopeSigner(1, p...) +} + +/* +NewMultiEnvelopeSigner creates an EnvelopeSigner that uses 1+ Signer +algorithms to sign the data. +Creates a verifier with threshold. +threashold indicates the amount of providers that must validate the envelope. +*/ +func NewMultiEnvelopeSigner(threshold int, p ...SignVerifier) (*EnvelopeSigner, error) { + var providers []SignVerifier + + for _, sv := range p { + if sv != nil { + providers = append(providers, sv) + } + } + + if len(providers) == 0 { + return nil, ErrNoSigners + } + + evps := []Verifier{} + for _, p := range providers { + evps = append(evps, p.(Verifier)) + } + + ev, err := NewMultiEnvelopeVerifier(threshold, evps...) + if err != nil { + return nil, err + } + + return &EnvelopeSigner{ + providers: providers, + ev: ev, + }, nil +} + +/* +SignPayload signs a payload and payload type according to DSSE. +Returned is an envelope as defined here: +https://github.com/secure-systems-lab/dsse/blob/master/envelope.md +One signature will be added for each Signer in the EnvelopeSigner. +*/ +func (es *EnvelopeSigner) SignPayload(payloadType string, body []byte) (*Envelope, error) { + var e = Envelope{ + Payload: base64.StdEncoding.EncodeToString(body), + PayloadType: payloadType, + } + + paeEnc := PAE(payloadType, body) + + for _, signer := range es.providers { + sig, err := signer.Sign(paeEnc) + if err != nil { + return nil, err + } + keyID, err := signer.KeyID() + if err != nil { + keyID = "" + } + + e.Signatures = append(e.Signatures, Signature{ + KeyID: keyID, + Sig: base64.StdEncoding.EncodeToString(sig), + }) + } + + return &e, nil +} + +/* +Verify decodes the payload and verifies the signature. +Any domain specific validation such as parsing the decoded body and +validating the payload type is left out to the caller. +Verify returns a list of accepted keys each including a keyid, public and signiture of the accepted provider keys. +*/ +func (es *EnvelopeSigner) Verify(e *Envelope) ([]AcceptedKey, error) { + return es.ev.Verify(e) +} + +/* +Both standard and url encoding are allowed: +https://github.com/secure-systems-lab/dsse/blob/master/envelope.md +*/ +func b64Decode(s string) ([]byte, error) { + b, err := base64.StdEncoding.DecodeString(s) + if err != nil { + b, err = base64.URLEncoding.DecodeString(s) + if err != nil { + return nil, err + } + } + + return b, nil +} diff --git a/vendor/github.com/secure-systems-lab/go-securesystemslib/dsse/verify.go b/vendor/github.com/secure-systems-lab/go-securesystemslib/dsse/verify.go new file mode 100644 index 000000000000..ead1c32ca80b --- /dev/null +++ b/vendor/github.com/secure-systems-lab/go-securesystemslib/dsse/verify.go @@ -0,0 +1,146 @@ +package dsse + +import ( + "crypto" + "errors" + "fmt" + + "golang.org/x/crypto/ssh" +) + +/* +Verifier verifies a complete message against a signature and key. +If the message was hashed prior to signature generation, the verifier +must perform the same steps. +If KeyID returns successfully, only signature matching the key ID will be verified. +*/ +type Verifier interface { + Verify(data, sig []byte) error + KeyID() (string, error) + Public() crypto.PublicKey +} + +type EnvelopeVerifier struct { + providers []Verifier + threshold int +} + +type AcceptedKey struct { + Public crypto.PublicKey + KeyID string + Sig Signature +} + +func (ev *EnvelopeVerifier) Verify(e *Envelope) ([]AcceptedKey, error) { + if e == nil { + return nil, errors.New("cannot verify a nil envelope") + } + + if len(e.Signatures) == 0 { + return nil, ErrNoSignature + } + + // Decode payload (i.e serialized body) + body, err := e.DecodeB64Payload() + if err != nil { + return nil, err + } + // Generate PAE(payloadtype, serialized body) + paeEnc := PAE(e.PayloadType, body) + + // If *any* signature is found to be incorrect, it is skipped + var acceptedKeys []AcceptedKey + usedKeyids := make(map[string]string) + unverified_providers := ev.providers + for _, s := range e.Signatures { + sig, err := b64Decode(s.Sig) + if err != nil { + return nil, err + } + + // Loop over the providers. + // If provider and signature include key IDs but do not match skip. + // If a provider recognizes the key, we exit + // the loop and use the result. + providers := unverified_providers + for i, v := range providers { + keyID, err := v.KeyID() + + // Verifiers that do not provide a keyid will be generated one using public. + if err != nil || keyID == "" { + keyID, err = SHA256KeyID(v.Public()) + if err != nil { + keyID = "" + } + } + + if s.KeyID != "" && keyID != "" && err == nil && s.KeyID != keyID { + continue + } + + err = v.Verify(paeEnc, sig) + if err != nil { + continue + } + + acceptedKey := AcceptedKey{ + Public: v.Public(), + KeyID: keyID, + Sig: s, + } + unverified_providers = removeIndex(providers, i) + + // See https://github.com/in-toto/in-toto/pull/251 + if _, ok := usedKeyids[keyID]; ok { + fmt.Printf("Found envelope signed by different subkeys of the same main key, Only one of them is counted towards the step threshold, KeyID=%s\n", keyID) + continue + } + + usedKeyids[keyID] = "" + acceptedKeys = append(acceptedKeys, acceptedKey) + break + } + } + + // Sanity if with some reflect magic this happens. + if ev.threshold <= 0 || ev.threshold > len(ev.providers) { + return nil, errors.New("Invalid threshold") + } + + if len(usedKeyids) < ev.threshold { + return acceptedKeys, errors.New(fmt.Sprintf("Accepted signatures do not match threshold, Found: %d, Expected %d", len(acceptedKeys), ev.threshold)) + } + + return acceptedKeys, nil +} + +func NewEnvelopeVerifier(v ...Verifier) (*EnvelopeVerifier, error) { + return NewMultiEnvelopeVerifier(1, v...) +} + +func NewMultiEnvelopeVerifier(threshold int, p ...Verifier) (*EnvelopeVerifier, error) { + + if threshold <= 0 || threshold > len(p) { + return nil, errors.New("Invalid threshold") + } + + ev := EnvelopeVerifier{ + providers: p, + threshold: threshold, + } + return &ev, nil +} + +func SHA256KeyID(pub crypto.PublicKey) (string, error) { + // Generate public key fingerprint + sshpk, err := ssh.NewPublicKey(pub) + if err != nil { + return "", err + } + fingerprint := ssh.FingerprintSHA256(sshpk) + return fingerprint, nil +} + +func removeIndex(v []Verifier, index int) []Verifier { + return append(v[:index], v[index+1:]...) +} diff --git a/vendor/github.com/shibumi/go-pathspec/.gitignore b/vendor/github.com/shibumi/go-pathspec/.gitignore new file mode 100644 index 000000000000..3e32393f1238 --- /dev/null +++ b/vendor/github.com/shibumi/go-pathspec/.gitignore @@ -0,0 +1,26 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test + +# ignore .idea +.idea diff --git a/vendor/github.com/shibumi/go-pathspec/GO-LICENSE b/vendor/github.com/shibumi/go-pathspec/GO-LICENSE new file mode 100644 index 000000000000..74487567632c --- /dev/null +++ b/vendor/github.com/shibumi/go-pathspec/GO-LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/shibumi/go-pathspec/LICENSE b/vendor/github.com/shibumi/go-pathspec/LICENSE new file mode 100644 index 000000000000..5c304d1a4a7b --- /dev/null +++ b/vendor/github.com/shibumi/go-pathspec/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/shibumi/go-pathspec/README.md b/vendor/github.com/shibumi/go-pathspec/README.md new file mode 100644 index 000000000000..c146cf69b012 --- /dev/null +++ b/vendor/github.com/shibumi/go-pathspec/README.md @@ -0,0 +1,45 @@ +# go-pathspec + +[![build](https://github.com/shibumi/go-pathspec/workflows/build/badge.svg)](https://github.com/shibumi/go-pathspec/actions?query=workflow%3Abuild) [![Coverage Status](https://coveralls.io/repos/github/shibumi/go-pathspec/badge.svg)](https://coveralls.io/github/shibumi/go-pathspec) [![PkgGoDev](https://pkg.go.dev/badge/github.com/shibumi/go-pathspec)](https://pkg.go.dev/github.com/shibumi/go-pathspec) + +go-pathspec implements gitignore-style pattern matching for paths. + +## Alternatives + +There are a few alternatives, that try to be gitignore compatible or even state +gitignore compatibility: + +### https://github.com/go-git/go-git + +go-git states it would be gitignore compatible, but actually they are missing a few +special cases. This issue describes one of the not working patterns: https://github.com/go-git/go-git/issues/108 + +What does not work is global filename pattern matching. Consider the following +`.gitignore` file: + +```gitignore +# gitignore test file +parse.go +``` + +Then `parse.go` should match on all filenames called `parse.go`. You can test this via +this shell script: +```shell +mkdir -p /tmp/test/internal/util +touch /tmp/test/internal/util/parse.go +cd /tmp/test/ +git init +echo "parse.go" > .gitignore +``` + +With git `parse.go` will be excluded. The go-git implementation behaves different. + +### https://github.com/monochromegane/go-gitignore + +monochromegane's go-gitignore does not support the use of `**`-operators. +This is not consistent to real gitignore behavior, too. + +## Authors + +Sander van Harmelen () +Christian Rebischke () diff --git a/vendor/github.com/shibumi/go-pathspec/gitignore.go b/vendor/github.com/shibumi/go-pathspec/gitignore.go new file mode 100644 index 000000000000..2b08d4e8a573 --- /dev/null +++ b/vendor/github.com/shibumi/go-pathspec/gitignore.go @@ -0,0 +1,299 @@ +// +// Copyright 2014, Sander van Harmelen +// Copyright 2020, Christian Rebischke +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Package pathspec implements git compatible gitignore pattern matching. +// See the description below, if you are unfamiliar with it: +// +// A blank line matches no files, so it can serve as a separator for readability. +// +// A line starting with # serves as a comment. Put a backslash ("\") in front of +// the first hash for patterns that begin with a hash. +// +// An optional prefix "!" which negates the pattern; any matching file excluded +// by a previous pattern will become included again. If a negated pattern matches, +// this will override lower precedence patterns sources. Put a backslash ("\") in +// front of the first "!" for patterns that begin with a literal "!", for example, +// "\!important!.txt". +// +// If the pattern ends with a slash, it is removed for the purpose of the following +// description, but it would only find a match with a directory. In other words, +// foo/ will match a directory foo and paths underneath it, but will not match a +// regular file or a symbolic link foo (this is consistent with the way how pathspec +// works in general in Git). +// +// If the pattern does not contain a slash /, Git treats it as a shell glob pattern +// and checks for a match against the pathname relative to the location of the +// .gitignore file (relative to the toplevel of the work tree if not from a +// .gitignore file). +// +// Otherwise, Git treats the pattern as a shell glob suitable for consumption by +// fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will not match +// a / in the pathname. For example, "Documentation/*.html" matches +// "Documentation/git.html" but not "Documentation/ppc/ppc.html" or/ +// "tools/perf/Documentation/perf.html". +// +// A leading slash matches the beginning of the pathname. For example, "/*.c" +// matches "cat-file.c" but not "mozilla-sha1/sha1.c". +// +// Two consecutive asterisks ("**") in patterns matched against full pathname +// may have special meaning: +// +// A leading "**" followed by a slash means match in all directories. For example, +// "**/foo" matches file or directory "foo" anywhere, the same as pattern "foo". +// "**/foo/bar" matches file or directory "bar" anywhere that is directly under +// directory "foo". +// +// A trailing "/" matches everything inside. For example, "abc/" matches all files +// inside directory "abc", relative to the location of the .gitignore file, with +// infinite depth. +// +// A slash followed by two consecutive asterisks then a slash matches zero or more +// directories. For example, "a/**/b" matches "a/b", "a/x/b", "a/x/y/b" and so on. +// +// Other consecutive asterisks are considered invalid. +package pathspec + +import ( + "bufio" + "bytes" + "io" + "path/filepath" + "regexp" + "strings" +) + +type gitIgnorePattern struct { + Regex string + Include bool +} + +// GitIgnore uses a string slice of patterns for matching on a filepath string. +// On match it returns true, otherwise false. On error it passes the error through. +func GitIgnore(patterns []string, name string) (ignore bool, err error) { + for _, pattern := range patterns { + p := parsePattern(pattern) + // Convert Windows paths to Unix paths + name = filepath.ToSlash(name) + match, err := regexp.MatchString(p.Regex, name) + if err != nil { + return ignore, err + } + if match { + if p.Include { + return false, nil + } + ignore = true + } + } + return ignore, nil +} + +// ReadGitIgnore implements the io.Reader interface for reading a gitignore file +// line by line. It behaves exactly like the GitIgnore function. The only difference +// is that GitIgnore works on a string slice. +// +// ReadGitIgnore returns a boolean value if we match or not and an error. +func ReadGitIgnore(content io.Reader, name string) (ignore bool, err error) { + scanner := bufio.NewScanner(content) + + for scanner.Scan() { + pattern := strings.TrimSpace(scanner.Text()) + if len(pattern) == 0 || pattern[0] == '#' { + continue + } + p := parsePattern(pattern) + // Convert Windows paths to Unix paths + name = filepath.ToSlash(name) + match, err := regexp.MatchString(p.Regex, name) + if err != nil { + return ignore, err + } + if match { + if p.Include { + return false, scanner.Err() + } + ignore = true + } + } + return ignore, scanner.Err() +} + +func parsePattern(pattern string) *gitIgnorePattern { + p := &gitIgnorePattern{} + + // An optional prefix "!" which negates the pattern; any matching file + // excluded by a previous pattern will become included again. + if strings.HasPrefix(pattern, "!") { + pattern = pattern[1:] + p.Include = true + } else { + p.Include = false + } + + // Remove leading back-slash escape for escaped hash ('#') or + // exclamation mark ('!'). + if strings.HasPrefix(pattern, "\\") { + pattern = pattern[1:] + } + + // Split pattern into segments. + patternSegs := strings.Split(pattern, "/") + + // A pattern beginning with a slash ('/') will only match paths + // directly on the root directory instead of any descendant paths. + // So remove empty first segment to make pattern absoluut to root. + // A pattern without a beginning slash ('/') will match any + // descendant path. This is equivilent to "**/{pattern}". So + // prepend with double-asterisks to make pattern relative to + // root. + if patternSegs[0] == "" { + patternSegs = patternSegs[1:] + } else if patternSegs[0] != "**" { + patternSegs = append([]string{"**"}, patternSegs...) + } + + // A pattern ending with a slash ('/') will match all descendant + // paths of if it is a directory but not if it is a regular file. + // This is equivalent to "{pattern}/**". So, set last segment to + // double asterisks to include all descendants. + if patternSegs[len(patternSegs)-1] == "" { + patternSegs[len(patternSegs)-1] = "**" + } + + // Build regular expression from pattern. + var expr bytes.Buffer + expr.WriteString("^") + needSlash := false + + for i, seg := range patternSegs { + switch seg { + case "**": + switch { + case i == 0 && i == len(patternSegs)-1: + // A pattern consisting solely of double-asterisks ('**') + // will match every path. + expr.WriteString(".+") + case i == 0: + // A normalized pattern beginning with double-asterisks + // ('**') will match any leading path segments. + expr.WriteString("(?:.+/)?") + needSlash = false + case i == len(patternSegs)-1: + // A normalized pattern ending with double-asterisks ('**') + // will match any trailing path segments. + expr.WriteString("/.+") + default: + // A pattern with inner double-asterisks ('**') will match + // multiple (or zero) inner path segments. + expr.WriteString("(?:/.+)?") + needSlash = true + } + case "*": + // Match single path segment. + if needSlash { + expr.WriteString("/") + } + expr.WriteString("[^/]+") + needSlash = true + default: + // Match segment glob pattern. + if needSlash { + expr.WriteString("/") + } + expr.WriteString(translateGlob(seg)) + needSlash = true + } + } + expr.WriteString("$") + p.Regex = expr.String() + return p +} + +// NOTE: This is derived from `fnmatch.translate()` and is similar to +// the POSIX function `fnmatch()` with the `FNM_PATHNAME` flag set. +func translateGlob(glob string) string { + var regex bytes.Buffer + escape := false + + for i := 0; i < len(glob); i++ { + char := glob[i] + // Escape the character. + switch { + case escape: + escape = false + regex.WriteString(regexp.QuoteMeta(string(char))) + case char == '\\': + // Escape character, escape next character. + escape = true + case char == '*': + // Multi-character wildcard. Match any string (except slashes), + // including an empty string. + regex.WriteString("[^/]*") + case char == '?': + // Single-character wildcard. Match any single character (except + // a slash). + regex.WriteString("[^/]") + case char == '[': + regex.WriteString(translateBracketExpression(&i, glob)) + default: + // Regular character, escape it for regex. + regex.WriteString(regexp.QuoteMeta(string(char))) + } + } + return regex.String() +} + +// Bracket expression wildcard. Except for the beginning +// exclamation mark, the whole bracket expression can be used +// directly as regex but we have to find where the expression +// ends. +// - "[][!]" matches ']', '[' and '!'. +// - "[]-]" matches ']' and '-'. +// - "[!]a-]" matches any character except ']', 'a' and '-'. +func translateBracketExpression(i *int, glob string) string { + regex := string(glob[*i]) + *i++ + j := *i + + // Pass bracket expression negation. + if j < len(glob) && glob[j] == '!' { + j++ + } + // Pass first closing bracket if it is at the beginning of the + // expression. + if j < len(glob) && glob[j] == ']' { + j++ + } + // Find closing bracket. Stop once we reach the end or find it. + for j < len(glob) && glob[j] != ']' { + j++ + } + + if j < len(glob) { + if glob[*i] == '!' { + regex = regex + "^" + *i++ + } + regex = regexp.QuoteMeta(glob[*i:j]) + *i = j + } else { + // Failed to find closing bracket, treat opening bracket as a + // bracket literal instead of as an expression. + regex = regexp.QuoteMeta(string(glob[*i])) + } + return "[" + regex + "]" +} diff --git a/vendor/github.com/spdx/tools-golang/LICENSE.code b/vendor/github.com/spdx/tools-golang/LICENSE.code new file mode 100644 index 000000000000..07efb6292aec --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/LICENSE.code @@ -0,0 +1,550 @@ +The tools-golang source code is provided and may be used, at your option, +under either: +* Apache License, version 2.0 (Apache-2.0), OR +* GNU General Public License, version 2.0 or later (GPL-2.0-or-later). + +Copies of both licenses are included below. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + += = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/vendor/github.com/spdx/tools-golang/LICENSE.docs b/vendor/github.com/spdx/tools-golang/LICENSE.docs new file mode 100644 index 000000000000..2c8e93cbda8c --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/LICENSE.docs @@ -0,0 +1,399 @@ +The tools-golang documentation is provided under the Creative Commons Attribution +4.0 International license (CC-BY-4.0), a copy of which is provided below. + +Attribution 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution 4.0 International Public License ("Public License"). To the +extent this Public License may be interpreted as a contract, You are +granted the Licensed Rights in consideration of Your acceptance of +these terms and conditions, and the Licensor grants You such rights in +consideration of benefits the Licensor receives from making the +Licensed Material available under these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + i. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. + diff --git a/vendor/github.com/spdx/tools-golang/jsonloader/jsonloader.go b/vendor/github.com/spdx/tools-golang/jsonloader/jsonloader.go new file mode 100644 index 000000000000..8e2646c89e54 --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/jsonloader/jsonloader.go @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package jsonloader + +import ( + "bytes" + "io" + + parser2v2 "github.com/spdx/tools-golang/jsonloader/parser2v2" + "github.com/spdx/tools-golang/spdx" +) + +// Takes in a file Reader and returns the pertaining spdx document +// or the error if any is encountered while setting the doc. +func Load2_2(content io.Reader) (*spdx.Document2_2, error) { + //convert io.Reader to a slice of bytes and call the parser + buf := new(bytes.Buffer) + buf.ReadFrom(content) + var doc, err = parser2v2.Load2_2(buf.Bytes()) + if err != nil { + return nil, err + } + return doc, nil +} diff --git a/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_annotations.go b/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_annotations.go new file mode 100644 index 000000000000..f9ceaa3dd3f3 --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_annotations.go @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package parser2v2 + +import ( + "fmt" + "reflect" + + "github.com/spdx/tools-golang/spdx" +) + +func (spec JSONSpdxDocument) parseJsonAnnotations2_2(key string, value interface{}, doc *spdxDocument2_2, SPDXElementId spdx.DocElementID) error { + //FIXME: SPDXID property not defined in spec but it is needed + if reflect.TypeOf(value).Kind() == reflect.Slice { + annotations := reflect.ValueOf(value) + for i := 0; i < annotations.Len(); i++ { + annotation := annotations.Index(i).Interface().(map[string]interface{}) + ann := spdx.Annotation2_2{AnnotationSPDXIdentifier: SPDXElementId} + // Remove loop all properties are mandatory in annotations + for k, v := range annotation { + switch k { + case "annotationDate": + ann.AnnotationDate = v.(string) + case "annotationType": + ann.AnnotationType = v.(string) + case "comment": + ann.AnnotationComment = v.(string) + case "annotator": + subkey, subvalue, err := extractSubs(v.(string)) + if err != nil { + return err + } + if subkey != "Person" && subkey != "Organization" && subkey != "Tool" { + return fmt.Errorf("unrecognized Annotator type %v", subkey) + } + ann.AnnotatorType = subkey + ann.Annotator = subvalue + + default: + return fmt.Errorf("received unknown tag %v in Annotation section", k) + } + } + doc.Annotations = append(doc.Annotations, &ann) + } + + } + return nil +} diff --git a/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_creation_info.go b/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_creation_info.go new file mode 100644 index 000000000000..9e818e8344e6 --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_creation_info.go @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package parser2v2 + +import ( + "fmt" + "reflect" + "strings" + + "github.com/spdx/tools-golang/spdx" +) + +func (spec JSONSpdxDocument) parseJsonCreationInfo2_2(key string, value interface{}, doc *spdxDocument2_2) error { + // create an SPDX Creation Info data struct if we don't have one already + + if doc.CreationInfo == nil { + doc.CreationInfo = &spdx.CreationInfo2_2{ + ExternalDocumentReferences: map[string]spdx.ExternalDocumentRef2_2{}, + } + } + ci := doc.CreationInfo + switch key { + case "dataLicense": + ci.DataLicense = value.(string) + case "spdxVersion": + ci.SPDXVersion = value.(string) + case "SPDXID": + id, err := extractElementID(value.(string)) + if err != nil { + return fmt.Errorf("%s", err) + } + ci.SPDXIdentifier = id + case "documentNamespace": + ci.DocumentNamespace = value.(string) + case "name": + ci.DocumentName = value.(string) + case "comment": + ci.DocumentComment = value.(string) + case "creationInfo": + creationInfo := value.(map[string]interface{}) + for key, val := range creationInfo { + switch key { + case "comment": + ci.CreatorComment = val.(string) + case "created": + ci.Created = val.(string) + case "licenseListVersion": + ci.LicenseListVersion = val.(string) + case "creators": + err := parseCreators(creationInfo["creators"], ci) + if err != nil { + return fmt.Errorf("%s", err) + } + } + } + case "externalDocumentRefs": + err := parseExternalDocumentRefs(value, ci) + if err != nil { + return fmt.Errorf("%s", err) + } + default: + return fmt.Errorf("unrecognized key %v", key) + + } + + return nil +} + +// ===== Helper functions ===== + +func parseCreators(creators interface{}, ci *spdx.CreationInfo2_2) error { + if reflect.TypeOf(creators).Kind() == reflect.Slice { + s := reflect.ValueOf(creators) + + for i := 0; i < s.Len(); i++ { + subkey, subvalue, err := extractSubs(s.Index(i).Interface().(string)) + if err != nil { + return err + } + switch subkey { + case "Person": + ci.CreatorPersons = append(ci.CreatorPersons, subvalue) + case "Organization": + ci.CreatorOrganizations = append(ci.CreatorOrganizations, subvalue) + case "Tool": + ci.CreatorTools = append(ci.CreatorTools, subvalue) + default: + return fmt.Errorf("unrecognized Creator type %v", subkey) + } + + } + } + return nil +} + +func parseExternalDocumentRefs(references interface{}, ci *spdx.CreationInfo2_2) error { + if reflect.TypeOf(references).Kind() == reflect.Slice { + s := reflect.ValueOf(references) + + for i := 0; i < s.Len(); i++ { + ref := s.Index(i).Interface().(map[string]interface{}) + documentRefID := ref["externalDocumentId"].(string) + if !strings.HasPrefix(documentRefID, "DocumentRef-") { + return fmt.Errorf("expected first element to have DocumentRef- prefix") + } + documentRefID = strings.TrimPrefix(documentRefID, "DocumentRef-") + if documentRefID == "" { + return fmt.Errorf("document identifier has nothing after prefix") + } + checksum := ref["checksum"].(map[string]interface{}) + edr := spdx.ExternalDocumentRef2_2{ + DocumentRefID: documentRefID, + URI: ref["spdxDocument"].(string), + Alg: checksum["algorithm"].(string), + Checksum: checksum["checksumValue"].(string), + } + + ci.ExternalDocumentReferences[documentRefID] = edr + } + } + return nil +} diff --git a/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_files.go b/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_files.go new file mode 100644 index 000000000000..090c5e78017b --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_files.go @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package parser2v2 + +import ( + "fmt" + "reflect" + + "github.com/spdx/tools-golang/spdx" +) + +//TODO: check whether file can contain annotations or not +func (spec JSONSpdxDocument) parseJsonFiles2_2(key string, value interface{}, doc *spdxDocument2_2) error { + + if doc.UnpackagedFiles == nil { + doc.UnpackagedFiles = map[spdx.ElementID]*spdx.File2_2{} + } + + if reflect.TypeOf(value).Kind() == reflect.Slice { + files := reflect.ValueOf(value) + for i := 0; i < files.Len(); i++ { + filemap := files.Index(i).Interface().(map[string]interface{}) + // create a new package + file := &spdx.File2_2{} + //extract the SPDXID of the package + eID, err := extractElementID(filemap["SPDXID"].(string)) + if err != nil { + return fmt.Errorf("%s", err) + } + file.FileSPDXIdentifier = eID + //range over all other properties now + for k, v := range filemap { + switch k { + case "SPDXID": + //redundant case + case "fileName": + file.FileName = v.(string) + case "fileTypes": + if reflect.TypeOf(v).Kind() == reflect.Slice { + texts := reflect.ValueOf(v) + for i := 0; i < texts.Len(); i++ { + file.FileType = append(file.FileType, texts.Index(i).Interface().(string)) + } + } + case "checksums": + //general function to parse checksums in utils + if reflect.TypeOf(v).Kind() == reflect.Slice { + checksums := reflect.ValueOf(v) + if file.FileChecksums == nil { + file.FileChecksums = make(map[spdx.ChecksumAlgorithm]spdx.Checksum) + } + for i := 0; i < checksums.Len(); i++ { + checksum := checksums.Index(i).Interface().(map[string]interface{}) + switch checksum["algorithm"].(string) { + case spdx.SHA1, spdx.SHA256, spdx.MD5: + algorithm := spdx.ChecksumAlgorithm(checksum["algorithm"].(string)) + file.FileChecksums[algorithm] = spdx.Checksum{Algorithm: algorithm, Value: checksum["checksumValue"].(string)} + default: + return fmt.Errorf("got unknown checksum type %s", checksum["algorithm"]) + } + } + } + case "annotations": + id, err := extractDocElementID(filemap["SPDXID"].(string)) + if err != nil { + return fmt.Errorf("%s", err) + } + err = spec.parseJsonAnnotations2_2(k, v, doc, id) + if err != nil { + return err + } + case "copyrightText": + file.FileCopyrightText = v.(string) + case "noticeText": + file.FileNotice = v.(string) + case "licenseComments": + file.LicenseComments = v.(string) + case "licenseConcluded": + file.LicenseConcluded = v.(string) + case "licenseInfoInFiles": + if reflect.TypeOf(v).Kind() == reflect.Slice { + info := reflect.ValueOf(v) + for i := 0; i < info.Len(); i++ { + file.LicenseInfoInFile = append(file.LicenseInfoInFile, info.Index(i).Interface().(string)) + } + } + case "fileContributors": + if reflect.TypeOf(v).Kind() == reflect.Slice { + info := reflect.ValueOf(v) + for i := 0; i < info.Len(); i++ { + file.FileContributor = append(file.FileContributor, info.Index(i).Interface().(string)) + } + } + case "fileDependencies": + if reflect.TypeOf(v).Kind() == reflect.Slice { + info := reflect.ValueOf(v) + for i := 0; i < info.Len(); i++ { + file.FileDependencies = append(file.FileDependencies, info.Index(i).Interface().(string)) + } + } + case "attributionTexts": + if reflect.TypeOf(v).Kind() == reflect.Slice { + info := reflect.ValueOf(v) + for i := 0; i < info.Len(); i++ { + file.FileAttributionTexts = append(file.FileAttributionTexts, info.Index(i).Interface().(string)) + } + } + case "comment": + file.FileComment = v.(string) + + default: + return fmt.Errorf("received unknown tag %v in files section", k) + } + } + doc.UnpackagedFiles[eID] = file + } + + } + return nil +} + +//relationship comment property diff --git a/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_other_license.go b/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_other_license.go new file mode 100644 index 000000000000..997ad025affe --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_other_license.go @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package parser2v2 + +import ( + "fmt" + "reflect" + + "github.com/spdx/tools-golang/spdx" +) + +func (spec JSONSpdxDocument) parseJsonOtherLicenses2_2(key string, value interface{}, doc *spdxDocument2_2) error { + if reflect.TypeOf(value).Kind() == reflect.Slice { + otherlicenses := reflect.ValueOf(value) + for i := 0; i < otherlicenses.Len(); i++ { + licensemap := otherlicenses.Index(i).Interface().(map[string]interface{}) + license := spdx.OtherLicense2_2{} + // Remove loop all properties are mandatory in annotations + for k, v := range licensemap { + switch k { + case "licenseId": + license.LicenseIdentifier = v.(string) + case "extractedText": + license.ExtractedText = v.(string) + case "name": + license.LicenseName = v.(string) + case "comment": + license.LicenseComment = v.(string) + case "seeAlsos": + if reflect.TypeOf(v).Kind() == reflect.Slice { + texts := reflect.ValueOf(v) + for i := 0; i < texts.Len(); i++ { + license.LicenseCrossReferences = append(license.LicenseCrossReferences, texts.Index(i).Interface().(string)) + } + } + default: + return fmt.Errorf("received unknown tag %v in Licenses section", k) + } + } + doc.OtherLicenses = append(doc.OtherLicenses, &license) + } + + } + return nil +} diff --git a/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_package.go b/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_package.go new file mode 100644 index 000000000000..f698da5c8582 --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_package.go @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package parser2v2 + +import ( + "fmt" + "reflect" + "strings" + + "github.com/spdx/tools-golang/spdx" +) + +func (spec JSONSpdxDocument) parseJsonPackages2_2(key string, value interface{}, doc *spdxDocument2_2) error { + + if doc.Packages == nil { + doc.Packages = map[spdx.ElementID]*spdx.Package2_2{} + } + + if reflect.TypeOf(value).Kind() == reflect.Slice { + packages := reflect.ValueOf(value) + for i := 0; i < packages.Len(); i++ { + pack := packages.Index(i).Interface().(map[string]interface{}) + // create a new package + pkg := &spdx.Package2_2{ + FilesAnalyzed: true, + IsFilesAnalyzedTagPresent: false, + } + //extract the SPDXID of the package + eID, err := extractElementID(pack["SPDXID"].(string)) + if err != nil { + return fmt.Errorf("%s", err) + } + pkg.PackageSPDXIdentifier = eID + //range over all other properties now + for k, v := range pack { + switch k { + case "SPDXID": + //redundant case + case "name": + pkg.PackageName = v.(string) + case "annotations": + packageId, err := extractDocElementID(pack["SPDXID"].(string)) + if err != nil { + return fmt.Errorf("%s", err) + } + //generalize function to parse annotations + err = spec.parseJsonAnnotations2_2("annotations", v, doc, packageId) + if err != nil { + return err + } + case "attributionTexts": + if reflect.TypeOf(v).Kind() == reflect.Slice { + texts := reflect.ValueOf(v) + for i := 0; i < texts.Len(); i++ { + pkg.PackageAttributionTexts = append(pkg.PackageAttributionTexts, texts.Index(i).Interface().(string)) + } + } + case "checksums": + //general function to parse checksums in utils + if reflect.TypeOf(v).Kind() == reflect.Slice { + checksums := reflect.ValueOf(v) + if pkg.PackageChecksums == nil { + pkg.PackageChecksums = make(map[spdx.ChecksumAlgorithm]spdx.Checksum) + } + for i := 0; i < checksums.Len(); i++ { + checksum := checksums.Index(i).Interface().(map[string]interface{}) + switch checksum["algorithm"].(string) { + case spdx.SHA1, spdx.SHA256, spdx.MD5: + algorithm := spdx.ChecksumAlgorithm(checksum["algorithm"].(string)) + pkg.PackageChecksums[algorithm] = spdx.Checksum{Algorithm: algorithm, Value: checksum["checksumValue"].(string)} + default: + return fmt.Errorf("got unknown checksum type %s", checksum["algorithm"]) + } + } + } + case "copyrightText": + pkg.PackageCopyrightText = v.(string) + case "description": + pkg.PackageDescription = v.(string) + case "downloadLocation": + pkg.PackageDownloadLocation = v.(string) + case "externalRefs": + //make a function to parse these + if reflect.TypeOf(v).Kind() == reflect.Slice { + extrefs := reflect.ValueOf(v) + for i := 0; i < extrefs.Len(); i++ { + ref := extrefs.Index(i).Interface().(map[string]interface{}) + newref := &spdx.PackageExternalReference2_2{} + //TODO: if either of these 3 missing then error + newref.RefType = ref["referenceType"].(string) + newref.Locator = ref["referenceLocator"].(string) + newref.Category = ref["referenceCategory"].(string) + if ref["comment"] != nil { + newref.ExternalRefComment = ref["comment"].(string) + } + pkg.PackageExternalReferences = append(pkg.PackageExternalReferences, newref) + } + } + case "filesAnalyzed": + pkg.IsFilesAnalyzedTagPresent = true + if !v.(bool) { + pkg.FilesAnalyzed = false + } else { + pkg.FilesAnalyzed = true + } + case "homepage": + pkg.PackageHomePage = v.(string) + case "licenseComments": + pkg.PackageLicenseComments = v.(string) + case "licenseConcluded": + pkg.PackageLicenseConcluded = v.(string) + case "licenseDeclared": + pkg.PackageLicenseDeclared = v.(string) + case "licenseInfoFromFiles": + if reflect.TypeOf(v).Kind() == reflect.Slice { + info := reflect.ValueOf(v) + for i := 0; i < info.Len(); i++ { + pkg.PackageLicenseInfoFromFiles = append(pkg.PackageLicenseInfoFromFiles, info.Index(i).Interface().(string)) + } + } + case "originator": + if v.(string) == "NOASSERTION" { + pkg.PackageOriginatorNOASSERTION = true + break + } + subkey, subvalue, err := extractSubs(v.(string)) + if err != nil { + return err + } + switch subkey { + case "Person": + pkg.PackageOriginatorPerson = subvalue + case "Organization": + pkg.PackageOriginatorOrganization = subvalue + default: + return fmt.Errorf("unrecognized PackageOriginator type %v", subkey) + } + case "packageFileName": + pkg.PackageFileName = v.(string) + case "packageVerificationCode": + code := v.(map[string]interface{}) + for codekey, codeval := range code { + switch codekey { + case "packageVerificationCodeExcludedFiles": + if reflect.TypeOf(codeval).Kind() == reflect.Slice { + efiles := reflect.ValueOf(codeval) + filename := efiles.Index(0).Interface().(string) + if strings.HasPrefix(filename, "excludes:") { + _, filename, err = extractSubs(efiles.Index(0).Interface().(string)) + if err != nil { + return fmt.Errorf("%s", err) + } + } + pkg.PackageVerificationCodeExcludedFile = strings.Trim(filename, " ") + } + case "packageVerificationCodeValue": + pkg.PackageVerificationCode = code["packageVerificationCodeValue"].(string) + } + } + case "sourceInfo": + pkg.PackageSourceInfo = v.(string) + case "summary": + pkg.PackageSummary = v.(string) + case "supplier": + if v.(string) == "NOASSERTION" { + pkg.PackageSupplierNOASSERTION = true + break + } + subkey, subvalue, err := extractSubs(v.(string)) + if err != nil { + return err + } + switch subkey { + case "Person": + pkg.PackageSupplierPerson = subvalue + case "Organization": + pkg.PackageSupplierOrganization = subvalue + default: + return fmt.Errorf("unrecognized PackageSupplier type %v", subkey) + } + + case "versionInfo": + pkg.PackageVersion = v.(string) + case "comment": + pkg.PackageComment = v.(string) + case "hasFiles": + if pkg.Files == nil { + pkg.Files = make(map[spdx.ElementID]*spdx.File2_2) + } + if reflect.TypeOf(v).Kind() == reflect.Slice { + SpdxIds := reflect.ValueOf(v) + for i := 0; i < SpdxIds.Len(); i++ { + fileId, err := extractElementID(SpdxIds.Index(i).Interface().(string)) + if err != nil { + return err + } + pkg.Files[fileId] = doc.UnpackagedFiles[fileId] + delete(doc.UnpackagedFiles, fileId) + } + } + + default: + return fmt.Errorf("received unknown tag %v in Annotation section", k) + } + } + doc.Packages[eID] = pkg + } + + } + return nil +} diff --git a/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_relationship.go b/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_relationship.go new file mode 100644 index 000000000000..b6d2bfa99272 --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_relationship.go @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package parser2v2 + +import ( + "fmt" + "reflect" + + "github.com/spdx/tools-golang/spdx" +) + +func (spec JSONSpdxDocument) parseJsonRelationships2_2(key string, value interface{}, doc *spdxDocument2_2) error { + + //FIXME : NOASSERTION and NONE in relationship B value not compatible + if reflect.TypeOf(value).Kind() == reflect.Slice { + relationships := reflect.ValueOf(value) + for i := 0; i < relationships.Len(); i++ { + relationship := relationships.Index(i).Interface().(map[string]interface{}) + rel := spdx.Relationship2_2{} + // Parse ref A of the relationship + aid, err := extractDocElementID(relationship["spdxElementId"].(string)) + if err != nil { + return fmt.Errorf("%s", err) + } + rel.RefA = aid + + // Parse the refB of the relationship + // NONE and NOASSERTION are permitted on right side + permittedSpecial := []string{"NONE", "NOASSERTION"} + bid, err := extractDocElementSpecial(relationship["relatedSpdxElement"].(string), permittedSpecial) + if err != nil { + return fmt.Errorf("%s", err) + } + rel.RefB = bid + // Parse relationship type + if relationship["relationshipType"] == nil { + return fmt.Errorf("%s , %d", "RelationshipType propty missing in relationship number", i) + } + rel.Relationship = relationship["relationshipType"].(string) + + // Parse the relationship comment + if relationship["comment"] != nil { + rel.RelationshipComment = relationship["comment"].(string) + } + + doc.Relationships = append(doc.Relationships, &rel) + } + + } + return nil +} diff --git a/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_reviews.go b/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_reviews.go new file mode 100644 index 000000000000..279e0b2e179b --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_reviews.go @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package parser2v2 + +import ( + "fmt" + "reflect" + + "github.com/spdx/tools-golang/spdx" +) + +func (spec JSONSpdxDocument) parseJsonReviews2_2(key string, value interface{}, doc *spdxDocument2_2) error { + //FIXME: Reviewer type property of review not specified in the spec + if reflect.TypeOf(value).Kind() == reflect.Slice { + reviews := reflect.ValueOf(value) + for i := 0; i < reviews.Len(); i++ { + reviewmap := reviews.Index(i).Interface().(map[string]interface{}) + review := spdx.Review2_2{} + // Remove loop all properties are mandatory in annotations + for k, v := range reviewmap { + switch k { + case "reviewer": + subkey, subvalue, err := extractSubs(v.(string)) + if err != nil { + return err + } + if subkey != "Person" && subkey != "Organization" && subkey != "Tool" { + return fmt.Errorf("unrecognized Reviewer type %v", subkey) + } + review.ReviewerType = subkey + review.Reviewer = subvalue + case "comment": + review.ReviewComment = v.(string) + case "reviewDate": + review.ReviewDate = v.(string) + default: + return fmt.Errorf("received unknown tag %v in Review Section section", k) + } + } + doc.Reviews = append(doc.Reviews, &review) + } + + } + return nil +} diff --git a/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_snippets.go b/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_snippets.go new file mode 100644 index 000000000000..a49191d7f8c5 --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parse_snippets.go @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package parser2v2 + +import ( + "fmt" + "reflect" + + "github.com/spdx/tools-golang/spdx" +) + +func (spec JSONSpdxDocument) parseJsonSnippets2_2(key string, value interface{}, doc *spdxDocument2_2) error { + + if reflect.TypeOf(value).Kind() == reflect.Slice { + snippets := reflect.ValueOf(value) + for i := 0; i < snippets.Len(); i++ { + snippetmap := snippets.Index(i).Interface().(map[string]interface{}) + // create a new package + snippet := &spdx.Snippet2_2{} + //extract the SPDXID of the package + eID, err := extractElementID(snippetmap["SPDXID"].(string)) + if err != nil { + return fmt.Errorf("%s", err) + } + snippet.SnippetSPDXIdentifier = eID + //range over all other properties now + for k, v := range snippetmap { + switch k { + case "SPDXID", "snippetFromFile": + //redundant case + case "name": + snippet.SnippetName = v.(string) + case "copyrightText": + snippet.SnippetCopyrightText = v.(string) + case "licenseComments": + snippet.SnippetLicenseComments = v.(string) + case "licenseConcluded": + snippet.SnippetLicenseConcluded = v.(string) + case "licenseInfoInSnippets": + if reflect.TypeOf(v).Kind() == reflect.Slice { + info := reflect.ValueOf(v) + for i := 0; i < info.Len(); i++ { + snippet.LicenseInfoInSnippet = append(snippet.LicenseInfoInSnippet, info.Index(i).Interface().(string)) + } + } + case "attributionTexts": + if reflect.TypeOf(v).Kind() == reflect.Slice { + info := reflect.ValueOf(v) + for i := 0; i < info.Len(); i++ { + snippet.SnippetAttributionTexts = append(snippet.SnippetAttributionTexts, info.Index(i).Interface().(string)) + } + } + case "comment": + snippet.SnippetComment = v.(string) + case "ranges": + //TODO: optimise this logic + if reflect.TypeOf(v).Kind() == reflect.Slice { + info := reflect.ValueOf(v) + for i := 0; i < info.Len(); i++ { + ranges := info.Index(i).Interface().(map[string]interface{}) + rangeStart := ranges["startPointer"].(map[string]interface{}) + rangeEnd := ranges["endPointer"].(map[string]interface{}) + if rangeStart["lineNumber"] != nil && rangeEnd["lineNumber"] != nil { + snippet.SnippetLineRangeStart = int(rangeStart["lineNumber"].(float64)) + snippet.SnippetLineRangeEnd = int(rangeEnd["lineNumber"].(float64)) + } else { + snippet.SnippetByteRangeStart = int(rangeStart["offset"].(float64)) + snippet.SnippetByteRangeEnd = int(rangeEnd["offset"].(float64)) + } + } + } + default: + return fmt.Errorf("received unknown tag %v in snippet section", k) + } + } + fileID, err2 := extractDocElementID(snippetmap["snippetFromFile"].(string)) + if err2 != nil { + return fmt.Errorf("%s", err2) + } + snippet.SnippetFromFileSPDXIdentifier = fileID + if doc.UnpackagedFiles[fileID.ElementRefID].Snippets == nil { + doc.UnpackagedFiles[fileID.ElementRefID].Snippets = make(map[spdx.ElementID]*spdx.Snippet2_2) + } + doc.UnpackagedFiles[fileID.ElementRefID].Snippets[eID] = snippet + } + + } + return nil +} diff --git a/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parser.go b/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parser.go new file mode 100644 index 000000000000..b1fbe8a74653 --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/parser.go @@ -0,0 +1,132 @@ +// Package jsonloader is used to load and parse SPDX JSON documents +// into tools-golang data structures. +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package parser2v2 + +import ( + "encoding/json" + "fmt" + + "github.com/spdx/tools-golang/spdx" +) + +//TODO : return spdx.Document2_2 +func Load2_2(content []byte) (*spdx.Document2_2, error) { + // check whetehr the Json is valid or not + if !json.Valid(content) { + return nil, fmt.Errorf("%s", "Invalid JSON Specification") + } + result := spdxDocument2_2{} + // unmarshall the json into the result struct + err := json.Unmarshal(content, &result) + resultfinal := spdx.Document2_2(result) + + if err != nil { + return nil, fmt.Errorf("%s", err) + } + + return &resultfinal, nil +} + +func (doc *spdxDocument2_2) UnmarshalJSON(data []byte) error { + var specs JSONSpdxDocument + //unmarshall the json into the intermediate stricture map[string]interface{} + err := json.Unmarshal(data, &specs) + if err != nil { + return err + } + // parse the data from the intermediate structure to the spdx.Document2_2{} + err = specs.newDocument(doc) + if err != nil { + return err + } + return nil +} + +func (spec JSONSpdxDocument) newDocument(doc *spdxDocument2_2) error { + // raneg through all the keys in the map and send them to appropriate arsing functions + for key, val := range spec { + switch key { + case "dataLicense", "spdxVersion", "SPDXID", "documentNamespace", "name", "comment", "creationInfo", "externalDocumentRefs": + err := spec.parseJsonCreationInfo2_2(key, val, doc) + if err != nil { + return err + } + case "annotations": + // if the json spec doenn't has any files then only this case will be executed + if spec["files"] == nil { + + id, err := extractDocElementID(spec["SPDXID"].(string)) + if err != nil { + return fmt.Errorf("%s", err) + } + err = spec.parseJsonAnnotations2_2(key, val, doc, id) + if err != nil { + return err + } + } + case "relationships": + err := spec.parseJsonRelationships2_2(key, val, doc) + if err != nil { + return err + } + case "files": + //first parse all the files + err := spec.parseJsonFiles2_2(key, val, doc) + if err != nil { + return err + } + //then parse the snippets + if spec["snippets"] != nil { + err = spec.parseJsonSnippets2_2("snippets", spec["snippets"], doc) + if err != nil { + return err + } + } + //then parse the packages + if spec["packages"] != nil { + err = spec.parseJsonPackages2_2("packages", spec["packages"], doc) + if err != nil { + return err + } + } + // then parse the annotations + if spec["annotations"] != nil { + id, err := extractDocElementID(spec["SPDXID"].(string)) + if err != nil { + return fmt.Errorf("%s", err) + } + err = spec.parseJsonAnnotations2_2("annotations", spec["annotations"], doc, id) + if err != nil { + return err + } + } + + case "packages": + // if the json spec doesn't has any files to parse then this switch case will be executed + if spec["files"] == nil { + err := spec.parseJsonPackages2_2("packages", spec["packages"], doc) + if err != nil { + return err + } + } + case "hasExtractedLicensingInfos": + err := spec.parseJsonOtherLicenses2_2(key, val, doc) + if err != nil { + return err + } + case "revieweds": + err := spec.parseJsonReviews2_2(key, val, doc) + if err != nil { + return err + } + case "snippets", "documentDescribes": + //redundant case + default: + return fmt.Errorf("unrecognized key here %v", key) + } + + } + return nil +} diff --git a/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/types.go b/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/types.go new file mode 100644 index 000000000000..b85b23ea6752 --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/types.go @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package parser2v2 + +import "github.com/spdx/tools-golang/spdx" + +type spdxDocument2_2 spdx.Document2_2 + +type JSONSpdxDocument map[string]interface{} diff --git a/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/util.go b/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/util.go new file mode 100644 index 000000000000..66768469b755 --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/jsonloader/parser2v2/util.go @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package parser2v2 + +import ( + "fmt" + "strings" + + "github.com/spdx/tools-golang/spdx" +) + +// used to extract key / value from embedded substrings +// returns subkey, subvalue, nil if no error, or "", "", error otherwise +func extractSubs(value string) (string, string, error) { + // parse the value to see if it's a valid subvalue format + sp := strings.SplitN(value, ":", 2) + if len(sp) == 1 { + return "", "", fmt.Errorf("invalid subvalue format for %s (no colon found)", value) + } + + subkey := strings.TrimSpace(sp[0]) + subvalue := strings.TrimSpace(sp[1]) + + return subkey, subvalue, nil +} + +// used to extract DocumentRef and SPDXRef values from an SPDX Identifier +// which can point either to this document or to a different one +func extractDocElementID(value string) (spdx.DocElementID, error) { + docRefID := "" + idStr := value + + // check prefix to see if it's a DocumentRef ID + if strings.HasPrefix(idStr, "DocumentRef-") { + // extract the part that comes between "DocumentRef-" and ":" + strs := strings.Split(idStr, ":") + // should be exactly two, part before and part after + if len(strs) < 2 { + return spdx.DocElementID{}, fmt.Errorf("no colon found although DocumentRef- prefix present") + } + if len(strs) > 2 { + return spdx.DocElementID{}, fmt.Errorf("more than one colon found") + } + + // trim the prefix and confirm non-empty + docRefID = strings.TrimPrefix(strs[0], "DocumentRef-") + if docRefID == "" { + return spdx.DocElementID{}, fmt.Errorf("document identifier has nothing after prefix") + } + // and use remainder for element ID parsing + idStr = strs[1] + } + + // check prefix to confirm it's got the right prefix for element IDs + if !strings.HasPrefix(idStr, "SPDXRef-") { + return spdx.DocElementID{}, fmt.Errorf("missing SPDXRef- prefix for element identifier") + } + + // make sure no colons are present + if strings.Contains(idStr, ":") { + // we know this means there was no DocumentRef- prefix, because + // we would have handled multiple colons above if it was + return spdx.DocElementID{}, fmt.Errorf("invalid colon in element identifier") + } + + // trim the prefix and confirm non-empty + eltRefID := strings.TrimPrefix(idStr, "SPDXRef-") + if eltRefID == "" { + return spdx.DocElementID{}, fmt.Errorf("element identifier has nothing after prefix") + } + + // we're good + return spdx.DocElementID{DocumentRefID: docRefID, ElementRefID: spdx.ElementID(eltRefID)}, nil +} + +// used to extract SPDXRef values from an SPDX Identifier, OR "special" strings +// from a specified set of permitted values. The primary use case for this is +// the right-hand side of Relationships, where beginning in SPDX 2.2 the values +// "NONE" and "NOASSERTION" are permitted. If the value does not match one of +// the specified permitted values, it will fall back to the ordinary +// DocElementID extractor. +func extractDocElementSpecial(value string, permittedSpecial []string) (spdx.DocElementID, error) { + // check value against special set first + for _, sp := range permittedSpecial { + if sp == value { + return spdx.DocElementID{SpecialID: sp}, nil + } + } + // not found, fall back to regular search + return extractDocElementID(value) +} + +// used to extract SPDXRef values only from an SPDX Identifier which can point +// to this document only. Use extractDocElementID for parsing IDs that can +// refer either to this document or a different one. +func extractElementID(value string) (spdx.ElementID, error) { + // check prefix to confirm it's got the right prefix for element IDs + if !strings.HasPrefix(value, "SPDXRef-") { + return spdx.ElementID(""), fmt.Errorf("missing SPDXRef- prefix for element identifier") + } + + // make sure no colons are present + if strings.Contains(value, ":") { + return spdx.ElementID(""), fmt.Errorf("invalid colon in element identifier") + } + + // trim the prefix and confirm non-empty + eltRefID := strings.TrimPrefix(value, "SPDXRef-") + if eltRefID == "" { + return spdx.ElementID(""), fmt.Errorf("element identifier has nothing after prefix") + } + + // we're good + return spdx.ElementID(eltRefID), nil +} diff --git a/vendor/github.com/spdx/tools-golang/spdx/annotation.go b/vendor/github.com/spdx/tools-golang/spdx/annotation.go new file mode 100644 index 000000000000..ede9c8ac05b5 --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/spdx/annotation.go @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package spdx + +// Annotation2_1 is an Annotation section of an SPDX Document for version 2.1 of the spec. +type Annotation2_1 struct { + + // 8.1: Annotator + // Cardinality: conditional (mandatory, one) if there is an Annotation + Annotator string + // including AnnotatorType: one of "Person", "Organization" or "Tool" + AnnotatorType string + + // 8.2: Annotation Date: YYYY-MM-DDThh:mm:ssZ + // Cardinality: conditional (mandatory, one) if there is an Annotation + AnnotationDate string + + // 8.3: Annotation Type: "REVIEW" or "OTHER" + // Cardinality: conditional (mandatory, one) if there is an Annotation + AnnotationType string + + // 8.4: SPDX Identifier Reference + // Cardinality: conditional (mandatory, one) if there is an Annotation + AnnotationSPDXIdentifier DocElementID + + // 8.5: Annotation Comment + // Cardinality: conditional (mandatory, one) if there is an Annotation + AnnotationComment string +} + +// Annotation2_2 is an Annotation section of an SPDX Document for version 2.2 of the spec. +type Annotation2_2 struct { + + // 8.1: Annotator + // Cardinality: conditional (mandatory, one) if there is an Annotation + Annotator string + // including AnnotatorType: one of "Person", "Organization" or "Tool" + AnnotatorType string + + // 8.2: Annotation Date: YYYY-MM-DDThh:mm:ssZ + // Cardinality: conditional (mandatory, one) if there is an Annotation + AnnotationDate string + + // 8.3: Annotation Type: "REVIEW" or "OTHER" + // Cardinality: conditional (mandatory, one) if there is an Annotation + AnnotationType string + + // 8.4: SPDX Identifier Reference + // Cardinality: conditional (mandatory, one) if there is an Annotation + AnnotationSPDXIdentifier DocElementID + + // 8.5: Annotation Comment + // Cardinality: conditional (mandatory, one) if there is an Annotation + AnnotationComment string +} diff --git a/vendor/github.com/spdx/tools-golang/spdx/checksum.go b/vendor/github.com/spdx/tools-golang/spdx/checksum.go new file mode 100644 index 000000000000..872aee29a518 --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/spdx/checksum.go @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package spdx + +// ChecksumAlgorithm2_2 represents the algorithm used to generate the file checksum in the Checksum2_2 struct. +type ChecksumAlgorithm string + +// The checksum algorithms mentioned in the spdxv2.2.0 https://spdx.github.io/spdx-spec/4-file-information/#44-file-checksum +const ( + SHA224 ChecksumAlgorithm = "SHA224" + SHA1 = "SHA1" + SHA256 = "SHA256" + SHA384 = "SHA384" + SHA512 = "SHA512" + MD2 = "MD2" + MD4 = "MD4" + MD5 = "MD5" + MD6 = "MD6" +) + +//Checksum2_2 struct Provide a unique identifier to match analysis information on each specific file in a package. +// The Algorithm field describes the ChecksumAlgorithm2_2 used and the Value represents the file checksum +type Checksum struct { + Algorithm ChecksumAlgorithm + Value string +} diff --git a/vendor/github.com/spdx/tools-golang/spdx/creation_info.go b/vendor/github.com/spdx/tools-golang/spdx/creation_info.go new file mode 100644 index 000000000000..1bdaaab76c56 --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/spdx/creation_info.go @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package spdx + +// CreationInfo2_1 is a Document Creation Information section of an +// SPDX Document for version 2.1 of the spec. +type CreationInfo2_1 struct { + + // 2.1: SPDX Version; should be in the format "SPDX-2.1" + // Cardinality: mandatory, one + SPDXVersion string + + // 2.2: Data License; should be "CC0-1.0" + // Cardinality: mandatory, one + DataLicense string + + // 2.3: SPDX Identifier; should be "DOCUMENT" to represent + // mandatory identifier of SPDXRef-DOCUMENT + // Cardinality: mandatory, one + SPDXIdentifier ElementID + + // 2.4: Document Name + // Cardinality: mandatory, one + DocumentName string + + // 2.5: Document Namespace + // Cardinality: mandatory, one + DocumentNamespace string + + // 2.6: External Document References + // Cardinality: optional, one or many + ExternalDocumentReferences map[string]ExternalDocumentRef2_1 + + // 2.7: License List Version + // Cardinality: optional, one + LicenseListVersion string + + // 2.8: Creators: may have multiple keys for Person, Organization + // and/or Tool + // Cardinality: mandatory, one or many + CreatorPersons []string + CreatorOrganizations []string + CreatorTools []string + + // 2.9: Created: data format YYYY-MM-DDThh:mm:ssZ + // Cardinality: mandatory, one + Created string + + // 2.10: Creator Comment + // Cardinality: optional, one + CreatorComment string + + // 2.11: Document Comment + // Cardinality: optional, one + DocumentComment string +} + +// ExternalDocumentRef2_1 is a reference to an external SPDX document +// as defined in section 2.6 for version 2.1 of the spec. +type ExternalDocumentRef2_1 struct { + + // DocumentRefID is the ID string defined in the start of the + // reference. It should _not_ contain the "DocumentRef-" part + // of the mandatory ID string. + DocumentRefID string + + // URI is the URI defined for the external document + URI string + + // Alg is the type of hash algorithm used, e.g. "SHA1", "SHA256" + Alg string + + // Checksum is the actual hash data + Checksum string +} + +// CreationInfo2_2 is a Document Creation Information section of an +// SPDX Document for version 2.2 of the spec. +type CreationInfo2_2 struct { + + // 2.1: SPDX Version; should be in the format "SPDX-2.2" + // Cardinality: mandatory, one + SPDXVersion string + + // 2.2: Data License; should be "CC0-1.0" + // Cardinality: mandatory, one + DataLicense string + + // 2.3: SPDX Identifier; should be "DOCUMENT" to represent + // mandatory identifier of SPDXRef-DOCUMENT + // Cardinality: mandatory, one + SPDXIdentifier ElementID + + // 2.4: Document Name + // Cardinality: mandatory, one + DocumentName string + + // 2.5: Document Namespace + // Cardinality: mandatory, one + DocumentNamespace string + + // 2.6: External Document References + // Cardinality: optional, one or many + ExternalDocumentReferences map[string]ExternalDocumentRef2_2 + + // 2.7: License List Version + // Cardinality: optional, one + LicenseListVersion string + + // 2.8: Creators: may have multiple keys for Person, Organization + // and/or Tool + // Cardinality: mandatory, one or many + CreatorPersons []string + CreatorOrganizations []string + CreatorTools []string + + // 2.9: Created: data format YYYY-MM-DDThh:mm:ssZ + // Cardinality: mandatory, one + Created string + + // 2.10: Creator Comment + // Cardinality: optional, one + CreatorComment string + + // 2.11: Document Comment + // Cardinality: optional, one + DocumentComment string +} + +// ExternalDocumentRef2_2 is a reference to an external SPDX document +// as defined in section 2.6 for version 2.2 of the spec. +type ExternalDocumentRef2_2 struct { + + // DocumentRefID is the ID string defined in the start of the + // reference. It should _not_ contain the "DocumentRef-" part + // of the mandatory ID string. + DocumentRefID string + + // URI is the URI defined for the external document + URI string + + // Alg is the type of hash algorithm used, e.g. "SHA1", "SHA256" + Alg string + + // Checksum is the actual hash data + Checksum string +} diff --git a/vendor/github.com/spdx/tools-golang/spdx/document.go b/vendor/github.com/spdx/tools-golang/spdx/document.go new file mode 100644 index 000000000000..6a7bc3d6f2ff --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/spdx/document.go @@ -0,0 +1,32 @@ +// Package spdx contains the struct definition for an SPDX Document +// and its constituent parts. +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +package spdx + +// Document2_1 is an SPDX Document for version 2.1 of the spec. +// See https://spdx.org/sites/cpstandard/files/pages/files/spdxversion2.1.pdf +type Document2_1 struct { + CreationInfo *CreationInfo2_1 + Packages map[ElementID]*Package2_1 + UnpackagedFiles map[ElementID]*File2_1 + OtherLicenses []*OtherLicense2_1 + Relationships []*Relationship2_1 + Annotations []*Annotation2_1 + + // DEPRECATED in version 2.0 of spec + Reviews []*Review2_1 +} + +// Document2_2 is an SPDX Document for version 2.2 of the spec. +// See https://spdx.github.io/spdx-spec/v2-draft/ (DRAFT) +type Document2_2 struct { + CreationInfo *CreationInfo2_2 + Packages map[ElementID]*Package2_2 + UnpackagedFiles map[ElementID]*File2_2 + OtherLicenses []*OtherLicense2_2 + Relationships []*Relationship2_2 + Annotations []*Annotation2_2 + + // DEPRECATED in version 2.0 of spec + Reviews []*Review2_2 +} diff --git a/vendor/github.com/spdx/tools-golang/spdx/file.go b/vendor/github.com/spdx/tools-golang/spdx/file.go new file mode 100644 index 000000000000..a745dc3b7e63 --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/spdx/file.go @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package spdx + +// File2_1 is a File section of an SPDX Document for version 2.1 of the spec. +type File2_1 struct { + + // 4.1: File Name + // Cardinality: mandatory, one + FileName string + + // 4.2: File SPDX Identifier: "SPDXRef-[idstring]" + // Cardinality: mandatory, one + FileSPDXIdentifier ElementID + + // 4.3: File Type + // Cardinality: optional, multiple + FileType []string + + // 4.4: File Checksum: may have keys for SHA1, SHA256 and/or MD5 + // Cardinality: mandatory, one SHA1, others may be optionally provided + FileChecksumSHA1 string + FileChecksumSHA256 string + FileChecksumMD5 string + + // 4.5: Concluded License: SPDX License Expression, "NONE" or "NOASSERTION" + // Cardinality: mandatory, one + LicenseConcluded string + + // 4.6: License Information in File: SPDX License Expression, "NONE" or "NOASSERTION" + // Cardinality: mandatory, one or many + LicenseInfoInFile []string + + // 4.7: Comments on License + // Cardinality: optional, one + LicenseComments string + + // 4.8: Copyright Text: copyright notice(s) text, "NONE" or "NOASSERTION" + // Cardinality: mandatory, one + FileCopyrightText string + + // DEPRECATED in version 2.1 of spec + // 4.9-4.11: Artifact of Project variables (defined below) + // Cardinality: optional, one or many + ArtifactOfProjects []*ArtifactOfProject2_1 + + // 4.12: File Comment + // Cardinality: optional, one + FileComment string + + // 4.13: File Notice + // Cardinality: optional, one + FileNotice string + + // 4.14: File Contributor + // Cardinality: optional, one or many + FileContributor []string + + // DEPRECATED in version 2.0 of spec + // 4.15: File Dependencies + // Cardinality: optional, one or many + FileDependencies []string + + // Snippets contained in this File + // Note that Snippets could be defined in a different Document! However, + // the only ones that _THIS_ document can contain are this ones that are + // defined here -- so this should just be an ElementID. + Snippets map[ElementID]*Snippet2_1 +} + +// ArtifactOfProject2_1 is a DEPRECATED collection of data regarding +// a Package, as defined in sections 4.9-4.11 in version 2.1 of the spec. +type ArtifactOfProject2_1 struct { + + // DEPRECATED in version 2.1 of spec + // 4.9: Artifact of Project Name + // Cardinality: conditional, required if present, one per AOP + Name string + + // DEPRECATED in version 2.1 of spec + // 4.10: Artifact of Project Homepage: URL or "UNKNOWN" + // Cardinality: optional, one per AOP + HomePage string + + // DEPRECATED in version 2.1 of spec + // 4.11: Artifact of Project Uniform Resource Identifier + // Cardinality: optional, one per AOP + URI string +} + +// File2_2 is a File section of an SPDX Document for version 2.2 of the spec. +type File2_2 struct { + + // 4.1: File Name + // Cardinality: mandatory, one + FileName string + + // 4.2: File SPDX Identifier: "SPDXRef-[idstring]" + // Cardinality: mandatory, one + FileSPDXIdentifier ElementID + + // 4.3: File Type + // Cardinality: optional, multiple + FileType []string + + // 4.4: File Checksum: may have keys for SHA1, SHA256 and/or MD5 + // Cardinality: mandatory, one SHA1, others may be optionally provided + FileChecksums map[ChecksumAlgorithm]Checksum + + // 4.5: Concluded License: SPDX License Expression, "NONE" or "NOASSERTION" + // Cardinality: mandatory, one + LicenseConcluded string + + // 4.6: License Information in File: SPDX License Expression, "NONE" or "NOASSERTION" + // Cardinality: mandatory, one or many + LicenseInfoInFile []string + + // 4.7: Comments on License + // Cardinality: optional, one + LicenseComments string + + // 4.8: Copyright Text: copyright notice(s) text, "NONE" or "NOASSERTION" + // Cardinality: mandatory, one + FileCopyrightText string + + // DEPRECATED in version 2.1 of spec + // 4.9-4.11: Artifact of Project variables (defined below) + // Cardinality: optional, one or many + ArtifactOfProjects []*ArtifactOfProject2_2 + + // 4.12: File Comment + // Cardinality: optional, one + FileComment string + + // 4.13: File Notice + // Cardinality: optional, one + FileNotice string + + // 4.14: File Contributor + // Cardinality: optional, one or many + FileContributor []string + + // 4.15: File Attribution Text + // Cardinality: optional, one or many + FileAttributionTexts []string + + // DEPRECATED in version 2.0 of spec + // 4.16: File Dependencies + // Cardinality: optional, one or many + FileDependencies []string + + // Snippets contained in this File + // Note that Snippets could be defined in a different Document! However, + // the only ones that _THIS_ document can contain are this ones that are + // defined here -- so this should just be an ElementID. + Snippets map[ElementID]*Snippet2_2 +} + +// ArtifactOfProject2_2 is a DEPRECATED collection of data regarding +// a Package, as defined in sections 4.9-4.11 in version 2.2 of the spec. +type ArtifactOfProject2_2 struct { + + // DEPRECATED in version 2.1 of spec + // 4.9: Artifact of Project Name + // Cardinality: conditional, required if present, one per AOP + Name string + + // DEPRECATED in version 2.1 of spec + // 4.10: Artifact of Project Homepage: URL or "UNKNOWN" + // Cardinality: optional, one per AOP + HomePage string + + // DEPRECATED in version 2.1 of spec + // 4.11: Artifact of Project Uniform Resource Identifier + // Cardinality: optional, one per AOP + URI string +} diff --git a/vendor/github.com/spdx/tools-golang/spdx/identifier.go b/vendor/github.com/spdx/tools-golang/spdx/identifier.go new file mode 100644 index 000000000000..baf44c1cfea0 --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/spdx/identifier.go @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package spdx + +// ElementID represents the identifier string portion of an SPDX element +// identifier. DocElementID should be used for any attributes which can +// contain identifiers defined in a different SPDX document. +// ElementIDs should NOT contain the mandatory 'SPDXRef-' portion. +type ElementID string + +// DocElementID represents an SPDX element identifier that could be defined +// in a different SPDX document, and therefore could have a "DocumentRef-" +// portion, such as Relationships and Annotations. +// ElementID is used for attributes in which a "DocumentRef-" portion cannot +// appear, such as a Package or File definition (since it is necessarily +// being defined in the present document). +// DocumentRefID will be the empty string for elements defined in the +// present document. +// DocElementIDs should NOT contain the mandatory 'DocumentRef-' or +// 'SPDXRef-' portions. +// SpecialID is used ONLY if the DocElementID matches a defined set of +// permitted special values for a particular field, e.g. "NONE" or +// "NOASSERTION" for the right-hand side of Relationships. If SpecialID +// is set, DocumentRefID and ElementRefID should be empty (and vice versa). +type DocElementID struct { + DocumentRefID string + ElementRefID ElementID + SpecialID string +} + +// TODO: add equivalents for LicenseRef- identifiers + +// MakeDocElementID takes strings (without prefixes) for the DocumentRef- +// and SPDXRef- identifiers, and returns a DocElementID. An empty string +// should be used for the DocumentRef- portion if it is referring to the +// present document. +func MakeDocElementID(docRef string, eltRef string) DocElementID { + return DocElementID{ + DocumentRefID: docRef, + ElementRefID: ElementID(eltRef), + } +} + +// MakeDocElementSpecial takes a "special" string (e.g. "NONE" or +// "NOASSERTION" for the right side of a Relationship), nd returns +// a DocElementID with it in the SpecialID field. Other fields will +// be empty. +func MakeDocElementSpecial(specialID string) DocElementID { + return DocElementID{SpecialID: specialID} +} + +// RenderElementID takes an ElementID and returns the string equivalent, +// with the SPDXRef- prefix reinserted. +func RenderElementID(eID ElementID) string { + return "SPDXRef-" + string(eID) +} + +// RenderDocElementID takes a DocElementID and returns the string equivalent, +// with the SPDXRef- prefix (and, if applicable, the DocumentRef- prefix) +// reinserted. If a SpecialID is present, it will be rendered verbatim and +// DocumentRefID and ElementRefID will be ignored. +func RenderDocElementID(deID DocElementID) string { + if deID.SpecialID != "" { + return deID.SpecialID + } + prefix := "" + if deID.DocumentRefID != "" { + prefix = "DocumentRef-" + deID.DocumentRefID + ":" + } + return prefix + "SPDXRef-" + string(deID.ElementRefID) +} diff --git a/vendor/github.com/spdx/tools-golang/spdx/other_license.go b/vendor/github.com/spdx/tools-golang/spdx/other_license.go new file mode 100644 index 000000000000..a509c472026a --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/spdx/other_license.go @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package spdx + +// OtherLicense2_1 is an Other License Information section of an +// SPDX Document for version 2.1 of the spec. +type OtherLicense2_1 struct { + + // 6.1: License Identifier: "LicenseRef-[idstring]" + // Cardinality: conditional (mandatory, one) if license is not + // on SPDX License List + LicenseIdentifier string + + // 6.2: Extracted Text + // Cardinality: conditional (mandatory, one) if there is a + // License Identifier assigned + ExtractedText string + + // 6.3: License Name: single line of text or "NOASSERTION" + // Cardinality: conditional (mandatory, one) if license is not + // on SPDX License List + LicenseName string + + // 6.4: License Cross Reference + // Cardinality: conditional (optional, one or many) if license + // is not on SPDX License List + LicenseCrossReferences []string + + // 6.5: License Comment + // Cardinality: optional, one + LicenseComment string +} + +// OtherLicense2_2 is an Other License Information section of an +// SPDX Document for version 2.2 of the spec. +type OtherLicense2_2 struct { + + // 6.1: License Identifier: "LicenseRef-[idstring]" + // Cardinality: conditional (mandatory, one) if license is not + // on SPDX License List + LicenseIdentifier string + + // 6.2: Extracted Text + // Cardinality: conditional (mandatory, one) if there is a + // License Identifier assigned + ExtractedText string + + // 6.3: License Name: single line of text or "NOASSERTION" + // Cardinality: conditional (mandatory, one) if license is not + // on SPDX License List + LicenseName string + + // 6.4: License Cross Reference + // Cardinality: conditional (optional, one or many) if license + // is not on SPDX License List + LicenseCrossReferences []string + + // 6.5: License Comment + // Cardinality: optional, one + LicenseComment string +} diff --git a/vendor/github.com/spdx/tools-golang/spdx/package.go b/vendor/github.com/spdx/tools-golang/spdx/package.go new file mode 100644 index 000000000000..9aeb8a205244 --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/spdx/package.go @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package spdx + +// Package2_1 is a Package section of an SPDX Document for version 2.1 of the spec. +type Package2_1 struct { + + // 3.1: Package Name + // Cardinality: mandatory, one + PackageName string + + // 3.2: Package SPDX Identifier: "SPDXRef-[idstring]" + // Cardinality: mandatory, one + PackageSPDXIdentifier ElementID + + // 3.3: Package Version + // Cardinality: optional, one + PackageVersion string + + // 3.4: Package File Name + // Cardinality: optional, one + PackageFileName string + + // 3.5: Package Supplier: may have single result for either Person or Organization, + // or NOASSERTION + // Cardinality: optional, one + PackageSupplierPerson string + PackageSupplierOrganization string + PackageSupplierNOASSERTION bool + + // 3.6: Package Originator: may have single result for either Person or Organization, + // or NOASSERTION + // Cardinality: optional, one + PackageOriginatorPerson string + PackageOriginatorOrganization string + PackageOriginatorNOASSERTION bool + + // 3.7: Package Download Location + // Cardinality: mandatory, one + PackageDownloadLocation string + + // 3.8: FilesAnalyzed + // Cardinality: optional, one; default value is "true" if omitted + FilesAnalyzed bool + // NOT PART OF SPEC: did FilesAnalyzed tag appear? + IsFilesAnalyzedTagPresent bool + + // 3.9: Package Verification Code + // Cardinality: mandatory, one if filesAnalyzed is true / omitted; + // zero (must be omitted) if filesAnalyzed is false + PackageVerificationCode string + // Spec also allows specifying a single file to exclude from the + // verification code algorithm; intended to enable exclusion of + // the SPDX document file itself. + PackageVerificationCodeExcludedFile string + + // 3.10: Package Checksum: may have keys for SHA1, SHA256 and/or MD5 + // Cardinality: optional, one or many + PackageChecksumSHA1 string + PackageChecksumSHA256 string + PackageChecksumMD5 string + + // 3.11: Package Home Page + // Cardinality: optional, one + PackageHomePage string + + // 3.12: Source Information + // Cardinality: optional, one + PackageSourceInfo string + + // 3.13: Concluded License: SPDX License Expression, "NONE" or "NOASSERTION" + // Cardinality: mandatory, one + PackageLicenseConcluded string + + // 3.14: All Licenses Info from Files: SPDX License Expression, "NONE" or "NOASSERTION" + // Cardinality: mandatory, one or many if filesAnalyzed is true / omitted; + // zero (must be omitted) if filesAnalyzed is false + PackageLicenseInfoFromFiles []string + + // 3.15: Declared License: SPDX License Expression, "NONE" or "NOASSERTION" + // Cardinality: mandatory, one + PackageLicenseDeclared string + + // 3.16: Comments on License + // Cardinality: optional, one + PackageLicenseComments string + + // 3.17: Copyright Text: copyright notice(s) text, "NONE" or "NOASSERTION" + // Cardinality: mandatory, one + PackageCopyrightText string + + // 3.18: Package Summary Description + // Cardinality: optional, one + PackageSummary string + + // 3.19: Package Detailed Description + // Cardinality: optional, one + PackageDescription string + + // 3.20: Package Comment + // Cardinality: optional, one + PackageComment string + + // 3.21: Package External Reference + // Cardinality: optional, one or many + PackageExternalReferences []*PackageExternalReference2_1 + + // 3.22: Package External Reference Comment + // Cardinality: conditional (optional, one) for each External Reference + // contained within PackageExternalReference2_1 struct, if present + + // Files contained in this Package + Files map[ElementID]*File2_1 +} + +// PackageExternalReference2_1 is an External Reference to additional info +// about a Package, as defined in section 3.21 in version 2.1 of the spec. +type PackageExternalReference2_1 struct { + + // category is "SECURITY", "PACKAGE-MANAGER" or "OTHER" + Category string + + // type is an [idstring] as defined in Appendix VI; + // called RefType here due to "type" being a Golang keyword + RefType string + + // locator is a unique string to access the package-specific + // info, metadata or content within the target location + Locator string + + // 3.22: Package External Reference Comment + // Cardinality: conditional (optional, one) for each External Reference + ExternalRefComment string +} + +// Package2_2 is a Package section of an SPDX Document for version 2.2 of the spec. +type Package2_2 struct { + + // NOT PART OF SPEC + // flag: does this "package" contain files that were in fact "unpackaged", + // e.g. included directly in the Document without being in a Package? + IsUnpackaged bool + + // 3.1: Package Name + // Cardinality: mandatory, one + PackageName string + + // 3.2: Package SPDX Identifier: "SPDXRef-[idstring]" + // Cardinality: mandatory, one + PackageSPDXIdentifier ElementID + + // 3.3: Package Version + // Cardinality: optional, one + PackageVersion string + + // 3.4: Package File Name + // Cardinality: optional, one + PackageFileName string + + // 3.5: Package Supplier: may have single result for either Person or Organization, + // or NOASSERTION + // Cardinality: optional, one + PackageSupplierPerson string + PackageSupplierOrganization string + PackageSupplierNOASSERTION bool + + // 3.6: Package Originator: may have single result for either Person or Organization, + // or NOASSERTION + // Cardinality: optional, one + PackageOriginatorPerson string + PackageOriginatorOrganization string + PackageOriginatorNOASSERTION bool + + // 3.7: Package Download Location + // Cardinality: mandatory, one + PackageDownloadLocation string + + // 3.8: FilesAnalyzed + // Cardinality: optional, one; default value is "true" if omitted + FilesAnalyzed bool + // NOT PART OF SPEC: did FilesAnalyzed tag appear? + IsFilesAnalyzedTagPresent bool + + // 3.9: Package Verification Code + // Cardinality: mandatory, one if filesAnalyzed is true / omitted; + // zero (must be omitted) if filesAnalyzed is false + PackageVerificationCode string + // Spec also allows specifying a single file to exclude from the + // verification code algorithm; intended to enable exclusion of + // the SPDX document file itself. + PackageVerificationCodeExcludedFile string + + // 3.10: Package Checksum: may have keys for SHA1, SHA256 and/or MD5 + // Cardinality: optional, one or many + PackageChecksums map[ChecksumAlgorithm]Checksum + + // 3.11: Package Home Page + // Cardinality: optional, one + PackageHomePage string + + // 3.12: Source Information + // Cardinality: optional, one + PackageSourceInfo string + + // 3.13: Concluded License: SPDX License Expression, "NONE" or "NOASSERTION" + // Cardinality: mandatory, one + PackageLicenseConcluded string + + // 3.14: All Licenses Info from Files: SPDX License Expression, "NONE" or "NOASSERTION" + // Cardinality: mandatory, one or many if filesAnalyzed is true / omitted; + // zero (must be omitted) if filesAnalyzed is false + PackageLicenseInfoFromFiles []string + + // 3.15: Declared License: SPDX License Expression, "NONE" or "NOASSERTION" + // Cardinality: mandatory, one + PackageLicenseDeclared string + + // 3.16: Comments on License + // Cardinality: optional, one + PackageLicenseComments string + + // 3.17: Copyright Text: copyright notice(s) text, "NONE" or "NOASSERTION" + // Cardinality: mandatory, one + PackageCopyrightText string + + // 3.18: Package Summary Description + // Cardinality: optional, one + PackageSummary string + + // 3.19: Package Detailed Description + // Cardinality: optional, one + PackageDescription string + + // 3.20: Package Comment + // Cardinality: optional, one + PackageComment string + + // 3.21: Package External Reference + // Cardinality: optional, one or many + PackageExternalReferences []*PackageExternalReference2_2 + + // 3.22: Package External Reference Comment + // Cardinality: conditional (optional, one) for each External Reference + // contained within PackageExternalReference2_1 struct, if present + + // 3.23: Package Attribution Text + // Cardinality: optional, one or many + PackageAttributionTexts []string + + // Files contained in this Package + Files map[ElementID]*File2_2 +} + +// PackageExternalReference2_2 is an External Reference to additional info +// about a Package, as defined in section 3.21 in version 2.2 of the spec. +type PackageExternalReference2_2 struct { + + // category is "SECURITY", "PACKAGE-MANAGER", "PERSISTENT-ID" or "OTHER" + Category string + + // type is an [idstring] as defined in Appendix VI; + // called RefType here due to "type" being a Golang keyword + RefType string + + // locator is a unique string to access the package-specific + // info, metadata or content within the target location + Locator string + + // 3.22: Package External Reference Comment + // Cardinality: conditional (optional, one) for each External Reference + ExternalRefComment string +} diff --git a/vendor/github.com/spdx/tools-golang/spdx/relationship.go b/vendor/github.com/spdx/tools-golang/spdx/relationship.go new file mode 100644 index 000000000000..9e06838c8750 --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/spdx/relationship.go @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package spdx + +// Relationship2_1 is a Relationship section of an SPDX Document for +// version 2.1 of the spec. +type Relationship2_1 struct { + + // 7.1: Relationship + // Cardinality: optional, one or more; one per Relationship2_1 + // one mandatory for SPDX Document with multiple packages + // RefA and RefB are first and second item + // Relationship is type from 7.1.1 + RefA DocElementID + RefB DocElementID + Relationship string + + // 7.2: Relationship Comment + // Cardinality: optional, one + RelationshipComment string +} + +// Relationship2_2 is a Relationship section of an SPDX Document for +// version 2.2 of the spec. +type Relationship2_2 struct { + + // 7.1: Relationship + // Cardinality: optional, one or more; one per Relationship2_2 + // one mandatory for SPDX Document with multiple packages + // RefA and RefB are first and second item + // Relationship is type from 7.1.1 + RefA DocElementID + RefB DocElementID + Relationship string + + // 7.2: Relationship Comment + // Cardinality: optional, one + RelationshipComment string +} diff --git a/vendor/github.com/spdx/tools-golang/spdx/review.go b/vendor/github.com/spdx/tools-golang/spdx/review.go new file mode 100644 index 000000000000..8ca6a77c3544 --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/spdx/review.go @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package spdx + +// Review2_1 is a Review section of an SPDX Document for version 2.1 of the spec. +// DEPRECATED in version 2.0 of spec; retained here for compatibility. +type Review2_1 struct { + + // DEPRECATED in version 2.0 of spec + // 9.1: Reviewer + // Cardinality: optional, one + Reviewer string + // including AnnotatorType: one of "Person", "Organization" or "Tool" + ReviewerType string + + // DEPRECATED in version 2.0 of spec + // 9.2: Review Date: YYYY-MM-DDThh:mm:ssZ + // Cardinality: conditional (mandatory, one) if there is a Reviewer + ReviewDate string + + // DEPRECATED in version 2.0 of spec + // 9.3: Review Comment + // Cardinality: optional, one + ReviewComment string +} + +// Review2_2 is a Review section of an SPDX Document for version 2.2 of the spec. +// DEPRECATED in version 2.0 of spec; retained here for compatibility. +type Review2_2 struct { + + // DEPRECATED in version 2.0 of spec + // 9.1: Reviewer + // Cardinality: optional, one + Reviewer string + // including AnnotatorType: one of "Person", "Organization" or "Tool" + ReviewerType string + + // DEPRECATED in version 2.0 of spec + // 9.2: Review Date: YYYY-MM-DDThh:mm:ssZ + // Cardinality: conditional (mandatory, one) if there is a Reviewer + ReviewDate string + + // DEPRECATED in version 2.0 of spec + // 9.3: Review Comment + // Cardinality: optional, one + ReviewComment string +} diff --git a/vendor/github.com/spdx/tools-golang/spdx/snippet.go b/vendor/github.com/spdx/tools-golang/spdx/snippet.go new file mode 100644 index 000000000000..5fe37ca3ad8b --- /dev/null +++ b/vendor/github.com/spdx/tools-golang/spdx/snippet.go @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package spdx + +// Snippet2_1 is a Snippet section of an SPDX Document for version 2.1 of the spec. +type Snippet2_1 struct { + + // 5.1: Snippet SPDX Identifier: "SPDXRef-[idstring]" + // Cardinality: mandatory, one + SnippetSPDXIdentifier ElementID + + // 5.2: Snippet from File SPDX Identifier + // Cardinality: mandatory, one + SnippetFromFileSPDXIdentifier DocElementID + + // 5.3: Snippet Byte Range: [start byte]:[end byte] + // Cardinality: mandatory, one + SnippetByteRangeStart int + SnippetByteRangeEnd int + + // 5.4: Snippet Line Range: [start line]:[end line] + // Cardinality: optional, one + SnippetLineRangeStart int + SnippetLineRangeEnd int + + // 5.5: Snippet Concluded License: SPDX License Expression, "NONE" or "NOASSERTION" + // Cardinality: mandatory, one + SnippetLicenseConcluded string + + // 5.6: License Information in Snippet: SPDX License Expression, "NONE" or "NOASSERTION" + // Cardinality: optional, one or many + LicenseInfoInSnippet []string + + // 5.7: Snippet Comments on License + // Cardinality: optional, one + SnippetLicenseComments string + + // 5.8: Snippet Copyright Text: copyright notice(s) text, "NONE" or "NOASSERTION" + // Cardinality: mandatory, one + SnippetCopyrightText string + + // 5.9: Snippet Comment + // Cardinality: optional, one + SnippetComment string + + // 5.10: Snippet Name + // Cardinality: optional, one + SnippetName string +} + +// Snippet2_2 is a Snippet section of an SPDX Document for version 2.2 of the spec. +type Snippet2_2 struct { + + // 5.1: Snippet SPDX Identifier: "SPDXRef-[idstring]" + // Cardinality: mandatory, one + SnippetSPDXIdentifier ElementID + + // 5.2: Snippet from File SPDX Identifier + // Cardinality: mandatory, one + SnippetFromFileSPDXIdentifier DocElementID + + // 5.3: Snippet Byte Range: [start byte]:[end byte] + // Cardinality: mandatory, one + SnippetByteRangeStart int + SnippetByteRangeEnd int + + // 5.4: Snippet Line Range: [start line]:[end line] + // Cardinality: optional, one + SnippetLineRangeStart int + SnippetLineRangeEnd int + + // 5.5: Snippet Concluded License: SPDX License Expression, "NONE" or "NOASSERTION" + // Cardinality: mandatory, one + SnippetLicenseConcluded string + + // 5.6: License Information in Snippet: SPDX License Expression, "NONE" or "NOASSERTION" + // Cardinality: optional, one or many + LicenseInfoInSnippet []string + + // 5.7: Snippet Comments on License + // Cardinality: optional, one + SnippetLicenseComments string + + // 5.8: Snippet Copyright Text: copyright notice(s) text, "NONE" or "NOASSERTION" + // Cardinality: mandatory, one + SnippetCopyrightText string + + // 5.9: Snippet Comment + // Cardinality: optional, one + SnippetComment string + + // 5.10: Snippet Name + // Cardinality: optional, one + SnippetName string + + // 5.11: Snippet Attribution Text + // Cardinality: optional, one or many + SnippetAttributionTexts []string +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 1ff32b1f7d02..b1b3943da457 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -170,6 +170,7 @@ github.com/containerd/containerd/pkg/seed github.com/containerd/containerd/pkg/userns github.com/containerd/containerd/platforms github.com/containerd/containerd/reference +github.com/containerd/containerd/reference/docker github.com/containerd/containerd/remotes github.com/containerd/containerd/remotes/docker github.com/containerd/containerd/remotes/docker/auth @@ -285,6 +286,9 @@ github.com/docker/go/canonical/json github.com/docker/go-connections/nat github.com/docker/go-connections/sockets github.com/docker/go-connections/tlsconfig +# github.com/docker/go-imageinspect v0.0.0-00010101000000-000000000000 => github.com/crazy-max/go-imageinspect v0.0.0-20221202013537-618679aa0cae +## explicit; go 1.18 +github.com/docker/go-imageinspect # github.com/docker/go-metrics v0.0.1 ## explicit; go 1.11 github.com/docker/go-metrics @@ -395,6 +399,10 @@ github.com/hashicorp/hcl/v2/json # github.com/imdario/mergo v0.3.13 ## explicit; go 1.13 github.com/imdario/mergo +# github.com/in-toto/in-toto-golang v0.3.4-0.20220709202702-fa494aaa0add +## explicit; go 1.17 +github.com/in-toto/in-toto-golang/in_toto +github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2 # github.com/inconshreveable/mousetrap v1.0.1 ## explicit; go 1.18 github.com/inconshreveable/mousetrap @@ -569,12 +577,24 @@ github.com/prometheus/procfs/internal/fs github.com/prometheus/procfs/internal/util # github.com/rogpeppe/go-internal v1.8.1 ## explicit; go 1.16 +# github.com/secure-systems-lab/go-securesystemslib v0.4.0 +## explicit; go 1.17 +github.com/secure-systems-lab/go-securesystemslib/cjson +github.com/secure-systems-lab/go-securesystemslib/dsse # github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002 ## explicit github.com/serialx/hashring +# github.com/shibumi/go-pathspec v1.3.0 +## explicit; go 1.17 +github.com/shibumi/go-pathspec # github.com/sirupsen/logrus v1.9.0 ## explicit; go 1.13 github.com/sirupsen/logrus +# github.com/spdx/tools-golang v0.3.0 +## explicit; go 1.13 +github.com/spdx/tools-golang/jsonloader +github.com/spdx/tools-golang/jsonloader/parser2v2 +github.com/spdx/tools-golang/spdx # github.com/spf13/cobra v1.6.1 ## explicit; go 1.15 github.com/spf13/cobra @@ -1107,6 +1127,7 @@ sigs.k8s.io/yaml # github.com/aws/aws-sdk-go-v2/config => github.com/aws/aws-sdk-go-v2/config v1.15.5 # github.com/docker/cli => github.com/docker/cli v20.10.3-0.20220803220330-418ca3b4d46f+incompatible # github.com/docker/docker => github.com/docker/docker v20.10.3-0.20221006005007-99aa9bb766b5+incompatible +# github.com/docker/go-imageinspect => github.com/crazy-max/go-imageinspect v0.0.0-20221202013537-618679aa0cae # k8s.io/api => k8s.io/api v0.22.4 # k8s.io/apimachinery => k8s.io/apimachinery v0.22.4 # k8s.io/client-go => k8s.io/client-go v0.22.4