mirror of
https://github.com/anchore/grype
synced 2024-11-10 06:34:13 +00:00
chore: prune cosign dependency for grype builds (#1100)
* feat: segment cosign dependency for grype builds for faster build times Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
This commit is contained in:
parent
530762f2d2
commit
788ed965ec
22 changed files with 46 additions and 1790 deletions
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
|
@ -58,7 +58,7 @@ jobs:
|
|||
- name: Set correct version of Golang to use during CodeQL run
|
||||
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0
|
||||
with:
|
||||
go-version: '1.18'
|
||||
go-version: '1.19'
|
||||
check-latest: true
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
|
|
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
|
@ -9,7 +9,7 @@ on:
|
|||
- "v*"
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.18.x"
|
||||
GO_VERSION: "1.19.x"
|
||||
GO_STABLE_VERSION: true
|
||||
|
||||
permissions:
|
||||
|
|
2
.github/workflows/update-bootstrap-tools.yml
vendored
2
.github/workflows/update-bootstrap-tools.yml
vendored
|
@ -6,7 +6,7 @@ on:
|
|||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.18.x"
|
||||
GO_VERSION: "1.19.x"
|
||||
GO_STABLE_VERSION: true
|
||||
|
||||
permissions:
|
||||
|
|
2
.github/workflows/update-syft-release.yml
vendored
2
.github/workflows/update-syft-release.yml
vendored
|
@ -6,7 +6,7 @@ on:
|
|||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.18.x"
|
||||
GO_VERSION: "1.19.x"
|
||||
GO_STABLE_VERSION: true
|
||||
|
||||
permissions:
|
||||
|
|
2
.github/workflows/validations.yaml
vendored
2
.github/workflows/validations.yaml
vendored
|
@ -7,7 +7,7 @@ on:
|
|||
pull_request:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.18.x"
|
||||
GO_VERSION: "1.19.x"
|
||||
GO_STABLE_VERSION: true
|
||||
PYTHON_VERSION: "3.10"
|
||||
|
||||
|
|
33
README.md
33
README.md
|
@ -11,7 +11,6 @@
|
|||
[![Slack Invite](https://img.shields.io/badge/Slack-Join-blue?logo=slack)](https://anchore.com/slack)
|
||||
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/anchore/grype/badge)](https://api.securityscorecards.dev/projects/github.com/anchore/grype)
|
||||
|
||||
|
||||
A vulnerability scanner for container images and filesystems. Easily [install the binary](#installation) to try it out. Works with [Syft](https://github.com/anchore/syft), the powerful SBOM (software bill of materials) tool for container images and filesystems.
|
||||
|
||||
### Join our community meetings!
|
||||
|
@ -47,7 +46,6 @@ For commercial support options with Syft or Grype, please [contact Anchore](http
|
|||
- PHP (Composer)
|
||||
- Rust (Cargo)
|
||||
- Supports Docker, OCI and [Singularity](https://github.com/sylabs/singularity) image formats.
|
||||
- Consume SBOM [attestations](https://github.com/anchore/syft#sbom-attestation).
|
||||
|
||||
If you encounter an issue, please [let us know using the issue tracker](https://github.com/anchore/grype/issues).
|
||||
|
||||
|
@ -140,7 +138,6 @@ singularity:path/to/yourimage.sif read directly from a Singularity Image Fo
|
|||
dir:path/to/yourproject read directly from a path on disk (any directory)
|
||||
sbom:path/to/syft.json read Syft JSON from path on disk
|
||||
registry:yourrepo/yourimage:tag pull image directly from a registry (no container runtime required)
|
||||
att:attestation.json --key cosign.pub explicitly use the input as an attestation
|
||||
```
|
||||
|
||||
Use SBOMs for even faster vulnerability scanning in Grype:
|
||||
|
@ -164,27 +161,15 @@ use the `--distro <distro>:<version>` flag. A full example is:
|
|||
grype --add-cpes-if-none --distro alpine:3.10 sbom:some-apline-3.10.spdx.json
|
||||
```
|
||||
|
||||
### Scan attestations
|
||||
|
||||
Grype can scan SBOMs from attestations as long as they are encoded [in-toto envelopes](https://github.com/in-toto/attestation/blob/main/spec/README.md#envelope).
|
||||
|
||||
Examples:
|
||||
|
||||
```sh
|
||||
# generate cosign key pair
|
||||
cosign generate-key-pair # after that you'll have two files: cosign.key and cosign.pub
|
||||
|
||||
# attest an image with Syft and your cosign private key (cosign.key)
|
||||
syft attest --output json --key cosign.key alpine:latest > alpine.att.json
|
||||
|
||||
# scan an SBOM from an attestation file with the cosign public key (cosign.pub)
|
||||
grype alpine.json --key cosign.pub
|
||||
|
||||
# explicitly tell Grype the input is an attestation file with the scheme `att:`
|
||||
grype att:alpine.json --key cosign.pub
|
||||
|
||||
# generate an attestation for an image with Syft and pipe it into Grype, just because you can :)
|
||||
syft attest --output json --key cosign.key alpine:latest | grype --key cosign.pub
|
||||
### Working with attestations
|
||||
Grype supports scanning SBOMs as input via stdin. Users can use [cosign](https://github.com/sigstore/cosign) to verify attestations
|
||||
with an SBOM as its content to scan an image for vulnerabilities:
|
||||
```
|
||||
COSIGN_EXPERIMENTAL=1 cosign verify-attestation caphill4/java-spdx-tools:latest \
|
||||
| jq -r .payload \
|
||||
| base64 --decode \
|
||||
| jq -r .predicate.Data \
|
||||
| grype
|
||||
```
|
||||
|
||||
### Vulnerability Summary
|
||||
|
|
26
cmd/root.go
26
cmd/root.go
|
@ -67,7 +67,6 @@ var (
|
|||
Supports the following image sources:
|
||||
{{.appName}} yourrepo/yourimage:tag defaults to using images from a Docker daemon
|
||||
{{.appName}} path/to/yourproject a Docker tar, OCI tar, OCI directory, SIF container, or generic filesystem directory
|
||||
{{.appName}} attestation.json --key cosign.pub extract and scan SBOM from attestation file
|
||||
|
||||
You can also explicitly specify the scheme to use:
|
||||
{{.appName}} podman:yourrepo/yourimage:tag explicitly use the Podman daemon
|
||||
|
@ -79,7 +78,6 @@ You can also explicitly specify the scheme to use:
|
|||
{{.appName}} dir:path/to/yourproject read directly from a path on disk (any directory)
|
||||
{{.appName}} sbom:path/to/syft.json read Syft JSON from path on disk
|
||||
{{.appName}} registry:yourrepo/yourimage:tag pull image directly from a registry (no container runtime required)
|
||||
{{.appName}} att:attestation.json --key cosign.pub explicitly use the input as an attestation
|
||||
{{.appName}} purl:path/to/purl/file read a newline separated file of purls from a path on disk
|
||||
|
||||
You can also pipe in Syft JSON directly:
|
||||
|
@ -126,7 +124,6 @@ func setGlobalCliOptions() {
|
|||
rootCmd.PersistentFlags().CountVarP(&persistentOpts.Verbosity, "verbose", "v", "increase verbosity (-v = info, -vv = debug)")
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func setRootFlags(flags *pflag.FlagSet) {
|
||||
flags.StringP(
|
||||
"scope", "s", source.SquashedScope.String(),
|
||||
|
@ -190,15 +187,6 @@ func setRootFlags(flags *pflag.FlagSet) {
|
|||
"platform", "", "",
|
||||
"an optional platform specifier for container image sources (e.g. 'linux/arm64', 'linux/arm64/v8', 'arm64', 'linux')",
|
||||
)
|
||||
|
||||
flags.String(
|
||||
// NOTE(jonasagx): the default value is present even for SBOM inputs, causing errors.
|
||||
// To avoid extra syscalls for file validation I will drop it for now.
|
||||
// I know Syft has a default key value, but I am not certain that is a safe explicit
|
||||
// approach when attesting.
|
||||
"key", "",
|
||||
"File path to a public key to validate attestation",
|
||||
)
|
||||
}
|
||||
|
||||
func bindRootConfigOptions(flags *pflag.FlagSet) error {
|
||||
|
@ -254,10 +242,6 @@ func bindRootConfigOptions(flags *pflag.FlagSet) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := viper.BindPFlag("attestation.public-key", flags.Lookup("key")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -481,12 +465,10 @@ func getMatchers() []matcher.Matcher {
|
|||
func getProviderConfig() pkg.ProviderConfig {
|
||||
return pkg.ProviderConfig{
|
||||
SyftProviderConfig: pkg.SyftProviderConfig{
|
||||
RegistryOptions: appConfig.Registry.ToOptions(),
|
||||
Exclusions: appConfig.Exclusions,
|
||||
CatalogingOptions: appConfig.Search.ToConfig(),
|
||||
Platform: appConfig.Platform,
|
||||
AttestationPublicKey: appConfig.Attestation.PublicKey,
|
||||
AttestationIgnoreVerification: appConfig.Attestation.SkipVerification,
|
||||
RegistryOptions: appConfig.Registry.ToOptions(),
|
||||
Exclusions: appConfig.Exclusions,
|
||||
CatalogingOptions: appConfig.Search.ToConfig(),
|
||||
Platform: appConfig.Platform,
|
||||
},
|
||||
SynthesisConfig: pkg.SynthesisConfig{
|
||||
GenerateMissingCPEs: appConfig.GenerateMissingCPEs,
|
||||
|
|
138
go.mod
138
go.mod
|
@ -1,6 +1,6 @@
|
|||
module github.com/anchore/grype
|
||||
|
||||
go 1.18
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/CycloneDX/cyclonedx-go v0.7.1-0.20221222100750-41a1ac565cce
|
||||
|
@ -55,53 +55,27 @@ require (
|
|||
github.com/anchore/sqlite v1.4.6-0.20220607210448-bcc6ee5c4963
|
||||
github.com/anchore/syft v0.69.0
|
||||
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
|
||||
github.com/in-toto/in-toto-golang v0.4.1-0.20221018183522-731d0640b65f
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.4.0
|
||||
github.com/sigstore/cosign v1.13.1
|
||||
github.com/sigstore/sigstore v1.5.1
|
||||
)
|
||||
|
||||
require (
|
||||
bitbucket.org/creachadair/shell v0.0.7 // indirect
|
||||
cloud.google.com/go v0.105.0 // indirect
|
||||
cloud.google.com/go/compute v1.14.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cloud.google.com/go/iam v0.8.0 // indirect
|
||||
cloud.google.com/go/storage v1.27.0 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.28 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/DataDog/zstd v1.4.5 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||
github.com/ThalesIgnite/crypto11 v1.2.5 // indirect
|
||||
github.com/acobaugh/osrelease v0.1.0 // indirect
|
||||
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb // indirect
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
|
||||
github.com/aws/aws-sdk-go v1.44.180 // indirect
|
||||
github.com/benbjohnson/clock v1.1.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
|
||||
github.com/bgentry/speakeasy v0.1.0 // indirect
|
||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||
github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490 // indirect
|
||||
github.com/containerd/containerd v1.6.12 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.12.1 // indirect
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/cyberphone/json-canonicalization v0.0.0-20210823021906-dc406ceaf94b // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/cli v20.10.20+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
|
@ -109,112 +83,56 @@ require (
|
|||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.2 // indirect
|
||||
github.com/felixge/fgprof v0.9.3 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/fullstorydev/grpcurl v1.8.7 // indirect
|
||||
github.com/go-chi/chi v4.1.2+incompatible // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/analysis v0.21.4 // indirect
|
||||
github.com/go-openapi/errors v0.20.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||
github.com/go-openapi/loads v0.21.2 // indirect
|
||||
github.com/go-openapi/runtime v0.24.2 // indirect
|
||||
github.com/go-openapi/spec v0.20.7 // indirect
|
||||
github.com/go-openapi/strfmt v0.21.3 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/go-openapi/validate v0.22.0 // indirect
|
||||
github.com/go-playground/locales v0.14.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.11.0 // indirect
|
||||
github.com/go-restruct/restruct v1.2.0-alpha // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
|
||||
github.com/golang/glog v1.0.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/certificate-transparency-go v1.1.3 // indirect
|
||||
github.com/google/go-containerregistry v0.13.0 // indirect
|
||||
github.com/google/go-github/v45 v45.2.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/martian/v3 v3.3.2 // indirect
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
|
||||
github.com/google/trillian v1.5.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.7.0 // indirect
|
||||
github.com/googleapis/gnostic v0.5.5 // indirect
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
|
||||
github.com/hashicorp/go-safetemp v1.0.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/huandu/xstrings v1.3.3 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b // indirect
|
||||
github.com/jhump/protoreflect v1.14.0 // indirect
|
||||
github.com/jinzhu/copier v0.3.5 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.4 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/jonboulle/clockwork v0.3.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.15.11 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/knqyf263/go-rpmdb v0.0.0-20221030135625-4082a22221ce // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/letsencrypt/boulder v0.0.0-20221109233200-85aa52084eaf // indirect
|
||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/microsoft/go-rustaudit v0.0.0-20220730194248-4b17361d90a5 // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/nwaples/rardecode v1.1.0 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/onsi/gomega v1.19.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.13.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sassoftware/go-rpmutils v0.2.0 // indirect
|
||||
github.com/sassoftware/relic v0.0.0-20210427151427-dfb082b79b74 // indirect
|
||||
github.com/shibumi/go-pathspec v1.3.0 // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/sigstore/rekor v0.12.1-0.20220915152154-4bb6f441c1b2 // indirect
|
||||
github.com/soheilhy/cmux v0.1.5 // indirect
|
||||
github.com/spdx/tools-golang v0.4.0 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
|
@ -222,49 +140,16 @@ require (
|
|||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/sylabs/sif/v2 v2.8.1 // indirect
|
||||
github.com/sylabs/squashfs v0.6.1 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
|
||||
github.com/tent/canonical-json-go v0.0.0-20130607151641-96e4ba3a7613 // indirect
|
||||
github.com/thales-e-security/pool v0.0.2 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/theupdateframework/go-tuf v0.5.2-0.20220930112810-3890c1e7ace4 // indirect
|
||||
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect
|
||||
github.com/transparency-dev/merkle v0.0.1 // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
github.com/urfave/cli v1.22.7 // indirect
|
||||
github.com/vbatts/go-mtree v0.5.2 // indirect
|
||||
github.com/vbatts/tar-split v0.11.2 // indirect
|
||||
github.com/vifraa/gopom v0.2.1 // indirect
|
||||
github.com/xanzy/go-gitlab v0.73.1 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||
github.com/zclconf/go-cty v1.10.0 // indirect
|
||||
go.etcd.io/bbolt v1.3.6 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.6.0-alpha.0 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0 // indirect
|
||||
go.etcd.io/etcd/client/v2 v2.306.0-alpha.0 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.6.0-alpha.0 // indirect
|
||||
go.etcd.io/etcd/etcdctl/v3 v3.6.0-alpha.0 // indirect
|
||||
go.etcd.io/etcd/etcdutl/v3 v3.6.0-alpha.0 // indirect
|
||||
go.etcd.io/etcd/pkg/v3 v3.6.0-alpha.0 // indirect
|
||||
go.etcd.io/etcd/raft/v3 v3.6.0-alpha.0 // indirect
|
||||
go.etcd.io/etcd/server/v3 v3.6.0-alpha.0 // indirect
|
||||
go.etcd.io/etcd/tests/v3 v3.6.0-alpha.0 // indirect
|
||||
go.etcd.io/etcd/v3 v3.6.0-alpha.0 // indirect
|
||||
go.mongodb.org/mongo-driver v1.10.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0 // indirect
|
||||
go.opentelemetry.io/otel v1.7.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.7.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.7.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.7.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.7.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.7.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.16.0 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
go.uber.org/zap v1.23.0 // indirect
|
||||
go.uber.org/goleak v1.2.0 // indirect
|
||||
golang.org/x/crypto v0.5.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20220823124025-807a23277127 // indirect
|
||||
golang.org/x/mod v0.7.0 // indirect
|
||||
|
@ -281,18 +166,8 @@ require (
|
|||
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect
|
||||
google.golang.org/grpc v1.52.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/api v0.23.5 // indirect
|
||||
k8s.io/apimachinery v0.23.5 // indirect
|
||||
k8s.io/client-go v0.23.5 // indirect
|
||||
k8s.io/klog/v2 v2.60.1-0.20220317184644-43cc75f9ae89 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf // indirect
|
||||
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
|
||||
lukechampine.com/uint128 v1.1.1 // indirect
|
||||
modernc.org/cc/v3 v3.36.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.6 // indirect
|
||||
|
@ -303,7 +178,4 @@ require (
|
|||
modernc.org/sqlite v1.17.3 // indirect
|
||||
modernc.org/strutil v1.1.1 // indirect
|
||||
modernc.org/token v1.0.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
|
|
|
@ -3,12 +3,10 @@ package event
|
|||
import "github.com/wagoodman/go-partybus"
|
||||
|
||||
const (
|
||||
AppUpdateAvailable partybus.EventType = "grype-app-update-available"
|
||||
UpdateVulnerabilityDatabase partybus.EventType = "grype-update-vulnerability-database"
|
||||
VulnerabilityScanningStarted partybus.EventType = "grype-vulnerability-scanning-started"
|
||||
VulnerabilityScanningFinished partybus.EventType = "grype-vulnerability-scanning-finished"
|
||||
AttestationVerified partybus.EventType = "grype-attestation-signature-passed"
|
||||
AttestationVerificationSkipped partybus.EventType = "grype-attestation-verification-skipped"
|
||||
NonRootCommandFinished partybus.EventType = "grype-non-root-command-finished"
|
||||
DatabaseDiffingStarted partybus.EventType = "grype-database-diffing-started"
|
||||
AppUpdateAvailable partybus.EventType = "grype-app-update-available"
|
||||
UpdateVulnerabilityDatabase partybus.EventType = "grype-update-vulnerability-database"
|
||||
VulnerabilityScanningStarted partybus.EventType = "grype-vulnerability-scanning-started"
|
||||
VulnerabilityScanningFinished partybus.EventType = "grype-vulnerability-scanning-finished"
|
||||
NonRootCommandFinished partybus.EventType = "grype-non-root-command-finished"
|
||||
DatabaseDiffingStarted partybus.EventType = "grype-database-diffing-started"
|
||||
)
|
||||
|
|
|
@ -11,12 +11,10 @@ type ProviderConfig struct {
|
|||
}
|
||||
|
||||
type SyftProviderConfig struct {
|
||||
CatalogingOptions cataloger.Config
|
||||
RegistryOptions *image.RegistryOptions
|
||||
Platform string
|
||||
Exclusions []string
|
||||
AttestationPublicKey string
|
||||
AttestationIgnoreVerification bool
|
||||
CatalogingOptions cataloger.Config
|
||||
RegistryOptions *image.RegistryOptions
|
||||
Platform string
|
||||
Exclusions []string
|
||||
}
|
||||
|
||||
type SynthesisConfig struct {
|
||||
|
|
|
@ -2,26 +2,15 @@ package pkg
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/in-toto/in-toto-golang/in_toto"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
ssldsse "github.com/secure-systems-lab/go-securesystemslib/dsse"
|
||||
"github.com/sigstore/cosign/pkg/signature"
|
||||
"github.com/sigstore/cosign/pkg/types"
|
||||
"github.com/sigstore/sigstore/pkg/signature/dsse"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
|
||||
"github.com/anchore/grype/grype/event"
|
||||
"github.com/anchore/grype/internal"
|
||||
"github.com/anchore/grype/internal/bus"
|
||||
"github.com/anchore/grype/internal/log"
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
|
@ -36,7 +25,7 @@ func (e errEmptySBOM) Error() string {
|
|||
}
|
||||
|
||||
func syftSBOMProvider(userInput string, config ProviderConfig) ([]Package, Context, *sbom.SBOM, error) {
|
||||
s, err := getSBOM(userInput, config)
|
||||
s, err := getSBOM(userInput)
|
||||
if err != nil {
|
||||
return nil, Context{}, nil, err
|
||||
}
|
||||
|
@ -62,8 +51,8 @@ type inputInfo struct {
|
|||
Scheme string
|
||||
}
|
||||
|
||||
func getSBOM(userInput string, config ProviderConfig) (*sbom.SBOM, error) {
|
||||
reader, err := getSBOMReader(userInput, config)
|
||||
func getSBOM(userInput string) (*sbom.SBOM, error) {
|
||||
reader, err := getSBOMReader(userInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -80,44 +69,27 @@ func getSBOM(userInput string, config ProviderConfig) (*sbom.SBOM, error) {
|
|||
return s, nil
|
||||
}
|
||||
|
||||
func getSBOMReader(userInput string, config ProviderConfig) (r io.Reader, err error) {
|
||||
r, info, err := extractReaderAndInfo(userInput, config)
|
||||
func getSBOMReader(userInput string) (r io.Reader, err error) {
|
||||
r, _, err = extractReaderAndInfo(userInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if info != nil {
|
||||
if (info.Scheme == "sbom" || info.ContentType == "sbom") && config.AttestationPublicKey != "" {
|
||||
return nil, fmt.Errorf("key is meant for attestation verification, your input is a plain SBOM and doesn't need it")
|
||||
}
|
||||
|
||||
if info.Scheme == "att" && info.ContentType != "att" {
|
||||
return nil, fmt.Errorf("scheme specify an attestation but the content is not an attestation")
|
||||
}
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func extractReaderAndInfo(userInput string, config ProviderConfig) (io.Reader, *inputInfo, error) {
|
||||
func extractReaderAndInfo(userInput string) (io.Reader, *inputInfo, error) {
|
||||
switch {
|
||||
// the order of cases matter
|
||||
case userInput == "":
|
||||
// we only want to attempt reading in from stdin if the user has not specified other
|
||||
// options from the CLI, otherwise we should not assume there is any valid input from stdin.
|
||||
return decodeStdin(stdinReader(), config)
|
||||
return decodeStdin(stdinReader())
|
||||
|
||||
case explicitlySpecifyingSBOM(userInput):
|
||||
filepath := strings.TrimPrefix(userInput, "sbom:")
|
||||
return parseSBOM("sbom", filepath)
|
||||
|
||||
case explicitlySpecifyAttestation(userInput):
|
||||
path := strings.TrimPrefix(userInput, "att:")
|
||||
return parseAttestation("att", path, config)
|
||||
|
||||
case isPossibleAttestation(userInput):
|
||||
return parseAttestation("", userInput, config)
|
||||
|
||||
case isPossibleSBOM(userInput):
|
||||
return parseSBOM("", userInput)
|
||||
|
||||
|
@ -135,36 +107,13 @@ func parseSBOM(scheme, path string) (io.Reader, *inputInfo, error) {
|
|||
return r, info, nil
|
||||
}
|
||||
|
||||
func parseAttestation(scheme, path string, config ProviderConfig) (io.Reader, *inputInfo, error) {
|
||||
f, err := openFile(path)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
r, err := getSBOMFromAttestation(f, config)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
info := newInputInfo(scheme, "att")
|
||||
return r, info, nil
|
||||
}
|
||||
|
||||
func decodeStdin(r io.Reader, config ProviderConfig) (io.Reader, *inputInfo, error) {
|
||||
func decodeStdin(r io.Reader) (io.Reader, *inputInfo, error) {
|
||||
b, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed reading stdin: %w", err)
|
||||
}
|
||||
|
||||
reader := bytes.NewReader(b)
|
||||
if isDSSEEnvelope(reader) {
|
||||
_, err := reader.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse stdin: %w", err)
|
||||
}
|
||||
|
||||
reader, err := getSBOMFromAttestation(reader, config)
|
||||
return reader, newInputInfo("", "att"), err
|
||||
}
|
||||
|
||||
_, err = reader.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse stdin: %w", err)
|
||||
|
@ -217,81 +166,6 @@ func closeFile(f *os.File) {
|
|||
}
|
||||
}
|
||||
|
||||
func getSBOMFromAttestation(r io.Reader, config ProviderConfig) (io.Reader, error) {
|
||||
env := &ssldsse.Envelope{}
|
||||
err := json.NewDecoder(r).Decode(env)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode attestation envelope: %w", err)
|
||||
}
|
||||
|
||||
if env.PayloadType != types.IntotoPayloadType {
|
||||
return nil, fmt.Errorf("invalid attestation payload")
|
||||
}
|
||||
|
||||
if !config.AttestationIgnoreVerification {
|
||||
if config.AttestationPublicKey == "" {
|
||||
return nil, fmt.Errorf("--key parameter is required to validate attestations")
|
||||
}
|
||||
|
||||
if err := verifyAttestationSignature(env, config.AttestationPublicKey); err != nil {
|
||||
return nil, fmt.Errorf("failed to verify attestation signature: %w", err)
|
||||
}
|
||||
} else {
|
||||
bus.Publish(partybus.Event{
|
||||
Type: event.AttestationVerificationSkipped,
|
||||
})
|
||||
}
|
||||
|
||||
b, err := base64.StdEncoding.DecodeString(env.Payload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode attestation payload: %w", err)
|
||||
}
|
||||
|
||||
// a statement contains predicate and subject, the digest present in the subject
|
||||
// comes from RepoDigests -- according to Syft's implementation
|
||||
stmt := &in_toto.Statement{}
|
||||
err = json.Unmarshal(b, stmt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract in-toto statement: %w", err)
|
||||
}
|
||||
|
||||
pb, err := json.Marshal(stmt.Predicate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bytes.NewReader(pb), nil
|
||||
}
|
||||
|
||||
func verifyAttestationSignature(env *ssldsse.Envelope, key string) error {
|
||||
pubKey, err := signature.PublicKeyFromKeyRef(context.Background(), key)
|
||||
if err != nil {
|
||||
log.Warnf("failed to get public get from key reference: %v", err)
|
||||
return fmt.Errorf("cannot decode public key")
|
||||
}
|
||||
|
||||
dssev, err := ssldsse.NewEnvelopeVerifier(&dsse.VerifierAdapter{SignatureVerifier: pubKey})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to verify payload: %w", err)
|
||||
}
|
||||
|
||||
acceptedKeys, err := dssev.Verify(env)
|
||||
if err != nil {
|
||||
log.Warnf("key and signature don't match: %v", err)
|
||||
return fmt.Errorf("key and signature don't match")
|
||||
}
|
||||
|
||||
for i, s := range acceptedKeys {
|
||||
log.Infof("verified signature (%d/%d): key id %s, sig: %s", i+1, len(env.Signatures), s.KeyID, s.Sig)
|
||||
}
|
||||
|
||||
bus.Publish(partybus.Event{
|
||||
Type: event.AttestationVerified,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func openFile(path string) (*os.File, error) {
|
||||
expandedPath, err := homedir.Expand(path)
|
||||
if err != nil {
|
||||
|
@ -327,28 +201,6 @@ func isPossibleSBOM(userInput string) bool {
|
|||
return isAncestorOfMimetype(mType, "text/plain")
|
||||
}
|
||||
|
||||
func isPossibleAttestation(userInput string) bool {
|
||||
f, err := openFile(userInput)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer closeFile(f)
|
||||
|
||||
return isDSSEEnvelope(f)
|
||||
}
|
||||
|
||||
// isDSSEEnvelope validates r contains a DSSE envelope, which is the best
|
||||
// indicator for an attestations created by Syft
|
||||
func isDSSEEnvelope(r io.Reader) bool {
|
||||
env := &ssldsse.Envelope{}
|
||||
err := json.NewDecoder(r).Decode(env)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return env.PayloadType == types.IntotoPayloadType
|
||||
}
|
||||
|
||||
func isAncestorOfMimetype(mType *mimetype.MIME, expected string) bool {
|
||||
for cur := mType; cur != nil; cur = cur.Parent() {
|
||||
if cur.Is(expected) {
|
||||
|
@ -361,7 +213,3 @@ func isAncestorOfMimetype(mType *mimetype.MIME, expected string) bool {
|
|||
func explicitlySpecifyingSBOM(userInput string) bool {
|
||||
return strings.HasPrefix(userInput, "sbom:")
|
||||
}
|
||||
|
||||
func explicitlySpecifyAttestation(userInput string) bool {
|
||||
return strings.HasPrefix(userInput, "att:")
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
|
@ -22,182 +21,6 @@ func assertAs(expected string) assert.ErrorAssertionFunc {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDecodeStdin(t *testing.T) {
|
||||
tests := []struct {
|
||||
Name string
|
||||
Input string
|
||||
Key string
|
||||
WantErr assert.ErrorAssertionFunc
|
||||
PkgsLen int
|
||||
}{
|
||||
{
|
||||
Name: "no key",
|
||||
Input: "test-fixtures/alpine.att.json",
|
||||
WantErr: assertAs("--key parameter is required to validate attestations"),
|
||||
},
|
||||
{
|
||||
Name: "happy path",
|
||||
Input: "test-fixtures/alpine.att.json",
|
||||
Key: "test-fixtures/cosign.pub",
|
||||
WantErr: assert.NoError,
|
||||
PkgsLen: 14,
|
||||
},
|
||||
{
|
||||
Name: "cycloneDX format",
|
||||
Input: "test-fixtures/alpine.cdx.att.json",
|
||||
Key: "test-fixtures/cosign.pub",
|
||||
WantErr: assert.NoError,
|
||||
PkgsLen: 14,
|
||||
},
|
||||
{
|
||||
Name: "broken key",
|
||||
Input: "test-fixtures/alpine.att.json",
|
||||
Key: "test-fixtures/cosign_broken.pub",
|
||||
WantErr: assertAs("failed to verify attestation signature: cannot decode public key"),
|
||||
},
|
||||
{
|
||||
Name: "different but valid key",
|
||||
Input: "test-fixtures/alpine.att.json",
|
||||
Key: "test-fixtures/another_cosign.pub",
|
||||
WantErr: assertAs("failed to verify attestation signature: key and signature don't match"),
|
||||
},
|
||||
{
|
||||
Name: "sbom with intoto mime string",
|
||||
Input: "test-fixtures/sbom-with-intoto-string.json",
|
||||
WantErr: assert.NoError,
|
||||
PkgsLen: 4,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
f, err := os.Open(tt.Input)
|
||||
require.NoError(t, err)
|
||||
r, info, err := decodeStdin(f, ProviderConfig{
|
||||
SyftProviderConfig: SyftProviderConfig{
|
||||
AttestationPublicKey: tt.Key,
|
||||
},
|
||||
})
|
||||
tt.WantErr(t, err)
|
||||
|
||||
if err == nil {
|
||||
require.NotNil(t, info)
|
||||
sbom, format, err := syft.Decode(r)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, format)
|
||||
assert.Len(t, FromCatalog(sbom.Artifacts.PackageCatalog, SynthesisConfig{}), tt.PkgsLen)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAttestation(t *testing.T) {
|
||||
tests := []struct {
|
||||
Name string
|
||||
Input string
|
||||
Key string
|
||||
WantErr assert.ErrorAssertionFunc
|
||||
PkgsLen int
|
||||
}{
|
||||
{
|
||||
Name: "happy path with scheme",
|
||||
Input: "att:test-fixtures/alpine.att.json",
|
||||
Key: "test-fixtures/cosign.pub",
|
||||
WantErr: assert.NoError,
|
||||
PkgsLen: 14,
|
||||
},
|
||||
{
|
||||
Name: "no scheme and no key",
|
||||
Input: "test-fixtures/alpine.att.json",
|
||||
WantErr: assertAs("--key parameter is required to validate attestations"),
|
||||
},
|
||||
{
|
||||
Name: "happy path without scheme",
|
||||
Input: "test-fixtures/alpine.att.json",
|
||||
Key: "test-fixtures/cosign.pub",
|
||||
WantErr: assert.NoError,
|
||||
PkgsLen: 14,
|
||||
},
|
||||
{
|
||||
Name: "cycloneDX format",
|
||||
Input: "test-fixtures/alpine.cdx.att.json",
|
||||
Key: "test-fixtures/cosign.pub",
|
||||
WantErr: assert.NoError,
|
||||
PkgsLen: 14,
|
||||
},
|
||||
{
|
||||
Name: "broken key",
|
||||
Input: "test-fixtures/alpine.att.json",
|
||||
Key: "test-fixtures/cosign_broken.pub",
|
||||
WantErr: assertAs("failed to verify attestation signature: cannot decode public key"),
|
||||
},
|
||||
{
|
||||
Name: "different but valid key",
|
||||
Input: "test-fixtures/alpine.att.json",
|
||||
Key: "test-fixtures/another_cosign.pub",
|
||||
WantErr: assertAs("failed to verify attestation signature: key and signature don't match"),
|
||||
},
|
||||
{
|
||||
Name: "not an attestation but has the scheme",
|
||||
Input: "att:test-fixtures/syft-spring.json",
|
||||
WantErr: assertAs("invalid attestation payload"),
|
||||
},
|
||||
{
|
||||
Name: "not an attestation but has key",
|
||||
Input: "test-fixtures/syft-spring.json",
|
||||
Key: "test-fixtures/cosign.pub",
|
||||
WantErr: assertAs("key is meant for attestation verification, your input is a plain SBOM and doesn't need it"),
|
||||
},
|
||||
{
|
||||
Name: "not an attestation but has key and scheme",
|
||||
Input: "att:test-fixtures/syft-spring.json",
|
||||
Key: "test-fixtures/cosign.pub",
|
||||
WantErr: assertAs("invalid attestation payload"),
|
||||
},
|
||||
{
|
||||
Name: "tampered attestation payload",
|
||||
Input: "att:test-fixtures/alpine-tampered.att.json",
|
||||
Key: "test-fixtures/cosign.pub",
|
||||
WantErr: assertAs("failed to verify attestation signature: key and signature don't match"),
|
||||
},
|
||||
{
|
||||
Name: "tampered envelope predicate type",
|
||||
Input: "att:test-fixtures/alpine-tampered.cdx.att.json",
|
||||
Key: "test-fixtures/cosign.pub",
|
||||
WantErr: assertAs("failed to verify attestation signature: key and signature don't match"),
|
||||
},
|
||||
{
|
||||
Name: "sbom with intoto mime string",
|
||||
Input: "test-fixtures/sbom-with-intoto-string.json",
|
||||
WantErr: assert.NoError,
|
||||
PkgsLen: 4,
|
||||
},
|
||||
{
|
||||
Name: "empty file",
|
||||
Input: "test-fixtures/empty.json",
|
||||
WantErr: assertAs("cannot provide packages from the given source"),
|
||||
},
|
||||
{
|
||||
Name: "invalid json",
|
||||
Input: "test-fixtures/empty.json",
|
||||
WantErr: assertAs("cannot provide packages from the given source"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
pkgs, _, _, err := syftSBOMProvider(tt.Input, ProviderConfig{
|
||||
SyftProviderConfig: SyftProviderConfig{
|
||||
AttestationPublicKey: tt.Key,
|
||||
},
|
||||
})
|
||||
tt.WantErr(t, err)
|
||||
require.Len(t, pkgs, tt.PkgsLen)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestParseSyftJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
Fixture string
|
||||
|
@ -442,6 +265,6 @@ func TestGetSBOMReader_EmptySBOM(t *testing.T) {
|
|||
filepath := sbomFile.Name()
|
||||
userInput := "sbom:" + filepath
|
||||
|
||||
_, err = getSBOMReader(userInput, ProviderConfig{})
|
||||
_, err = getSBOMReader(userInput)
|
||||
assert.ErrorAs(t, err, &errEmptySBOM{})
|
||||
}
|
||||
|
|
|
@ -53,7 +53,6 @@ type Application struct {
|
|||
FailOnSeverity *vulnerability.Severity `yaml:"-" json:"-"`
|
||||
Registry registry `yaml:"registry" json:"registry" mapstructure:"registry"`
|
||||
Log logging `yaml:"log" json:"log" mapstructure:"log"`
|
||||
Attestation Attestation `yaml:"attestation" json:"attestation" mapstructure:"attestation"`
|
||||
ShowSuppressed bool `yaml:"show-suppressed" json:"show-suppressed" mapstructure:"show-suppressed"`
|
||||
ByCVE bool `yaml:"by-cve" json:"by-cve" mapstructure:"by-cve"` // --by-cve, indicates if the original match vulnerability IDs should be preserved or the CVE should be used instead
|
||||
}
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
package config
|
||||
|
||||
type Attestation struct {
|
||||
PublicKey string `yaml:"public-key" json:"public-key" mapstructure:"public-key"`
|
||||
SkipVerification bool `yaml:"skip-verification" json:"skip-verification" mapstructure:"skip-verification"`
|
||||
}
|
|
@ -57,29 +57,6 @@ func TestSBOMInput_AsArgument(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAttestationInput_AsArgument(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
}{
|
||||
{
|
||||
name: "no scheme",
|
||||
args: []string{"./test-fixtures/alpine.att.json", "--key", "./test-fixtures/cosign.pub"},
|
||||
},
|
||||
{
|
||||
name: "with scheme",
|
||||
args: []string{"att:test-fixtures/alpine.att.json", "--key", "./test-fixtures/cosign.pub"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cmd := getGrypeCommand(t, tt.args...)
|
||||
assertCommandExecutionSuccess(t, cmd)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSBOMInput_FromStdin(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -88,48 +65,6 @@ func TestSBOMInput_FromStdin(t *testing.T) {
|
|||
wantErr require.ErrorAssertionFunc
|
||||
wantOutput string
|
||||
}{
|
||||
{
|
||||
name: "no schema and no key",
|
||||
input: "./test-fixtures/alpine.att.json",
|
||||
args: []string{"-c", "../grype-test-config.yaml"},
|
||||
wantErr: require.Error,
|
||||
wantOutput: "--key parameter is required to validate attestations",
|
||||
},
|
||||
{
|
||||
name: "cycloneDX format",
|
||||
input: "test-fixtures/alpine.cdx.att.json",
|
||||
args: []string{
|
||||
"-c", "../grype-test-config.yaml",
|
||||
"--key", "./test-fixtures/cosign.pub",
|
||||
},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "broken key",
|
||||
input: "test-fixtures/alpine.att.json",
|
||||
args: []string{
|
||||
"-c", "../grype-test-config.yaml",
|
||||
"--key", "./test-fixtures/cosign_broken.pub",
|
||||
},
|
||||
wantErr: require.Error,
|
||||
wantOutput: "failed to verify attestation signature: cannot decode public key",
|
||||
},
|
||||
{
|
||||
name: "different but valid key",
|
||||
input: "test-fixtures/alpine.att.json",
|
||||
args: []string{
|
||||
"-c", "../grype-test-config.yaml",
|
||||
"--key", "./test-fixtures/another_cosign.pub",
|
||||
},
|
||||
wantErr: require.Error,
|
||||
wantOutput: "failed to verify attestation signature: key and signature don't match",
|
||||
},
|
||||
{
|
||||
name: "sbom with intoto mime string",
|
||||
input: "./test-fixtures/sbom-with-intoto-string.json",
|
||||
args: []string{"-c", "../grype-test-config.yaml"},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "empty file",
|
||||
input: "./test-fixtures/empty.json",
|
||||
|
@ -143,28 +78,6 @@ func TestSBOMInput_FromStdin(t *testing.T) {
|
|||
args: []string{"-c", "../grype-test-config.yaml"},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "sbom with unused attestation key",
|
||||
input: "./test-fixtures/sbom-ubuntu-20.04--pruned.json",
|
||||
args: []string{
|
||||
"-c", "../grype-test-config.yaml",
|
||||
"--key", "./test-fixtures/cosign.pub"},
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "attestation",
|
||||
input: "./test-fixtures/alpine.att.json",
|
||||
args: []string{
|
||||
"-c", "../grype-test-config.yaml",
|
||||
"--key", "./test-fixtures/cosign.pub"},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "attestation without key validation",
|
||||
input: "./test-fixtures/alpine.att.json",
|
||||
args: []string{"-c", "../ignore-att-signature.yaml"},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,266 +0,0 @@
|
|||
{
|
||||
"artifacts": [
|
||||
{
|
||||
"name": "gcc-10-base",
|
||||
"version": "10.2.0-5ubuntu1~20.04",
|
||||
"type": "deb",
|
||||
"foundBy": "dpkgdb-cataloger",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/var/lib/dpkg/status",
|
||||
"layerID": "sha256:9f32931c9d28f10104a8eb1330954ba90e76d92b02c5256521ba864feec14009"
|
||||
},
|
||||
{
|
||||
"path": "/var/lib/dpkg/info/gcc-10-base:amd64.md5sums",
|
||||
"layerID": "sha256:9f32931c9d28f10104a8eb1330954ba90e76d92b02c5256521ba864feec14009"
|
||||
},
|
||||
{
|
||||
"path": "/usr/share/doc/gcc-10-base/copyright",
|
||||
"layerID": "sha256:9f32931c9d28f10104a8eb1330954ba90e76d92b02c5256521ba864feec14009"
|
||||
}
|
||||
],
|
||||
"licenses": [],
|
||||
"language": "",
|
||||
"cpes": [
|
||||
"cpe:2.3:a:gcc-10-base:gcc-10-base:10.2.0-5ubuntu1~20.04:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:*:gcc-10-base:10.2.0-5ubuntu1~20.04:*:*:*:*:*:*:*"
|
||||
],
|
||||
"purl": "pkg:deb/ubuntu/gcc-10-base@10.2.0-5ubuntu1~20.04?arch=amd64",
|
||||
"metadataType": "DpkgMetadata",
|
||||
"metadata": {
|
||||
"package": "gcc-10-base",
|
||||
"source": "gcc-10",
|
||||
"version": "10.2.0-5ubuntu1~20.04",
|
||||
"sourceVersion": "",
|
||||
"architecture": "amd64",
|
||||
"maintainer": "application/vnd.in-toto+json this is here to make grype think it is an attestation",
|
||||
"installedSize": 260,
|
||||
"files": [
|
||||
{
|
||||
"path": "/usr/share/doc/gcc-10-base/README.Debian.amd64.gz",
|
||||
"md5": "3c03902e06eef5dcfe3005376c23a120"
|
||||
},
|
||||
{
|
||||
"path": "/usr/share/doc/gcc-10-base/TODO.Debian",
|
||||
"md5": "8afe308ec72834f3c24b209fbc4d149e"
|
||||
},
|
||||
{
|
||||
"path": "/usr/share/doc/gcc-10-base/changelog.Debian.gz",
|
||||
"md5": "0e3cbc1152a18bddf7c24fe3913866c6"
|
||||
},
|
||||
{
|
||||
"path": "/usr/share/doc/gcc-10-base/copyright",
|
||||
"md5": "a80ca2e181b9eecc3e4d373fd7ca59f2"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "hostname",
|
||||
"version": "3.23",
|
||||
"type": "deb",
|
||||
"foundBy": "dpkgdb-cataloger",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/var/lib/dpkg/status",
|
||||
"layerID": "sha256:9f32931c9d28f10104a8eb1330954ba90e76d92b02c5256521ba864feec14009"
|
||||
},
|
||||
{
|
||||
"path": "/var/lib/dpkg/info/hostname.md5sums",
|
||||
"layerID": "sha256:9f32931c9d28f10104a8eb1330954ba90e76d92b02c5256521ba864feec14009"
|
||||
},
|
||||
{
|
||||
"path": "/usr/share/doc/hostname/copyright",
|
||||
"layerID": "sha256:9f32931c9d28f10104a8eb1330954ba90e76d92b02c5256521ba864feec14009"
|
||||
}
|
||||
],
|
||||
"licenses": [],
|
||||
"language": "",
|
||||
"cpes": [
|
||||
"cpe:2.3:a:hostname:hostname:3.23:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:*:hostname:3.23:*:*:*:*:*:*:*"
|
||||
],
|
||||
"purl": "pkg:deb/ubuntu/hostname@3.23?arch=amd64",
|
||||
"metadataType": "DpkgMetadata",
|
||||
"metadata": {
|
||||
"package": "hostname",
|
||||
"source": "",
|
||||
"version": "3.23",
|
||||
"sourceVersion": "",
|
||||
"architecture": "amd64",
|
||||
"maintainer": "Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>",
|
||||
"installedSize": 54,
|
||||
"files": [
|
||||
{
|
||||
"path": "/bin/hostname",
|
||||
"md5": "1ce73d718e3dccc1aaa7bce6ae2ef0a7"
|
||||
},
|
||||
{
|
||||
"path": "/usr/share/doc/hostname/changelog.gz",
|
||||
"md5": "087a3eabd7427692c216a5d7a4341127"
|
||||
},
|
||||
{
|
||||
"path": "/usr/share/doc/hostname/copyright",
|
||||
"md5": "460b6a1df2db2b5e80f05a44ec21c62f"
|
||||
},
|
||||
{
|
||||
"path": "/usr/share/man/man1/hostname.1.gz",
|
||||
"md5": "62e6be6a928b4b9f2a985778fee171fd"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "libacl1",
|
||||
"version": "2.2.53-6",
|
||||
"type": "deb",
|
||||
"foundBy": "dpkgdb-cataloger",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/var/lib/dpkg/status",
|
||||
"layerID": "sha256:9f32931c9d28f10104a8eb1330954ba90e76d92b02c5256521ba864feec14009"
|
||||
},
|
||||
{
|
||||
"path": "/var/lib/dpkg/info/libacl1:amd64.md5sums",
|
||||
"layerID": "sha256:9f32931c9d28f10104a8eb1330954ba90e76d92b02c5256521ba864feec14009"
|
||||
},
|
||||
{
|
||||
"path": "/usr/share/doc/libacl1/copyright",
|
||||
"layerID": "sha256:9f32931c9d28f10104a8eb1330954ba90e76d92b02c5256521ba864feec14009"
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
"GPL-2+",
|
||||
"LGPL-2+"
|
||||
],
|
||||
"language": "",
|
||||
"cpes": [
|
||||
"cpe:2.3:a:libacl1:libacl1:2.2.53-6:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:*:libacl1:2.2.53-6:*:*:*:*:*:*:*"
|
||||
],
|
||||
"purl": "pkg:deb/ubuntu/libacl1@2.2.53-6?arch=amd64",
|
||||
"metadataType": "DpkgMetadata",
|
||||
"metadata": {
|
||||
"package": "libacl1",
|
||||
"source": "acl",
|
||||
"version": "2.2.53-6",
|
||||
"sourceVersion": "",
|
||||
"architecture": "amd64",
|
||||
"maintainer": "Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>",
|
||||
"installedSize": 70,
|
||||
"files": [
|
||||
{
|
||||
"path": "/usr/lib/x86_64-linux-gnu/libacl.so.1.1.2253",
|
||||
"md5": "e77bf61a72656a594ef49768a7d6097b"
|
||||
},
|
||||
{
|
||||
"path": "/usr/share/doc/libacl1/changelog.Debian.gz",
|
||||
"md5": "65de3b787d67d4755ad3ae0584aee9f2"
|
||||
},
|
||||
{
|
||||
"path": "/usr/share/doc/libacl1/copyright",
|
||||
"md5": "40822d07cf4c0fb9ab13c2bebf51d981"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "libattr1",
|
||||
"version": "1:2.4.48-5",
|
||||
"type": "deb",
|
||||
"foundBy": "dpkgdb-cataloger",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/var/lib/dpkg/status",
|
||||
"layerID": "sha256:9f32931c9d28f10104a8eb1330954ba90e76d92b02c5256521ba864feec14009"
|
||||
},
|
||||
{
|
||||
"path": "/var/lib/dpkg/info/libattr1:amd64.md5sums",
|
||||
"layerID": "sha256:9f32931c9d28f10104a8eb1330954ba90e76d92b02c5256521ba864feec14009"
|
||||
},
|
||||
{
|
||||
"path": "/usr/share/doc/libattr1/copyright",
|
||||
"layerID": "sha256:9f32931c9d28f10104a8eb1330954ba90e76d92b02c5256521ba864feec14009"
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
"GPL-2+",
|
||||
"LGPL-2+"
|
||||
],
|
||||
"language": "",
|
||||
"cpes": [
|
||||
"cpe:2.3:a:libattr1:libattr1:1:2.4.48-5:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:*:libattr1:1:2.4.48-5:*:*:*:*:*:*:*"
|
||||
],
|
||||
"purl": "pkg:deb/ubuntu/libattr1@1:2.4.48-5?arch=amd64",
|
||||
"metadataType": "DpkgMetadata",
|
||||
"metadata": {
|
||||
"package": "libattr1",
|
||||
"source": "attr",
|
||||
"version": "1:2.4.48-5",
|
||||
"sourceVersion": "",
|
||||
"architecture": "amd64",
|
||||
"maintainer": "Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>",
|
||||
"installedSize": 57,
|
||||
"files": [
|
||||
{
|
||||
"path": "/usr/lib/x86_64-linux-gnu/libattr.so.1.1.2448",
|
||||
"md5": "708453da8ebde1aaca2ca69c04d4c0a8"
|
||||
},
|
||||
{
|
||||
"path": "/usr/share/doc/libattr1/changelog.Debian.gz",
|
||||
"md5": "6465a4cda28287d4ea9979b530648ee3"
|
||||
},
|
||||
{
|
||||
"path": "/usr/share/doc/libattr1/copyright",
|
||||
"md5": "1e0c5c8b55170890f960aad90336aaed"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"source": {
|
||||
"type": "image",
|
||||
"target": {
|
||||
"userInput": "ubuntu:20.04",
|
||||
"imageID": "sha256:f63181f19b2fe819156dcb068b3b5bc036820bec7014c5f77277cfa341d4cb5e",
|
||||
"manifestDigest": "sha256:5146935f9248826d44dfc2489abfd5f4bdfbc319a738c04dfe1ef071f228a1ac",
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"tags": [
|
||||
"ubuntu:20.04"
|
||||
],
|
||||
"imageSize": 72898411,
|
||||
"scope": "Squashed",
|
||||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:9f32931c9d28f10104a8eb1330954ba90e76d92b02c5256521ba864feec14009",
|
||||
"size": 72897593
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:dbf2c0f42a39b60301f6d3936f7f8adb59bb97d31ec11cc4a049ce81155fef89",
|
||||
"size": 811
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:02473afd360bd5391fa51b6e7849ce88732ae29f50f3630c3551f528eba66d1e",
|
||||
"size": 7
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"distro": {
|
||||
"name": "ubuntu",
|
||||
"version": "20.04",
|
||||
"idLike": "debian"
|
||||
},
|
||||
"descriptor": {
|
||||
"name": "syft",
|
||||
"version": "0.12.7"
|
||||
},
|
||||
"schema": {
|
||||
"version": "1.0.1",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-1.0.1.json"
|
||||
}
|
||||
}
|
|
@ -1,3 +1 @@
|
|||
check-for-app-update: false
|
||||
attestation:
|
||||
skip-verification: true
|
||||
check-for-app-update: false
|
|
@ -133,42 +133,6 @@ func (r *Handler) VulnerabilityScanningStartedHandler(ctx context.Context, fr *f
|
|||
return nil
|
||||
}
|
||||
|
||||
func (r *Handler) VerifyAttestationSignature(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error {
|
||||
line, err := fr.Append()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
spin := color.Green.Sprint(completedStatus)
|
||||
title := tileFormat.Sprint("Attestation verified")
|
||||
_, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate+"%s", spin, title, ""))
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Handler) SkippedAttestationVerification(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error {
|
||||
line, err := fr.Append()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
spin := color.Green.Sprint(completedStatus)
|
||||
title := tileFormat.Sprint("Skipped attestation verification")
|
||||
_, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate+"%s", spin, title, ""))
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//nolint:dupl
|
||||
func (r *Handler) DatabaseDiffingStartedHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error {
|
||||
monitor, err := grypeEventParsers.ParseDatabaseDiffingStarted(event)
|
||||
|
|
|
@ -25,8 +25,6 @@ func (r *Handler) RespondsTo(event partybus.Event) bool {
|
|||
switch event.Type {
|
||||
case grypeEvent.VulnerabilityScanningStarted,
|
||||
grypeEvent.UpdateVulnerabilityDatabase,
|
||||
grypeEvent.AttestationVerified,
|
||||
grypeEvent.AttestationVerificationSkipped,
|
||||
grypeEvent.DatabaseDiffingStarted:
|
||||
return true
|
||||
default:
|
||||
|
@ -40,10 +38,6 @@ func (r *Handler) Handle(ctx context.Context, fr *frame.Frame, event partybus.Ev
|
|||
return r.VulnerabilityScanningStartedHandler(ctx, fr, event, wg)
|
||||
case grypeEvent.UpdateVulnerabilityDatabase:
|
||||
return r.UpdateVulnerabilityDatabaseHandler(ctx, fr, event, wg)
|
||||
case grypeEvent.AttestationVerified:
|
||||
return r.VerifyAttestationSignature(ctx, fr, event, wg)
|
||||
case grypeEvent.AttestationVerificationSkipped:
|
||||
return r.SkippedAttestationVerification(ctx, fr, event, wg)
|
||||
case grypeEvent.DatabaseDiffingStarted:
|
||||
return r.DatabaseDiffingStartedHandler(ctx, fr, event, wg)
|
||||
default:
|
||||
|
|
Loading…
Reference in a new issue