add experimental CVE reporting

This commit is contained in:
Ryan Mulligan 2019-10-06 16:17:08 -07:00
parent 303aed0afc
commit ab2b5a6b91
3 changed files with 95 additions and 13 deletions

View File

@ -7,6 +7,7 @@ module CVE
, CVE(..)
, CVEID
, cveMatcherList
, cveLI
) where
import OurPrelude
@ -46,7 +47,12 @@ data CVE =
, cvePublished :: UTCTime
, cveLastModified :: UTCTime
}
deriving (Show)
deriving (Show, Eq, Ord)
-- | cve list item
cveLI :: CVE -> Text
cveLI c =
"- [" <> cveID c <> "](https://nvd.nist.gov/vuln/detail/" <> cveID c <> ")"
-- This decodes an entire CPE string and related attributes, but we only use
-- cpeVulnerable, cpeProduct, cpeVersion and cpeMatcher.

View File

@ -11,12 +11,12 @@ import Control.Applicative ((<**>))
import qualified Data.Text as T
import qualified Data.Text.IO as T
import DeleteMerged (deleteDone)
import NVD (getCVEs, withVulnDB)
import NVD (withVulnDB)
import qualified Nix
import qualified Options.Applicative as O
import System.Posix.Env (setEnv)
import Update (updateAll)
import Utils (Options(..), setupNixpkgs)
import Update (cveAll, cveReport, updateAll)
import Utils (Options(..), UpdateEnv(..), setupNixpkgs)
default (T.Text)
@ -30,7 +30,8 @@ data Command
| DeleteDone
| Version
| UpdateVulnDB
| CheckVulnerable Text Text
| CheckAllVulnerable
| CheckVulnerable Text Text Text
updateOptionsParser :: O.Parser Command
updateOptionsParser =
@ -64,12 +65,18 @@ commandParser =
(O.progDesc "Updates the vulnerability database")) <>
O.command
"check-vulnerable"
(O.info checkVulnerable (O.progDesc "checks if something is vulnerable")))
(O.info checkVulnerable (O.progDesc "checks if something is vulnerable")) <>
O.command
"check-all-vulnerable"
(O.info
(pure CheckAllVulnerable)
(O.progDesc "checks all packages to update for vulnerabilities")))
checkVulnerable :: O.Parser Command
checkVulnerable =
CheckVulnerable <$> O.strArgument (O.metavar "PRODUCT_ID") <*>
O.strArgument (O.metavar "VERSION")
O.strArgument (O.metavar "OLD_VERSION") <*>
O.strArgument (O.metavar "NEW_VERSION")
programInfo :: O.ParserInfo Command
programInfo =
@ -105,7 +112,11 @@ main = do
Left t -> T.putStrLn ("error:" <> t)
Right t -> T.putStrLn t
UpdateVulnDB -> withVulnDB $ \_conn -> pure ()
CheckVulnerable productId version ->
withVulnDB $ \conn -> do
cves <- getCVEs conn productId version
mapM_ print cves
CheckAllVulnerable -> do
updates <- T.readFile "packages-to-update.txt"
cveAll (Options undefined undefined) updates
CheckVulnerable productID oldVersion newVersion -> do
report <-
cveReport
(UpdateEnv productID oldVersion newVersion (Options False undefined))
T.putStrLn report

View File

@ -7,11 +7,14 @@
module Update
( updateAll
, cveReport
, cveAll
) where
import OurPrelude
import qualified Blacklist
import CVE (cveLI)
import qualified Check
import Control.Concurrent
import qualified Data.ByteString.Lazy.Char8 as BSL
@ -23,6 +26,7 @@ import Data.Time.Clock (UTCTime, getCurrentTime)
import qualified File
import qualified GH
import qualified Git
import NVD (getCVEs, withVulnDB)
import qualified Nix
import Outpaths
import Prelude hiding (log)
@ -63,6 +67,17 @@ updateAll o updates = do
liftIO $ newIORef (MergeBaseOutpathsInfo twoHoursAgo S.empty)
updateLoop o log (parseUpdates updates) mergeBaseOutpathSet
cveAll :: Options -> Text -> IO ()
cveAll o updates = do
let u' = rights $ parseUpdates updates
results <-
mapM
(\(p, oldV, newV) -> do
r <- cveReport (UpdateEnv p oldV newV o)
return $ p <> ": " <> oldV <> " -> " <> newV <> "\n" <> r)
u'
T.putStrLn (T.unlines results)
updateLoop ::
MonadIO m
=> Options
@ -178,6 +193,7 @@ publishPackage log updateEnv oldSrcUrl newSrcUrl attrPath result opDiff = do
Left msg -> pure msg
d <- Nix.getDescription attrPath <|> return T.empty
u <- Nix.getHomepage attrPath <|> return T.empty
cveRep <- liftIO $ cveReport updateEnv
let metaDescription =
if d == T.empty
then ""
@ -225,7 +241,8 @@ publishPackage log updateEnv oldSrcUrl newSrcUrl attrPath result opDiff = do
attrPath
maintainersCc
result
(outpathReport opDiff))
(outpathReport opDiff)
cveRep)
Git.cleanAndResetTo "master"
repologyUrl :: UpdateEnv -> Text
@ -266,7 +283,8 @@ prMessage ::
-> Text
-> Text
-> Text
prMessage updateEnv isBroken metaDescription metaHomepage releaseUrlMessage compareUrlMessage resultCheckReport commitHash attrPath maintainersCc resultPath opReport =
-> Text
prMessage updateEnv isBroken metaDescription metaHomepage releaseUrlMessage compareUrlMessage resultCheckReport commitHash attrPath maintainersCc resultPath opReport cveRep =
let brokenMsg = brokenWarning isBroken
title = prTitle updateEnv attrPath
repologyLink = repologyUrl updateEnv
@ -327,6 +345,9 @@ prMessage updateEnv isBroken metaDescription metaHomepage releaseUrlMessage comp
</details>
<br/>
$cveRep
$maintainersCc
|]
@ -348,3 +369,47 @@ assertNotUpdatedOn updateEnv derivationFile branch = do
Git.cleanAndResetTo branch
derivationContents <- fmapLT tshow $ tryIO $ T.readFile derivationFile
Nix.assertOldVersionOn updateEnv branch derivationContents
cveReport :: UpdateEnv -> IO Text
cveReport updateEnv =
withVulnDB $ \conn
-- TODO try other heuristics for project id
-- example false positive in current plan "vault"
-> do
oldCVEs <-
S.fromList <$> getCVEs conn (packageName updateEnv) (oldVersion updateEnv)
newCVEs <-
S.fromList <$> getCVEs conn (packageName updateEnv) (newVersion updateEnv)
let inOldButNotNew = S.difference oldCVEs newCVEs
inNewButNotOld = S.difference newCVEs oldCVEs
inBoth = S.intersection oldCVEs newCVEs
ifEmptyNone t =
if t == T.empty
then "none"
else t
toMkdownList = S.toList >>> fmap cveLI >>> T.unlines >>> ifEmptyNone
fixedList = toMkdownList inOldButNotNew
newList = toMkdownList inNewButNotOld
unresolvedList = toMkdownList inBoth
if fixedList == "none" && unresolvedList == "none" && newList == "none"
then return ""
else return
[interpolate|
<details>
<summary>
Experimental: CVE security report (click to expand)
</summary>
CVEs resolved by this update:
$fixedList
CVEs introduced by this update:
$newList
CVEs present in both versions:
$unresolvedList
</details>
<br/>
|]