Add matching by CPE (#40)

* Commit just to share progress, needs to be squashed/fixed-up once working.

Signed-off-by: Zach Hill <zach@anchore.com>

* minor fixes

* add cpe obj

* add cpe matching

* report cpe in search key

* add verbose logging for matches; bump vulnscan-db ver

* add dev profiler option; tweak logging

* test support for CPE URI bindings

addresses https://github.com/anchore/vulnscan/pull/40#discussion_r455389937

* rename nvdv2 to nvd

* reduce scope of cpe matching to non-distro packages

* normalize nil constraint strings

Co-authored-by: Zach Hill <zach@anchore.com>
This commit is contained in:
Alex Goodman 2020-07-16 15:12:19 -04:00 committed by GitHub
parent afb8597aa2
commit bbff869499
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 1072 additions and 131 deletions

9
.gitignore vendored
View file

@ -1,9 +1,14 @@
*.tar.gz
*.profile
.server
Dockerfile
/metadata.json
/listing.json
.vscode/
.server/
*.db
*.db-journal
!**/test-fixtures/**/*.db
*.tar
*tar.gz
.idea/
*.log
.images

View file

@ -22,7 +22,6 @@ linters:
- gosimple
- govet
- ineffassign
- maligned
- misspell
- nakedret
- nolintlint
@ -48,6 +47,7 @@ linters:
# - gomnd # this is too aggressive
# - interfacer # this is a good idea, but is no longer supported and is prone to false positives
# - lll # without a way to specify per-line exception cases, this is not usable
# - maligned # this is an excellent linter, but tricky to optimize and we are not sensitive to memory layout optimizations
# - nestif
# - testpackage
# - wsl

View file

@ -3,6 +3,7 @@ package cmd
import (
"fmt"
"os"
"runtime/pprof"
"github.com/anchore/imgbom/imgbom"
_distro "github.com/anchore/imgbom/imgbom/distro"
@ -30,7 +31,25 @@ var rootCmd = &cobra.Command{
}),
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
os.Exit(runDefaultCmd(cmd, args))
if appConfig.Dev.ProfileCPU {
f, err := os.Create("cpu.profile")
if err != nil {
log.Errorf("unable to create CPU profile: %+v", err)
} else {
err := pprof.StartCPUProfile(f)
if err != nil {
log.Errorf("unable to start CPU profile: %+v", err)
}
}
}
exitCode := runDefaultCmd(cmd, args)
if appConfig.Dev.ProfileCPU {
pprof.StopCPUProfile()
}
os.Exit(exitCode)
},
}

2
go.mod
View file

@ -9,6 +9,8 @@ require (
github.com/anchore/imgbom v0.0.0-20200713170720-e8d11eec6992
github.com/anchore/siren-db v0.0.0-20200716152335-9bc4580f72a1
github.com/anchore/stereoscope v0.0.0-20200706164556-7cf39d7f4639
github.com/facebookincubator/nvdtools v0.1.4-0.20200622182922-aed862a62ae6
github.com/go-test/deep v1.0.7
github.com/hashicorp/go-getter v1.4.1
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d

45
go.sum
View file

@ -144,7 +144,6 @@ github.com/aws/aws-sdk-go v1.31.6 h1:nKjQbpXhdImctBh1e0iLg9iQW/X297LPPuY/9f92R2k
github.com/aws/aws-sdk-go v1.31.6/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
@ -184,7 +183,6 @@ github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDG
github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
@ -237,11 +235,12 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/facebookincubator/nvdtools v0.1.4-0.20200622182922-aed862a62ae6 h1:+GR1Gkrl/JervFT1aKR4kzG8T10QWYMSKfYfhCJX0vU=
github.com/facebookincubator/nvdtools v0.1.4-0.20200622182922-aed862a62ae6/go.mod h1:0/FIVnSEl9YHXLq3tKBPpKaI0iUceDhdSHPlIwIX44Y=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
@ -275,7 +274,6 @@ github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.6 h1:UHSEyLZUwX9Qoi99vVwvewiMC8mM2bf7XEM2nqvzEn8=
github.com/go-test/deep v1.0.6/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
@ -356,12 +354,10 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-containerregistry v0.0.0-20200430153450-5cbd060f5c92/go.mod h1:pD1UFYs7MCAx+ZLShBdttcaOSbyc8F9Na/9IZLNwJeA=
github.com/google/go-containerregistry v0.1.0 h1:hL5mVw7cTX3SBr64Arpv+cJH93L+Z9Q6WjckImYLB3g=
github.com/google/go-containerregistry v0.1.0/go.mod h1:npTSyywOeILcgWqd+rvtzGWflIPPcBQhYoOONaY4ltM=
github.com/google/go-containerregistry v0.1.1 h1:AG8FSAfXglim2l5qSrqp5VK2Xl03PiBf25NiTGGamws=
github.com/google/go-containerregistry v0.1.1/go.mod h1:npTSyywOeILcgWqd+rvtzGWflIPPcBQhYoOONaY4ltM=
@ -384,7 +380,6 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0/go.mod h1:RaTPr0KUf2K7fnZYLNDrr8rxAamWs3iNywJLtQ2AzBg=
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.3.0/go.mod h1:i1DMg/Lu8Sz5yYl25iOdmc5CT5qusaa+zmRWs16741s=
github.com/google/wire v0.4.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
@ -483,7 +478,6 @@ github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeY
github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@ -496,7 +490,6 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
@ -555,7 +548,6 @@ github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.6/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
@ -565,7 +557,6 @@ github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
@ -580,7 +571,6 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.3.1 h1:cCBH2gTD2K0OtLlv/Y5H01VQCqmlDxz30kS5Y5bqfLA=
github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
@ -628,7 +618,6 @@ github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59P
github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw=
github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=
@ -647,20 +636,16 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
@ -699,7 +684,6 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8=
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
@ -711,13 +695,10 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k
github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE=
github.com/sourcegraph/go-diff v0.5.3/go.mod h1:v9JDtjCE4HHHCZGId75rg8gkKKa98RVjBcBGsVmMmak=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/afero v1.3.2 h1:GDarE4TJQI52kYSbSAmLiId1Elfj+xgSDqrUZxFhxlU=
github.com/spf13/afero v1.3.2/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
@ -726,14 +707,12 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
@ -749,7 +728,6 @@ github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRci
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgho=
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@ -791,7 +769,6 @@ github.com/wagoodman/go-partybus v0.0.0-20200526224238-eb215533f07d/go.mod h1:JP
github.com/wagoodman/go-progress v0.0.0-20200621122631-1a2120f0695a/go.mod h1:jLXFoL31zFaHKAAyZUh+sxiTDFe1L1ZHrcK2T1itVKA=
github.com/wagoodman/go-progress v0.0.0-20200621153512-2778c704bf22 h1:GYaiTP0ywrCjJ4qMxxCg+YKPSDMeFJg6i1X9X55LJCA=
github.com/wagoodman/go-progress v0.0.0-20200621153512-2778c704bf22/go.mod h1:jLXFoL31zFaHKAAyZUh+sxiTDFe1L1ZHrcK2T1itVKA=
github.com/wagoodman/jotframe v0.0.0-20200622123948-2995cbd43525 h1:fGlwSBQrl9/axciK2+gJ9q86SeQYJpbPx4vOrExvZXY=
github.com/wagoodman/jotframe v0.0.0-20200622123948-2995cbd43525/go.mod h1:DzXZ1wfRedNhC3KQTick8Gf3CEPMFHsP5k4R/ldjKtw=
github.com/xanzy/go-gitlab v0.31.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug=
github.com/xanzy/go-gitlab v0.32.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug=
@ -802,7 +779,6 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 h1:VcrIfasaLFkyjk6KNlXQSzO+B0fZcnECiDrKJsfxka0=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@ -841,7 +817,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -865,7 +840,6 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
@ -875,7 +849,6 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@ -919,7 +892,6 @@ golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2 h1:eDrdRpKgkcCqKZQwyZRyeFZgfqt37SL7Kv3tok06cKE=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
@ -936,7 +908,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -962,7 +933,6 @@ golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190620070143-6f217b454f45/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -990,9 +960,7 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200610111108-226ff32320da h1:bGb80FudwxpeucJUjPYJXuJ8Hk91vNtfvrymzwiei38=
golang.org/x/sys v0.0.0-20200610111108-226ff32320da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -1004,7 +972,6 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI=
@ -1061,7 +1028,6 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204192400-7124308813f3/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200210192313-1ace956b0e17 h1:a/Fd23DJvg1CaeDH0dYHahE+hCI0v9rFgxSNIThoUcM=
golang.org/x/tools v0.0.0-20200210192313-1ace956b0e17/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@ -1140,11 +1106,8 @@ google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200519141106-08726f379972/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece h1:1YM0uhfumvoDu9sx8+RyWwTI63zoCQvI23IYFRlvte0=
google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200604104852-0b0486081ffb h1:ek2py5bOqzR7MR/6obzk0rXUgYCLmjyLnaO9ssT+l6w=
google.golang.org/genproto v0.0.0-20200604104852-0b0486081ffb/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200615140333-fd031eab31e7 h1:1N7l1PuXZwEK7OhHdmKQROOM75PnUjABGwvVRbLBgFk=
google.golang.org/genproto v0.0.0-20200615140333-fd031eab31e7/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
@ -1168,7 +1131,6 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
@ -1187,7 +1149,6 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
@ -1200,7 +1161,6 @@ gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@ -1215,7 +1175,6 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=

View file

@ -26,7 +26,8 @@ type Application struct {
Quiet bool `mapstructure:"quiet"`
Log Logging `mapstructure:"log"`
CliOptions CliOnlyOptions
Db Database `mapstructure:"db"`
Db Database `mapstructure:"db"`
Dev Development `mapstructure:"dev"`
}
type Logging struct {
@ -42,6 +43,10 @@ type Database struct {
UpdateOnStartup bool `mapstructure:"update-on-startup"`
}
type Development struct {
ProfileCPU bool `mapstructure:"profile-cpu"`
}
func (d Database) ToCuratorConfig() db.Config {
return db.Config{
DbDir: d.Dir,
@ -59,6 +64,7 @@ func setNonCliDefaultValues(v *viper.Viper) {
v.SetDefault("db.update-url", "http://localhost:5000/listing.json")
// TODO: set this to true before release
v.SetDefault("db.update-on-startup", false)
v.SetDefault("dev.profile-cpu", false)
}
func LoadConfigFromFile(v *viper.Viper, cliOpts *CliOnlyOptions) (*Application, error) {
@ -97,7 +103,7 @@ func (cfg *Application) Build() error {
if cfg.Quiet {
// TODO: this is bad: quiet option trumps all other logging options
// we should be able to quiet the consol logging and leave file logging alone...
// we should be able to quiet the console logging and leave file logging alone...
// ... this will be an enhancement for later
cfg.Log.LevelOpt = zapcore.PanicLevel
} else {
@ -132,7 +138,7 @@ func readConfig(v *viper.Viper, configPath string) error {
v.SetEnvPrefix(internal.ApplicationName)
// allow for nested options to be specified via environment variables
// e.g. pod.context = APPNAME_POD_CONTEXT
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
// use explicitly the given user config
if configPath != "" {

140
vulnscan/cpe/cpe.go Normal file
View file

@ -0,0 +1,140 @@
package cpe
import (
"fmt"
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/vulnscan/internal"
"github.com/facebookincubator/nvdtools/wfn"
)
// TODO: would be great to allow these to be overridden by user data/config
var targetSoftware = map[pkg.Language][]string{
pkg.Java: {
"java",
"maven",
"jenkins",
"cloudbees_jenkins",
},
//pkg.JavaScript: {
// "node.js",
//},
pkg.Python: {
"python",
},
pkg.Ruby: {
"ruby",
"rails",
},
}
const ANY = "*"
type CPE = wfn.Attributes
func New(cpeStr string) (CPE, error) {
value, err := wfn.Parse(cpeStr)
// we need to compare the raw data since we are constructing CPEs in other locations
value.Vendor = wfn.StripSlashes(value.Vendor)
value.Product = wfn.StripSlashes(value.Product)
value.Language = wfn.StripSlashes(value.Language)
value.Version = wfn.StripSlashes(value.Version)
value.TargetSW = wfn.StripSlashes(value.TargetSW)
value.Part = wfn.StripSlashes(value.Part)
value.Edition = wfn.StripSlashes(value.Edition)
value.Other = wfn.StripSlashes(value.Other)
value.SWEdition = wfn.StripSlashes(value.SWEdition)
value.TargetHW = wfn.StripSlashes(value.TargetHW)
value.Update = wfn.StripSlashes(value.Update)
if value == nil || err != nil {
return CPE{}, fmt.Errorf("failed to parse CPE (%s): %w", cpeStr, err)
}
return *value, nil
}
func NewSlice(cpeStrs ...string) ([]CPE, error) {
ret := make([]CPE, len(cpeStrs))
for idx, c := range cpeStrs {
value, err := New(c)
if err != nil {
return nil, err
}
ret[idx] = value
}
return ret, nil
}
// Generate Create a list of CPEs, trying to guess the vendor, product tuple and setting TargetSoftware if possible
func Generate(p *pkg.Package) ([]CPE, error) {
targetSoftwares := candidateTargetSoftwareAttrs(p)
vendors := candidateVendors(p)
products := candidateProducts(p)
keys := internal.NewStringSet()
cpes := make([]CPE, 0)
for _, product := range products {
for _, vendor := range vendors {
for _, targetSw := range targetSoftwares {
// prevent duplicate entries...
key := fmt.Sprintf("%s|%s|%s|%s", product, vendor, p.Version, targetSw)
if keys.Contains(key) {
continue
}
keys.Add(key)
// add a new entry...
candidateCpe := wfn.NewAttributesWithAny()
candidateCpe.Product = product
candidateCpe.Vendor = vendor
candidateCpe.Version = p.Version
candidateCpe.TargetSW = targetSw
cpes = append(cpes, *candidateCpe)
}
}
}
return cpes, nil
}
func candidateTargetSoftwareAttrs(p *pkg.Package) []string {
// TODO: expand with package metadata (from type assert)
mappedNames := targetSoftware[p.Language]
if mappedNames == nil {
mappedNames = []string{}
}
attrs := make([]string, len(mappedNames))
copy(attrs, targetSoftware[p.Language])
// last element is the any match, present for all
attrs = append(attrs, ANY)
return attrs
}
func candidateVendors(p *pkg.Package) []string {
// TODO: expand with package metadata (from type assert)
ret := []string{p.Name}
if p.Language == pkg.Python {
ret = append(ret, fmt.Sprintf("python-%s", p.Name))
}
return ret
}
func candidateProducts(p *pkg.Package) []string {
// TODO: expand with package metadata (from type assert)
return []string{p.Name}
}
func MatchWithoutVersion(c CPE, candidates []CPE) []CPE {
results := make([]CPE, 0)
for _, candidate := range candidates {
canCopy := candidate
if c.MatchWithoutVersion(&canCopy) {
results = append(results, candidate)
}
}
return results
}

219
vulnscan/cpe/cpe_test.go Normal file
View file

@ -0,0 +1,219 @@
package cpe
import (
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/sergi/go-diff/diffmatchpatch"
"testing"
)
func must(c CPE, e error) CPE {
if e != nil {
panic(e)
}
return c
}
func TestNew(t *testing.T) {
tests := []struct {
name string
input string
expected CPE
}{
{
name: "gocase",
input: `cpe:/a:10web:form_maker:1.0.0::~~~wordpress~~`,
expected: must(New(`cpe:2.3:a:10web:form_maker:1.0.0:*:*:*:*:wordpress:*:*`)),
},
{
name: "dashes",
input: `cpe:/a:7-zip:7-zip:4.56:beta:~~~windows~~`,
expected: must(New(`cpe:2.3:a:7-zip:7-zip:4.56:beta:*:*:*:windows:*:*`)),
},
{
name: "URL escape characters",
input: `cpe:/a:%240.99_kindle_books_project:%240.99_kindle_books:6::~~~android~~`,
expected: must(New(`cpe:2.3:a:$0.99_kindle_books_project:$0.99_kindle_books:6:*:*:*:*:android:*:*`)),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual, err := New(test.input)
if err != nil {
t.Fatalf("got an error while creating CPE: %+v", err)
}
if actual.BindToFmtString() != test.expected.BindToFmtString() {
t.Errorf("mismatched entries:\n\texpected:%+v\n\t actual:%+v\n", test.expected.BindToFmtString(), actual.BindToFmtString())
}
})
}
}
func TestGenerate(t *testing.T) {
tests := []struct {
name string
p pkg.Package
expected []CPE
}{
{
name: "simple package",
p: pkg.Package{
Name: "name",
Version: "3.2",
FoundBy: "some-analyzer",
Language: pkg.Java,
Type: pkg.DebPkg,
},
expected: []CPE{
must(New("cpe:2.3:*:name:name:3.2:*:*:*:*:java:*:*")),
must(New("cpe:2.3:*:name:name:3.2:*:*:*:*:maven:*:*")),
must(New("cpe:2.3:*:name:name:3.2:*:*:*:*:jenkins:*:*")),
must(New("cpe:2.3:*:name:name:3.2:*:*:*:*:cloudbees_jenkins:*:*")),
must(New("cpe:2.3:*:name:name:3.2:*:*:*:*:*:*:*")),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual, err := Generate(&test.p)
if err != nil {
t.Fatalf("got an error while generating CPEs: %+v", err)
}
if len(actual) != len(test.expected) {
for _, e := range actual {
t.Errorf(" unexpected entry: %+v", e.BindToFmtString())
}
t.Fatalf("unexpected number of entries: %d", len(actual))
}
for idx, a := range actual {
e := test.expected[idx]
if a.BindToFmtString() != e.BindToFmtString() {
t.Errorf("mismatched entries @ %d:\n\texpected:%+v\n\t actual:%+v\n", idx, e.BindToFmtString(), a.BindToFmtString())
}
}
})
}
}
func TestMatchWithoutVersion(t *testing.T) {
tests := []struct {
name string
compare CPE
candidates []CPE
expected []CPE
}{
{
name: "GoCase",
compare: must(New("cpe:2.3:*:python-requests:requests:2.3.0:*:*:*:*:python:*:*")),
candidates: []CPE{
must(New("cpe:2.3:a:python-requests:requests:2.2.1:*:*:*:*:*:*:*")),
},
expected: []CPE{
must(New("cpe:2.3:a:python-requests:requests:2.2.1:*:*:*:*:*:*:*")),
},
},
{
name: "IgnoreVersion",
compare: must(New("cpe:2.3:*:name:name:3.2:*:*:*:*:java:*:*")),
candidates: []CPE{
must(New("cpe:2.3:*:name:name:3.2:*:*:*:*:java:*:*")),
must(New("cpe:2.3:*:name:name:3.3:*:*:*:*:java:*:*")),
must(New("cpe:2.3:*:name:name:5.5:*:*:*:*:java:*:*")),
},
expected: []CPE{
must(New("cpe:2.3:*:name:name:3.2:*:*:*:*:java:*:*")),
must(New("cpe:2.3:*:name:name:3.3:*:*:*:*:java:*:*")),
must(New("cpe:2.3:*:name:name:5.5:*:*:*:*:java:*:*")),
},
},
{
name: "MatchByTargetSW",
compare: must(New("cpe:2.3:*:name:name:3.2:*:*:*:*:java:*:*")),
candidates: []CPE{
must(New("cpe:2.3:*:name:name:3.2:*:*:*:*:java:*:*")),
must(New("cpe:2.3:*:name:name:3.2:*:*:*:*:maven:*:*")),
must(New("cpe:2.3:*:name:name:3.2:*:*:*:*:jenkins:*:*")),
must(New("cpe:2.3:*:name:name:3.2:*:*:*:*:cloudbees_jenkins:*:*")),
must(New("cpe:2.3:*:name:name:3.2:*:*:*:*:*:*:*")),
},
expected: []CPE{
must(New("cpe:2.3:*:name:name:3.2:*:*:*:*:java:*:*")),
must(New("cpe:2.3:*:name:name:3.2:*:*:*:*:*:*:*")),
},
},
{
name: "MatchByName",
compare: must(New("cpe:2.3:*:name:name5:3.2:*:*:*:*:java:*:*")),
candidates: []CPE{
must(New("cpe:2.3:*:name:name1:3.2:*:*:*:*:java:*:*")),
must(New("cpe:2.3:*:name:name2:3.2:*:*:*:*:java:*:*")),
must(New("cpe:2.3:*:name:name3:3.2:*:*:*:*:java:*:*")),
must(New("cpe:2.3:*:name:name4:3.2:*:*:*:*:java:*:*")),
must(New("cpe:2.3:*:name:name5:3.2:*:*:*:*:*:*:*")),
},
expected: []CPE{
must(New("cpe:2.3:*:name:name5:3.2:*:*:*:*:*:*:*")),
},
},
{
name: "MatchByVendor",
compare: must(New("cpe:2.3:*:name3:name:3.2:*:*:*:*:java:*:*")),
candidates: []CPE{
must(New("cpe:2.3:*:name1:name:3.2:*:*:*:*:java:*:*")),
must(New("cpe:2.3:*:name3:name:3.2:*:*:*:*:jaba-no-bother:*:*")),
must(New("cpe:2.3:*:name3:name:3.2:*:*:*:*:java:*:*")),
must(New("cpe:2.3:*:name4:name:3.2:*:*:*:*:java:*:*")),
must(New("cpe:2.3:*:name5:name:3.2:*:*:*:*:*:*:*")),
},
expected: []CPE{
must(New("cpe:2.3:*:name3:name:3.2:*:*:*:*:java:*:*")),
},
},
{
name: "MatchAnyVendorOrTargetSW",
compare: must(New("cpe:2.3:*:*:name:3.2:*:*:*:*:*:*:*")),
candidates: []CPE{
must(New("cpe:2.3:*:name1:name:3.2:*:*:*:*:java:*:*")),
must(New("cpe:2.3:*:name3:name:3.2:*:*:*:*:jaba-no-bother:*:*")),
must(New("cpe:2.3:*:name3:name:3.2:*:*:*:*:java:*:*")),
must(New("cpe:2.3:*:name4:name:3.2:*:*:*:*:java:*:*")),
must(New("cpe:2.3:*:name5:name:3.2:*:*:*:*:*:*:*")),
must(New("cpe:2.3:*:name5:NOMATCH:3.2:*:*:*:*:*:*:*")),
},
expected: []CPE{
must(New("cpe:2.3:*:name1:name:3.2:*:*:*:*:java:*:*")),
must(New("cpe:2.3:*:name3:name:3.2:*:*:*:*:jaba-no-bother:*:*")),
must(New("cpe:2.3:*:name3:name:3.2:*:*:*:*:java:*:*")),
must(New("cpe:2.3:*:name4:name:3.2:*:*:*:*:java:*:*")),
must(New("cpe:2.3:*:name5:name:3.2:*:*:*:*:*:*:*")),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual := MatchWithoutVersion(test.compare, test.candidates)
if len(actual) != len(test.expected) {
for _, e := range actual {
t.Errorf(" unexpected entry: %+v", e.BindToFmtString())
}
t.Fatalf("unexpected number of entries: %d", len(actual))
}
for idx, a := range actual {
e := test.expected[idx]
if a.BindToFmtString() != e.BindToFmtString() {
dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(a.BindToFmtString(), e.BindToFmtString(), true)
t.Errorf("mismatched entries @ %d:\n\texpected:%+v\n\t actual:%+v\n\t diff:%+v\n", idx, e.BindToFmtString(), a.BindToFmtString(), dmp.DiffPrettyText(diffs))
}
}
})
}
}

View file

@ -22,3 +22,7 @@ type Match struct {
func (m Match) String() string {
return fmt.Sprintf("Match(pkg=%s vuln=%s confidence=%f type='%s' key='%s' foundBy='%s')", m.Package, m.Vulnerability.String(), m.Confidence, m.Type, m.SearchKey, m.Matcher)
}
func (m Match) Summary() string {
return fmt.Sprintf("vuln='%s' confidence=%0.2f type='%s' key='%s' foundBy='%s')", m.Vulnerability.ID, m.Confidence, m.Type, m.SearchKey, m.Matcher)
}

View file

@ -6,6 +6,7 @@ const (
UnknownMatchType Type = iota
ExactDirectMatch
ExactIndirectMatch
FuzzyMatch
)
type Type int
@ -14,6 +15,7 @@ var typeStr = []string{
"UnknownMatchType",
"Exact-Direct Match",
"Exact-Indirect Match",
"Fuzzy Match",
}
func ParseType(userStr string) Type {

View file

@ -19,6 +19,18 @@ func (m *Matcher) Name() string {
return "bundler-matcher"
}
func (m *Matcher) Match(store vulnerability.Provider, d distro.Distro, p *pkg.Package) ([]match.Match, error) {
return common.FindMatchesByPackageLanguage(store, p.Language, p, m.Name())
func (m *Matcher) Match(store vulnerability.Provider, _ distro.Distro, p *pkg.Package) ([]match.Match, error) {
var matches = make([]match.Match, 0)
langMatches, err := common.FindMatchesByPackageLanguage(store, p.Language, p, m.Name())
if err != nil {
return nil, err
}
matches = append(matches, langMatches...)
cpeMatches, err := common.FindMatchesByPackageCPE(store, p, m.Name())
if err != nil {
return nil, err
}
matches = append(matches, cpeMatches...)
return matches, nil
}

View file

@ -0,0 +1,52 @@
package common
import (
"fmt"
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/vulnscan/vulnscan/match"
"github.com/anchore/vulnscan/vulnscan/version"
"github.com/anchore/vulnscan/vulnscan/vulnerability"
)
func FindMatchesByPackageCPE(store vulnerability.ProviderByCPE, p *pkg.Package, matcherName string) ([]match.Match, error) {
verObj, err := version.NewVersionFromPkg(p)
if err != nil {
return nil, fmt.Errorf("matcher failed to parse version pkg='%s' ver='%s': %w", p.Name, p.Version, err)
}
matches := make([]match.Match, 0)
vulnSet := vulnerability.NewSet()
for _, cpe := range verObj.CPEs() {
allPkgVulns, err := store.GetByCPE(cpe)
if err != nil {
return nil, fmt.Errorf("matcher failed to fetch by CPE pkg='%s': %w", p.Name, err)
}
for _, vuln := range allPkgVulns {
if vulnSet.Contains(vuln) {
continue
}
vulnSet.Add(vuln)
// if the constraint it met, then the given package has the vulnerability
isPackageVulnerable, err := vuln.Constraint.Satisfied(verObj)
if err != nil {
return nil, fmt.Errorf("cpe matcher failed to check constraint='%s' version='%s': %w", vuln.Constraint, verObj, err)
}
if isPackageVulnerable {
matches = append(matches, match.Match{
Type: match.FuzzyMatch,
Confidence: 0.9, // TODO: this is hard coded for now
Vulnerability: *vuln,
Package: p,
Matcher: matcherName,
SearchKey: fmt.Sprintf("cpe[%s] constraint[%s]", cpe.BindToFmtString(), vuln.Constraint.String()),
})
}
}
}
return matches, err
}

View file

@ -0,0 +1,187 @@
package common
import (
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/vulnscan/internal"
"github.com/anchore/vulnscan/vulnscan/cpe"
"github.com/anchore/vulnscan/vulnscan/match"
"github.com/anchore/vulnscan/vulnscan/version"
"github.com/anchore/vulnscan/vulnscan/vulnerability"
"testing"
)
func must(c cpe.CPE, e error) cpe.CPE {
if e != nil {
panic(e)
}
return c
}
type mockCPEProvider struct {
data map[string]map[string][]*vulnerability.Vulnerability
}
func newMockProviderByCPE() *mockCPEProvider {
pr := mockCPEProvider{
data: make(map[string]map[string][]*vulnerability.Vulnerability),
}
pr.stub()
return &pr
}
func (pr *mockCPEProvider) stub() {
pr.data["nvd"] = map[string][]*vulnerability.Vulnerability{
"activerecord": {
{
Constraint: version.MustGetConstraint("< 3.7.6", version.SemanticFormat),
ID: "CVE-2017-fake-1",
CPEs: []cpe.CPE{
must(cpe.New("cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*")),
},
},
{
Constraint: version.MustGetConstraint("< 3.7.4", version.SemanticFormat),
ID: "CVE-2017-fake-2",
CPEs: []cpe.CPE{
must(cpe.New("cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:ruby:*:*")),
},
},
{
Constraint: version.MustGetConstraint("= 4.0.1", version.SemanticFormat),
ID: "CVE-2017-fake-3",
CPEs: []cpe.CPE{
must(cpe.New("cpe:2.3:*:couldntgetthisrightcouldyou:activerecord:4.0.1:*:*:*:*:*:*:*")),
},
},
},
"awesome": {
{
Constraint: version.MustGetConstraint("< 98SP3", version.UnknownFormat),
ID: "CVE-2017-fake-4",
CPEs: []cpe.CPE{
must(cpe.New("cpe:2.3:*:awesome:awesome:*:*:*:*:*:*:*:*")),
},
},
},
}
}
func (pr *mockCPEProvider) GetByCPE(c cpe.CPE) ([]*vulnerability.Vulnerability, error) {
return pr.data["nvd"][c.Product], nil
}
func TestFindMatchesByPackageCPE(t *testing.T) {
tests := []struct {
name string
p pkg.Package
expected []string
}{
{
name: "match from range",
p: pkg.Package{
Name: "activerecord",
Version: "3.7.5",
Language: pkg.Ruby,
Type: pkg.BundlerPkg,
},
expected: []string{
"CVE-2017-fake-1",
},
},
{
name: "multiple matches",
p: pkg.Package{
Name: "activerecord",
Version: "3.7.3",
Language: pkg.Ruby,
Type: pkg.BundlerPkg,
},
expected: []string{
"CVE-2017-fake-1",
"CVE-2017-fake-2",
},
},
{
name: "exact match",
p: pkg.Package{
Name: "activerecord",
Version: "4.0.1",
Language: pkg.Ruby,
Type: pkg.BundlerPkg,
},
expected: []string{
"CVE-2017-fake-3",
},
},
{
name: "no match",
p: pkg.Package{
Name: "couldntgetthisrightcouldyou",
Version: "4.0.1",
Language: pkg.Ruby,
Type: pkg.BundlerPkg,
},
expected: []string{},
},
{
name: "fuzzy version match",
p: pkg.Package{
Name: "awesome",
Version: "98SE1",
},
expected: []string{
"CVE-2017-fake-4",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
store := newMockProviderByCPE()
actual, err := FindMatchesByPackageCPE(store, &test.p, "SOME_OTHER_MATCHER")
if err != nil {
t.Fatalf("error while finding matches: %+v", err)
}
if len(actual) != len(test.expected) {
for _, a := range actual {
t.Errorf(" entry: %+v", a)
}
t.Fatalf("unexpected matches count: %d", len(actual))
}
foundCVEs := internal.NewStringSet()
for _, a := range actual {
foundCVEs.Add(a.Vulnerability.ID)
if a.Type != match.FuzzyMatch {
t.Error("fuzzy match not indicated")
}
if a.Package.Name != test.p.Name {
t.Errorf("failed to capture correct original package: %s", a.Package.Name)
}
if a.Matcher != "SOME_OTHER_MATCHER" {
t.Errorf("failed to capture matcher name: %s", a.Matcher)
}
if a.IndirectPackage != nil {
t.Fatalf("should not have captured indirect package")
}
if a.Confidence != 0.9 {
t.Fatalf("unexpected confidence: %f", a.Confidence)
}
}
for _, id := range test.expected {
if !foundCVEs.Contains(id) {
t.Errorf("missing CVE: %s", id)
}
}
})
}
}

View file

@ -1,8 +1,11 @@
// nolint:dupl
package common
import (
"fmt"
"github.com/anchore/vulnscan/vulnscan/version"
"github.com/anchore/imgbom/imgbom/distro"
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/vulnscan/vulnscan/match"
@ -10,15 +13,35 @@ import (
)
func FindMatchesByPackageDistro(store vulnerability.ProviderByDistro, d distro.Distro, p *pkg.Package, matcherName string) ([]match.Match, error) {
allPkgVulns, err := store.GetByDistro(d, p)
verObj, err := version.NewVersionFromPkg(p)
if err != nil {
return nil, fmt.Errorf("distro matcher failed to fetch distro='%s' pkg='%s': %w", d, p.Name, err)
return nil, fmt.Errorf("matcher failed to parse version pkg='%s' ver='%s': %w", p.Name, p.Version, err)
}
matches, err := FindMatchesForPackage(allPkgVulns, p, matcherName)
for idx := range matches {
// explicitly set the search key to indicate a distro match
matches[idx].SearchKey = fmt.Sprintf("distro=[%s] pkg=[%s:%s]", d, p.Name, p.Version)
allPkgVulns, err := store.GetByDistro(d, p)
if err != nil {
return nil, fmt.Errorf("matcher failed to fetch distro='%s' pkg='%s': %w", d, p.Name, err)
}
matches := make([]match.Match, 0)
for _, vuln := range allPkgVulns {
// if the constraint it met, then the given package has the vulnerability
isPackageVulnerable, err := vuln.Constraint.Satisfied(verObj)
if err != nil {
return nil, fmt.Errorf("distro matcher failed to check constraint='%s' version='%s': %w", vuln.Constraint, verObj, err)
}
if isPackageVulnerable {
matches = append(matches, match.Match{
Type: match.ExactDirectMatch,
Confidence: 1.0, // TODO: this is hard coded for now
Vulnerability: *vuln,
Package: p,
Matcher: matcherName,
SearchKey: fmt.Sprintf("distro[%s] constraint[%s]", d, vuln.Constraint.String()),
})
}
}
return matches, err
}

View file

@ -1,38 +0,0 @@
package common
import (
"fmt"
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/vulnscan/vulnscan/match"
"github.com/anchore/vulnscan/vulnscan/version"
"github.com/anchore/vulnscan/vulnscan/vulnerability"
)
func FindMatchesForPackage(allPkgVulns []*vulnerability.Vulnerability, p *pkg.Package, matcherName string) ([]match.Match, error) {
matches := make([]match.Match, 0)
verObj, err := version.NewVersionFromPkg(p)
if err != nil {
return nil, fmt.Errorf("matcher failed to parse version pkg='%s' ver='%s': %w", p.Name, p.Version, err)
}
for _, vuln := range allPkgVulns {
// if the constraint it met, then the given package has the vulnerability
isPackageVulnerable, err := vuln.Constraint.Satisfied(verObj)
if err != nil {
// TODO: not enough information (cannot back track constraint object)
return nil, fmt.Errorf("language matcher failed to check constraint='%s' version='%s': %w", vuln.Constraint, verObj, err)
}
if isPackageVulnerable {
matches = append(matches, match.Match{
Type: match.ExactDirectMatch,
Confidence: 1.0, // TODO: this is hard coded for now
Vulnerability: *vuln,
Package: p,
Matcher: matcherName,
})
}
}
return matches, nil
}

View file

@ -1,23 +1,46 @@
// nolint:dupl
package common
import (
"fmt"
"github.com/anchore/vulnscan/vulnscan/version"
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/vulnscan/vulnscan/match"
"github.com/anchore/vulnscan/vulnscan/vulnerability"
)
func FindMatchesByPackageLanguage(store vulnerability.ProviderByLanguage, l pkg.Language, p *pkg.Package, matcherName string) ([]match.Match, error) {
allPkgVulns, err := store.GetByLanguage(l, p)
verObj, err := version.NewVersionFromPkg(p)
if err != nil {
return nil, fmt.Errorf("language matcher failed to fetch language='%s' pkg='%s': %w", l, p.Name, err)
return nil, fmt.Errorf("matcher failed to parse version pkg='%s' ver='%s': %w", p.Name, p.Version, err)
}
matches, err := FindMatchesForPackage(allPkgVulns, p, matcherName)
for idx := range matches {
// explicitly set the search key to indicate a language match
matches[idx].SearchKey = fmt.Sprintf("language=[%s] pkg=[%s:%s]", l, p.Name, p.Version)
allPkgVulns, err := store.GetByLanguage(l, p)
if err != nil {
return nil, fmt.Errorf("matcher failed to fetch language='%s' pkg='%s': %w", l, p.Name, err)
}
matches := make([]match.Match, 0)
for _, vuln := range allPkgVulns {
// if the constraint it met, then the given package has the vulnerability
isPackageVulnerable, err := vuln.Constraint.Satisfied(verObj)
if err != nil {
return nil, fmt.Errorf("language matcher failed to check constraint='%s' version='%s': %w", vuln.Constraint, verObj, err)
}
if isPackageVulnerable {
matches = append(matches, match.Match{
Type: match.ExactDirectMatch,
Confidence: 1.0, // TODO: this is hard coded for now
Vulnerability: *vuln,
Package: p,
Matcher: matcherName,
SearchKey: fmt.Sprintf("language[%s] constraint[%s]", l, vuln.Constraint.String()),
})
}
}
return matches, err
}

View file

@ -4,6 +4,7 @@ import (
"github.com/anchore/imgbom/imgbom/distro"
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/vulnscan/internal/log"
"github.com/anchore/vulnscan/vulnscan/match"
"github.com/anchore/vulnscan/vulnscan/matcher/bundler"
"github.com/anchore/vulnscan/vulnscan/matcher/dpkg"
"github.com/anchore/vulnscan/vulnscan/matcher/java"
@ -51,15 +52,18 @@ func (c *controller) add(matchers ...Matcher) {
func (c *controller) findMatches(s vulnerability.Provider, o distro.Distro, packages ...*pkg.Package) result.Result {
res := result.NewResult()
for _, p := range packages {
log.Debugf("searching for vulnerability matches for pkg=%s", p)
matchers, ok := c.matchers[p.Type]
if !ok {
log.Errorf("no matchers available for package type=%s pkg=%s", p.Type, p)
log.Errorf("no matchers available for package pkg=%s", p)
}
for _, m := range matchers {
matches, err := m.Match(s, o, p)
if err != nil {
log.Errorf("matcher failed for pkg=%s: %+v", p, err)
} else {
logMatches(p, matches)
res.Add(p, matches...)
}
}
@ -70,3 +74,16 @@ func (c *controller) findMatches(s vulnerability.Provider, o distro.Distro, pack
func FindMatches(s vulnerability.Provider, o distro.Distro, packages ...*pkg.Package) result.Result {
return controllerInstance.findMatches(s, o, packages...)
}
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())
}
}
}

View file

@ -19,6 +19,18 @@ func (m *Matcher) Name() string {
return "python-matcher"
}
func (m *Matcher) Match(store vulnerability.Provider, d distro.Distro, p *pkg.Package) ([]match.Match, error) {
return common.FindMatchesByPackageLanguage(store, p.Language, p, m.Name())
func (m *Matcher) Match(store vulnerability.Provider, _ distro.Distro, p *pkg.Package) ([]match.Match, error) {
var matches = make([]match.Match, 0)
langMatches, err := common.FindMatchesByPackageLanguage(store, p.Language, p, m.Name())
if err != nil {
return nil, err
}
matches = append(matches, langMatches...)
cpeMatches, err := common.FindMatchesByPackageCPE(store, p, m.Name())
if err != nil {
return nil, err
}
matches = append(matches, cpeMatches...)
return matches, nil
}

View file

@ -5,7 +5,6 @@ import (
"io"
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/vulnscan/internal/log"
"github.com/anchore/vulnscan/vulnscan/result"
)
@ -20,9 +19,16 @@ func NewPresenter() *Presenter {
// ResultObj is a single item for the JSON array reported
type ResultObj struct {
Cve string `json:"cve"`
FoundBy FoundBy `json:"found-by"`
Package Package `json:"package"`
}
// FoundBy contains all data that indicates how the result match was found
type FoundBy struct {
Matcher string `json:"matcher"`
SearchKey string `json:"search-key"`
}
// Package is a nested JSON object from ResultObj
type Package struct {
Name string `json:"name"`
@ -35,22 +41,23 @@ func (pres *Presenter) Present(output io.Writer, catalog *pkg.Catalog, results r
doc := make([]ResultObj, 0)
for match := range results.Enumerate() {
pkg := catalog.Package(match.Package.ID())
p := catalog.Package(match.Package.ID())
doc = append(
doc,
ResultObj{
Cve: match.Vulnerability.ID,
Package: Package{Name: pkg.Name, Version: pkg.Version, Type: pkg.Type.String()}},
Cve: match.Vulnerability.ID,
FoundBy: FoundBy{
Matcher: match.Matcher,
SearchKey: match.SearchKey,
},
Package: Package{Name: p.Name, Version: p.Version, Type: p.Type.String()},
},
)
}
bytes, err := json.Marshal(&doc)
if err != nil {
log.Errorf("failed to marshal json (presenter=json): %w", err)
}
_, err = output.Write(bytes)
return err
enc := json.NewEncoder(output)
// prevent > and < from being escaped in the payload
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
return enc.Encode(&doc)
}

View file

@ -1 +1,26 @@
[{"cve":"CVE-1999-0001","package":{"name":"package-1","version":"1.0.1","type":"deb"}},{"cve":"CVE-1999-0002","package":{"name":"package-1","version":"1.0.1","type":"deb"}}]
[
{
"cve": "CVE-1999-0001",
"found-by": {
"matcher": "",
"search-key": ""
},
"package": {
"name": "package-1",
"version": "1.0.1",
"type": "deb"
}
},
{
"cve": "CVE-1999-0002",
"found-by": {
"matcher": "",
"search-key": ""
},
"package": {
"name": "package-1",
"version": "1.0.1",
"type": "deb"
}
}
]

View file

@ -23,6 +23,7 @@ func GetConstraint(constStr string, format Format) (Constraint, error) {
return nil, fmt.Errorf("could not find constraint for given format: %s", format)
}
// MustGetConstraint is meant for testing only, do not use within the library
func MustGetConstraint(constStr string, format Format) Constraint {
constraint, err := GetConstraint(constStr, format)
if err != nil {

View file

@ -81,7 +81,7 @@ func (c debConstraint) Satisfied(version *Version) (bool, error) {
func (c debConstraint) String() string {
if c.raw == "" {
return "[no constraint] (deb)"
return "none (deb)"
}
return fmt.Sprintf("%s (deb)", c.raw)
}

View file

@ -21,6 +21,8 @@ func (c *testCase) name() string {
}
func (c *testCase) assert(t *testing.T, format Format, constraint Constraint) {
t.Helper()
verObj, err := NewVersion(c.version, format)
if !errors.Is(err, c.createErr) {
t.Fatalf("unexpected create error: '%+v'!='%+v'", err, c.createErr)

View file

@ -71,7 +71,7 @@ func (c rpmConstraint) Satisfied(version *Version) (bool, error) {
func (c rpmConstraint) String() string {
if c.raw == "" {
return "--- (rpm)"
return "none (rpm)"
}
return fmt.Sprintf("%s (rpm)", c.raw)
}

View file

@ -22,6 +22,7 @@ type semanticConstraint struct {
func newSemanticConstraint(constStr string) (semanticConstraint, error) {
normalized := normalizer.Replace(constStr)
constraints, err := hashiVer.NewConstraint(normalized)
if err != nil {
return semanticConstraint{}, err
@ -48,5 +49,8 @@ func (c semanticConstraint) Satisfied(version *Version) (bool, error) {
}
func (c semanticConstraint) String() string {
return fmt.Sprintf("%s (semantic)", c.raw)
if c.raw == "" {
return "none (semver)"
}
return fmt.Sprintf("%s (semver)", c.raw)
}

View file

@ -43,6 +43,29 @@ func TestVersionSemantic(t *testing.T) {
{version: "2.3.1+meta", constraint: "< 2.0", satisfied: false},
{version: "2.3.1+meta", constraint: "< 2", satisfied: false},
{version: "2.3.1+meta", constraint: "< 2, > 3", satisfied: false},
// from https://github.com/hashicorp/go-version/issues/61
// and https://semver.org/#spec-item-11
// A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding identifiers are equal.
{version: "1.0.0-alpha", constraint: "> 1.0.0-alpha.1", satisfied: false},
{version: "1.0.0-alpha", constraint: "< 1.0.0-alpha.1", satisfied: true},
{version: "1.0.0-alpha.1", constraint: "> 1.0.0-alpha.beta", satisfied: false},
{version: "1.0.0-alpha.1", constraint: "< 1.0.0-alpha.beta", satisfied: true},
{version: "1.0.0-alpha.beta", constraint: "> 1.0.0-beta", satisfied: false},
{version: "1.0.0-alpha.beta", constraint: "< 1.0.0-beta", satisfied: true},
{version: "1.0.0-beta", constraint: "> 1.0.0-beta.2", satisfied: false},
{version: "1.0.0-beta", constraint: "< 1.0.0-beta.2", satisfied: true},
{version: "1.0.0-beta.2", constraint: "> 1.0.0-beta.11", satisfied: false},
{version: "1.0.0-beta.2", constraint: "< 1.0.0-beta.11", satisfied: true},
{version: "1.0.0-beta.11", constraint: "> 1.0.0-rc.1", satisfied: false},
{version: "1.0.0-beta.11", constraint: "< 1.0.0-rc.1", satisfied: true},
{version: "1.0.0-rc.1", constraint: "> 1.0.0", satisfied: false},
{version: "1.0.0-rc.1", constraint: "< 1.0.0", satisfied: true},
{version: "1.0.0-alpha.1", constraint: "> 1.0.0-alpha.1", satisfied: false},
{version: "1.0.0-alpha.2", constraint: "> 1.0.0-alpha.1", satisfied: true},
{version: "1.2.0-beta", constraint: ">1.0, <2.0", satisfied: true},
{version: "1.2.0-beta", constraint: ">1.0", satisfied: true},
{version: "1.2.0-beta", constraint: "<2.0", satisfied: true},
{version: "1.2.0", constraint: ">1.0, <2.0", satisfied: true},
}
for _, test := range tests {

View file

@ -5,6 +5,7 @@ import (
hashiVer "github.com/anchore/go-version"
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/vulnscan/vulnscan/cpe"
deb "github.com/knqyf263/go-deb-version"
)
@ -15,9 +16,10 @@ type Version struct {
}
type rich struct {
semVer *hashiVer.Version
debVer *deb.Version
rpmVer *rpmVersion
cpeVers []cpe.CPE
semVer *hashiVer.Version
debVer *deb.Version
rpmVer *rpmVersion
}
func NewVersion(raw string, format Format) (*Version, error) {
@ -35,7 +37,17 @@ func NewVersion(raw string, format Format) (*Version, error) {
}
func NewVersionFromPkg(p *pkg.Package) (*Version, error) {
return NewVersion(p.Version, FormatFromPkgType(p.Type))
ver, err := NewVersion(p.Version, FormatFromPkgType(p.Type))
if err != nil {
return nil, err
}
cpes, err := cpe.Generate(p)
if err != nil {
return nil, err
}
ver.rich.cpeVers = cpes
return ver, nil
}
func (v *Version) populate() error {
@ -59,6 +71,10 @@ func (v *Version) populate() error {
return fmt.Errorf("no rich version populated (format=%s)", v.Format)
}
func (v Version) CPEs() []cpe.CPE {
return v.rich.cpeVers
}
func (v Version) String() string {
return fmt.Sprintf("%s (%s)", v.Raw, v.Format)
}

View file

@ -60,3 +60,7 @@ func languageNamespaces(l pkg.Language) map[string]namer {
}
return namespaces
}
func cpeNamespaces() []string {
return []string{"nvd"}
}

View file

@ -3,11 +3,13 @@ package vulnerability
import (
"github.com/anchore/imgbom/imgbom/distro"
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/vulnscan/vulnscan/cpe"
)
type Provider interface {
ProviderByDistro
ProviderByLanguage
ProviderByCPE
}
type ProviderByDistro interface {
@ -17,3 +19,7 @@ type ProviderByDistro interface {
type ProviderByLanguage interface {
GetByLanguage(pkg.Language, *pkg.Package) ([]*Vulnerability, error)
}
type ProviderByCPE interface {
GetByCPE(cpe.CPE) ([]*Vulnerability, error)
}

View file

@ -0,0 +1,20 @@
package vulnerability
type Set map[string]struct{}
func NewSet() Set {
return make(Set)
}
func (s Set) Add(v *Vulnerability) {
s[v.hash()] = struct{}{}
}
func (s Set) Remove(v *Vulnerability) {
delete(s, v.hash())
}
func (s Set) Contains(v *Vulnerability) bool {
_, ok := s[v.hash()]
return ok
}

View file

@ -6,7 +6,9 @@ import (
"github.com/anchore/imgbom/imgbom/distro"
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/siren-db/pkg/db"
"github.com/anchore/vulnscan/vulnscan/cpe"
"github.com/anchore/vulnscan/vulnscan/version"
"github.com/facebookincubator/nvdtools/wfn"
)
type StoreProvider struct {
@ -80,3 +82,52 @@ func (pr *StoreProvider) GetByLanguage(l pkg.Language, p *pkg.Package) ([]*Vulne
return vulns, nil
}
func (pr *StoreProvider) GetByCPE(requestCPE cpe.CPE) ([]*Vulnerability, error) {
vulns := make([]*Vulnerability, 0)
namespaces := cpeNamespaces()
if namespaces == nil {
return nil, fmt.Errorf("no store namespaces found for arbitrary CPEs")
}
if requestCPE.Product == wfn.Any || requestCPE.Product == wfn.NA {
return nil, fmt.Errorf("product name is required")
}
for _, namespace := range namespaces {
// TODO: should we encode vendor + name? or leave it as just package name (product)?
allPkgVulns, err := pr.store.GetVulnerability(namespace, requestCPE.Product)
if err != nil {
return nil, fmt.Errorf("provider failed to fetch namespace='%s' product='%s': %w", namespace, requestCPE.Product, err)
}
for _, vuln := range allPkgVulns {
format := version.ParseFormat(vuln.VersionFormat)
constraint, err := version.GetConstraint(vuln.VersionConstraint, format)
if err != nil {
return nil, fmt.Errorf("provider failed to parse cpe='%s' constraint='%s' format='%s': %w", requestCPE.BindToFmtString(), vuln.VersionConstraint, format, err)
}
vulnCPEs, err := cpe.NewSlice(vuln.CPEs...)
if err != nil {
return nil, err
}
// compare the request CPE to the potential matches (excluding version, which is handled downstream)
candidateMatchCpes := cpe.MatchWithoutVersion(requestCPE, vulnCPEs)
if len(candidateMatchCpes) > 0 {
vulns = append(vulns, &Vulnerability{
Constraint: constraint,
CPEs: candidateMatchCpes,
ID: vuln.ID,
})
}
}
}
return vulns, nil
}

View file

@ -33,10 +33,50 @@ func (d *mockStore) stub() {
},
},
}
}
func (d *mockStore) AddVulnerability(v *db.Vulnerability) error {
return nil
d.data["nvd"] = map[string][]db.Vulnerability{
"activerecord": {
{
PackageName: "activerecord",
Namespace: "nvd",
VersionConstraint: "< 3.7.6",
ID: "CVE-2014-fake-3",
VersionFormat: "unknown",
CPEs: []string{
"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*",
},
},
{
PackageName: "activerecord",
Namespace: "nvd",
VersionConstraint: "< 3.7.4",
ID: "CVE-2014-fake-4",
VersionFormat: "unknown",
CPEs: []string{
"cpe:2.3:*:activerecord:activerecord:*:*:something:*:*:ruby:*:*",
},
},
{
PackageName: "activerecord",
Namespace: "nvd",
VersionConstraint: "= 4.0.1",
ID: "CVE-2014-fake-5",
VersionFormat: "unknown",
CPEs: []string{
"cpe:2.3:*:couldntgetthisrightcouldyou:activerecord:4.0.1:*:*:*:*:*:*:*",
},
},
{
PackageName: "activerecord",
Namespace: "nvd",
VersionConstraint: "< 98SP3",
ID: "CVE-2014-fake-6",
VersionFormat: "unknown",
CPEs: []string{
"cpe:2.3:*:awesome:awesome:*:*:*:*:*:*:*:*",
},
},
},
}
}
func (d *mockStore) GetVulnerability(namespace, name string) ([]db.Vulnerability, error) {

View file

@ -1,6 +1,8 @@
package vulnerability
import (
"github.com/anchore/vulnscan/vulnscan/cpe"
"github.com/go-test/deep"
"testing"
"github.com/anchore/imgbom/imgbom/distro"
@ -50,3 +52,92 @@ func TestGetByDistro(t *testing.T) {
}
}
func must(c cpe.CPE, e error) cpe.CPE {
if e != nil {
panic(e)
}
return c
}
func TestGetByCPE(t *testing.T) {
tests := []struct {
name string
cpe cpe.CPE
expected []Vulnerability
err bool
}{
{
name: "match from name and target SW",
cpe: must(cpe.New("cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:ruby:*:*")),
expected: []Vulnerability{
{
Constraint: version.MustGetConstraint("< 3.7.4", version.UnknownFormat),
ID: "CVE-2014-fake-4",
CPEs: []cpe.CPE{
must(cpe.New("cpe:2.3:*:activerecord:activerecord:*:*:something:*:*:ruby:*:*")),
},
},
},
},
{
name: "match from vendor & name",
cpe: must(cpe.New("cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:*:*:*")),
expected: []Vulnerability{
{
Constraint: version.MustGetConstraint("< 3.7.6", version.UnknownFormat),
ID: "CVE-2014-fake-3",
CPEs: []cpe.CPE{
must(cpe.New("cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*")),
},
},
{
Constraint: version.MustGetConstraint("< 3.7.4", version.UnknownFormat),
ID: "CVE-2014-fake-4",
CPEs: []cpe.CPE{
must(cpe.New("cpe:2.3:*:activerecord:activerecord:*:*:something:*:*:ruby:*:*")),
},
},
},
},
{
name: "dont allow any name",
cpe: must(cpe.New("cpe:2.3:*:couldntgetthisrightcouldyou:*:*:*:*:*:*:*:*:*")),
err: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
provider := NewProviderFromStore(newMockStore())
actual, err := provider.GetByCPE(test.cpe)
if err != nil && !test.err {
t.Fatalf("expected no err, got: %+v", err)
} else if err == nil && test.err {
t.Fatalf("expected an err, got none")
}
if len(actual) != len(test.expected) {
for _, a := range actual {
t.Errorf(" entry: %+v", a)
}
t.Fatalf("unexpected length: %d", len(actual))
}
for idx, vuln := range actual {
diffs := deep.Equal(&test.expected[idx], vuln)
if len(diffs) > 0 {
for _, d := range diffs {
t.Errorf("diff: %+v", d)
}
}
}
})
}
}

View file

@ -3,14 +3,21 @@ package vulnerability
import (
"fmt"
"github.com/anchore/vulnscan/vulnscan/cpe"
"github.com/anchore/vulnscan/vulnscan/version"
)
type Vulnerability struct {
Constraint version.Constraint
CPEs []cpe.CPE
ID string
}
func (v Vulnerability) String() string {
return fmt.Sprintf("Vuln(id=%s constraint='%s')", v.ID, v.Constraint)
}
func (v *Vulnerability) hash() string {
return fmt.Sprintf("%s|%s|%+v", v.ID, v.Constraint, v.CPEs)
}