module Ecluse.Core.Cve (
CveDb (..),
openCveDb,
CveLookup (..),
AdvisoryRange (..),
CveDbRejected (..),
CveQueryFault (..),
DbEtag (..),
insideAffectedRange,
severityAtLeast,
) where
import UnliftIO.Exception (catch, catchAny, onException, throwIO)
import Ecluse.Core.Cve.Internal (AdvisoryRange (..), CveDbRejected (..), advisoriesQuery, openHardenedConnection, probeQuery, provenanceQuery)
import Ecluse.Core.Ecosystem (Ecosystem)
import Ecluse.Core.Version (compareVersions, mkVersion)
import Database.SQLite.Simple (Connection, SQLError, close)
newtype DbEtag = DbEtag Text
deriving stock (DbEtag -> DbEtag -> Bool
(DbEtag -> DbEtag -> Bool)
-> (DbEtag -> DbEtag -> Bool) -> Eq DbEtag
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: DbEtag -> DbEtag -> Bool
== :: DbEtag -> DbEtag -> Bool
$c/= :: DbEtag -> DbEtag -> Bool
/= :: DbEtag -> DbEtag -> Bool
Eq, Int -> DbEtag -> ShowS
[DbEtag] -> ShowS
DbEtag -> String
(Int -> DbEtag -> ShowS)
-> (DbEtag -> String) -> ([DbEtag] -> ShowS) -> Show DbEtag
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> DbEtag -> ShowS
showsPrec :: Int -> DbEtag -> ShowS
$cshow :: DbEtag -> String
show :: DbEtag -> String
$cshowList :: [DbEtag] -> ShowS
showList :: [DbEtag] -> ShowS
Show)
data CveLookup = CveLookup
{ CveLookup -> Text -> Text -> IO Bool
cveRemediationProbe :: Text -> Text -> IO Bool
, CveLookup -> Text -> IO [AdvisoryRange]
cveAdvisoriesFor :: Text -> IO [AdvisoryRange]
}
data CveQueryFault = CveQueryFault
{ CveQueryFault -> Text
cqfQuery :: Text
, CveQueryFault -> Text
cqfDetail :: Text
}
deriving stock (CveQueryFault -> CveQueryFault -> Bool
(CveQueryFault -> CveQueryFault -> Bool)
-> (CveQueryFault -> CveQueryFault -> Bool) -> Eq CveQueryFault
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: CveQueryFault -> CveQueryFault -> Bool
== :: CveQueryFault -> CveQueryFault -> Bool
$c/= :: CveQueryFault -> CveQueryFault -> Bool
/= :: CveQueryFault -> CveQueryFault -> Bool
Eq, Int -> CveQueryFault -> ShowS
[CveQueryFault] -> ShowS
CveQueryFault -> String
(Int -> CveQueryFault -> ShowS)
-> (CveQueryFault -> String)
-> ([CveQueryFault] -> ShowS)
-> Show CveQueryFault
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> CveQueryFault -> ShowS
showsPrec :: Int -> CveQueryFault -> ShowS
$cshow :: CveQueryFault -> String
show :: CveQueryFault -> String
$cshowList :: [CveQueryFault] -> ShowS
showList :: [CveQueryFault] -> ShowS
Show)
instance Exception CveQueryFault
data CveDb = CveDb
{ CveDb -> CveLookup
cveDbLookup :: CveLookup
, CveDb -> IO ()
cveDbClose :: IO ()
, CveDb -> [(Text, Text)]
cveDbMeta :: [(Text, Text)]
}
openCveDb :: Ecosystem -> FilePath -> IO (Either CveDbRejected CveDb)
openCveDb :: Ecosystem -> String -> IO (Either CveDbRejected CveDb)
openCveDb Ecosystem
eco String
dbFile =
Ecosystem -> String -> IO (Either CveDbRejected Connection)
openHardenedConnection Ecosystem
eco String
dbFile IO (Either CveDbRejected Connection)
-> (Either CveDbRejected Connection
-> IO (Either CveDbRejected CveDb))
-> IO (Either CveDbRejected CveDb)
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Left CveDbRejected
rejection -> Either CveDbRejected CveDb -> IO (Either CveDbRejected CveDb)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (CveDbRejected -> Either CveDbRejected CveDb
forall a b. a -> Either a b
Left CveDbRejected
rejection)
Right Connection
conn -> do
meta <- Connection -> IO [(Text, Text)]
provenanceQuery Connection
conn IO [(Text, Text)] -> IO () -> IO [(Text, Text)]
forall (m :: * -> *) a b. MonadUnliftIO m => m a -> m b -> m a
`onException` Connection -> IO ()
close Connection
conn
pure (Right (mkCveDb conn meta))
mkCveDb :: Connection -> [(Text, Text)] -> CveDb
mkCveDb :: Connection -> [(Text, Text)] -> CveDb
mkCveDb Connection
conn [(Text, Text)]
meta =
CveDb
{ cveDbLookup :: CveLookup
cveDbLookup =
CveLookup
{ cveRemediationProbe :: Text -> Text -> IO Bool
cveRemediationProbe = \Text
name Text
version -> Text -> IO Bool -> IO Bool
forall a. Text -> IO a -> IO a
taggedQuery Text
"remediation-probe" (Connection -> Text -> Text -> IO Bool
probeQuery Connection
conn Text
name Text
version)
, cveAdvisoriesFor :: Text -> IO [AdvisoryRange]
cveAdvisoriesFor = Text -> IO [AdvisoryRange] -> IO [AdvisoryRange]
forall a. Text -> IO a -> IO a
taggedQuery Text
"advisories-for" (IO [AdvisoryRange] -> IO [AdvisoryRange])
-> (Text -> IO [AdvisoryRange]) -> Text -> IO [AdvisoryRange]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Connection -> Text -> IO [AdvisoryRange]
advisoriesQuery Connection
conn
}
,
cveDbClose :: IO ()
cveDbClose = Connection -> IO ()
close Connection
conn IO () -> (SomeException -> IO ()) -> IO ()
forall (m :: * -> *) a.
MonadUnliftIO m =>
m a -> (SomeException -> m a) -> m a
`catchAny` IO () -> SomeException -> IO ()
forall a b. a -> b -> a
const IO ()
forall (f :: * -> *). Applicative f => f ()
pass
, cveDbMeta :: [(Text, Text)]
cveDbMeta = [(Text, Text)]
meta
}
taggedQuery :: Text -> IO a -> IO a
taggedQuery :: forall a. Text -> IO a -> IO a
taggedQuery Text
tag IO a
act = IO a
act IO a -> (SQLError -> IO a) -> IO a
forall (m :: * -> *) e a.
(MonadUnliftIO m, Exception e) =>
m a -> (e -> m a) -> m a
`catch` \(SQLError
err :: SQLError) -> CveQueryFault -> IO a
forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO (Text -> Text -> CveQueryFault
CveQueryFault Text
tag (SQLError -> Text
forall b a. (Show a, IsString b) => a -> b
show SQLError
err))
insideAffectedRange :: Ecosystem -> Text -> AdvisoryRange -> Bool
insideAffectedRange :: Ecosystem -> Text -> AdvisoryRange -> Bool
insideAffectedRange Ecosystem
eco Text
versionText AdvisoryRange
ar = Bool
atOrAboveIntroduced Bool -> Bool -> Bool
&& Bool
withinUpperBound
where
v :: Version
v = Ecosystem -> Text -> Version
mkVersion Ecosystem
eco Text
versionText
atOrAboveIntroduced :: Bool
atOrAboveIntroduced = case AdvisoryRange -> Maybe Text
arIntroduced AdvisoryRange
ar of
Maybe Text
Nothing -> Bool
True
Just Text
i -> case Version -> Version -> Maybe Ordering
compareVersions Version
v (Ecosystem -> Text -> Version
mkVersion Ecosystem
eco Text
i) of
Just Ordering
LT -> Bool
False
Just Ordering
_ -> Bool
True
Maybe Ordering
Nothing -> Bool
True
withinUpperBound :: Bool
withinUpperBound = case (AdvisoryRange -> Maybe Text
arFixed AdvisoryRange
ar, AdvisoryRange -> Maybe Text
arLastAffected AdvisoryRange
ar) of
(Just Text
f, Maybe Text
_) -> case Version -> Version -> Maybe Ordering
compareVersions Version
v (Ecosystem -> Text -> Version
mkVersion Ecosystem
eco Text
f) of
Just Ordering
LT -> Bool
True
Just Ordering
_ -> Bool
False
Maybe Ordering
Nothing -> Bool
True
(Maybe Text
Nothing, Just Text
la) -> case Version -> Version -> Maybe Ordering
compareVersions Version
v (Ecosystem -> Text -> Version
mkVersion Ecosystem
eco Text
la) of
Just Ordering
GT -> Bool
False
Just Ordering
_ -> Bool
True
Maybe Ordering
Nothing -> Bool
True
(Maybe Text
Nothing, Maybe Text
Nothing) -> Bool
True
severityAtLeast :: Double -> Maybe Double -> Bool
severityAtLeast :: Double -> Maybe Double -> Bool
severityAtLeast Double
threshold = Bool -> (Double -> Bool) -> Maybe Double -> Bool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
True (Double -> Double -> Bool
forall a. Ord a => a -> a -> Bool
>= Double
threshold)