From ccc0e33c8e96fbbcc17e50f266755a360d8bcc8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Wed, 19 Jul 2023 17:45:24 -0600 Subject: [PATCH 01/29] go.mod: Pull OpenVEX go modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit pulls the OpenVEX libraries into the grype source. Signed-off-by: Adolfo García Veytia (Puerco) --- go.mod | 40 +++++++++++++++++++++------------- go.sum | 69 ++++++++++++++++++++++++++++++++-------------------------- 2 files changed, 63 insertions(+), 46 deletions(-) diff --git a/go.mod b/go.mod index 4cd8037f783..d0ac8580f20 100644 --- a/go.mod +++ b/go.mod @@ -62,16 +62,21 @@ require ( require modernc.org/sqlite v1.25.0 require ( - cloud.google.com/go v0.110.0 // indirect - cloud.google.com/go/compute v1.19.3 // indirect + github.com/google/go-containerregistry v0.16.1 + github.com/openvex/go-vex v0.2.5 +) + +require ( + cloud.google.com/go v0.110.2 // indirect + cloud.google.com/go/compute v1.20.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v0.13.0 // indirect - cloud.google.com/go/storage v1.28.1 // indirect + cloud.google.com/go/iam v1.1.0 // indirect + cloud.google.com/go/storage v1.29.0 // indirect dario.cat/mergo v1.0.0 // indirect github.com/DataDog/zstd v1.4.5 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver v1.5.0 // indirect - github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 // indirect github.com/acobaugh/osrelease v0.1.0 // indirect @@ -82,7 +87,7 @@ require ( github.com/andybalholm/brotli v1.0.4 // indirect github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 // indirect github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 // indirect - github.com/aws/aws-sdk-go v1.44.180 // indirect + github.com/aws/aws-sdk-go v1.44.288 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/becheran/wildmatch-go v1.0.0 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect @@ -116,12 +121,11 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/go-containerregistry v0.16.1 // indirect github.com/google/licensecheck v0.3.1 // indirect github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 // indirect - github.com/google/s2a-go v0.1.3 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect - github.com/googleapis/gax-go/v2 v2.8.0 // indirect + github.com/google/s2a-go v0.1.4 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.4 // indirect + github.com/googleapis/gax-go/v2 v2.11.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect @@ -154,15 +158,17 @@ require ( 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/moby/term v0.5.0 // indirect github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.15.2 // indirect github.com/nwaples/rardecode v1.1.0 // indirect github.com/onsi/ginkgo v1.16.5 // indirect - github.com/onsi/gomega v1.19.0 // indirect + github.com/onsi/gomega v1.27.4 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc3 // indirect + github.com/package-url/packageurl-go v0.1.1 // indirect github.com/pborman/indent v1.2.1 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect @@ -211,11 +217,13 @@ require ( golang.org/x/time v0.2.0 // indirect golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.122.0 // indirect + google.golang.org/api v0.128.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect - google.golang.org/grpc v1.55.0 // indirect - google.golang.org/protobuf v1.30.0 // indirect + google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect + google.golang.org/grpc v1.56.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect @@ -229,3 +237,5 @@ require ( modernc.org/strutil v1.1.3 // indirect modernc.org/token v1.1.0 // indirect ) + +replace google.golang.org/grpc@latest => google.golang.org/grpc v1.29.1 diff --git a/go.sum b/go.sum index f5af9ed1473..e879b88a9fe 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w9 cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= -cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go v0.110.2 h1:sdFPBr6xG9/wkBbfhmUz/JmZC7X6LavQgcrVINrKiVA= +cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw= cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= @@ -71,8 +71,8 @@ cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.19.3 h1:DcTwsFgGev/wV5+q8o2fzgcHOaac+DKGC91ZlvpsQds= -cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI= +cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg= +cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= @@ -113,14 +113,12 @@ cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y97 cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k= -cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/iam v1.1.0 h1:67gSqaPukx7O8WLLHMa0PNs3EBGd2eE4d+psbO/CO94= +cloud.google.com/go/iam v1.1.0/go.mod h1:nxdHjaKfCr7fNYx/HJMM8LgiMugmveWlkatear5gVyk= cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= -cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= -cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= @@ -178,8 +176,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= -cloud.google.com/go/storage v1.28.1 h1:F5QDG5ChchaAVQhINh24U99OWHURqrW8OmQcGKXcbgI= -cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= +cloud.google.com/go/storage v1.29.0 h1:6weCgzRvMg7lzuUurI4697AqIRPU1SvzHhynwpW31jI= +cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= @@ -209,8 +207,9 @@ github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJ github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= @@ -275,8 +274,8 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go v1.44.180 h1:VLZuAHI9fa/3WME5JjpVjcPCNfpGHVMiHx8sLHWhMgI= -github.com/aws/aws-sdk-go v1.44.180/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.288 h1:Ln7fIao/nl0ACtelgR1I4AiEw/GLNkKcXfCaHupUW5Q= +github.com/aws/aws-sdk-go v1.44.288/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/becheran/wildmatch-go v1.0.0 h1:mE3dGGkTmpKtT4Z+88t8RStG40yN9T+kFEGj2PZFSzA= @@ -525,8 +524,8 @@ github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8I github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.3 h1:FAgZmpLl/SXurPEZyCMPBIiiYeTbqfjlbdnCNTAkbGE= -github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= +github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -535,8 +534,8 @@ github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= -github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.4 h1:uGy6JWR/uMIILU8wbf+OkstIrNiMjGpEIyhx8f6W7s4= +github.com/googleapis/enterprise-certificate-proxy v0.2.4/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= @@ -546,8 +545,8 @@ github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99 github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/gax-go/v2 v2.8.0 h1:UBtEZqx1bjXtOQ5BVTkuYghXrr3N4V123VKJK67vJZc= -github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= +github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= @@ -734,8 +733,8 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= -github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -766,14 +765,18 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= -github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= +github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8= github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/openvex/go-vex v0.2.5 h1:41utdp2rHgAGCsG+UbjmfMG5CWQxs15nGqir1eRgSrQ= +github.com/openvex/go-vex v0.2.5/go.mod h1:j+oadBxSUELkrKh4NfNb+BPo77U3q7gdKME88IO/0Wo= github.com/owenrumney/go-sarif v1.1.1 h1:QNObu6YX1igyFKhdzd7vgzmw7XsWN3/6NMGuDzBgXmE= github.com/owenrumney/go-sarif v1.1.1/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U= +github.com/package-url/packageurl-go v0.1.1 h1:KTRE0bK3sKbFKAk3yy63DpeskU7Cvs/x/Da5l+RtzyU= +github.com/package-url/packageurl-go v0.1.1/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/indent v1.2.1 h1:lFiviAbISHv3Rf0jcuh489bi06hj98JsVMtIDZQb9yM= @@ -1391,8 +1394,8 @@ google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.122.0 h1:zDobeejm3E7pEG1mNHvdxvjs5XJoCMzyNH+CmwL94Es= -google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms= +google.golang.org/api v0.128.0 h1:RjPESny5CnQRn9V6siglged+DZCgfu9l6mO9dkX9VOg= +google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWLY750= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1508,8 +1511,12 @@ google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqw google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdLy/60bS+52xfymkE72wv1asokgtao= +google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= +google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM= +google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1546,8 +1553,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= -google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= +google.golang.org/grpc v1.56.0 h1:+y7Bs8rtMd07LeXmL3NxcTLn7mUkbKZqEpPhMNkwJEE= +google.golang.org/grpc v1.56.0/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1564,8 +1571,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From c6746270a2321266b66d0a651fb42d8b2900bfeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Wed, 19 Jul 2023 18:10:53 -0600 Subject: [PATCH 02/29] Add generic VEX processor package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds a generic VEX processor package. It is implementation agnostic. It has a single option for now: The documents used to load the VEX data. The processor has a single method: ApplyVEX() which takes a set of scan results and applies VEX data to them. For now, the only modification that is done is filtering of results, that is moving results to the ignored list as a response to VEX documents. Signed-off-by: Adolfo García Veytia (Puerco) --- grype/vex/processor.go | 73 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 grype/vex/processor.go diff --git a/grype/vex/processor.go b/grype/vex/processor.go new file mode 100644 index 00000000000..e3fa6937238 --- /dev/null +++ b/grype/vex/processor.go @@ -0,0 +1,73 @@ +package vex + +import ( + "fmt" + + "github.com/anchore/grype/grype/match" + "github.com/anchore/grype/grype/pkg" +) + +type Processor struct { + Options ProcessorOptions + impl vexProcessorImplementation +} + +type vexProcessorImplementation interface { + // Read ReadVexDocuments takes a list of vex filenames and returns a single + // value representing the VEX information in the underlying implementation's + // format. Returns an error if the files cannot be processed. + ReadVexDocuments(docs []string) (interface{}, error) + + // Filter matches receives the underlying VEX implementation VEX data and + // the scanning context and matching results and filters the fixed and + // not_affected results,moving them to the list of ignored matches. + FilterMatches(interface{}, *pkg.Context, *match.Matches, []match.IgnoredMatch) (*match.Matches, []match.IgnoredMatch, error) +} + +// getVexImplementation this function returns the vex processor implementation +// at some point it can read the options and choose a user configured implementation. +func getVexImplementation() vexProcessorImplementation { + return nil +} + +// NewProcessor returns a new VEX processor. For now, it defaults to the only vex +// implementation: OpenVEX +func NewProcessor(opts ProcessorOptions) *Processor { + return &Processor{ + Options: opts, + impl: getVexImplementation(), + } +} + +// ProcessorOptions captures the optiones of the VEX processor. +type ProcessorOptions struct { + Documents []string + Context pkg.Context +} + +// ApplyVEX receives the results from a scan run and applies any VEX information +// in the files specified in the grype invocation. Any filtered results will +// be moved to the ignored matches slice. +func (vm *Processor) ApplyVEX(pkgContext pkg.Context, remainingMatches *match.Matches, ignoredMatches []match.IgnoredMatch) (*match.Matches, []match.IgnoredMatch, error) { + var err error + + // If no VEX documents are loaded, just pass through the matches, effectivle NOOP + if len(vm.Options.Documents) == 0 { + return remainingMatches, ignoredMatches, nil + } + + // Merge all files into a single OpenVEX doc + doc, err := vm.impl.ReadVexDocuments(vm.Options.Documents) + if err != nil { + return nil, nil, fmt.Errorf("parsing vex document: %w", err) + } + + remainingMatches, ignoredMatches, err = vm.impl.FilterMatches( + doc, &vm.Options.Context, remainingMatches, ignoredMatches, + ) + if err != nil { + return nil, nil, fmt.Errorf("checking matches against VEX data: %w", err) + } + + return remainingMatches, ignoredMatches, nil +} From c4064baff48501e1a5ce8459aa9c99329005415e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Wed, 19 Jul 2023 18:18:46 -0600 Subject: [PATCH 03/29] vex: Add OpenVEX processor implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds an openvex implementation of the vex processor. It also wires the VEX processor to use it as default. Signed-off-by: Adolfo García Veytia (Puerco) --- grype/vex/openvex/implementation.go | 173 ++++++++++++++++++++++++++++ grype/vex/processor.go | 3 +- 2 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 grype/vex/openvex/implementation.go diff --git a/grype/vex/openvex/implementation.go b/grype/vex/openvex/implementation.go new file mode 100644 index 00000000000..db0d37fb8f1 --- /dev/null +++ b/grype/vex/openvex/implementation.go @@ -0,0 +1,173 @@ +package openvex + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/anchore/grype/grype/match" + "github.com/anchore/grype/grype/pkg" + "github.com/anchore/packageurl-go" + "github.com/anchore/syft/syft/source" + openvex "github.com/openvex/go-vex/pkg/vex" + vexctl "github.com/openvex/vexctl/pkg/ctl" +) + +type Processor struct{} + +func New() *Processor { + return &Processor{} +} + +// ReadVexDocuments reads and merges VEX documents +func (ovm *Processor) ReadVexDocuments(docs []string) (interface{}, error) { + // Combine all VEX documents into a single VEX document + vexdata, err := vexctl.New().MergeFiles(context.Background(), &vexctl.MergeOptions{}, docs) + if err != nil { + return nil, fmt.Errorf("merging vex documents: %w", err) + } + + return vexdata, nil +} + +// productIDentifiersFromContext reads the package context and returns software +// identifiers identifying the scanned image. +func productIDentifiersFromContext(pkgContext *pkg.Context) ([]string, error) { + switch v := pkgContext.Source.Metadata.(type) { + case source.StereoscopeImageSourceMetadata: + // TODO(puerco): We can create a wider definition here. This effectively + // adds the multiarch image and the image of the OS running grype. We + // could generate more identifiers to match better. + return identifiersFromDigests(v.RepoDigests), nil + default: + // Fail for now + return nil, errors.New("source type not supported for VEX") + } +} + +func identifiersFromDigests(digests []string) []string { + identifiers := []string{} + + for _, d := range digests { + // The first identifier is the original image reference: + identifiers = append(identifiers, d) + + // Now, parse the digest + parts := strings.SplitN(d, "@", 2) + if len(parts) != 2 { + continue + } + + name := "" + repoURL := "" + digestString := strings.TrimPrefix(parts[1], "sha256:") + subparts := strings.Split(parts[0], "/") + switch len(subparts) { + case 1: + name = subparts[0] + repoURL = "" + default: + name = subparts[(len(subparts) - 1)] + repoURL = strings.Join(subparts[0:len(subparts)-1], "/") + } + + if name == "" { + continue + } + qMap := map[string]string{} + // Add + if repoURL != "" { + qMap["repository_url"] = repoURL + } + qs := packageurl.QualifiersFromMap(qMap) + identifiers = append(identifiers, packageurl.NewPackageURL( + "oci", "", name, fmt.Sprintf("sha256%%3A%s", digestString), qs, "", + ).String()) + + // TODO(puerco): Should also pass the digests only? They could be listed + // in the openvex document an foks may choose to vex on the hash. + } + return identifiers +} + +// subcomponentIdentifiersFromMatch returns the list of identifiers from the +// package where grype did the match. +func subcomponentIdentifiersFromMatch(m *match.Match) []string { + ret := []string{} + if m.Package.PURL != "" { + ret = append(ret, m.Package.PURL) + } + + // TODO(puerco):Implement CPE matching in openvex/go-vex + /* + for _, c := range m.Package.CPEs { + ret = append(ret, c.String()) + } + */ + return ret +} + +// FilterMatches takes a set of scanning results and moves any results marked in +// the VEX data as fixed or not_affected to the ignored list. +func (ovm *Processor) FilterMatches( + docRaw interface{}, pkgContext *pkg.Context, matches *match.Matches, ignoredMatches []match.IgnoredMatch, +) (*match.Matches, []match.IgnoredMatch, error) { + doc, ok := docRaw.(*openvex.VEX) + if !ok { + return nil, nil, errors.New("unable to cast vex document as openvex") + } + + remainingMatches := match.NewMatches() + + products, err := productIDentifiersFromContext(pkgContext) + if err != nil { + return nil, nil, fmt.Errorf("reading product identifiers from context: %w", err) + } + + // Now, let's go through grype's matches + sorted := matches.Sorted() + for i := range sorted { + var statement *openvex.Statement + subcmp := subcomponentIdentifiersFromMatch(&sorted[i]) + + // Range through the product's different names + for _, product := range products { + if matchingStatements := doc.Matches(sorted[i].Vulnerability.ID, product, subcmp); len(matchingStatements) != 0 { + statement = &matchingStatements[0] + break + } + } + + // No data about this match's component. Next. + if statement == nil { + remainingMatches.Add(sorted[i]) + continue + } + + // Filtering only applies to not_affected and fixed statuses + if statement.Status != openvex.StatusNotAffected && statement.Status != openvex.StatusFixed { + remainingMatches.Add(sorted[i]) + continue + } + + ignoredMatches = append(ignoredMatches, match.IgnoredMatch{ + Match: sorted[i], + AppliedIgnoreRules: []match.IgnoreRule{ + { + Vulnerability: sorted[i].Vulnerability.ID, + Namespace: "vex", + FixState: string(statement.Status), + Package: match.IgnoreRulePackage{ + Name: sorted[i].Package.Name, + Version: sorted[i].Package.Version, + Language: sorted[i].Package.Language.String(), + Type: string(sorted[i].Package.Type), + Location: "", + }, + }, + }, + }) + } + return &remainingMatches, ignoredMatches, nil +} diff --git a/grype/vex/processor.go b/grype/vex/processor.go index e3fa6937238..2ede9f476b3 100644 --- a/grype/vex/processor.go +++ b/grype/vex/processor.go @@ -5,6 +5,7 @@ import ( "github.com/anchore/grype/grype/match" "github.com/anchore/grype/grype/pkg" + "github.com/anchore/grype/grype/vex/openvex" ) type Processor struct { @@ -27,7 +28,7 @@ type vexProcessorImplementation interface { // getVexImplementation this function returns the vex processor implementation // at some point it can read the options and choose a user configured implementation. func getVexImplementation() vexProcessorImplementation { - return nil + return openvex.New() } // NewProcessor returns a new VEX processor. For now, it defaults to the only vex From 3de9d5e8247f6d0d48332b842f14db4b46401fa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Wed, 19 Jul 2023 18:26:56 -0600 Subject: [PATCH 04/29] Table presenter: Highligt results suppressed by VEX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit marks results suppressed by VEX when presenting them to the user. Signed-off-by: Adolfo García Veytia (Puerco) --- grype/presenter/table/presenter.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/grype/presenter/table/presenter.go b/grype/presenter/table/presenter.go index 4d7f5058fe4..4c0a4d3cfea 100644 --- a/grype/presenter/table/presenter.go +++ b/grype/presenter/table/presenter.go @@ -16,7 +16,8 @@ import ( ) const ( - appendSuppressed = " (suppressed)" + appendSuppressed = " (suppressed)" + appendSuppressedVEX = " (suppressed by VEX)" ) // Presenter is a generic struct for holding fields needed for reporting @@ -56,7 +57,15 @@ func (pres *Presenter) Present(output io.Writer) error { // Generate rows for suppressed vulnerabilities if pres.showSuppressed { for _, m := range pres.ignoredMatches { - row, err := createRow(m.Match, pres.metadataProvider, appendSuppressed) + msg := appendSuppressed + if m.AppliedIgnoreRules != nil { + for i := range m.AppliedIgnoreRules { + if m.AppliedIgnoreRules[i].Namespace == "vex" { + msg = appendSuppressedVEX + } + } + } + row, err := createRow(m.Match, pres.metadataProvider, msg) if err != nil { return err From 13e4c65b58eaac72e3c48b699003c429f2105cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Wed, 19 Jul 2023 18:50:06 -0600 Subject: [PATCH 05/29] Define VEX status constants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit defines a set of local constants of each of the VEX statuses based on the openvex constants. Signed-off-by: Adolfo García Veytia (Puerco) --- grype/vex/processor.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/grype/vex/processor.go b/grype/vex/processor.go index 2ede9f476b3..c0b99e3c352 100644 --- a/grype/vex/processor.go +++ b/grype/vex/processor.go @@ -3,11 +3,22 @@ package vex import ( "fmt" + gopenvex "github.com/openvex/go-vex/pkg/vex" + "github.com/anchore/grype/grype/match" "github.com/anchore/grype/grype/pkg" "github.com/anchore/grype/grype/vex/openvex" ) +type Status string + +const ( + StatusNotAffected Status = Status(gopenvex.StatusNotAffected) + StatusAffected Status = Status(gopenvex.StatusAffected) + StatusFixed Status = Status(gopenvex.StatusFixed) + StatusUnderInvestigation Status = Status(gopenvex.StatusUnderInvestigation) +) + type Processor struct { Options ProcessorOptions impl vexProcessorImplementation From 123da3bbd1b273cac73fbadc02dd7fde9f6fb66e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Wed, 19 Jul 2023 18:52:10 -0600 Subject: [PATCH 06/29] Add VexStatus to ignore rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit modifies the ignore rules structure to support defining a vex status. Any rules defining vex are ignored by the standard ignore rules processing as they will be handled by the VEX processor. Signed-off-by: Adolfo García Veytia (Puerco) --- grype/match/ignore.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/grype/match/ignore.go b/grype/match/ignore.go index cd719a70392..394590ae4e9 100644 --- a/grype/match/ignore.go +++ b/grype/match/ignore.go @@ -20,6 +20,7 @@ type IgnoreRule struct { Vulnerability string `yaml:"vulnerability" json:"vulnerability" mapstructure:"vulnerability"` Namespace string `yaml:"namespace" json:"namespace" mapstructure:"namespace"` FixState string `yaml:"fix-state" json:"fix-state" mapstructure:"fix-state"` + VexStatus string `yaml:"vex-status" json:"vex-status" mapstructure:"vex-status"` Package IgnoreRulePackage `yaml:"package" json:"package" mapstructure:"package"` } @@ -67,6 +68,11 @@ func ApplyIgnoreRules(matches Matches, rules []IgnoreRule) (Matches, []IgnoredMa } func shouldIgnore(match Match, rule IgnoreRule) bool { + // VEX rules are handled by the vex processor + if rule.VexStatus != "" { + return false + } + ignoreConditions := getIgnoreConditionsForRule(rule) if len(ignoreConditions) == 0 { // this rule specifies no criteria, so it doesn't apply to the Match From 836f616449e052f2f73b16e1e4b883f1872bd371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Wed, 19 Jul 2023 21:13:19 -0600 Subject: [PATCH 07/29] Add IgnoreRule HasConditions method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a new HasConditions method to the IgnoreRule object to check if the rule is empty. Signed-off-by: Adolfo García Veytia (Puerco) --- grype/match/ignore.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/grype/match/ignore.go b/grype/match/ignore.go index 394590ae4e9..a2315fb9d9a 100644 --- a/grype/match/ignore.go +++ b/grype/match/ignore.go @@ -90,6 +90,12 @@ func shouldIgnore(match Match, rule IgnoreRule) bool { return true } +// HasConditions returns true if the ignore rule has conditions +// that can cause a match to be ignored +func (ir IgnoreRule) HasConditions() bool { + return len(getIgnoreConditionsForRule(ir)) == 0 +} + // An ignoreCondition is a function that returns a boolean indicating whether // the given Match should be ignored. type ignoreCondition func(match Match) bool From 4468b51e5ccd39596c7c76e925d812905e53cd04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Wed, 19 Jul 2023 21:15:15 -0600 Subject: [PATCH 08/29] Control VEX filtering through IgnoreRules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit modifies how the vex processor is controlled. The processor now takes a list of IgnoreRules which can act on the VEX status in addition to the regular rule parameters. Signed-off-by: Adolfo García Veytia (Puerco) --- grype/vex/openvex/implementation.go | 70 ++++++++++++++++++++++------- grype/vex/processor.go | 27 ++++++++--- 2 files changed, 74 insertions(+), 23 deletions(-) diff --git a/grype/vex/openvex/implementation.go b/grype/vex/openvex/implementation.go index db0d37fb8f1..aa27b579372 100644 --- a/grype/vex/openvex/implementation.go +++ b/grype/vex/openvex/implementation.go @@ -111,7 +111,7 @@ func subcomponentIdentifiersFromMatch(m *match.Match) []string { // FilterMatches takes a set of scanning results and moves any results marked in // the VEX data as fixed or not_affected to the ignored list. func (ovm *Processor) FilterMatches( - docRaw interface{}, pkgContext *pkg.Context, matches *match.Matches, ignoredMatches []match.IgnoredMatch, + docRaw interface{}, ignoreRules []match.IgnoreRule, pkgContext *pkg.Context, matches *match.Matches, ignoredMatches []match.IgnoredMatch, ) (*match.Matches, []match.IgnoredMatch, error) { doc, ok := docRaw.(*openvex.VEX) if !ok { @@ -145,29 +145,67 @@ func (ovm *Processor) FilterMatches( continue } + rule := matchingRule(ignoreRules, sorted[i], statement) + if rule == nil { + remainingMatches.Add(sorted[i]) + continue + } + // Filtering only applies to not_affected and fixed statuses if statement.Status != openvex.StatusNotAffected && statement.Status != openvex.StatusFixed { remainingMatches.Add(sorted[i]) continue } + rule.Package = match.IgnoreRulePackage{ + Name: sorted[i].Package.Name, + Version: sorted[i].Package.Version, + Language: sorted[i].Package.Language.String(), + Type: string(sorted[i].Package.Type), + } + ignoredMatches = append(ignoredMatches, match.IgnoredMatch{ - Match: sorted[i], - AppliedIgnoreRules: []match.IgnoreRule{ - { - Vulnerability: sorted[i].Vulnerability.ID, - Namespace: "vex", - FixState: string(statement.Status), - Package: match.IgnoreRulePackage{ - Name: sorted[i].Package.Name, - Version: sorted[i].Package.Version, - Language: sorted[i].Package.Language.String(), - Type: string(sorted[i].Package.Type), - Location: "", - }, - }, - }, + Match: sorted[i], + AppliedIgnoreRules: []match.IgnoreRule{*rule}, }) } return &remainingMatches, ignoredMatches, nil } + +// matchingRule cycles through a set of ignore rules and returns the first +// one that matches the statement and the match. Returns nil if none match. +func matchingRule(ignoreRules []match.IgnoreRule, m match.Match, statement *openvex.Statement) *match.IgnoreRule { + ms := match.NewMatches() + ms.Add(m) + + for _, rule := range ignoreRules { + // If the rule has more conditions than just the VEX statement, check if + // it applies to the current match. + if rule.HasConditions() { + r := rule + r.VexStatus = "" + if _, ignored := match.ApplyIgnoreRules(ms, []match.IgnoreRule{r}); len(ignored) == 0 { + continue + } + } + + // If the status in the statement is not the same in the rule + // and the vex statement, it does not apply + if string(statement.Status) != string(rule.VexStatus) { + continue + } + + // If the vulnerability is blank in the rule it means we will honor + // any status with any vulnerability. + if rule.Vulnerability == "" { + return &rule + } + + // If the vulnerability is set, the rule applies if it is the same + // in the statment and the rule. + if statement.Vulnerability.Matches(rule.Vulnerability) { + return &rule + } + } + return nil +} diff --git a/grype/vex/processor.go b/grype/vex/processor.go index c0b99e3c352..0ade378d7b5 100644 --- a/grype/vex/processor.go +++ b/grype/vex/processor.go @@ -33,7 +33,7 @@ type vexProcessorImplementation interface { // Filter matches receives the underlying VEX implementation VEX data and // the scanning context and matching results and filters the fixed and // not_affected results,moving them to the list of ignored matches. - FilterMatches(interface{}, *pkg.Context, *match.Matches, []match.IgnoredMatch) (*match.Matches, []match.IgnoredMatch, error) + FilterMatches(interface{}, []match.IgnoreRule, *pkg.Context, *match.Matches, []match.IgnoredMatch) (*match.Matches, []match.IgnoredMatch, error) } // getVexImplementation this function returns the vex processor implementation @@ -53,14 +53,14 @@ func NewProcessor(opts ProcessorOptions) *Processor { // ProcessorOptions captures the optiones of the VEX processor. type ProcessorOptions struct { - Documents []string - Context pkg.Context + Documents []string + IgnoreRules []match.IgnoreRule } // ApplyVEX receives the results from a scan run and applies any VEX information // in the files specified in the grype invocation. Any filtered results will // be moved to the ignored matches slice. -func (vm *Processor) ApplyVEX(pkgContext pkg.Context, remainingMatches *match.Matches, ignoredMatches []match.IgnoredMatch) (*match.Matches, []match.IgnoredMatch, error) { +func (vm *Processor) ApplyVEX(pkgContext *pkg.Context, remainingMatches *match.Matches, ignoredMatches []match.IgnoredMatch) (*match.Matches, []match.IgnoredMatch, error) { var err error // If no VEX documents are loaded, just pass through the matches, effectivle NOOP @@ -68,14 +68,14 @@ func (vm *Processor) ApplyVEX(pkgContext pkg.Context, remainingMatches *match.Ma return remainingMatches, ignoredMatches, nil } - // Merge all files into a single OpenVEX doc - doc, err := vm.impl.ReadVexDocuments(vm.Options.Documents) + // Read VEX data from all passed documents + rawVexData, err := vm.impl.ReadVexDocuments(vm.Options.Documents) if err != nil { return nil, nil, fmt.Errorf("parsing vex document: %w", err) } remainingMatches, ignoredMatches, err = vm.impl.FilterMatches( - doc, &vm.Options.Context, remainingMatches, ignoredMatches, + rawVexData, extractVexRules(vm.Options.IgnoreRules), pkgContext, remainingMatches, ignoredMatches, ) if err != nil { return nil, nil, fmt.Errorf("checking matches against VEX data: %w", err) @@ -83,3 +83,16 @@ func (vm *Processor) ApplyVEX(pkgContext pkg.Context, remainingMatches *match.Ma return remainingMatches, ignoredMatches, nil } + +// extractVexRules is autility function that takes a set of ignore rules and +// extracts those that act on VEX statuses. +func extractVexRules(rules []match.IgnoreRule) []match.IgnoreRule { + newRules := []match.IgnoreRule{} + for _, r := range rules { + if r.VexStatus != "" { + newRules = append(newRules, r) + newRules[len(newRules)-1].Namespace = "vex" + } + } + return newRules +} From 5fc15f88aa98dd731f16c193bb40b3cfa60ed091 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Thu, 20 Jul 2023 14:24:50 -0600 Subject: [PATCH 09/29] vex: Allow rules to match on VEX justification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit expands the ingore rules to also work on vex the justification of not_affected statements. Signed-off-by: Adolfo García Veytia (Puerco) --- grype/match/ignore.go | 11 ++++++----- grype/vex/openvex/implementation.go | 7 +++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/grype/match/ignore.go b/grype/match/ignore.go index a2315fb9d9a..57683639e0b 100644 --- a/grype/match/ignore.go +++ b/grype/match/ignore.go @@ -17,11 +17,12 @@ type IgnoredMatch struct { // specified criteria must be met by the vulnerability match in order for the // rule to apply. type IgnoreRule struct { - Vulnerability string `yaml:"vulnerability" json:"vulnerability" mapstructure:"vulnerability"` - Namespace string `yaml:"namespace" json:"namespace" mapstructure:"namespace"` - FixState string `yaml:"fix-state" json:"fix-state" mapstructure:"fix-state"` - VexStatus string `yaml:"vex-status" json:"vex-status" mapstructure:"vex-status"` - Package IgnoreRulePackage `yaml:"package" json:"package" mapstructure:"package"` + Vulnerability string `yaml:"vulnerability" json:"vulnerability" mapstructure:"vulnerability"` + Namespace string `yaml:"namespace" json:"namespace" mapstructure:"namespace"` + FixState string `yaml:"fix-state" json:"fix-state" mapstructure:"fix-state"` + VexStatus string `yaml:"vex-status" json:"vex-status" mapstructure:"vex-status"` + VexJustification string `yaml:"vex-justification" json:"vex-justification" mapstructure:"vex-justification"` + Package IgnoreRulePackage `yaml:"package" json:"package" mapstructure:"package"` } // IgnoreRulePackage describes the Package-specific fields that comprise the IgnoreRule. diff --git a/grype/vex/openvex/implementation.go b/grype/vex/openvex/implementation.go index aa27b579372..a8446d0f07e 100644 --- a/grype/vex/openvex/implementation.go +++ b/grype/vex/openvex/implementation.go @@ -195,6 +195,13 @@ func matchingRule(ignoreRules []match.IgnoreRule, m match.Match, statement *open continue } + // If the rule applies to a VEX justification it needs to match the + // statement, note that justifications only apply to not_affected: + if statement.Status == openvex.StatusNotAffected && rule.VexJustification != "" && + rule.VexJustification != string(statement.Justification) { + continue + } + // If the vulnerability is blank in the rule it means we will honor // any status with any vulnerability. if rule.Vulnerability == "" { From 0669737c34950ae427bdfe2d1220a1eddcd0fbdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Fri, 18 Aug 2023 09:50:54 -0600 Subject: [PATCH 10/29] Use go-vex merge implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Adolfo García Veytia (Puerco) --- grype/vex/openvex/implementation.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/grype/vex/openvex/implementation.go b/grype/vex/openvex/implementation.go index a8446d0f07e..81f0c39e9bc 100644 --- a/grype/vex/openvex/implementation.go +++ b/grype/vex/openvex/implementation.go @@ -1,7 +1,6 @@ package openvex import ( - "context" "errors" "fmt" "strings" @@ -11,7 +10,6 @@ import ( "github.com/anchore/packageurl-go" "github.com/anchore/syft/syft/source" openvex "github.com/openvex/go-vex/pkg/vex" - vexctl "github.com/openvex/vexctl/pkg/ctl" ) type Processor struct{} @@ -23,7 +21,7 @@ func New() *Processor { // ReadVexDocuments reads and merges VEX documents func (ovm *Processor) ReadVexDocuments(docs []string) (interface{}, error) { // Combine all VEX documents into a single VEX document - vexdata, err := vexctl.New().MergeFiles(context.Background(), &vexctl.MergeOptions{}, docs) + vexdata, err := openvex.MergeFiles(docs) if err != nil { return nil, fmt.Errorf("merging vex documents: %w", err) } From 7b1a7a154aa30c7983a786720ac687fefb5c6a93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Tue, 22 Aug 2023 21:20:29 -0600 Subject: [PATCH 11/29] Add OpenVEX matcher to matcher list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds a new entry to the matchers: An openvex matcher This matcher is used when openvex augments results, moving matches from the ignore list to the active results. Signed-off-by: Adolfo García Veytia (Puerco) --- grype/match/matcher_type.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/grype/match/matcher_type.go b/grype/match/matcher_type.go index 6b596a88521..e8e8858f9cf 100644 --- a/grype/match/matcher_type.go +++ b/grype/match/matcher_type.go @@ -14,6 +14,7 @@ const ( MsrcMatcher MatcherType = "msrc-matcher" PortageMatcher MatcherType = "portage-matcher" GoModuleMatcher MatcherType = "go-module-matcher" + OpenVexMatcher MatcherType = "openvex-matcher" ) var AllMatcherTypes = []MatcherType{ @@ -28,6 +29,7 @@ var AllMatcherTypes = []MatcherType{ MsrcMatcher, PortageMatcher, GoModuleMatcher, + OpenVexMatcher, } type MatcherType string From e50cb32e9efecce38caeaf5926f8780285fc8166 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Tue, 22 Aug 2023 21:27:27 -0600 Subject: [PATCH 12/29] Add vex.AugmentMatches() to the vex processor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds a new AugmentMatches() phase to the VEX processor. This new step goes throught the configured ignore rules and acts on any that have `affected` or `under_investigtion` as status. The purpose of this rule is to move matches back from the ignored matches list to the active results when a statement with either of those statuses apply to ignored matches. Signed-off-by: Adolfo García Veytia (Puerco) --- grype/vex/openvex/implementation.go | 115 +++++++++++++++++++++++++++- grype/vex/processor.go | 12 +++ 2 files changed, 123 insertions(+), 4 deletions(-) diff --git a/grype/vex/openvex/implementation.go b/grype/vex/openvex/implementation.go index 81f0c39e9bc..efd95feec97 100644 --- a/grype/vex/openvex/implementation.go +++ b/grype/vex/openvex/implementation.go @@ -18,6 +18,30 @@ func New() *Processor { return &Processor{} } +// Match captures the criteria that caused a vulnerability to match +type Match struct { + Statement openvex.Statement +} + +// SearchedBy captures the prameters used to search through the VEX data +type SearchedBy struct { + Vulnerability string + Product string + Subcomponents []string +} + +// augmentStatuses are the VEX statuses that augment results +var augmentStatuses = []openvex.Status{ + openvex.StatusAffected, + openvex.StatusUnderInvestigation, +} + +// filterStatuses are the VEX statuses that filter matched to the ignore list +var ignoreStatuses = []openvex.Status{ + openvex.StatusNotAffected, + openvex.StatusFixed, +} + // ReadVexDocuments reads and merges VEX documents func (ovm *Processor) ReadVexDocuments(docs []string) (interface{}, error) { // Combine all VEX documents into a single VEX document @@ -143,7 +167,7 @@ func (ovm *Processor) FilterMatches( continue } - rule := matchingRule(ignoreRules, sorted[i], statement) + rule := matchingRule(ignoreRules, sorted[i], statement, ignoreStatuses) if rule == nil { remainingMatches.Add(sorted[i]) continue @@ -172,10 +196,15 @@ func (ovm *Processor) FilterMatches( // matchingRule cycles through a set of ignore rules and returns the first // one that matches the statement and the match. Returns nil if none match. -func matchingRule(ignoreRules []match.IgnoreRule, m match.Match, statement *openvex.Statement) *match.IgnoreRule { +func matchingRule(ignoreRules []match.IgnoreRule, m match.Match, statement *openvex.Statement, allowedStatuses []openvex.Status) *match.IgnoreRule { ms := match.NewMatches() ms.Add(m) + revStatuses := map[string]struct{}{} + for _, s := range allowedStatuses { + revStatuses[string(s)] = struct{}{} + } + for _, rule := range ignoreRules { // If the rule has more conditions than just the VEX statement, check if // it applies to the current match. @@ -189,10 +218,17 @@ func matchingRule(ignoreRules []match.IgnoreRule, m match.Match, statement *open // If the status in the statement is not the same in the rule // and the vex statement, it does not apply - if string(statement.Status) != string(rule.VexStatus) { + if string(statement.Status) != rule.VexStatus { continue } + // If the rule has a statement other than the allowed ones, skip: + if len(revStatuses) > 0 && rule.VexStatus != "" { + if _, ok := revStatuses[rule.VexStatus]; !ok { + continue + } + } + // If the rule applies to a VEX justification it needs to match the // statement, note that justifications only apply to not_affected: if statement.Status == openvex.StatusNotAffected && rule.VexJustification != "" && @@ -207,10 +243,81 @@ func matchingRule(ignoreRules []match.IgnoreRule, m match.Match, statement *open } // If the vulnerability is set, the rule applies if it is the same - // in the statment and the rule. + // in the statement and the rule. if statement.Vulnerability.Matches(rule.Vulnerability) { return &rule } } return nil } + +// AugmentMatches adds results to the match.Matches array when matching data +// about an affected VEX product is found on loaded VEX documents. Matches +// are moved from the ignore list or synthesized when no previous data is found. +func (ovm *Processor) AugmentMatches( + docRaw interface{}, ignoreRules []match.IgnoreRule, pkgContext *pkg.Context, remainingMatches *match.Matches, ignoredMatches []match.IgnoredMatch, +) (*match.Matches, []match.IgnoredMatch, error) { + doc, ok := docRaw.(*openvex.VEX) + if !ok { + return nil, nil, errors.New("unable to cast vex document as openvex") + } + + nignoredMatches := []match.IgnoredMatch{} + + products, err := productIDentifiersFromContext(pkgContext) + if err != nil { + return nil, nil, fmt.Errorf("reading product identifiers from context: %w", err) + } + + // Now, let's go through grype's matches + for i := range ignoredMatches { + var statement *openvex.Statement + var searchedBy *SearchedBy + subcmp := subcomponentIdentifiersFromMatch(&ignoredMatches[i].Match) + + // Range through the product's different names to see if they match the + // statement data + for _, product := range products { + if matchingStatements := doc.Matches(ignoredMatches[i].Vulnerability.ID, product, subcmp); len(matchingStatements) != 0 { + if matchingStatements[0].Status != openvex.StatusAffected && + matchingStatements[0].Status != openvex.StatusUnderInvestigation { + break + } + statement = &matchingStatements[0] + searchedBy = &SearchedBy{ + Vulnerability: ignoredMatches[i].Vulnerability.ID, + Product: product, + Subcomponents: subcmp, + } + break + } + } + + // No data about this match's component. Next. + if statement == nil { + nignoredMatches = append(nignoredMatches, ignoredMatches[i]) + continue + } + + // Only match if rules to augment are configured + rule := matchingRule(ignoreRules, ignoredMatches[i].Match, statement, augmentStatuses) + if rule == nil { + nignoredMatches = append(nignoredMatches, ignoredMatches[i]) + continue + } + + newMatch := ignoredMatches[i].Match + newMatch.Details = append(newMatch.Details, match.Detail{ + Type: match.ExactDirectMatch, + SearchedBy: searchedBy, + Found: Match{ + Statement: *statement, + }, + Matcher: match.OpenVexMatcher, + }) + + remainingMatches.Add(newMatch) + } + + return remainingMatches, nignoredMatches, nil +} diff --git a/grype/vex/processor.go b/grype/vex/processor.go index 0ade378d7b5..15b814bbe69 100644 --- a/grype/vex/processor.go +++ b/grype/vex/processor.go @@ -34,6 +34,11 @@ type vexProcessorImplementation interface { // the scanning context and matching results and filters the fixed and // not_affected results,moving them to the list of ignored matches. FilterMatches(interface{}, []match.IgnoreRule, *pkg.Context, *match.Matches, []match.IgnoredMatch) (*match.Matches, []match.IgnoredMatch, error) + + // AugmentMatches reads known affected VEX products from loaded documents and + // adds new results to the scanner results when the product is marked as + // affected in the VEX data. + AugmentMatches(interface{}, []match.IgnoreRule, *pkg.Context, *match.Matches, []match.IgnoredMatch) (*match.Matches, []match.IgnoredMatch, error) } // getVexImplementation this function returns the vex processor implementation @@ -81,6 +86,13 @@ func (vm *Processor) ApplyVEX(pkgContext *pkg.Context, remainingMatches *match.M return nil, nil, fmt.Errorf("checking matches against VEX data: %w", err) } + remainingMatches, ignoredMatches, err = vm.impl.AugmentMatches( + rawVexData, extractVexRules(vm.Options.IgnoreRules), pkgContext, remainingMatches, ignoredMatches, + ) + if err != nil { + return nil, nil, fmt.Errorf("checking matches to augment from VEX data: %w", err) + } + return remainingMatches, ignoredMatches, nil } From a4e71de96f988cad05745eeba71ebb6dabc085c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Tue, 22 Aug 2023 22:41:58 -0600 Subject: [PATCH 13/29] Parse context identifiers using GGC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit modifies the identifier synthesizer function to parse references using GGCR. It also adds a simple test. Signed-off-by: Adolfo García Veytia (Puerco) --- grype/vex/openvex/implementation.go | 47 +++++++++++++----------- grype/vex/openvex/implementation_test.go | 38 +++++++++++++++++++ 2 files changed, 64 insertions(+), 21 deletions(-) create mode 100644 grype/vex/openvex/implementation_test.go diff --git a/grype/vex/openvex/implementation.go b/grype/vex/openvex/implementation.go index efd95feec97..6a8848453a9 100644 --- a/grype/vex/openvex/implementation.go +++ b/grype/vex/openvex/implementation.go @@ -3,13 +3,16 @@ package openvex import ( "errors" "fmt" + "net/url" "strings" + "github.com/google/go-containerregistry/pkg/name" + openvex "github.com/openvex/go-vex/pkg/vex" + "github.com/anchore/grype/grype/match" "github.com/anchore/grype/grype/pkg" "github.com/anchore/packageurl-go" "github.com/anchore/syft/syft/source" - openvex "github.com/openvex/go-vex/pkg/vex" ) type Processor struct{} @@ -75,40 +78,42 @@ func identifiersFromDigests(digests []string) []string { // The first identifier is the original image reference: identifiers = append(identifiers, d) - // Now, parse the digest - parts := strings.SplitN(d, "@", 2) - if len(parts) != 2 { + // Not an image reference, skip + ref, err := name.ParseReference(d) + if err != nil { continue } - name := "" - repoURL := "" - digestString := strings.TrimPrefix(parts[1], "sha256:") - subparts := strings.Split(parts[0], "/") - switch len(subparts) { - case 1: - name = subparts[0] - repoURL = "" - default: - name = subparts[(len(subparts) - 1)] - repoURL = strings.Join(subparts[0:len(subparts)-1], "/") - } + var digestString, repoURL string + shaString := ref.Identifier() - if name == "" { + // If not a digest, we can't form a purl, so skip it + if !strings.HasPrefix(shaString, "sha256:") { continue } + + digestString = url.QueryEscape(shaString) + + pts := strings.Split(ref.Context().RepositoryStr(), "/") + name := pts[len(pts)-1] + repoURL = strings.TrimSuffix( + ref.Context().RegistryStr()+"/"+ref.Context().RepositoryStr(), + fmt.Sprintf("/%s", name), + ) + qMap := map[string]string{} - // Add + if repoURL != "" { qMap["repository_url"] = repoURL } qs := packageurl.QualifiersFromMap(qMap) identifiers = append(identifiers, packageurl.NewPackageURL( - "oci", "", name, fmt.Sprintf("sha256%%3A%s", digestString), qs, "", + "oci", "", name, digestString, qs, "", ).String()) - // TODO(puerco): Should also pass the digests only? They could be listed - // in the openvex document an foks may choose to vex on the hash. + // Add a hash to the identifier list in case people want to vex + // using the value of the image digest + identifiers = append(identifiers, strings.TrimPrefix(shaString, "sha256:")) } return identifiers } diff --git a/grype/vex/openvex/implementation_test.go b/grype/vex/openvex/implementation_test.go new file mode 100644 index 00000000000..6407df46e24 --- /dev/null +++ b/grype/vex/openvex/implementation_test.go @@ -0,0 +1,38 @@ +package openvex + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestIdentifiersFromDigests(t *testing.T) { + for _, tc := range []struct { + sut string + expected []string + }{ + { + "alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126", + []string{ + "alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126", + "pkg:oci/alpine@sha256%3A124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126?repository_url=index.docker.io/library", + "124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126", + }, + }, + { + "cgr.dev/chainguard/curl@sha256:9543ed09a38605c25c75486573cf530bd886615b993d5e1d1aa58fe5491287bc", + []string{ + "cgr.dev/chainguard/curl@sha256:9543ed09a38605c25c75486573cf530bd886615b993d5e1d1aa58fe5491287bc", + "pkg:oci/curl@sha256%3A9543ed09a38605c25c75486573cf530bd886615b993d5e1d1aa58fe5491287bc?repository_url=cgr.dev/chainguard", + "9543ed09a38605c25c75486573cf530bd886615b993d5e1d1aa58fe5491287bc", + }, + }, + { + "alpine", + []string{"alpine"}, + }, + } { + res := identifiersFromDigests([]string{tc.sut}) + require.Equal(t, tc.expected, res) + } +} From ae54e930f3d987d4833f0e728246ef52e3f95514 Mon Sep 17 00:00:00 2001 From: "Adolfo Garcia Veytia (puerco)" Date: Fri, 25 Aug 2023 12:07:02 -0600 Subject: [PATCH 14/29] Bump funlen linter to 73 This commit bumps the maximum function length to 73 to accomodate the new flag in AddFlags() Signed-off-by: Adolfo Garcia Veytia (puerco) --- .golangci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.golangci.yaml b/.golangci.yaml index 184f0f8ec2d..c34b7de94e7 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -44,7 +44,7 @@ linters-settings: # Checks the number of lines in a function. # If lower than 0, disable the check. # Default: 60 - lines: 70 + lines: 73 # Checks the number of statements in a function. # If lower than 0, disable the check. # Default: 40 From ba27ba311b87c4509b6f8bd436ca65b400336cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Thu, 31 Aug 2023 00:55:44 -0600 Subject: [PATCH 15/29] Add VEX testing to matchers test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds a new test and fixtures to test the VEX matchers along the rest of the matchers in TestMatchByImage(). As the VEX matchers operate on previously ignored matches a new loop was added to the test to accomodate the different testing model. Signed-off-by: Adolfo García Veytia (Puerco) --- test/integration/match_by_image_test.go | 135 ++++++++++++++++++ .../vex/openvex/affected.openvex.json | 23 +++ .../openvex/under_investigation.openvex.json | 24 ++++ 3 files changed, 182 insertions(+) create mode 100644 test/integration/test-fixtures/vex/openvex/affected.openvex.json create mode 100644 test/integration/test-fixtures/vex/openvex/under_investigation.openvex.json diff --git a/test/integration/match_by_image_test.go b/test/integration/match_by_image_test.go index b10e3e3c233..8b83b63b7b9 100644 --- a/test/integration/match_by_image_test.go +++ b/test/integration/match_by_image_test.go @@ -5,6 +5,7 @@ import ( "strings" "testing" + "github.com/facebookincubator/nvdtools/wfn" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/require" @@ -15,10 +16,12 @@ import ( "github.com/anchore/grype/grype/matcher" "github.com/anchore/grype/grype/pkg" "github.com/anchore/grype/grype/store" + "github.com/anchore/grype/grype/vex" "github.com/anchore/grype/grype/vulnerability" "github.com/anchore/grype/internal/stringutil" "github.com/anchore/stereoscope/pkg/imagetest" "github.com/anchore/syft/syft" + "github.com/anchore/syft/syft/linux" syftPkg "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger" "github.com/anchore/syft/syft/source" @@ -653,6 +656,49 @@ func TestMatchByImage(t *testing.T) { }) } + // Test that VEX matchers produce matches when fed documents with "affected" + // statuses. + for n, tc := range map[string]struct { + vexStatus vex.Status + vexDocuments []string + }{ + "openvex-affected": {vex.StatusAffected, []string{"test-fixtures/vex/openvex/affected.openvex.json"}}, + "openvex-under_investigation": {vex.StatusUnderInvestigation, []string{"test-fixtures/vex/openvex/under_investigation.openvex.json"}}, + } { + t.Run(n, func(t *testing.T) { + ignoredMatches := testIgnoredMatches() + vexedResults := vexMatches(t, ignoredMatches, tc.vexStatus, tc.vexDocuments) + if len(vexedResults.Sorted()) != 1 { + t.Errorf("expected one vexed result, got none") + } + + expectedMatches := match.NewMatches() + + // The single match in the actual results is the same in ignoredMatched + // but must the details of the VEX matcher appended + result := vexedResults.Sorted()[0] + if len(result.Details) != len(ignoredMatches[0].Match.Details)+1 { + t.Errorf( + "Details in VEXed results don't match (expected %d, got %d)", + len(ignoredMatches[0].Match.Details)+1, len(result.Details), + ) + } + + result.Details = result.Details[:len(result.Details)-1] + actualResults := match.NewMatches() + actualResults.Add(result) + + expectedMatches.Add(ignoredMatches[0].Match) + assertMatches(t, expectedMatches.Sorted(), actualResults.Sorted()) + + for _, m := range vexedResults.Sorted() { + for _, d := range m.Details { + observedMatchers.Add(string(d.Matcher)) + } + } + }) + } + // ensure that integration test cases stay in sync with the implemented matchers observedMatchers.Remove(string(match.StockMatcher)) definedMatchers.Remove(string(match.StockMatcher)) @@ -670,6 +716,95 @@ func TestMatchByImage(t *testing.T) { } +// testIgnoredMatches returns an list of ignored matches to test the vex +// matchers +func testIgnoredMatches() []match.IgnoredMatch { + return []match.IgnoredMatch{ + { + Match: match.Match{ + Vulnerability: vulnerability.Vulnerability{ + ID: "CVE-alpine-libvncserver", + Namespace: "alpine:distro:alpine:3.12", + }, + Package: pkg.Package{ + ID: "44fa3691ae360cac", + Name: "libvncserver", + Version: "0.9.9", + Licenses: []string{"GPL-2.0-or-later"}, + Type: "apk", + CPEs: []wfn.Attributes{ + { + Part: "a", + Vendor: "libvncserver", + Product: "libvncserver", + Version: "0.9.9", + }, + }, + PURL: "pkg:apk/alpine/libvncserver@0.9.9?arch=x86_64&distro=alpine-3.12.0", + Upstreams: []pkg.UpstreamPackage{{Name: "libvncserver"}}, + }, + Details: []match.Detail{ + { + Type: "exact-indirect-match", + SearchedBy: map[string]any{ + "distro": map[string]string{ + "type": "alpine", + "version": "3.12.0", + }, + "namespace": "alpine:distro:alpine:3.12", + "package": map[string]string{ + "name": "libvncserver", + "version": "0.9.9", + }, + }, + Found: map[string]any{ + "versionConstraint": "< 0.9.10 (unknown)", + "vulnerabilityID": "CVE-alpine-libvncserver", + }, + Matcher: "apk-matcher", + Confidence: 1, + }, + }, + }, + AppliedIgnoreRules: []match.IgnoreRule{}, + }, + } +} + +// vexMatches moves the first match of a matches list to an ignore list and +// applies a VEX "affected" document to it to move it to the matches list. +func vexMatches(t *testing.T, ignoredMatches []match.IgnoredMatch, vexStatus vex.Status, vexDocuments []string) match.Matches { + matches := match.NewMatches() + vexMatcher := vex.NewProcessor(vex.ProcessorOptions{ + Documents: vexDocuments, + IgnoreRules: []match.IgnoreRule{ + {VexStatus: string(vexStatus)}, + }, + }) + + pctx := &pkg.Context{ + Source: &source.Description{ + Metadata: source.StereoscopeImageSourceMetadata{ + RepoDigests: []string{ + "alpine@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + }, + }, + }, + Distro: &linux.Release{}, + } + + vexedMatches, ignoredMatches, err := vexMatcher.ApplyVEX(pctx, &matches, ignoredMatches) + if err != nil { + t.Errorf("applying VEX data: %s", err) + } + + if len(ignoredMatches) != 0 { + t.Errorf("VEX text fixture %s must affect all ignored matches (%d left)", vexDocuments, len(ignoredMatches)) + } + + return *vexedMatches +} + func assertMatches(t *testing.T, expected, actual []match.Match) { t.Helper() var opts = []cmp.Option{ diff --git a/test/integration/test-fixtures/vex/openvex/affected.openvex.json b/test/integration/test-fixtures/vex/openvex/affected.openvex.json new file mode 100644 index 00000000000..78b24f5b80b --- /dev/null +++ b/test/integration/test-fixtures/vex/openvex/affected.openvex.json @@ -0,0 +1,23 @@ +{ + "@context": "https://openvex.dev/ns/v0.2.0", + "@id": "https://openvex.dev/docs/public/vex-d4e9020b6d0d26f131d535e055902dd6ccf3e2088bce3079a8cd3588a4b14c78", + "author": "The OpenVEX Project ", + "timestamp": "2023-07-17T18:28:47.696004345-06:00", + "version": 1, + "statements": [ + { + "vulnerability": { + "name": "CVE-alpine-libvncserver" + }, + "products": [ + { + "@id": "pkg:oci/alpine@sha256%3Affffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "subcomponents": [ + { "@id": "pkg:apk/alpine/libvncserver@0.9.9?arch=x86_64&distro=alpine-3.12.0" } + ] + } + ], + "status": "affected" + } + ] +} diff --git a/test/integration/test-fixtures/vex/openvex/under_investigation.openvex.json b/test/integration/test-fixtures/vex/openvex/under_investigation.openvex.json new file mode 100644 index 00000000000..f9e4c60e38e --- /dev/null +++ b/test/integration/test-fixtures/vex/openvex/under_investigation.openvex.json @@ -0,0 +1,24 @@ +{ + "@context": "https://openvex.dev/ns/v0.2.0", + "@id": "https://openvex.dev/docs/public/vex-d4e9020b6d0d26f131d535e055902dd6ccf3e2088bce3079a8cd3588a4b14c78", + "author": "The OpenVEX Project ", + "timestamp": "2023-07-17T18:28:47.696004345-06:00", + "version": 1, + "statements": [ + { + "timestamp": "2023-07-16T18:28:47.696004345-06:00", + "vulnerability": { + "name": "CVE-alpine-libvncserver" + }, + "products": [ + { + "@id": "pkg:oci/alpine@sha256%3Affffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "subcomponents": [ + { "@id": "pkg:apk/alpine/libvncserver@0.9.9?arch=x86_64&distro=alpine-3.12.0" } + ] + } + ], + "status": "under_investigation" + } + ] +} From 6af9fc9f0aa837295017d6885b980cb04dda5273 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Fri, 8 Sep 2023 11:19:00 -0400 Subject: [PATCH 16/29] add vex status and justification to ignored rule json model Signed-off-by: Alex Goodman --- grype/match/ignore.go | 2 +- grype/presenter/models/ignore.go | 16 ++++++++++------ grype/vex/openvex/implementation.go | 8 ++++---- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/grype/match/ignore.go b/grype/match/ignore.go index 57683639e0b..5984938d83d 100644 --- a/grype/match/ignore.go +++ b/grype/match/ignore.go @@ -20,9 +20,9 @@ type IgnoreRule struct { Vulnerability string `yaml:"vulnerability" json:"vulnerability" mapstructure:"vulnerability"` Namespace string `yaml:"namespace" json:"namespace" mapstructure:"namespace"` FixState string `yaml:"fix-state" json:"fix-state" mapstructure:"fix-state"` + Package IgnoreRulePackage `yaml:"package" json:"package" mapstructure:"package"` VexStatus string `yaml:"vex-status" json:"vex-status" mapstructure:"vex-status"` VexJustification string `yaml:"vex-justification" json:"vex-justification" mapstructure:"vex-justification"` - Package IgnoreRulePackage `yaml:"package" json:"package" mapstructure:"package"` } // IgnoreRulePackage describes the Package-specific fields that comprise the IgnoreRule. diff --git a/grype/presenter/models/ignore.go b/grype/presenter/models/ignore.go index 4f4176945e4..b85465de8b3 100644 --- a/grype/presenter/models/ignore.go +++ b/grype/presenter/models/ignore.go @@ -8,9 +8,11 @@ type IgnoredMatch struct { } type IgnoreRule struct { - Vulnerability string `json:"vulnerability,omitempty"` - FixState string `json:"fix-state,omitempty"` - Package *IgnoreRulePackage `json:"package,omitempty"` + Vulnerability string `json:"vulnerability,omitempty"` + FixState string `json:"fix-state,omitempty"` + Package *IgnoreRulePackage `json:"package,omitempty"` + VexStatus string `json:"vex-status,omitempty"` + VexJustification string `json:"vex-justification,omitempty"` } type IgnoreRulePackage struct { @@ -34,9 +36,11 @@ func newIgnoreRule(r match.IgnoreRule) IgnoreRule { } return IgnoreRule{ - Vulnerability: r.Vulnerability, - FixState: r.FixState, - Package: ignoreRulePackage, + Vulnerability: r.Vulnerability, + FixState: r.FixState, + Package: ignoreRulePackage, + VexStatus: r.VexStatus, + VexJustification: r.VexJustification, } } diff --git a/grype/vex/openvex/implementation.go b/grype/vex/openvex/implementation.go index 6a8848453a9..4efa0ad227f 100644 --- a/grype/vex/openvex/implementation.go +++ b/grype/vex/openvex/implementation.go @@ -56,9 +56,9 @@ func (ovm *Processor) ReadVexDocuments(docs []string) (interface{}, error) { return vexdata, nil } -// productIDentifiersFromContext reads the package context and returns software +// productIdentifiersFromContext reads the package context and returns software // identifiers identifying the scanned image. -func productIDentifiersFromContext(pkgContext *pkg.Context) ([]string, error) { +func productIdentifiersFromContext(pkgContext *pkg.Context) ([]string, error) { switch v := pkgContext.Source.Metadata.(type) { case source.StereoscopeImageSourceMetadata: // TODO(puerco): We can create a wider definition here. This effectively @@ -147,7 +147,7 @@ func (ovm *Processor) FilterMatches( remainingMatches := match.NewMatches() - products, err := productIDentifiersFromContext(pkgContext) + products, err := productIdentifiersFromContext(pkgContext) if err != nil { return nil, nil, fmt.Errorf("reading product identifiers from context: %w", err) } @@ -269,7 +269,7 @@ func (ovm *Processor) AugmentMatches( nignoredMatches := []match.IgnoredMatch{} - products, err := productIDentifiersFromContext(pkgContext) + products, err := productIdentifiersFromContext(pkgContext) if err != nil { return nil, nil, fmt.Errorf("reading product identifiers from context: %w", err) } From 1f17f91ad03b15276a622beb595c27f024950c85 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Fri, 8 Sep 2023 16:56:29 -0400 Subject: [PATCH 17/29] nit rename + add TODO question about augmenting ignored matches Signed-off-by: Alex Goodman --- grype/vex/openvex/implementation.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/grype/vex/openvex/implementation.go b/grype/vex/openvex/implementation.go index 4efa0ad227f..24d26a90ae3 100644 --- a/grype/vex/openvex/implementation.go +++ b/grype/vex/openvex/implementation.go @@ -152,6 +152,9 @@ func (ovm *Processor) FilterMatches( return nil, nil, fmt.Errorf("reading product identifiers from context: %w", err) } + // TODO(alex): should we apply the vex ignore rules to the already ignored matches? + // that way the end user sees all of the reasons a match was ignored in case multiple apply + // Now, let's go through grype's matches sorted := matches.Sorted() for i := range sorted { @@ -267,7 +270,7 @@ func (ovm *Processor) AugmentMatches( return nil, nil, errors.New("unable to cast vex document as openvex") } - nignoredMatches := []match.IgnoredMatch{} + additionalIgnoredMatches := []match.IgnoredMatch{} products, err := productIdentifiersFromContext(pkgContext) if err != nil { @@ -300,14 +303,14 @@ func (ovm *Processor) AugmentMatches( // No data about this match's component. Next. if statement == nil { - nignoredMatches = append(nignoredMatches, ignoredMatches[i]) + additionalIgnoredMatches = append(additionalIgnoredMatches, ignoredMatches[i]) continue } // Only match if rules to augment are configured rule := matchingRule(ignoreRules, ignoredMatches[i].Match, statement, augmentStatuses) if rule == nil { - nignoredMatches = append(nignoredMatches, ignoredMatches[i]) + additionalIgnoredMatches = append(additionalIgnoredMatches, ignoredMatches[i]) continue } @@ -324,5 +327,5 @@ func (ovm *Processor) AugmentMatches( remainingMatches.Add(newMatch) } - return remainingMatches, nignoredMatches, nil + return remainingMatches, additionalIgnoredMatches, nil } From aafb0303bd43166298c92a2f44703cc0ff544fd5 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Fri, 8 Sep 2023 16:57:52 -0400 Subject: [PATCH 18/29] nit document comment updates + common variable extraction Signed-off-by: Alex Goodman --- grype/vex/processor.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/grype/vex/processor.go b/grype/vex/processor.go index 15b814bbe69..2c744d9f360 100644 --- a/grype/vex/processor.go +++ b/grype/vex/processor.go @@ -25,12 +25,12 @@ type Processor struct { } type vexProcessorImplementation interface { - // Read ReadVexDocuments takes a list of vex filenames and returns a single + // ReadVexDocuments takes a list of vex filenames and returns a single // value representing the VEX information in the underlying implementation's // format. Returns an error if the files cannot be processed. ReadVexDocuments(docs []string) (interface{}, error) - // Filter matches receives the underlying VEX implementation VEX data and + // FilterMatches matches receives the underlying VEX implementation VEX data and // the scanning context and matching results and filters the fixed and // not_affected results,moving them to the list of ignored matches. FilterMatches(interface{}, []match.IgnoreRule, *pkg.Context, *match.Matches, []match.IgnoredMatch) (*match.Matches, []match.IgnoredMatch, error) @@ -79,15 +79,17 @@ func (vm *Processor) ApplyVEX(pkgContext *pkg.Context, remainingMatches *match.M return nil, nil, fmt.Errorf("parsing vex document: %w", err) } + vexRules := extractVexRules(vm.Options.IgnoreRules) + remainingMatches, ignoredMatches, err = vm.impl.FilterMatches( - rawVexData, extractVexRules(vm.Options.IgnoreRules), pkgContext, remainingMatches, ignoredMatches, + rawVexData, vexRules, pkgContext, remainingMatches, ignoredMatches, ) if err != nil { return nil, nil, fmt.Errorf("checking matches against VEX data: %w", err) } remainingMatches, ignoredMatches, err = vm.impl.AugmentMatches( - rawVexData, extractVexRules(vm.Options.IgnoreRules), pkgContext, remainingMatches, ignoredMatches, + rawVexData, vexRules, pkgContext, remainingMatches, ignoredMatches, ) if err != nil { return nil, nil, fmt.Errorf("checking matches to augment from VEX data: %w", err) @@ -96,7 +98,7 @@ func (vm *Processor) ApplyVEX(pkgContext *pkg.Context, remainingMatches *match.M return remainingMatches, ignoredMatches, nil } -// extractVexRules is autility function that takes a set of ignore rules and +// extractVexRules is a utility function that takes a set of ignore rules and // extracts those that act on VEX statuses. func extractVexRules(rules []match.IgnoreRule) []match.IgnoreRule { newRules := []match.IgnoreRule{} From 307fa7cd5a61f128f6947ab5b17de92d1c65f63c Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Fri, 8 Sep 2023 16:58:42 -0400 Subject: [PATCH 19/29] migrate legacy matcher function to vulnerability matcher object Signed-off-by: Alex Goodman --- grype/deprecated.go | 20 +- grype/event/monitor/matching.go | 10 +- grype/match/match.go | 4 - grype/match/matches.go | 10 + grype/matcher/matchers.go | 219 --------------------- grype/vulnerability_matcher.go | 335 +++++++++++++++++++++++++++++++- 6 files changed, 364 insertions(+), 234 deletions(-) diff --git a/grype/deprecated.go b/grype/deprecated.go index f67e1206355..5407d968665 100644 --- a/grype/deprecated.go +++ b/grype/deprecated.go @@ -5,13 +5,14 @@ import ( "github.com/anchore/grype/grype/matcher" "github.com/anchore/grype/grype/pkg" "github.com/anchore/grype/grype/store" + "github.com/anchore/grype/internal/log" "github.com/anchore/stereoscope/pkg/image" "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/pkg/cataloger" "github.com/anchore/syft/syft/source" ) -// TODO: deprecated, remove in v1.0.0 +// TODO: deprecated, will remove before v1.0.0 func FindVulnerabilities(store store.Store, userImageStr string, scopeOpt source.Scope, registryOptions *image.RegistryOptions) (match.Matches, pkg.Context, []pkg.Package, error) { providerConfig := pkg.ProviderConfig{ SyftProviderConfig: pkg.SyftProviderConfig{ @@ -31,7 +32,20 @@ func FindVulnerabilities(store store.Store, userImageStr string, scopeOpt source return FindVulnerabilitiesForPackage(store, context.Distro, matchers, packages), context, packages, nil } -// TODO: deprecated, remove in v1.0.0 +// TODO: deprecated, will remove before v1.0.0 func FindVulnerabilitiesForPackage(store store.Store, d *linux.Release, matchers []matcher.Matcher, packages []pkg.Package) match.Matches { - return matcher.FindMatches(store, d, matchers, packages) + runner := VulnerabilityMatcher{ + Store: store, + Matchers: matchers, + NormalizeByCVE: false, + } + + actualResults, _, err := runner.FindMatches(packages, pkg.Context{ + Distro: d, + }) + if err != nil || actualResults == nil { + log.WithFields("error", err).Error("unable to find vulnerabilities") + return match.NewMatches() + } + return *actualResults } diff --git a/grype/event/monitor/matching.go b/grype/event/monitor/matching.go index 28967521174..f8280b09e36 100644 --- a/grype/event/monitor/matching.go +++ b/grype/event/monitor/matching.go @@ -7,8 +7,10 @@ import ( ) type Matching struct { - PackagesProcessed progress.Monitorable - VulnerabilitiesDiscovered progress.Monitorable - Fixed progress.Monitorable - BySeverity map[vulnerability.Severity]progress.Monitorable + PackagesProcessed progress.Progressable + MatchesDiscovered progress.Monitorable + Fixed progress.Monitorable + Ignored progress.Monitorable + Dropped progress.Monitorable + BySeverity map[vulnerability.Severity]progress.Monitorable } diff --git a/grype/match/match.go b/grype/match/match.go index 128ceae169f..d7982335ef6 100644 --- a/grype/match/match.go +++ b/grype/match/match.go @@ -26,10 +26,6 @@ func (m Match) String() string { return fmt.Sprintf("Match(pkg=%s vuln=%q types=%q)", m.Package, m.Vulnerability.String(), m.Details.Types()) } -func (m Match) Summary() string { - return fmt.Sprintf("vuln=%q matchers=%s", m.Vulnerability.ID, m.Details.Matchers()) -} - func (m Match) Fingerprint() Fingerprint { return Fingerprint{ vulnerabilityID: m.Vulnerability.ID, diff --git a/grype/match/matches.go b/grype/match/matches.go index 0df9a977884..e469c6dd3a1 100644 --- a/grype/match/matches.go +++ b/grype/match/matches.go @@ -52,6 +52,16 @@ func (r *Matches) Merge(other Matches) { } } +func (r *Matches) Diff(other Matches) *Matches { + diff := newMatches() + for fingerprint := range r.byFingerprint { + if _, exists := other.byFingerprint[fingerprint]; !exists { + diff.Add(r.byFingerprint[fingerprint]) + } + } + return &diff +} + func (r *Matches) Add(matches ...Match) { if len(matches) == 0 { return diff --git a/grype/matcher/matchers.go b/grype/matcher/matchers.go index 7181ac330b8..a27faa442f0 100644 --- a/grype/matcher/matchers.go +++ b/grype/matcher/matchers.go @@ -1,14 +1,6 @@ package matcher import ( - "github.com/wagoodman/go-partybus" - "github.com/wagoodman/go-progress" - - grypeDb "github.com/anchore/grype/grype/db/v5" - "github.com/anchore/grype/grype/distro" - "github.com/anchore/grype/grype/event" - "github.com/anchore/grype/grype/event/monitor" - "github.com/anchore/grype/grype/match" "github.com/anchore/grype/grype/matcher/apk" "github.com/anchore/grype/grype/matcher/dotnet" "github.com/anchore/grype/grype/matcher/dpkg" @@ -21,57 +13,8 @@ import ( "github.com/anchore/grype/grype/matcher/rpm" "github.com/anchore/grype/grype/matcher/ruby" "github.com/anchore/grype/grype/matcher/stock" - "github.com/anchore/grype/grype/pkg" - "github.com/anchore/grype/grype/vulnerability" - "github.com/anchore/grype/internal/bus" - "github.com/anchore/grype/internal/log" - "github.com/anchore/syft/syft/linux" - syftPkg "github.com/anchore/syft/syft/pkg" ) -type monitorWriter struct { - PackagesProcessed *progress.Manual - VulnerabilitiesDiscovered *progress.Manual - Fixed *progress.Manual - BySeverity map[vulnerability.Severity]*progress.Manual -} - -func newMonitor() (monitorWriter, monitor.Matching) { - manualBySev := make(map[vulnerability.Severity]*progress.Manual) - for _, severity := range vulnerability.AllSeverities() { - manualBySev[severity] = progress.NewManual(-1) - } - manualBySev[vulnerability.UnknownSeverity] = progress.NewManual(-1) - - m := monitorWriter{ - PackagesProcessed: progress.NewManual(-1), - VulnerabilitiesDiscovered: progress.NewManual(-1), - Fixed: progress.NewManual(-1), - BySeverity: manualBySev, - } - - monitorableBySev := make(map[vulnerability.Severity]progress.Monitorable) - for sev, manual := range manualBySev { - monitorableBySev[sev] = manual - } - - return m, monitor.Matching{ - PackagesProcessed: m.PackagesProcessed, - VulnerabilitiesDiscovered: m.VulnerabilitiesDiscovered, - Fixed: m.Fixed, - BySeverity: monitorableBySev, - } -} - -func (m *monitorWriter) SetCompleted() { - m.PackagesProcessed.SetCompleted() - m.VulnerabilitiesDiscovered.SetCompleted() - m.Fixed.SetCompleted() - for _, v := range m.BySeverity { - v.SetCompleted() - } -} - // Config contains values used by individual matcher structs for advanced configuration type Config struct { Java java.MatcherConfig @@ -99,165 +42,3 @@ func NewDefaultMatchers(mc Config) []Matcher { stock.NewStockMatcher(mc.Stock), } } - -func trackMatcher() *monitorWriter { - writer, reader := newMonitor() - - bus.Publish(partybus.Event{ - Type: event.VulnerabilityScanningStarted, - Value: reader, - }) - - return &writer -} - -func newMatcherIndex(matchers []Matcher) (map[syftPkg.Type][]Matcher, Matcher) { - matcherIndex := make(map[syftPkg.Type][]Matcher) - var defaultMatcher Matcher - for _, m := range matchers { - if m.Type() == match.StockMatcher { - defaultMatcher = m - continue - } - for _, t := range m.PackageTypes() { - if _, ok := matcherIndex[t]; !ok { - matcherIndex[t] = make([]Matcher, 0) - } - - matcherIndex[t] = append(matcherIndex[t], m) - log.Debugf("adding matcher: %+v", t) - } - } - - return matcherIndex, defaultMatcher -} - -func FindMatches(store interface { - vulnerability.Provider - vulnerability.MetadataProvider - match.ExclusionProvider -}, release *linux.Release, matchers []Matcher, packages []pkg.Package) match.Matches { - var err error - res := match.NewMatches() - matcherIndex, defaultMatcher := newMatcherIndex(matchers) - - var ignored []match.IgnoredMatch - - var d *distro.Distro - if release != nil { - d, err = distro.NewFromRelease(*release) - if err != nil { - log.Warnf("unable to determine linux distribution: %+v", err) - } - if d != nil && d.Disabled() { - log.Warnf("unsupported linux distribution: %s", d.Name()) - return match.Matches{} - } - } - - progressMonitor := trackMatcher() - - if defaultMatcher == nil { - defaultMatcher = stock.NewStockMatcher(stock.MatcherConfig{UseCPEs: true}) - } - for _, p := range packages { - progressMonitor.PackagesProcessed.Increment() - log.Debugf("searching for vulnerability matches for pkg=%s", p) - - matchAgainst, ok := matcherIndex[p.Type] - if !ok { - matchAgainst = []Matcher{defaultMatcher} - } - for _, m := range matchAgainst { - matches, err := m.Match(store, d, p) - if err != nil { - log.Warnf("matcher failed for pkg=%s: %+v", p, err) - } else { - // Filter out matches based on records in the database exclusion table and hard-coded rules - filtered, ignores := match.ApplyExplicitIgnoreRules(store, match.NewMatches(matches...)) - ignored = append(ignored, ignores...) - matches := filtered.Sorted() - logMatches(p, matches) - res.Add(matches...) - progressMonitor.VulnerabilitiesDiscovered.Add(int64(len(matches))) - updateVulnerabilityList(progressMonitor, matches, store) - } - } - } - - progressMonitor.SetCompleted() - - logListSummary(progressMonitor) - - logIgnoredMatches(ignored) - - return res -} - -func logListSummary(vl *monitorWriter) { - log.Infof("found %d vulnerabilities for %d packages", vl.VulnerabilitiesDiscovered.Current(), vl.PackagesProcessed.Current()) - log.Debugf(" ├── fixed: %d", vl.Fixed.Current()) - log.Debugf(" └── matched: %d", vl.VulnerabilitiesDiscovered.Current()) - - var unknownCount int64 - if count, ok := vl.BySeverity[vulnerability.UnknownSeverity]; ok { - unknownCount = count.Current() - } - log.Debugf(" ├── %s: %d", vulnerability.UnknownSeverity.String(), unknownCount) - - allSeverities := vulnerability.AllSeverities() - for idx, sev := range allSeverities { - branch := "├" - if idx == len(allSeverities)-1 { - branch = "└" - } - log.Debugf(" %s── %s: %d", branch, sev.String(), vl.BySeverity[sev].Current()) - } -} - -func logIgnoredMatches(ignored []match.IgnoredMatch) { - if len(ignored) > 0 { - log.Debugf("Removed %d explicit vulnerability matches:", len(ignored)) - for idx, i := range ignored { - branch := "├──" - if idx == len(ignored)-1 { - branch = "└──" - } - log.Debugf(" %s %s : %s", branch, i.Match.Vulnerability.ID, i.Package.PURL) - } - } -} - -func updateVulnerabilityList(list *monitorWriter, matches []match.Match, metadataProvider vulnerability.MetadataProvider) { - for _, m := range matches { - metadata, err := metadataProvider.GetMetadata(m.Vulnerability.ID, m.Vulnerability.Namespace) - if err != nil || metadata == nil { - list.BySeverity[vulnerability.UnknownSeverity].Increment() - continue - } - - sevManualProgress, ok := list.BySeverity[vulnerability.ParseSeverity(metadata.Severity)] - if !ok { - list.BySeverity[vulnerability.UnknownSeverity].Increment() - continue - } - sevManualProgress.Increment() - - if m.Vulnerability.Fix.State == grypeDb.FixedState { - list.Fixed.Increment() - } - } -} - -func logMatches(p pkg.Package, matches []match.Match) { - if len(matches) > 0 { - log.Debugf("found %d vulnerabilities for pkg=%s", len(matches), p) - for idx, m := range matches { - var branch = "├──" - if idx == len(matches)-1 { - branch = "└──" - } - log.Debugf(" %s %s", branch, m.Summary()) - } - } -} diff --git a/grype/vulnerability_matcher.go b/grype/vulnerability_matcher.go index 3eff7f81d25..ce875a4e00b 100644 --- a/grype/vulnerability_matcher.go +++ b/grype/vulnerability_matcher.go @@ -1,15 +1,33 @@ package grype import ( + "fmt" "strings" + "github.com/wagoodman/go-partybus" + "github.com/wagoodman/go-progress" + + grypeDb "github.com/anchore/grype/grype/db/v5" + "github.com/anchore/grype/grype/distro" + "github.com/anchore/grype/grype/event" + "github.com/anchore/grype/grype/event/monitor" "github.com/anchore/grype/grype/grypeerr" "github.com/anchore/grype/grype/match" "github.com/anchore/grype/grype/matcher" + "github.com/anchore/grype/grype/matcher/stock" "github.com/anchore/grype/grype/pkg" "github.com/anchore/grype/grype/store" + "github.com/anchore/grype/grype/vex" "github.com/anchore/grype/grype/vulnerability" + "github.com/anchore/grype/internal/bus" "github.com/anchore/grype/internal/log" + "github.com/anchore/syft/syft/linux" + syftPkg "github.com/anchore/syft/syft/pkg" +) + +const ( + branch = "├──" + leaf = "└──" ) type VulnerabilityMatcher struct { @@ -18,6 +36,7 @@ type VulnerabilityMatcher struct { IgnoreRules []match.IgnoreRule FailSeverity *vulnerability.Severity NormalizeByCVE bool + VexProcessor *vex.Processor } func DefaultVulnerabilityMatcher(store store.Store) *VulnerabilityMatcher { @@ -43,8 +62,32 @@ func (m *VulnerabilityMatcher) WithIgnoreRules(ignoreRules []match.IgnoreRule) * } func (m *VulnerabilityMatcher) FindMatches(pkgs []pkg.Package, context pkg.Context) (*match.Matches, []match.IgnoredMatch, error) { + progressMonitor := trackMatcher(len(pkgs)) + + defer progressMonitor.SetCompleted() + + remainingMatches, ignoredMatches, err := m.findDBMatches(pkgs, context, progressMonitor) + if err != nil { + return nil, nil, fmt.Errorf("unable to find matches against DB: %w", err) + } + + remainingMatches, ignoredMatches, err = m.findVEXMatches(context, remainingMatches, ignoredMatches, progressMonitor) + if err != nil { + return nil, nil, fmt.Errorf("unable to find matches against VEX sources: %w", err) + } + + logListSummary(progressMonitor) + + logIgnoredMatches(ignoredMatches) + + return remainingMatches, ignoredMatches, nil +} + +func (m *VulnerabilityMatcher) findDBMatches(pkgs []pkg.Package, context pkg.Context, progressMonitor *monitorWriter) (*match.Matches, []match.IgnoredMatch, error) { var ignoredMatches []match.IgnoredMatch - matches := matcher.FindMatches(m.Store, context.Distro, m.Matchers, pkgs) + + log.Trace("finding matches against DB") + matches := m.searchDBForMatches(context.Distro, pkgs, progressMonitor) matches, ignoredMatches = m.applyIgnoreRules(matches) @@ -69,6 +112,85 @@ func (m *VulnerabilityMatcher) FindMatches(pkgs []pkg.Package, context pkg.Conte return &matches, ignoredMatches, err } +func (m *VulnerabilityMatcher) searchDBForMatches( + release *linux.Release, + packages []pkg.Package, + progressMonitor *monitorWriter, +) match.Matches { + var err error + res := match.NewMatches() + matcherIndex, defaultMatcher := newMatcherIndex(m.Matchers) + + var d *distro.Distro + if release != nil { + d, err = distro.NewFromRelease(*release) + if err != nil { + log.Warnf("unable to determine linux distribution: %+v", err) + } + if d != nil && d.Disabled() { + log.Warnf("unsupported linux distribution: %s", d.Name()) + return match.NewMatches() + } + } + + if defaultMatcher == nil { + defaultMatcher = stock.NewStockMatcher(stock.MatcherConfig{UseCPEs: true}) + } + for _, p := range packages { + progressMonitor.PackagesProcessed.Increment() + log.WithFields("package", displayPackage(p)).Trace("searching for vulnerability matches") + + matchAgainst, ok := matcherIndex[p.Type] + if !ok { + matchAgainst = []matcher.Matcher{defaultMatcher} + } + for _, theMatcher := range matchAgainst { + matches, err := theMatcher.Match(m.Store, d, p) + if err != nil { + log.WithFields("error", err, "package", displayPackage(p)).Warn("matcher failed") + } else { + // Filter out matches based on records in the database exclusion table and hard-coded rules + filtered, dropped := match.ApplyExplicitIgnoreRules(m.Store, match.NewMatches(matches...)) + + additionalMatches := filtered.Sorted() + logPackageMatches(p, additionalMatches) + logExplicitDroppedPackageMatches(p, dropped) + res.Add(additionalMatches...) + + progressMonitor.MatchesDiscovered.Add(int64(len(additionalMatches))) + + // note: there is a difference between "ignore" and "dropped" matches. + // ignored: matches that are filtered out due to user-provided ignore rules + // dropped: matches that are filtered out due to hard-coded rules + updateVulnerabilityList(progressMonitor, additionalMatches, nil, dropped, m.Store) + } + } + } + + return res +} + +func (m *VulnerabilityMatcher) findVEXMatches(context pkg.Context, remainingMatches *match.Matches, ignoredMatches []match.IgnoredMatch, progressMonitor *monitorWriter) (*match.Matches, []match.IgnoredMatch, error) { + if m.VexProcessor == nil { + log.Trace("no VEX documents provided, skipping VEX matching") + return remainingMatches, ignoredMatches, nil + } + + log.Trace("finding matches against available VEX documents") + matchesAfterVex, ignoredMatchesAfterVex, err := m.VexProcessor.ApplyVEX(&context, remainingMatches, ignoredMatches) + if err != nil { + return nil, nil, fmt.Errorf("unable to find matches against VEX documents: %w", err) + } + + diffMatches := matchesAfterVex.Diff(*remainingMatches) + // note: this assumes that the diff can only be additive + diffIgnoredMatches := ignoredMatchesDiff(ignoredMatchesAfterVex, ignoredMatches) + + updateVulnerabilityList(progressMonitor, diffMatches.Sorted(), diffIgnoredMatches, nil, m.Store) + + return matchesAfterVex, ignoredMatchesAfterVex, nil +} + func (m *VulnerabilityMatcher) applyIgnoreRules(matches match.Matches) (match.Matches, []match.IgnoredMatch) { var ignoredMatches []match.IgnoredMatch if len(m.IgnoreRules) == 0 { @@ -98,12 +220,19 @@ func (m *VulnerabilityMatcher) normalizeByCVE(match match.Match) match.Match { switch len(effectiveCVERecordRefs) { case 0: - // TODO: trace logging + log.WithFields( + "vuln", match.Vulnerability.ID, + "package", displayPackage(match.Package), + ).Trace("unable to find CVE record for vulnerability, skipping normalization") return match case 1: break default: - // TODO: trace logging + log.WithFields( + "refs", fmt.Sprintf("%+v", effectiveCVERecordRefs), + "vuln", match.Vulnerability.ID, + "package", displayPackage(match.Package), + ).Trace("found multiple CVE records for vulnerability, skipping normalization") return match } @@ -111,7 +240,7 @@ func (m *VulnerabilityMatcher) normalizeByCVE(match match.Match) match.Match { upstreamMetadata, err := m.Store.GetMetadata(ref.ID, ref.Namespace) if err != nil { - log.Warnf("unable to fetch effective CVE metadata for id=%q namespace=%q : %v", ref.ID, ref.Namespace, err) + log.WithFields("id", ref.ID, "namespace", ref.Namespace, "error", err).Warn("unable to fetch effective CVE metadata") return match } @@ -131,6 +260,53 @@ func (m *VulnerabilityMatcher) normalizeByCVE(match match.Match) match.Match { return match } +func displayPackage(p pkg.Package) string { + if p.PURL != "" { + return p.PURL + } + return fmt.Sprintf("%s@%s (%s)", p.Name, p.Version, p.Type) +} + +func ignoredMatchesDiff(subject []match.IgnoredMatch, other []match.IgnoredMatch) []match.IgnoredMatch { + // TODO(alex): the downside with this implementation is that it does not account for the same ignored match being + // ignored for different reasons (the appliedIgnoreRules field). + + otherMap := make(map[match.Fingerprint]struct{}) + for _, a := range other { + otherMap[a.Match.Fingerprint()] = struct{}{} + } + + var diff []match.IgnoredMatch + for _, b := range subject { + if _, ok := otherMap[b.Match.Fingerprint()]; !ok { + diff = append(diff, b) + } + } + + return diff +} + +func newMatcherIndex(matchers []matcher.Matcher) (map[syftPkg.Type][]matcher.Matcher, matcher.Matcher) { + matcherIndex := make(map[syftPkg.Type][]matcher.Matcher) + var defaultMatcher matcher.Matcher + for _, m := range matchers { + if m.Type() == match.StockMatcher { + defaultMatcher = m + continue + } + for _, t := range m.PackageTypes() { + if _, ok := matcherIndex[t]; !ok { + matcherIndex[t] = make([]matcher.Matcher, 0) + } + + matcherIndex[t] = append(matcherIndex[t], m) + log.Debugf("adding matcher: %+v", t) + } + } + + return matcherIndex, defaultMatcher +} + func isCVE(id string) bool { return strings.HasPrefix(strings.ToLower(id), "cve-") } @@ -151,3 +327,154 @@ func HasSeverityAtOrAbove(store vulnerability.MetadataProvider, severity vulnera } return false } + +func logListSummary(vl *monitorWriter) { + log.Infof("found %d vulnerability matches across %d packages", vl.MatchesDiscovered.Current(), vl.PackagesProcessed.Current()) + log.Debugf(" ├── fixed: %d", vl.Fixed.Current()) + log.Debugf(" ├── ignored: %d (due to user-provided rule)", vl.Ignored.Current()) + log.Debugf(" ├── dropped: %d (due to hard-coded correction)", vl.Dropped.Current()) + log.Debugf(" └── matched: %d", vl.MatchesDiscovered.Current()) + + var unknownCount int64 + if count, ok := vl.BySeverity[vulnerability.UnknownSeverity]; ok { + unknownCount = count.Current() + } + log.Debugf(" ├── %s: %d", vulnerability.UnknownSeverity.String(), unknownCount) + + allSeverities := vulnerability.AllSeverities() + for idx, sev := range allSeverities { + arm := selectArm(idx, len(allSeverities)) + log.Debugf(" %s %s: %d", arm, sev.String(), vl.BySeverity[sev].Current()) + } +} + +func updateVulnerabilityList(mon *monitorWriter, matches []match.Match, ignores []match.IgnoredMatch, dropped []match.IgnoredMatch, metadataProvider vulnerability.MetadataProvider) { + for _, m := range matches { + metadata, err := metadataProvider.GetMetadata(m.Vulnerability.ID, m.Vulnerability.Namespace) + if err != nil || metadata == nil { + mon.BySeverity[vulnerability.UnknownSeverity].Increment() + continue + } + + sevManualProgress, ok := mon.BySeverity[vulnerability.ParseSeverity(metadata.Severity)] + if !ok { + mon.BySeverity[vulnerability.UnknownSeverity].Increment() + continue + } + sevManualProgress.Increment() + + if m.Vulnerability.Fix.State == grypeDb.FixedState { + mon.Fixed.Increment() + } + } + + mon.Ignored.Add(int64(len(ignores))) + mon.Dropped.Add(int64(len(dropped))) +} + +func logPackageMatches(p pkg.Package, matches []match.Match) { + if len(matches) == 0 { + return + } + + log.WithFields("package", displayPackage(p)).Debugf("found %d vulnerabilities", len(matches)) + for idx, m := range matches { + arm := selectArm(idx, len(matches)) + log.WithFields("vuln", m.Vulnerability.ID, "namespace", m.Vulnerability.Namespace).Debugf(" %s", arm) + } +} + +func selectArm(idx, total int) string { + if idx == total-1 { + return leaf + } + return branch +} + +func logExplicitDroppedPackageMatches(p pkg.Package, ignored []match.IgnoredMatch) { + if len(ignored) == 0 { + return + } + + log.WithFields("package", displayPackage(p)).Debugf("dropped %d vulnerability matches due to hard-coded correction", len(ignored)) + for idx, i := range ignored { + arm := selectArm(idx, len(ignored)) + + log.WithFields("vuln", i.Match.Vulnerability.ID, "rules", len(i.AppliedIgnoreRules)).Debugf(" %s", arm) + } +} + +func logIgnoredMatches(ignored []match.IgnoredMatch) { + if len(ignored) == 0 { + return + } + + log.Infof("ignored %d vulnerability matches", len(ignored)) + for idx, i := range ignored { + arm := selectArm(idx, len(ignored)) + + log.WithFields("vuln", i.Match.Vulnerability.ID, "rules", len(i.AppliedIgnoreRules), "package", displayPackage(i.Package)).Debugf(" %s", arm) + } +} + +type monitorWriter struct { + PackagesProcessed *progress.Manual + MatchesDiscovered *progress.Manual + Fixed *progress.Manual + Ignored *progress.Manual + Dropped *progress.Manual + BySeverity map[vulnerability.Severity]*progress.Manual +} + +func newMonitor(pkgCount int) (monitorWriter, monitor.Matching) { + manualBySev := make(map[vulnerability.Severity]*progress.Manual) + for _, severity := range vulnerability.AllSeverities() { + manualBySev[severity] = progress.NewManual(-1) + } + manualBySev[vulnerability.UnknownSeverity] = progress.NewManual(-1) + + m := monitorWriter{ + PackagesProcessed: progress.NewManual(int64(pkgCount)), + MatchesDiscovered: progress.NewManual(-1), + Fixed: progress.NewManual(-1), + Ignored: progress.NewManual(-1), + Dropped: progress.NewManual(-1), + BySeverity: manualBySev, + } + + monitorableBySev := make(map[vulnerability.Severity]progress.Monitorable) + for sev, manual := range manualBySev { + monitorableBySev[sev] = manual + } + + return m, monitor.Matching{ + PackagesProcessed: m.PackagesProcessed, + MatchesDiscovered: m.MatchesDiscovered, + Fixed: m.Fixed, + Ignored: m.Ignored, + Dropped: m.Dropped, + BySeverity: monitorableBySev, + } +} + +func (m *monitorWriter) SetCompleted() { + m.PackagesProcessed.SetCompleted() + m.MatchesDiscovered.SetCompleted() + m.Fixed.SetCompleted() + m.Ignored.SetCompleted() + m.Dropped.SetCompleted() + for _, v := range m.BySeverity { + v.SetCompleted() + } +} + +func trackMatcher(pkgCount int) *monitorWriter { + writer, reader := newMonitor(pkgCount) + + bus.Publish(partybus.Event{ + Type: event.VulnerabilityScanningStarted, + Value: reader, + }) + + return &writer +} From 2e6cc2e75398c95f23222ff93cea89e2231f840e Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Fri, 8 Sep 2023 17:00:49 -0400 Subject: [PATCH 20/29] update tui to respond to ignored and dropped matches Signed-off-by: Alex Goodman --- ...e_vulnerability_scanning_started_test.snap | 12 +++---- .../handle_vulnerability_scanning_started.go | 31 +++++++++++++----- ...dle_vulnerability_scanning_started_test.go | 32 +++++++++++++++---- 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/cmd/grype/cli/ui/__snapshots__/handle_vulnerability_scanning_started_test.snap b/cmd/grype/cli/ui/__snapshots__/handle_vulnerability_scanning_started_test.snap index 6a0244c4192..2d424d73be9 100755 --- a/cmd/grype/cli/ui/__snapshots__/handle_vulnerability_scanning_started_test.snap +++ b/cmd/grype/cli/ui/__snapshots__/handle_vulnerability_scanning_started_test.snap @@ -1,18 +1,18 @@ [TestHandler_handleVulnerabilityScanningStarted/vulnerability_scanning_in_progress/task_line - 1] - ⠋ Scanning for vulnerabilities [20 vulnerabilities] + ⠋ Scanning for vulnerabilities [36 vulnerability matches] --- [TestHandler_handleVulnerabilityScanningStarted/vulnerability_scanning_in_progress/tree - 1] - ├── 1 critical, 2 high, 3 medium, 4 low, 5 negligible (6 unknown) - └── 30 fixed + ├── by severity: 1 critical, 2 high, 3 medium, 4 low, 5 negligible (6 unknown) + └── by status: 30 fixed, 10 not-fixed, 4 ignored (2 dropped) --- [TestHandler_handleVulnerabilityScanningStarted/vulnerability_scanning_complete/task_line - 1] - ✔ Scanned for vulnerabilities [25 vulnerabilities] + ✔ Scanned for vulnerabilities [40 vulnerability matches] --- [TestHandler_handleVulnerabilityScanningStarted/vulnerability_scanning_complete/tree - 1] - ├── 1 critical, 2 high, 3 medium, 4 low, 5 negligible (6 unknown) - └── 35 fixed + ├── by severity: 1 critical, 2 high, 3 medium, 4 low, 5 negligible (6 unknown) + └── by status: 35 fixed, 10 not-fixed, 5 ignored (3 dropped) --- diff --git a/cmd/grype/cli/ui/handle_vulnerability_scanning_started.go b/cmd/grype/cli/ui/handle_vulnerability_scanning_started.go index 54ef042bcb8..90a157df787 100644 --- a/cmd/grype/cli/ui/handle_vulnerability_scanning_started.go +++ b/cmd/grype/cli/ui/handle_vulnerability_scanning_started.go @@ -32,6 +32,9 @@ type vulnerabilityProgressTree struct { countBySeverity map[vulnerability.Severity]int64 unknownCount int64 fixedCount int64 + ignoredCount int64 + droppedCount int64 + totalCount int64 severities []vulnerability.Severity id uint32 @@ -65,19 +68,19 @@ type vulnerabilityScanningAdapter struct { } func (p vulnerabilityScanningAdapter) Current() int64 { - return p.mon.VulnerabilitiesDiscovered.Current() + return p.mon.PackagesProcessed.Current() } func (p vulnerabilityScanningAdapter) Error() error { - return p.mon.VulnerabilitiesDiscovered.Error() + return p.mon.MatchesDiscovered.Error() } func (p vulnerabilityScanningAdapter) Size() int64 { - return -1 + return p.mon.PackagesProcessed.Size() } func (p vulnerabilityScanningAdapter) Stage() string { - return fmt.Sprintf("%d vulnerabilities", p.mon.VulnerabilitiesDiscovered.Current()) + return fmt.Sprintf("%d vulnerability matches", p.mon.MatchesDiscovered.Current()-p.mon.Ignored.Current()) } func (m *Handler) handleVulnerabilityScanningStarted(e partybus.Event) []tea.Model { @@ -131,7 +134,10 @@ func (l vulnerabilityProgressTree) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case vulnerabilityProgressTreeTickMsg: // update the model + l.totalCount = l.mon.MatchesDiscovered.Current() l.fixedCount = l.mon.Fixed.Current() + l.ignoredCount = l.mon.Ignored.Current() + l.droppedCount = l.mon.Dropped.Current() l.unknownCount = l.mon.BySeverity[vulnerability.UnknownSeverity].Current() for _, sev := range l.severities { l.countBySeverity[sev] = l.mon.BySeverity[sev].Current() @@ -164,12 +170,21 @@ func (l vulnerabilityProgressTree) View() string { status := sb.String() sb.Reset() - sevStr := l.textStyle.Render(fmt.Sprintf(" %s %s", branch, status)) - fixedStr := l.textStyle.Render(fmt.Sprintf(" %s %d fixed", end, l.fixedCount)) + sevStr := l.textStyle.Render(fmt.Sprintf(" %s by severity: %s", branch, status)) sb.WriteString(sevStr) - sb.WriteString("\n") - sb.WriteString(fixedStr) + + dropped := "" + if l.droppedCount > 0 { + dropped = fmt.Sprintf("(%d dropped)", l.droppedCount) + } + + fixedStr := l.textStyle.Render( + fmt.Sprintf(" %s by status: %d fixed, %d not-fixed, %d ignored %s", + end, l.fixedCount, l.totalCount-l.fixedCount, l.ignoredCount, dropped, + ), + ) + sb.WriteString("\n" + fixedStr) return sb.String() } diff --git a/cmd/grype/cli/ui/handle_vulnerability_scanning_started_test.go b/cmd/grype/cli/ui/handle_vulnerability_scanning_started_test.go index ab23fd7d4fd..c3ce11b8fda 100644 --- a/cmd/grype/cli/ui/handle_vulnerability_scanning_started_test.go +++ b/cmd/grype/cli/ui/handle_vulnerability_scanning_started_test.go @@ -96,10 +96,10 @@ func getVulnerabilityMonitor(completed bool) monitor.Matching { vulns := &progress.Manual{} vulns.SetTotal(-1) if completed { - vulns.Set(25) + vulns.Set(45) vulns.SetCompleted() } else { - vulns.Set(20) + vulns.Set(40) } fixed := &progress.Manual{} @@ -111,6 +111,24 @@ func getVulnerabilityMonitor(completed bool) monitor.Matching { fixed.Set(30) } + ignored := &progress.Manual{} + ignored.SetTotal(-1) + if completed { + ignored.Set(5) + ignored.SetCompleted() + } else { + ignored.Set(4) + } + + dropped := &progress.Manual{} + dropped.SetTotal(-1) + if completed { + dropped.Set(3) + dropped.SetCompleted() + } else { + dropped.Set(2) + } + bySeverityWriter := map[vulnerability.Severity]*progress.Manual{ vulnerability.CriticalSeverity: {}, vulnerability.HighSeverity: {}, @@ -137,9 +155,11 @@ func getVulnerabilityMonitor(completed bool) monitor.Matching { } return monitor.Matching{ - PackagesProcessed: pkgs, - VulnerabilitiesDiscovered: vulns, - Fixed: fixed, - BySeverity: bySeverity, + PackagesProcessed: pkgs, + MatchesDiscovered: vulns, + Fixed: fixed, + Ignored: ignored, + Dropped: dropped, + BySeverity: bySeverity, } } From 675713dfabaea111b7bb87f1577f0ad11709baa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Mon, 11 Sep 2023 18:23:26 -0600 Subject: [PATCH 21/29] migrate vex processing to vulnerability match object MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Based on Alex's previous caommit Co-authored-by: Alex Goodman Signed-off-by: Adolfo García Veytia (Puerco) --- cmd/grype/cli/commands/root.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/grype/cli/commands/root.go b/cmd/grype/cli/commands/root.go index 5b6195c3b6c..17be3561088 100644 --- a/cmd/grype/cli/commands/root.go +++ b/cmd/grype/cli/commands/root.go @@ -174,6 +174,10 @@ func runGrype(app clio.Application, opts *options.Grype, userInput string) error NormalizeByCVE: opts.ByCVE, FailSeverity: opts.FailOnServerity(), Matchers: getMatchers(opts), + VexProcessor: vex.NewProcessor(vex.ProcessorOptions{ + Documents: appConfig.VexDocuments, + IgnoreRules: appConfig.Ignore, + }), } remainingMatches, ignoredMatches, err := vulnMatcher.FindMatches(packages, pkgContext) From 21a507294aa2cc3bbc4f4504257c96833793df19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Mon, 11 Sep 2023 19:04:02 -0600 Subject: [PATCH 22/29] Migrate VEX options and app config from legacy CLI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Adolfo García Veytia (Puerco) --- cmd/grype/cli/commands/root.go | 38 ++++++++++++++++++++++++++++++++-- cmd/grype/cli/options/grype.go | 8 +++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/cmd/grype/cli/commands/root.go b/cmd/grype/cli/commands/root.go index 17be3561088..e3dfc167a7c 100644 --- a/cmd/grype/cli/commands/root.go +++ b/cmd/grype/cli/commands/root.go @@ -30,6 +30,7 @@ import ( "github.com/anchore/grype/grype/pkg" "github.com/anchore/grype/grype/presenter/models" "github.com/anchore/grype/grype/store" + "github.com/anchore/grype/grype/vex" "github.com/anchore/grype/internal" "github.com/anchore/grype/internal/bus" "github.com/anchore/grype/internal/format" @@ -94,6 +95,11 @@ var ignoreFixedMatches = []match.IgnoreRule{ {FixState: string(grypeDb.FixedState)}, } +var ignoreVEXFixedNotAffected = []match.IgnoreRule{ + {VexStatus: string(vex.StatusNotAffected)}, + {VexStatus: string(vex.StatusFixed)}, +} + //nolint:funlen func runGrype(app clio.Application, opts *options.Grype, userInput string) error { errs := make(chan error) @@ -166,6 +172,11 @@ func runGrype(app clio.Application, opts *options.Grype, userInput string) error opts.Ignore = append(opts.Ignore, ignoreFixedMatches...) } + if err := applyVexRules(opts); err != nil { + errs <- fmt.Errorf("applying vex rules: %w", err) + return + } + applyDistroHint(packages, &pkgContext, opts) vulnMatcher := grype.VulnerabilityMatcher{ @@ -175,8 +186,8 @@ func runGrype(app clio.Application, opts *options.Grype, userInput string) error FailSeverity: opts.FailOnServerity(), Matchers: getMatchers(opts), VexProcessor: vex.NewProcessor(vex.ProcessorOptions{ - Documents: appConfig.VexDocuments, - IgnoreRules: appConfig.Ignore, + Documents: opts.VexDocuments, + IgnoreRules: opts.Ignore, }), } @@ -346,3 +357,26 @@ func validateRootArgs(cmd *cobra.Command, args []string) error { return cobra.MaximumNArgs(1)(cmd, args) } + +func applyVexRules(opts *options.Grype) error { + if len(opts.Ignore) == 0 && len(opts.VexDocuments) > 0 { + opts.Ignore = append(opts.Ignore, ignoreVEXFixedNotAffected...) + } + + for _, vexStatus := range opts.VexAdd { + switch vexStatus { + case string(vex.StatusAffected): + opts.Ignore = append( + opts.Ignore, match.IgnoreRule{VexStatus: string(vex.StatusAffected)}, + ) + case string(vex.StatusUnderInvestigation): + opts.Ignore = append( + opts.Ignore, match.IgnoreRule{VexStatus: string(vex.StatusUnderInvestigation)}, + ) + default: + return fmt.Errorf("invalid VEX status in vex-add setting: %s", vexStatus) + } + } + + return nil +} diff --git a/cmd/grype/cli/options/grype.go b/cmd/grype/cli/options/grype.go index 165f296cacc..3d139e8f109 100644 --- a/cmd/grype/cli/options/grype.go +++ b/cmd/grype/cli/options/grype.go @@ -32,6 +32,8 @@ type Grype struct { 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 Name string `yaml:"name" json:"name" mapstructure:"name"` DefaultImagePullSource string `yaml:"default-image-pull-source" json:"default-image-pull-source" mapstructure:"default-image-pull-source"` + VexDocuments []string `yaml:"vex-documents" json:"vex-documents" mapstructure:"vex-documents"` + VexAdd []string `yaml:"vex-add" json:"vex-add" mapstructure:"vex-add"` // GRYPE_VEX_ADD } var _ interface { @@ -46,6 +48,7 @@ func DefaultGrype(id clio.Identification) *Grype { Match: defaultMatchConfig(), ExternalSources: defaultExternalSources(), CheckForAppUpdate: true, + VexAdd: []string{}, } } @@ -118,6 +121,11 @@ func (o *Grype) AddFlags(flags clio.FlagSet) { "platform", "", "an optional platform specifier for container image sources (e.g. 'linux/arm64', 'linux/arm64/v8', 'arm64', 'linux')", ) + + flags.StringArrayVarP(&o.VexDocuments, + "vex", "", + "a list of VEX documents to consider when producing scanning results", + ) } func (o *Grype) PostLoad() error { From d086099b6ed0ef51f8f53cc15c663d67beb8ecad Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Tue, 12 Sep 2023 12:44:14 -0400 Subject: [PATCH 23/29] update table snapshot tests with suppressed vex entries Signed-off-by: Alex Goodman --- grype/presenter/internal/test_helpers.go | 108 +++++++++++------- grype/presenter/models/metadata_mock.go | 21 ++++ .../table/__snapshots__/presenter_test.snap | 29 +++++ grype/presenter/table/presenter_test.go | 76 +++--------- .../TestDisplaysIgnoredMatches.golden | 5 - .../snapshot/TestEmptyTablePresenter.golden | 1 - .../snapshot/TestHidesIgnoredMatches.golden | 3 - .../snapshot/TestTablePresenter.golden | 3 - 8 files changed, 131 insertions(+), 115 deletions(-) create mode 100755 grype/presenter/table/__snapshots__/presenter_test.snap delete mode 100644 grype/presenter/table/test-fixtures/snapshot/TestDisplaysIgnoredMatches.golden delete mode 100644 grype/presenter/table/test-fixtures/snapshot/TestEmptyTablePresenter.golden delete mode 100644 grype/presenter/table/test-fixtures/snapshot/TestHidesIgnoredMatches.golden delete mode 100644 grype/presenter/table/test-fixtures/snapshot/TestTablePresenter.golden diff --git a/grype/presenter/internal/test_helpers.go b/grype/presenter/internal/test_helpers.go index c1471822b77..2ef882518e1 100644 --- a/grype/presenter/internal/test_helpers.go +++ b/grype/presenter/internal/test_helpers.go @@ -10,6 +10,7 @@ import ( "github.com/anchore/grype/grype/match" "github.com/anchore/grype/grype/pkg" "github.com/anchore/grype/grype/presenter/models" + "github.com/anchore/grype/grype/vex" "github.com/anchore/grype/grype/vulnerability" "github.com/anchore/stereoscope/pkg/image" "github.com/anchore/syft/syft/artifact" @@ -148,64 +149,89 @@ func generateMatches(t *testing.T, p, p2 pkg.Package) match.Matches { return collection } +// nolint: funlen func generateIgnoredMatches(t *testing.T, p pkg.Package) []match.IgnoredMatch { t.Helper() - matches := []match.Match{ + return []match.IgnoredMatch{ { - - Vulnerability: vulnerability.Vulnerability{ - ID: "CVE-1999-0001", - Namespace: "source-1", - }, - Package: p, - Details: []match.Detail{ - { - Type: match.ExactDirectMatch, - Matcher: match.DpkgMatcher, - SearchedBy: map[string]interface{}{ - "distro": map[string]string{ - "type": "ubuntu", - "version": "20.04", + Match: match.Match{ + Vulnerability: vulnerability.Vulnerability{ + ID: "CVE-1999-0001", + Namespace: "source-1", + }, + Package: p, + Details: []match.Detail{ + { + Type: match.ExactDirectMatch, + Matcher: match.DpkgMatcher, + SearchedBy: map[string]interface{}{ + "distro": map[string]string{ + "type": "ubuntu", + "version": "20.04", + }, + }, + Found: map[string]interface{}{ + "constraint": ">= 20", }, }, - Found: map[string]interface{}{ - "constraint": ">= 20", + }, + }, + AppliedIgnoreRules: []match.IgnoreRule{}, + }, + { + Match: match.Match{ + Vulnerability: vulnerability.Vulnerability{ + ID: "CVE-1999-0002", + Namespace: "source-2", + }, + Package: p, + Details: []match.Detail{ + { + Type: match.ExactDirectMatch, + Matcher: match.DpkgMatcher, + SearchedBy: map[string]interface{}{ + "cpe": "somecpe", + }, + Found: map[string]interface{}{ + "constraint": "somecpe", + }, }, }, }, + AppliedIgnoreRules: []match.IgnoreRule{}, }, { - - Vulnerability: vulnerability.Vulnerability{ - ID: "CVE-1999-0002", - Namespace: "source-2", + Match: match.Match{ + Vulnerability: vulnerability.Vulnerability{ + ID: "CVE-1999-0004", + Namespace: "source-2", + }, + Package: p, + Details: []match.Detail{ + { + Type: match.ExactDirectMatch, + Matcher: match.DpkgMatcher, + SearchedBy: map[string]interface{}{ + "cpe": "somecpe", + }, + Found: map[string]interface{}{ + "constraint": "somecpe", + }, + }, + }, }, - Package: p, - Details: []match.Detail{ + AppliedIgnoreRules: []match.IgnoreRule{ { - Type: match.ExactDirectMatch, - Matcher: match.DpkgMatcher, - SearchedBy: map[string]interface{}{ - "cpe": "somecpe", - }, - Found: map[string]interface{}{ - "constraint": "somecpe", - }, + Vulnerability: "CVE-1999-0004", + Namespace: "vex", + Package: match.IgnoreRulePackage{}, + VexStatus: string(vex.StatusNotAffected), + VexJustification: "this isn't the vulnerability match you're looking for... *waves hand*", }, }, }, } - - var ignoredMatches []match.IgnoredMatch - for _, m := range matches { - ignoredMatches = append(ignoredMatches, match.IgnoredMatch{ - Match: m, - AppliedIgnoreRules: []match.IgnoreRule{}, - }) - } - - return ignoredMatches } func generatePackages(t *testing.T) []pkg.Package { diff --git a/grype/presenter/models/metadata_mock.go b/grype/presenter/models/metadata_mock.go index 326fec60f9e..cade2230f02 100644 --- a/grype/presenter/models/metadata_mock.go +++ b/grype/presenter/models/metadata_mock.go @@ -60,6 +60,27 @@ func NewMetadataMock() *MetadataMock { Severity: "High", }, }, + "CVE-1999-0004": { + "source-2": { + Description: "1999-04 description", + Severity: "Critical", + Cvss: []vulnerability.Cvss{ + { + Metrics: vulnerability.NewCvssMetrics( + 1, + 2, + 3, + ), + Vector: "vector", + Version: "2.0", + VendorMetadata: MockVendorMetadata{ + BaseSeverity: "Low", + Status: "verified", + }, + }, + }, + }, + }, }, } } diff --git a/grype/presenter/table/__snapshots__/presenter_test.snap b/grype/presenter/table/__snapshots__/presenter_test.snap new file mode 100755 index 00000000000..11c2b0a8ba9 --- /dev/null +++ b/grype/presenter/table/__snapshots__/presenter_test.snap @@ -0,0 +1,29 @@ + +[TestTablePresenter - 1] +NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY +package-1 1.1.1 the-next-version rpm CVE-1999-0001 Low +package-2 2.2.2 deb CVE-1999-0002 Critical + +--- + +[TestEmptyTablePresenter - 1] +No vulnerabilities found + +--- + +[TestHidesIgnoredMatches - 1] +NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY +package-1 1.1.1 rpm CVE-1999-0002 Critical +package-1 1.1.1 the-next-version rpm CVE-1999-0001 Low + +--- + +[TestDisplaysIgnoredMatches - 1] +NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY +package-1 1.1.1 rpm CVE-1999-0002 Critical +package-1 1.1.1 the-next-version rpm CVE-1999-0001 Low +package-2 2.2.2 deb CVE-1999-0004 Critical (suppressed by VEX) +package-2 2.2.2 deb CVE-1999-0002 Critical (suppressed) +package-2 2.2.2 deb CVE-1999-0001 Low (suppressed) + +--- diff --git a/grype/presenter/table/presenter_test.go b/grype/presenter/table/presenter_test.go index ee45e0769d9..c2c7e789893 100644 --- a/grype/presenter/table/presenter_test.go +++ b/grype/presenter/table/presenter_test.go @@ -2,15 +2,14 @@ package table import ( "bytes" - "flag" "testing" + "github.com/gkampitakis/go-snaps/snaps" "github.com/go-test/deep" "github.com/google/go-cmp/cmp" - "github.com/sergi/go-diff/diffmatchpatch" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" - "github.com/anchore/go-testutils" "github.com/anchore/grype/grype/match" "github.com/anchore/grype/grype/pkg" "github.com/anchore/grype/grype/presenter/internal" @@ -19,8 +18,6 @@ import ( syftPkg "github.com/anchore/syft/syft/pkg" ) -var update = flag.Bool("update", false, "update the *.golden files for table presenters") - func TestCreateRow(t *testing.T) { pkg1 := pkg.Package{ ID: "package-1-id", @@ -88,21 +85,10 @@ func TestTablePresenter(t *testing.T) { // run presenter err := pres.Present(&buffer) - if err != nil { - t.Fatal(err) - } - actual := buffer.Bytes() - if *update { - testutils.UpdateGoldenFileContents(t, actual) - } + require.NoError(t, err) - var expected = testutils.GetGoldenFileContents(t) - - if !bytes.Equal(expected, actual) { - dmp := diffmatchpatch.New() - diffs := dmp.DiffMain(string(expected), string(actual), true) - t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs)) - } + actual := buffer.String() + snaps.MatchSnapshot(t, actual) // TODO: add me back in when there is a JSON schema // validateAgainstDbSchema(t, string(actual)) @@ -125,22 +111,10 @@ func TestEmptyTablePresenter(t *testing.T) { // run presenter err := pres.Present(&buffer) - if err != nil { - t.Fatal(err) - } - actual := buffer.Bytes() - if *update { - testutils.UpdateGoldenFileContents(t, actual) - } - - var expected = testutils.GetGoldenFileContents(t) - - if !bytes.Equal(expected, actual) { - dmp := diffmatchpatch.New() - diffs := dmp.DiffMain(string(expected), string(actual), true) - t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs)) - } + require.NoError(t, err) + actual := buffer.String() + snaps.MatchSnapshot(t, actual) } func TestRemoveDuplicateRows(t *testing.T) { @@ -215,21 +189,10 @@ func TestHidesIgnoredMatches(t *testing.T) { pres := NewPresenter(pb, false) err := pres.Present(&buffer) - if err != nil { - t.Fatal(err) - } - actual := buffer.Bytes() - if *update { - testutils.UpdateGoldenFileContents(t, actual) - } + require.NoError(t, err) - var expected = testutils.GetGoldenFileContents(t) - - if !bytes.Equal(expected, actual) { - dmp := diffmatchpatch.New() - diffs := dmp.DiffMain(string(expected), string(actual), true) - t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs)) - } + actual := buffer.String() + snaps.MatchSnapshot(t, actual) } func TestDisplaysIgnoredMatches(t *testing.T) { @@ -246,19 +209,8 @@ func TestDisplaysIgnoredMatches(t *testing.T) { pres := NewPresenter(pb, true) err := pres.Present(&buffer) - if err != nil { - t.Fatal(err) - } - actual := buffer.Bytes() - if *update { - testutils.UpdateGoldenFileContents(t, actual) - } + require.NoError(t, err) - var expected = testutils.GetGoldenFileContents(t) - - if !bytes.Equal(expected, actual) { - dmp := diffmatchpatch.New() - diffs := dmp.DiffMain(string(expected), string(actual), true) - t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs)) - } + actual := buffer.String() + snaps.MatchSnapshot(t, actual) } diff --git a/grype/presenter/table/test-fixtures/snapshot/TestDisplaysIgnoredMatches.golden b/grype/presenter/table/test-fixtures/snapshot/TestDisplaysIgnoredMatches.golden deleted file mode 100644 index 3c512570384..00000000000 --- a/grype/presenter/table/test-fixtures/snapshot/TestDisplaysIgnoredMatches.golden +++ /dev/null @@ -1,5 +0,0 @@ -NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY -package-1 1.1.1 rpm CVE-1999-0002 Critical -package-1 1.1.1 the-next-version rpm CVE-1999-0001 Low -package-2 2.2.2 deb CVE-1999-0002 Critical (suppressed) -package-2 2.2.2 deb CVE-1999-0001 Low (suppressed) diff --git a/grype/presenter/table/test-fixtures/snapshot/TestEmptyTablePresenter.golden b/grype/presenter/table/test-fixtures/snapshot/TestEmptyTablePresenter.golden deleted file mode 100644 index 8900c02cd74..00000000000 --- a/grype/presenter/table/test-fixtures/snapshot/TestEmptyTablePresenter.golden +++ /dev/null @@ -1 +0,0 @@ -No vulnerabilities found diff --git a/grype/presenter/table/test-fixtures/snapshot/TestHidesIgnoredMatches.golden b/grype/presenter/table/test-fixtures/snapshot/TestHidesIgnoredMatches.golden deleted file mode 100644 index 4d13482c154..00000000000 --- a/grype/presenter/table/test-fixtures/snapshot/TestHidesIgnoredMatches.golden +++ /dev/null @@ -1,3 +0,0 @@ -NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY -package-1 1.1.1 rpm CVE-1999-0002 Critical -package-1 1.1.1 the-next-version rpm CVE-1999-0001 Low diff --git a/grype/presenter/table/test-fixtures/snapshot/TestTablePresenter.golden b/grype/presenter/table/test-fixtures/snapshot/TestTablePresenter.golden deleted file mode 100644 index e16b5919029..00000000000 --- a/grype/presenter/table/test-fixtures/snapshot/TestTablePresenter.golden +++ /dev/null @@ -1,3 +0,0 @@ -NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY -package-1 1.1.1 the-next-version rpm CVE-1999-0001 Low -package-2 2.2.2 deb CVE-1999-0002 Critical From 803d15b6673023896def455c8e029ce5e438783d Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Tue, 12 Sep 2023 12:44:34 -0400 Subject: [PATCH 24/29] add tests for match.Matches.Diff() Signed-off-by: Alex Goodman --- grype/match/matches_test.go | 64 +++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/grype/match/matches_test.go b/grype/match/matches_test.go index 59d829335c4..b26e21c3c14 100644 --- a/grype/match/matches_test.go +++ b/grype/match/matches_test.go @@ -290,3 +290,67 @@ func assertIgnoredMatchOrder(t *testing.T, expected, actual []IgnoredMatch) { // make certain the fields are what you'd expect assert.Equal(t, expected, actual) } + +func TestMatches_Diff(t *testing.T) { + a := Match{ + Vulnerability: vulnerability.Vulnerability{ + ID: "vuln-a", + Namespace: "name-a", + }, + Package: pkg.Package{ + ID: "package-a", + }, + } + + b := Match{ + Vulnerability: vulnerability.Vulnerability{ + ID: "vuln-b", + Namespace: "name-b", + }, + Package: pkg.Package{ + ID: "package-b", + }, + } + + c := Match{ + Vulnerability: vulnerability.Vulnerability{ + ID: "vuln-c", + Namespace: "name-c", + }, + Package: pkg.Package{ + ID: "package-c", + }, + } + + tests := []struct { + name string + subject Matches + other Matches + want Matches + }{ + { + name: "no diff", + subject: NewMatches(a, b, c), + other: NewMatches(a, b, c), + want: newMatches(), + }, + { + name: "extra items in subject", + subject: NewMatches(a, b, c), + other: NewMatches(a, b), + want: NewMatches(c), + }, + { + // this demonstrates that this is not meant to implement a symmetric diff + name: "extra items in other (results in no diff)", + subject: NewMatches(a, b), + other: NewMatches(a, b, c), + want: NewMatches(), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, &tt.want, tt.subject.Diff(tt.other), "Diff(%v)", tt.other) + }) + } +} From 655b7d6f5c18c5371fb289b8aabdedf8c18b37a5 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Tue, 12 Sep 2023 12:45:28 -0400 Subject: [PATCH 25/29] add tests for vex processor Signed-off-by: Alex Goodman --- grype/vex/openvex/implementation.go | 7 - grype/vex/processor_test.go | 314 ++++++++++++++++++ .../vex/testdata/vex-docs/openvex-demo1.json | 24 ++ .../vex/testdata/vex-docs/openvex-demo2.json | 89 +++++ 4 files changed, 427 insertions(+), 7 deletions(-) create mode 100644 grype/vex/processor_test.go create mode 100644 grype/vex/testdata/vex-docs/openvex-demo1.json create mode 100644 grype/vex/testdata/vex-docs/openvex-demo2.json diff --git a/grype/vex/openvex/implementation.go b/grype/vex/openvex/implementation.go index 24d26a90ae3..fa20ce75222 100644 --- a/grype/vex/openvex/implementation.go +++ b/grype/vex/openvex/implementation.go @@ -187,13 +187,6 @@ func (ovm *Processor) FilterMatches( continue } - rule.Package = match.IgnoreRulePackage{ - Name: sorted[i].Package.Name, - Version: sorted[i].Package.Version, - Language: sorted[i].Package.Language.String(), - Type: string(sorted[i].Package.Type), - } - ignoredMatches = append(ignoredMatches, match.IgnoredMatch{ Match: sorted[i], AppliedIgnoreRules: []match.IgnoreRule{*rule}, diff --git a/grype/vex/processor_test.go b/grype/vex/processor_test.go new file mode 100644 index 00000000000..85168f2b1ab --- /dev/null +++ b/grype/vex/processor_test.go @@ -0,0 +1,314 @@ +package vex + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + v5 "github.com/anchore/grype/grype/db/v5" + "github.com/anchore/grype/grype/match" + "github.com/anchore/grype/grype/pkg" + "github.com/anchore/grype/grype/vulnerability" + "github.com/anchore/syft/syft/source" +) + +func TestProcessor_ApplyVEX(t *testing.T) { + pkgContext := &pkg.Context{ + Source: &source.Description{ + Name: "alpine", + Version: "3.17", + Metadata: source.StereoscopeImageSourceMetadata{ + RepoDigests: []string{ + "alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126", + }, + }, + }, + Distro: nil, + } + + libCryptoPackage := pkg.Package{ + ID: "cc8f90662d91481d", + Name: "libcrypto3", + Version: "3.0.8-r3", + + Type: "apk", + PURL: "pkg:apk/alpine/libcrypto3@3.0.8-r3?arch=x86_64&upstream=openssl&distro=alpine-3.17.3", + Upstreams: []pkg.UpstreamPackage{ + { + Name: "openssl", + }, + }, + } + + libCryptoCVE_2023_3817 := match.Match{ + Vulnerability: vulnerability.Vulnerability{ + ID: "CVE-2023-3817", + Namespace: "alpine:distro:alpine:3.17", + Fix: vulnerability.Fix{ + Versions: []string{"3.0.10-r0"}, + State: v5.FixedState, + }, + }, + Package: libCryptoPackage, + } + + libCryptoCVE_2023_1255 := match.Match{ + Vulnerability: vulnerability.Vulnerability{ + ID: "CVE-2023-1255", + Namespace: "alpine:distro:alpine:3.17", + Fix: vulnerability.Fix{ + Versions: []string{"3.0.8-r4"}, + State: v5.FixedState, + }, + }, + Package: libCryptoPackage, + } + + libCryptoCVE_2023_2975 := match.Match{ + Vulnerability: vulnerability.Vulnerability{ + ID: "CVE-2023-2975", + Namespace: "alpine:distro:alpine:3.17", + Fix: vulnerability.Fix{ + Versions: []string{"3.0.9-r2"}, + State: v5.FixedState, + }, + }, + Package: libCryptoPackage, + } + + getSubject := func() *match.Matches { + s := match.NewMatches( + // not-affected justification example + libCryptoCVE_2023_3817, + + // fixed status example + matching CVE + libCryptoCVE_2023_1255, + + // fixed status example + libCryptoCVE_2023_2975, + ) + + return &s + } + + metchesRef := func(ms ...match.Match) *match.Matches { + m := match.NewMatches(ms...) + return &m + } + + type args struct { + pkgContext *pkg.Context + matches *match.Matches + ignoredMatches []match.IgnoredMatch + } + + tests := []struct { + name string + options ProcessorOptions + args args + wantMatches *match.Matches + wantIgnoredMatches []match.IgnoredMatch + wantErr require.ErrorAssertionFunc + }{ + { + name: "openvex-demo1 - ignore by fixed status", + options: ProcessorOptions{ + Documents: []string{ + "testdata/vex-docs/openvex-demo1.json", + }, + IgnoreRules: []match.IgnoreRule{ + { + VexStatus: "fixed", + }, + }, + }, + args: args{ + pkgContext: pkgContext, + matches: getSubject(), + }, + wantMatches: metchesRef(libCryptoCVE_2023_3817, libCryptoCVE_2023_2975), + wantIgnoredMatches: []match.IgnoredMatch{ + { + Match: libCryptoCVE_2023_1255, + AppliedIgnoreRules: []match.IgnoreRule{ + { + Namespace: "vex", // note: an additional namespace was added + VexStatus: "fixed", + }, + }, + }, + }, + }, + { + name: "openvex-demo1 - ignore by fixed status and CVE", // no real difference from the first test other than the AppliedIgnoreRules + options: ProcessorOptions{ + Documents: []string{ + "testdata/vex-docs/openvex-demo1.json", + }, + IgnoreRules: []match.IgnoreRule{ + { + Vulnerability: "CVE-2023-1255", // note: this is the difference between this test and the last test + VexStatus: "fixed", + }, + }, + }, + args: args{ + pkgContext: pkgContext, + matches: getSubject(), + }, + wantMatches: metchesRef(libCryptoCVE_2023_3817, libCryptoCVE_2023_2975), + wantIgnoredMatches: []match.IgnoredMatch{ + { + Match: libCryptoCVE_2023_1255, + AppliedIgnoreRules: []match.IgnoreRule{ + { + Namespace: "vex", + Vulnerability: "CVE-2023-1255", // note: this is the difference between this test and the last test + VexStatus: "fixed", + }, + }, + }, + }, + }, + { + name: "openvex-demo2 - ignore by fixed status", + options: ProcessorOptions{ + Documents: []string{ + "testdata/vex-docs/openvex-demo2.json", + }, + IgnoreRules: []match.IgnoreRule{ + { + VexStatus: "fixed", + }, + }, + }, + args: args{ + pkgContext: pkgContext, + matches: getSubject(), + }, + wantMatches: metchesRef(libCryptoCVE_2023_3817), + wantIgnoredMatches: []match.IgnoredMatch{ + { + Match: libCryptoCVE_2023_1255, + AppliedIgnoreRules: []match.IgnoreRule{ + { + Namespace: "vex", + VexStatus: "fixed", + }, + }, + }, + { + Match: libCryptoCVE_2023_2975, + AppliedIgnoreRules: []match.IgnoreRule{ + { + Namespace: "vex", + VexStatus: "fixed", + }, + }, + }, + }, + }, + { + name: "openvex-demo2 - ignore by fixed status and CVE", + options: ProcessorOptions{ + Documents: []string{ + "testdata/vex-docs/openvex-demo2.json", + }, + IgnoreRules: []match.IgnoreRule{ + { + Vulnerability: "CVE-2023-1255", // note: this is the difference between this test and the last test + VexStatus: "fixed", + }, + }, + }, + args: args{ + pkgContext: pkgContext, + matches: getSubject(), + }, + wantMatches: metchesRef(libCryptoCVE_2023_3817, libCryptoCVE_2023_2975), + wantIgnoredMatches: []match.IgnoredMatch{ + { + Match: libCryptoCVE_2023_1255, + AppliedIgnoreRules: []match.IgnoreRule{ + { + Namespace: "vex", + Vulnerability: "CVE-2023-1255", // note: this is the difference between this test and the last test + VexStatus: "fixed", + }, + }, + }, + }, + }, + { + name: "openvex-demo1 - ignore by not_affected status and vulnerable_code_not_present justification", + options: ProcessorOptions{ + Documents: []string{ + "testdata/vex-docs/openvex-demo1.json", + }, + IgnoreRules: []match.IgnoreRule{ + { + VexStatus: "not_affected", + VexJustification: "vulnerable_code_not_present", + }, + }, + }, + args: args{ + pkgContext: pkgContext, + matches: getSubject(), + }, + // nothing gets ignored! + wantMatches: metchesRef(libCryptoCVE_2023_3817, libCryptoCVE_2023_2975, libCryptoCVE_2023_1255), + wantIgnoredMatches: []match.IgnoredMatch{}, + }, + { + name: "openvex-demo2 - ignore by not_affected status and vulnerable_code_not_present justification", + options: ProcessorOptions{ + Documents: []string{ + "testdata/vex-docs/openvex-demo2.json", + }, + IgnoreRules: []match.IgnoreRule{ + { + VexStatus: "not_affected", + VexJustification: "vulnerable_code_not_present", + }, + }, + }, + args: args{ + pkgContext: pkgContext, + matches: getSubject(), + }, + wantMatches: metchesRef(libCryptoCVE_2023_2975, libCryptoCVE_2023_1255), + wantIgnoredMatches: []match.IgnoredMatch{ + { + Match: libCryptoCVE_2023_3817, + AppliedIgnoreRules: []match.IgnoreRule{ + { + Namespace: "vex", + VexStatus: "not_affected", + VexJustification: "vulnerable_code_not_present", + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + + p := NewProcessor(tt.options) + actualMatches, actualIgnoredMatches, err := p.ApplyVEX(tt.args.pkgContext, tt.args.matches, tt.args.ignoredMatches) + tt.wantErr(t, err) + if err != nil { + return + } + + assert.Equal(t, tt.wantMatches.Sorted(), actualMatches.Sorted()) + assert.Equal(t, tt.wantIgnoredMatches, actualIgnoredMatches) + + }) + } +} diff --git a/grype/vex/testdata/vex-docs/openvex-demo1.json b/grype/vex/testdata/vex-docs/openvex-demo1.json new file mode 100644 index 00000000000..47549499ad5 --- /dev/null +++ b/grype/vex/testdata/vex-docs/openvex-demo1.json @@ -0,0 +1,24 @@ +{ + "@context": "https://openvex.dev/ns/v0.2.0", + "@id": "https://openvex.dev/docs/public/vex-d4e9020b6d0d26f131d535e055902dd6ccf3e2088bce3079a8cd3588a4b14c78", + "author": "The OpenVEX Project ", + "timestamp": "2023-07-17T18:28:47.696004345-06:00", + "version": 1, + "statements": [ + { + "vulnerability": { + "name": "CVE-2023-1255" + }, + "products": [ + { + "@id": "pkg:oci/alpine@sha256%3A124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126", + "subcomponents": [ + { "@id": "pkg:apk/alpine/libssl3@3.0.8-r3" }, + { "@id": "pkg:apk/alpine/libcrypto3@3.0.8-r3" } + ] + } + ], + "status": "fixed" + } + ] +} diff --git a/grype/vex/testdata/vex-docs/openvex-demo2.json b/grype/vex/testdata/vex-docs/openvex-demo2.json new file mode 100644 index 00000000000..637d0907822 --- /dev/null +++ b/grype/vex/testdata/vex-docs/openvex-demo2.json @@ -0,0 +1,89 @@ +{ + "@context": "https://openvex.dev/ns/v0.2.0", + "@id": "https://openvex.dev/docs/public/vex-d4e9020b6d0d26f131d535e055902dd6ccf3e2088bce3079a8cd3588a4b14c78", + "author": "The OpenVEX Project ", + "role": "Demo Writer", + "timestamp": "2023-07-17T18:28:47.696004345-06:00", + "version": 1, + "statements": [ + { + "vulnerability": { + "name": "CVE-2023-1255" + }, + "products": [ + { + "@id": "pkg:oci/alpine@sha256%3A124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126", + "subcomponents": [ + { "@id": "pkg:apk/alpine/libssl3@3.0.8-r3" }, + { "@id": "pkg:apk/alpine/libcrypto3@3.0.8-r3" } + ] + } + ], + "status": "fixed" + }, + { + "vulnerability": { + "name": "CVE-2023-2650" + }, + "products": [ + { + "@id": "pkg:oci/alpine@sha256%3A124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126", + "subcomponents": [ + { "@id": "pkg:apk/alpine/libssl3@3.0.8-r3" }, + { "@id": "pkg:apk/alpine/libcrypto3@3.0.8-r3" } + ] + } + ], + "status": "fixed" + }, + { + "vulnerability": { + "name": "CVE-2023-2975" + }, + "products": [ + { + "@id": "pkg:oci/alpine@sha256%3A124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126", + "subcomponents": [ + { "@id": "pkg:apk/alpine/libssl3@3.0.8-r3" }, + { "@id": "pkg:apk/alpine/libcrypto3@3.0.8-r3" } + ] + } + ], + "status": "fixed" + }, + { + "vulnerability": { + "name": "CVE-2023-3446" + }, + "products": [ + { + "@id": "pkg:oci/alpine@sha256%3A124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126", + "subcomponents": [ + { "@id": "pkg:apk/alpine/libssl3@3.0.8-r3" }, + { "@id": "pkg:apk/alpine/libcrypto3@3.0.8-r3" } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_present", + "impact_statement": "affected functions were removed before packaging" + }, + { + "vulnerability": { + "name": "CVE-2023-3817" + }, + "products": [ + { + "@id": "pkg:oci/alpine@sha256%3A124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126", + "subcomponents": [ + { "@id": "pkg:apk/alpine/libssl3@3.0.8-r3" }, + { "@id": "pkg:apk/alpine/libcrypto3@3.0.8-r3" } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_present", + "impact_statement": "affected functions were removed before packaging" + } + ] +} From 43a246e795e9f5c3de5e995aa02bd12df427fa56 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Tue, 12 Sep 2023 12:47:06 -0400 Subject: [PATCH 26/29] fix linting and restore global funlen rule Signed-off-by: Alex Goodman --- .gitignore | 1 + .golangci.yaml | 2 +- cmd/grype/cli/options/grype.go | 1 + go.mod | 10 +++------- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index d75b1549039..c395a26f3f3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/.tool-versions /go.work /go.work.sum /.grype.yaml diff --git a/.golangci.yaml b/.golangci.yaml index c34b7de94e7..184f0f8ec2d 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -44,7 +44,7 @@ linters-settings: # Checks the number of lines in a function. # If lower than 0, disable the check. # Default: 60 - lines: 73 + lines: 70 # Checks the number of statements in a function. # If lower than 0, disable the check. # Default: 40 diff --git a/cmd/grype/cli/options/grype.go b/cmd/grype/cli/options/grype.go index 3d139e8f109..d58d6be55ec 100644 --- a/cmd/grype/cli/options/grype.go +++ b/cmd/grype/cli/options/grype.go @@ -52,6 +52,7 @@ func DefaultGrype(id clio.Identification) *Grype { } } +// nolint:funlen func (o *Grype) AddFlags(flags clio.FlagSet) { flags.StringVarP(&o.Search.Scope, "scope", "s", diff --git a/go.mod b/go.mod index d0ac8580f20..0f51665e9c3 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( github.com/gkampitakis/go-snaps v0.4.10 github.com/go-test/deep v1.1.0 github.com/google/go-cmp v0.5.9 + github.com/google/go-containerregistry v0.16.1 github.com/google/uuid v1.3.1 github.com/gookit/color v1.5.4 github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b @@ -40,6 +41,7 @@ require ( github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/mapstructure v1.5.0 github.com/olekukonko/tablewriter v0.0.5 + github.com/openvex/go-vex v0.2.5 github.com/owenrumney/go-sarif v1.1.1 github.com/pkg/profile v1.7.0 // indirect // pinned to pull in 386 arch fix: https://github.com/scylladb/go-set/commit/cc7b2070d91ebf40d233207b633e28f5bd8f03a5 @@ -57,13 +59,7 @@ require ( github.com/x-cray/logrus-prefixed-formatter v0.5.2 golang.org/x/term v0.12.0 // indirect gorm.io/gorm v1.23.10 -) - -require modernc.org/sqlite v1.25.0 - -require ( - github.com/google/go-containerregistry v0.16.1 - github.com/openvex/go-vex v0.2.5 + modernc.org/sqlite v1.25.0 ) require ( From 893c414b3779534c812ab84eb0468e893bbb232c Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Tue, 12 Sep 2023 12:51:32 -0400 Subject: [PATCH 27/29] remove grpc pin Signed-off-by: Alex Goodman --- go.mod | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.mod b/go.mod index 0f51665e9c3..5f097147b63 100644 --- a/go.mod +++ b/go.mod @@ -233,5 +233,3 @@ require ( modernc.org/strutil v1.1.3 // indirect modernc.org/token v1.1.0 // indirect ) - -replace google.golang.org/grpc@latest => google.golang.org/grpc v1.29.1 From 3bacab80cea1eb388107afd1602b1b7156524a89 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Tue, 12 Sep 2023 13:34:51 -0400 Subject: [PATCH 28/29] always return remaining and ignroed matches from matcher object Signed-off-by: Alex Goodman --- grype/vulnerability_matcher.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/grype/vulnerability_matcher.go b/grype/vulnerability_matcher.go index ce875a4e00b..e26dc323115 100644 --- a/grype/vulnerability_matcher.go +++ b/grype/vulnerability_matcher.go @@ -68,12 +68,12 @@ func (m *VulnerabilityMatcher) FindMatches(pkgs []pkg.Package, context pkg.Conte remainingMatches, ignoredMatches, err := m.findDBMatches(pkgs, context, progressMonitor) if err != nil { - return nil, nil, fmt.Errorf("unable to find matches against DB: %w", err) + return remainingMatches, ignoredMatches, err } remainingMatches, ignoredMatches, err = m.findVEXMatches(context, remainingMatches, ignoredMatches, progressMonitor) if err != nil { - return nil, nil, fmt.Errorf("unable to find matches against VEX sources: %w", err) + return remainingMatches, ignoredMatches, fmt.Errorf("unable to find matches against VEX sources: %w", err) } logListSummary(progressMonitor) From 21ed51b773ea23fe7101ce1c2453ebf0382163d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Tue, 12 Sep 2023 12:53:51 -0600 Subject: [PATCH 29/29] Add VEX documentation to main README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds a VEX section to the main Grype README. It adds an example document and details on how vex rules can be written. Signed-off-by: Adolfo García Veytia (Puerco) --- README.md | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/README.md b/README.md index 7f3b11b3761..a71f5825361 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ 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. +- [OpenVEX](https://github.com/openvex) support for filtering and augmenting scanning results. If you encounter an issue, please [let us know using the issue tracker](https://github.com/anchore/grype/issues). @@ -322,6 +323,9 @@ ignore: # This is the full set of supported rule fields: - vulnerability: CVE-2008-4318 fix-state: unknown + # VEX fields apply when Grype reads vex data: + vex-status: not_affected + vex-justification: vulnerable_code_not_present package: name: libcurl version: 1.5.1 @@ -370,6 +374,78 @@ apk-tools 2.10.6-r0 2.10.7-r0 CVE-2021-36159 Critical If you want Grype to only report vulnerabilities **that do not have a confirmed fix**, you can use the `--only-notfixed` flag. (This automatically adds [ignore rules](#specifying-matches-to-ignore) into Grype's configuration, such that vulnerabilities that are fixed will be ignored.) +## VEX Support + +Grype can use VEX (Vulnerability Exploitability Exchange) data to filter false +positives or provide additional context, augmenting matches. When scanning a +container image, you can use the `--vex` flag to point to one or more +[OpenVEX](https://github.com/openvex) documents. + +VEX statements relate a product (a container image), a vulnerability, and a VEX +status to express an assertion of the vulnerability's impact. There are four +[VEX statuses](https://github.com/openvex/spec/blob/main/OPENVEX-SPEC.md#status-labels): +`not_affected`, `affected`, `fixed` and `under_investigation`. + +Here is an example of a simple OpenVEX document. (tip: use +[`vexctl`](https://github.com/openvex/vexctl) to generate your own documents). + +```json +{ + "@context": "https://openvex.dev/ns/v0.2.0", + "@id": "https://openvex.dev/docs/public/vex-d4e9020b6d0d26f131d535e055902dd6ccf3e2088bce3079a8cd3588a4b14c78", + "author": "A Grype User ", + "timestamp": "2023-07-17T18:28:47.696004345-06:00", + "version": 1, + "statements": [ + { + "vulnerability": { + "name": "CVE-2023-1255" + }, + "products": [ + { + "@id": "pkg:oci/alpine@sha256%3A124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126", + "subcomponents": [ + { "@id": "pkg:apk/alpine/libssl3@3.0.8-r3" }, + { "@id": "pkg:apk/alpine/libcrypto3@3.0.8-r3" } + ] + } + ], + "status": "fixed" + } + ] +} +``` + +By default, Grype will use any statements in specified VEX documents with a +status of `not_affected` or `fixed` to move matches to the ignore set. + +Any matches ignored as a result of VEX statements are flagged when using +`--show-suppreessed`: + +``` +libcrypto3 3.0.8-r3 3.0.8-r4 apk CVE-2023-1255 Medium (suppressed by VEX) +``` + +Statements with an `affected` or `under_investigation` status will only be +considered to augment the result set when specifically requested using the +`GRYPE_VEX_ADD` environment variable or in a configuration file. + + +### VEX Ignore Rules + +Ignore rules can be written to control how Grype honors VEX statements. For +example, to configure Grype to only act on VEX statements when the justification is `vulnerable_code_not_present`, you can write a rule like this: + +```yaml +--- +ignore: + - vex-status: not_affected + vex-justification: vulnerable_code_not_present +``` + +See the [list of justifications](https://github.com/openvex/spec/blob/main/OPENVEX-SPEC.md#status-justifications) for details. You can mix `vex-status` and `vex-justification` +with other ignore rule parameters. + ## Grype's database When Grype performs a scan for vulnerabilities, it does so using a vulnerability database that's stored on your local filesystem, which is constructed by pulling data from a variety of publicly available vulnerability data sources. These sources include: