From b13f93a2d3c4c2626bf940133509843961ce2850 Mon Sep 17 00:00:00 2001 From: Kota Kanbe Date: Wed, 6 Jan 2021 11:36:41 +0900 Subject: [PATCH] feat(scan): support dnf modules (#1114) * feat(scan): support dnf modules * change dnf module list --installed to --enabled * chore: refactor * feat(report): detect logic for dnf modularity label * fix func name * chore: update go mods --- go.mod | 7 ++-- go.sum | 23 ++++++++---- models/scanresults.go | 1 + oval/util.go | 22 ++++++++++-- oval/util_test.go | 79 ++++++++++++++++++++++++++++++++++++++++- report/report.go | 14 ++++---- scan/base.go | 1 + scan/redhatbase.go | 62 +++++++++++++++++++++++++------- scan/redhatbase_test.go | 41 +++++++++++++++++++++ scan/serverapi.go | 3 ++ 10 files changed, 219 insertions(+), 34 deletions(-) diff --git a/go.mod b/go.mod index f4ab85551d..a179b05396 100644 --- a/go.mod +++ b/go.mod @@ -43,7 +43,7 @@ require ( github.com/knqyf263/gost v0.1.7 github.com/kotakanbe/go-cve-dictionary v0.5.6 github.com/kotakanbe/go-pingscanner v0.1.0 - github.com/kotakanbe/goval-dictionary v0.2.16 + github.com/kotakanbe/goval-dictionary v0.3.0 github.com/kotakanbe/logrus-prefixed-formatter v0.0.0-20180123152602-928f7356cb96 github.com/magiconair/properties v1.8.4 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect @@ -65,10 +65,11 @@ require ( github.com/takuzoo3868/go-msfdb v0.1.3 go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.16.0 // indirect - golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 + golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect + golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5 - golang.org/x/sys v0.0.0-20201218084310-7d0127a74742 // indirect + golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 // indirect golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 diff --git a/go.sum b/go.sum index cb55a46a29..66ab794c7c 100644 --- a/go.sum +++ b/go.sum @@ -342,6 +342,8 @@ github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7a github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-redis/redis v6.15.7+incompatible h1:3skhDh95XQMpnqeqNftPkQD9jL9e5e36z/1SUm6dy1U= github.com/go-redis/redis v6.15.7+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= +github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis/v8 v8.4.0 h1:J5NCReIgh3QgUJu398hUncxDExN4gMOHI11NVbVicGQ= github.com/go-redis/redis/v8 v8.4.0/go.mod h1:A1tbYoHSa1fXwN+//ljcCYYJeLmVrwL9hbQN45Jdy0M= github.com/go-redis/redis/v8 v8.4.4 h1:fGqgxCTR1sydaKI00oQf3OmkU/DIe/I/fYXvGklCIuc= @@ -583,14 +585,13 @@ github.com/kotakanbe/go-cve-dictionary v0.5.6 h1:xTq6AcWYkmdqHCwL5DiqH+/C0Ga4IHl github.com/kotakanbe/go-cve-dictionary v0.5.6/go.mod h1:CtZPPDJUrU/+3TvUcD1xFHVWWlM9SSEZYRZ11pblmDQ= github.com/kotakanbe/go-pingscanner v0.1.0 h1:VG4/9l0i8WeToXclj7bIGoAZAu7a07Z3qmQiIfU0gT0= github.com/kotakanbe/go-pingscanner v0.1.0/go.mod h1:/761QZzuZFcfN8h/1QuawUA+pKukp3qcNj5mxJCOiAk= -github.com/kotakanbe/goval-dictionary v0.2.16 h1:AmlzIWS5LiMnYyVDXxPqtzbGVD8LrPNGj4uSE8YrcW8= -github.com/kotakanbe/goval-dictionary v0.2.16/go.mod h1:sNRtiJrWJg92zrT9vucHI5C1R8rCBW32X1nGAOQ2sK8= +github.com/kotakanbe/goval-dictionary v0.3.0 h1:f8itkjyrcrHaEWQcqquldifQYRndErxFHyjtMi+rbHc= +github.com/kotakanbe/goval-dictionary v0.3.0/go.mod h1:NFnlcNWtD4dXkovJqGG+IFNba4q3qXYBbq56O9fHL0o= github.com/kotakanbe/logrus-prefixed-formatter v0.0.0-20180123152602-928f7356cb96 h1:xNVK0mQJdQjw+QYeaMM4G6fvucWr8rTGGIhlPakx1wU= github.com/kotakanbe/logrus-prefixed-formatter v0.0.0-20180123152602-928f7356cb96/go.mod h1:ljq48H1V+0Vh0u7ucA3LjR4AfkAeCpxrf7LaaCk8Vmo= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -601,7 +602,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= -github.com/labstack/gommon v0.2.9/go.mod h1:E8ZTmW9vw5az5/ZyHWCp0Lw4OH2ecsaBP1C/NKavGG4= +github.com/labstack/echo/v4 v4.1.17 h1:PQIBaRplyRy3OjwILGkPg89JRtH2x5bssi59G2EL3fo= +github.com/labstack/echo/v4 v4.1.17/go.mod h1:Tn2yRQL/UclUalpb5rPdXDevbkJ+lp/2svdyFBg6CHQ= github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= @@ -654,6 +656,8 @@ github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsO github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/mattn/go-sqlite3 v1.14.5 h1:1IdxlwTNazvbKJQSxoJ5/9ECbEeaTTyeU7sEAZ5KKTQ= github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= +github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= @@ -968,9 +972,12 @@ golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 h1:3wPMTskHO3+O6jqTEXyFcsnuxMQOqYSaHsDxcbUXpqA= golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1061,6 +1068,8 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201216054612-986b41b23924 h1:QsnDpLLOKwHBBDa8nDws4DYNc/ryVW2vCpxCs09d4PY= golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1102,7 +1111,6 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1139,13 +1147,14 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7 golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201006155630-ac719f4daadf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201218084310-7d0127a74742 h1:+CBz4km/0KPU3RGTwARGh/noP3bEwtHcq+0YcBQM2JQ= -golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 h1:/dSxr6gT0FNI1MO5WLJo8mTmItROeOKTkDn+7OwWBos= +golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/models/scanresults.go b/models/scanresults.go index 000c5ce1fc..ea0b8ef5f3 100644 --- a/models/scanresults.go +++ b/models/scanresults.go @@ -48,6 +48,7 @@ type ScanResult struct { RunningKernel Kernel `json:"runningKernel"` Packages Packages `json:"packages"` SrcPackages SrcPackages `json:",omitempty"` + EnabledDnfModules []string `json:"enabledDnfModules,omitempty"` // for dnf modules WordPressPackages *WordPressPackages `json:",omitempty"` LibraryScanners LibraryScanners `json:"libraries,omitempty"` CweDict CweDict `json:"cweDict,omitempty"` diff --git a/oval/util.go b/oval/util.go index 39f7712cf5..4d995075fe 100644 --- a/oval/util.go +++ b/oval/util.go @@ -78,6 +78,7 @@ type request struct { arch string binaryPackNames []string isSrcPack bool + modularityLabel string // RHEL 8 or later only } type response struct { @@ -147,7 +148,7 @@ func getDefsByPackNameViaHTTP(r *models.ScanResult) ( select { case res := <-resChan: for _, def := range res.defs { - affected, notFixedYet, fixedIn := isOvalDefAffected(def, res.request, r.Family, r.RunningKernel) + affected, notFixedYet, fixedIn := isOvalDefAffected(def, res.request, r.Family, r.RunningKernel, r.EnabledDnfModules) if !affected { continue } @@ -250,7 +251,7 @@ func getDefsByPackNameFromOvalDB(driver db.DB, r *models.ScanResult) (relatedDef return relatedDefs, xerrors.Errorf("Failed to get %s OVAL info by package: %#v, err: %w", r.Family, req, err) } for _, def := range definitions { - affected, notFixedYet, fixedIn := isOvalDefAffected(def, req, r.Family, r.RunningKernel) + affected, notFixedYet, fixedIn := isOvalDefAffected(def, req, r.Family, r.RunningKernel, r.EnabledDnfModules) if !affected { continue } @@ -291,12 +292,27 @@ func major(version string) string { return ver[0:strings.Index(ver, ".")] } -func isOvalDefAffected(def ovalmodels.Definition, req request, family string, running models.Kernel) (affected, notFixedYet bool, fixedIn string) { +func isOvalDefAffected(def ovalmodels.Definition, req request, family string, running models.Kernel, enabledMods []string) (affected, notFixedYet bool, fixedIn string) { for _, ovalPack := range def.AffectedPacks { if req.packName != ovalPack.Name { continue } + isModularityLabelEmptyOrSame := false + if ovalPack.ModularityLabel != "" { + for _, mod := range enabledMods { + if mod == ovalPack.ModularityLabel { + isModularityLabelEmptyOrSame = true + break + } + } + } else { + isModularityLabelEmptyOrSame = true + } + if !isModularityLabelEmptyOrSame { + continue + } + if running.Release != "" { switch family { case config.RedHat, config.CentOS: diff --git a/oval/util_test.go b/oval/util_test.go index 2ed0d3fd59..e0eeab6780 100644 --- a/oval/util_test.go +++ b/oval/util_test.go @@ -202,6 +202,7 @@ func TestIsOvalDefAffected(t *testing.T) { req request family string kernel models.Kernel + mods []string } var tests = []struct { in in @@ -1076,9 +1077,85 @@ func TestIsOvalDefAffected(t *testing.T) { notFixedYet: false, fixedIn: "3.1.0", }, + // dnf module + { + in: in{ + family: config.RedHat, + def: ovalmodels.Definition{ + AffectedPacks: []ovalmodels.Package{ + { + Name: "nginx", + Version: "1.16.1-1.module+el8.3.0+8844+e5e7039f.1", + NotFixedYet: false, + ModularityLabel: "nginx:1.16", + }, + }, + }, + req: request{ + packName: "nginx", + versionRelease: "1.16.0-1.module+el8.3.0+8844+e5e7039f.1", + }, + mods: []string{ + "nginx:1.16", + }, + }, + affected: true, + notFixedYet: false, + fixedIn: "1.16.1-1.module+el8.3.0+8844+e5e7039f.1", + }, + // dnf module 2 + { + in: in{ + family: config.RedHat, + def: ovalmodels.Definition{ + AffectedPacks: []ovalmodels.Package{ + { + Name: "nginx", + Version: "1.16.1-1.module+el8.3.0+8844+e5e7039f.1", + NotFixedYet: false, + ModularityLabel: "nginx:1.16", + }, + }, + }, + req: request{ + packName: "nginx", + versionRelease: "1.16.2-1.module+el8.3.0+8844+e5e7039f.1", + }, + mods: []string{ + "nginx:1.16", + }, + }, + affected: false, + notFixedYet: false, + }, + // dnf module 3 + { + in: in{ + family: config.RedHat, + def: ovalmodels.Definition{ + AffectedPacks: []ovalmodels.Package{ + { + Name: "nginx", + Version: "1.16.1-1.module+el8.3.0+8844+e5e7039f.1", + NotFixedYet: false, + ModularityLabel: "nginx:1.16", + }, + }, + }, + req: request{ + packName: "nginx", + versionRelease: "1.16.0-1.module+el8.3.0+8844+e5e7039f.1", + }, + mods: []string{ + "nginx:1.14", + }, + }, + affected: false, + notFixedYet: false, + }, } for i, tt := range tests { - affected, notFixedYet, fixedIn := isOvalDefAffected(tt.in.def, tt.in.req, tt.in.family, tt.in.kernel) + affected, notFixedYet, fixedIn := isOvalDefAffected(tt.in.def, tt.in.req, tt.in.family, tt.in.kernel, tt.in.mods) if tt.affected != affected { t.Errorf("[%d] affected\nexpected: %v\n actual: %v\n", i, tt.affected, affected) } diff --git a/report/report.go b/report/report.go index b4cc38832d..3ccb21cae8 100644 --- a/report/report.go +++ b/report/report.go @@ -7,21 +7,19 @@ import ( "strings" "time" - "github.com/future-architect/vuls/libmanager" - gostdb "github.com/knqyf263/gost/db" - - "github.com/future-architect/vuls/config" c "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/contrib/owasp-dependency-check/parser" "github.com/future-architect/vuls/cwe" "github.com/future-architect/vuls/exploit" "github.com/future-architect/vuls/github" "github.com/future-architect/vuls/gost" + "github.com/future-architect/vuls/libmanager" "github.com/future-architect/vuls/models" "github.com/future-architect/vuls/msf" "github.com/future-architect/vuls/oval" "github.com/future-architect/vuls/util" "github.com/future-architect/vuls/wordpress" + gostdb "github.com/knqyf263/gost/db" cvedb "github.com/kotakanbe/go-cve-dictionary/db" cvemodels "github.com/kotakanbe/go-cve-dictionary/models" ovaldb "github.com/kotakanbe/goval-dictionary/db" @@ -170,7 +168,7 @@ func DetectPkgCves(dbclient DBClient, r *models.ScanResult) error { } } else if reuseScannedCves(r) { util.Log.Infof("r.Release is empty. Use CVEs as it as.") - } else if r.Family == config.ServerTypePseudo { + } else if r.Family == c.ServerTypePseudo { util.Log.Infof("pseudo type. Skip OVAL and gost detection") } else { return xerrors.Errorf("Failed to fill CVEs. r.Release is empty") @@ -207,7 +205,7 @@ func DetectPkgCves(dbclient DBClient, r *models.ScanResult) error { } // DetectGitHubCves fetches CVEs from GitHub Security Alerts -func DetectGitHubCves(r *models.ScanResult, githubConfs map[string]config.GitHubConf) error { +func DetectGitHubCves(r *models.ScanResult, githubConfs map[string]c.GitHubConf) error { if len(githubConfs) == 0 { return nil } @@ -228,7 +226,7 @@ func DetectGitHubCves(r *models.ScanResult, githubConfs map[string]config.GitHub } // DetectWordPressCves detects CVEs of WordPress -func DetectWordPressCves(r *models.ScanResult, wpCnf *config.WordPressConf) error { +func DetectWordPressCves(r *models.ScanResult, wpCnf *c.WordPressConf) error { if wpCnf.WPVulnDBToken == "" { return nil } @@ -431,7 +429,7 @@ func fillWithMetasploit(driver metasploitdb.DB, r *models.ScanResult) (nMetasplo // DetectCpeURIsCves detects CVEs of given CPE-URIs func DetectCpeURIsCves(driver cvedb.DB, r *models.ScanResult, cpeURIs []string) error { nCVEs := 0 - if len(cpeURIs) != 0 && driver == nil && !config.Conf.CveDict.IsFetchViaHTTP() { + if len(cpeURIs) != 0 && driver == nil && !c.Conf.CveDict.IsFetchViaHTTP() { return xerrors.Errorf("cpeURIs %s specified, but cve-dictionary DB not found. Fetch cve-dictionary before reporting. For details, see `https://github.com/kotakanbe/go-cve-dictionary#deploy-go-cve-dictionary`", cpeURIs) } diff --git a/scan/base.go b/scan/base.go index a27fd2af60..42b080d176 100644 --- a/scan/base.go +++ b/scan/base.go @@ -449,6 +449,7 @@ func (l *base) convertToModel() models.ScanResult { RunningKernel: l.Kernel, Packages: l.Packages, SrcPackages: l.SrcPackages, + EnabledDnfModules: l.EnabledDnfModules, WordPressPackages: l.WordPress, LibraryScanners: l.LibraryScanners, Optional: l.ServerInfo.Optional, diff --git a/scan/redhatbase.go b/scan/redhatbase.go index 39827f9ab3..e212b8ff70 100644 --- a/scan/redhatbase.go +++ b/scan/redhatbase.go @@ -201,11 +201,14 @@ func (o *redhatBase) detectIPAddr() (err error) { func (o *redhatBase) scanPackages() error { installed, err := o.scanInstalledPackages() if err != nil { - o.log.Errorf("Failed to scan installed packages: %s", err) - return err + return xerrors.Errorf("Failed to scan installed packages: %w", err) } o.Packages = installed + if o.EnabledDnfModules, err = o.detectEnabledDnfModules(); err != nil { + return xerrors.Errorf("Failed to detect installed dnf modules: %w", err) + } + rebootRequired, err := o.rebootRequired() if err != nil { err = xerrors.Errorf("Failed to detect the kernel reboot required: %w", err) @@ -261,7 +264,7 @@ func (o *redhatBase) scanInstalledPackages() (models.Packages, error) { Version: version, } - r := o.exec(o.rpmQa(o.Distro), noSudo) + r := o.exec(o.rpmQa(), noSudo) if !r.isSuccess() { return nil, xerrors.Errorf("Scan packages failed: %s", r) } @@ -640,7 +643,7 @@ func (o *redhatBase) procPathToFQPN(execCommand string) (string, error) { } func (o *redhatBase) getPkgName(paths []string) (pkgNames []string, err error) { - cmd := o.rpmQf(o.Distro) + strings.Join(paths, " ") + cmd := o.rpmQf() + strings.Join(paths, " ") r := o.exec(util.PrependProxyEnv(cmd), noSudo) if !r.isSuccess(0, 2, 4, 8) { return nil, xerrors.Errorf("Failed to rpm -qf: %s, cmd: %s", r, cmd) @@ -659,36 +662,71 @@ func (o *redhatBase) getPkgName(paths []string) (pkgNames []string, err error) { return pkgNames, nil } -func (o *redhatBase) rpmQa(distro config.Distro) string { +func (o *redhatBase) rpmQa() string { const old = `rpm -qa --queryformat "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n"` const new = `rpm -qa --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n"` - switch distro.Family { + switch o.Distro.Family { case config.SUSEEnterpriseServer: - if v, _ := distro.MajorVersion(); v < 12 { + if v, _ := o.Distro.MajorVersion(); v < 12 { return old } return new default: - if v, _ := distro.MajorVersion(); v < 6 { + if v, _ := o.Distro.MajorVersion(); v < 6 { return old } return new } } -func (o *redhatBase) rpmQf(distro config.Distro) string { +func (o *redhatBase) rpmQf() string { const old = `rpm -qf --queryformat "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n" ` const new = `rpm -qf --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n" ` - switch distro.Family { + switch o.Distro.Family { case config.SUSEEnterpriseServer: - if v, _ := distro.MajorVersion(); v < 12 { + if v, _ := o.Distro.MajorVersion(); v < 12 { return old } return new default: - if v, _ := distro.MajorVersion(); v < 6 { + if v, _ := o.Distro.MajorVersion(); v < 6 { return old } return new } } + +func (o *redhatBase) detectEnabledDnfModules() ([]string, error) { + switch o.Distro.Family { + case config.RedHat, config.CentOS: + //TODO OracleLinux + default: + return nil, nil + } + if v, _ := o.Distro.MajorVersion(); v < 8 { + return nil, nil + } + + cmd := `dnf --cacheonly --color=never --quiet module list --enabled` + r := o.exec(util.PrependProxyEnv(cmd), noSudo) + if !r.isSuccess() { + return nil, xerrors.Errorf("Failed to dnf module list: %s, cmd: %s", r, cmd) + } + return o.parseDnfModuleList(r.Stdout) +} + +func (o *redhatBase) parseDnfModuleList(stdout string) (labels []string, err error) { + scanner := bufio.NewScanner(strings.NewReader(stdout)) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "Hint:") || !strings.Contains(line, "[i]") { + continue + } + ss := strings.Fields(line) + if len(ss) < 2 { + continue + } + labels = append(labels, fmt.Sprintf("%s:%s", ss[0], ss[1])) + } + return +} diff --git a/scan/redhatbase_test.go b/scan/redhatbase_test.go index 95de7a17ff..cc9bf26334 100644 --- a/scan/redhatbase_test.go +++ b/scan/redhatbase_test.go @@ -397,3 +397,44 @@ func TestParseNeedsRestarting(t *testing.T) { } } } + +func Test_redhatBase_parseDnfModuleList(t *testing.T) { + type args struct { + stdout string + } + tests := []struct { + name string + args args + wantLabels []string + wantErr bool + }{ + { + name: "Success", + args: args{ + stdout: `Red Hat Enterprise Linux 8 for x86_64 - AppStream from RHUI (RPMs) +Name Stream Profiles Summary +virt rhel [d][e] common [d] Virtualization module +nginx 1.14 [d][e] common [d] [i] nginx webserver + +Hint: [d]efault, [e]nabled, [x]disabled, [i]nstalled`, + }, + wantLabels: []string{ + "nginx:1.14", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o := &redhatBase{} + gotLabels, err := o.parseDnfModuleList(tt.args.stdout) + if (err != nil) != tt.wantErr { + t.Errorf("redhatBase.parseDnfModuleList() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotLabels, tt.wantLabels) { + t.Errorf("redhatBase.parseDnfModuleList() = %v, want %v", gotLabels, tt.wantLabels) + } + }) + } +} diff --git a/scan/serverapi.go b/scan/serverapi.go index 139d12730a..f7587ec63d 100644 --- a/scan/serverapi.go +++ b/scan/serverapi.go @@ -70,6 +70,9 @@ type osPackages struct { // installed source packages (Debian based only) SrcPackages models.SrcPackages + // enabled dnf modules or packages + EnabledDnfModules []string + // unsecure packages VulnInfos models.VulnInfos