Skip to content

Commit

Permalink
[wip] start building cyclone dx 1.6 sbom output
Browse files Browse the repository at this point in the history
  • Loading branch information
MangoIV committed Jun 9, 2024
1 parent bc82249 commit ac44da7
Show file tree
Hide file tree
Showing 11 changed files with 209 additions and 23 deletions.
1 change: 1 addition & 0 deletions .hlint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- ignore: {name: 'Use :'}
12 changes: 10 additions & 2 deletions cabal-audit.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ common common-all
NamedFieldPuns
NoStarIsType
OverloadedLists
OverloadedLists
OverloadedRecordDot
OverloadedStrings
PartialTypeSignatures
RankNTypes
Expand All @@ -67,27 +69,33 @@ library
Control.Effect.Pretty
Distribution.Audit
Security.Advisories.Cabal
Security.Advisories.SBom.CycloneDX
Security.Advisories.SBom.Types

build-depends:
, aeson
, base <5
, bytestring
, Cabal
, cabal-install
, chronos
, colourista
, containers
, filepath
, fused-effects
, hsec-core ^>=0.1
, hsec-tools ^>=0.2
, hsec-core
, hsec-tools
, http-client
, kan-extensions
, optparse-applicative
, pretty
, process
, sel
, temporary
, text
, transformers
, unliftio
, uuid
, validation-selective
, vector

Expand Down
8 changes: 8 additions & 0 deletions nix/cabal-audit.nix
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
bytestring,
Cabal,
cabal-install,
chronos,
colourista,
containers,
filepath,
Expand All @@ -16,11 +17,14 @@
kan-extensions,
lib,
optparse-applicative,
pretty,
process,
sel,
temporary,
text,
transformers,
unliftio,
uuid,
validation-selective,
vector,
}:
Expand All @@ -36,6 +40,7 @@ mkDerivation {
bytestring
Cabal
cabal-install
chronos
colourista
containers
filepath
Expand All @@ -45,11 +50,14 @@ mkDerivation {
http-client
kan-extensions
optparse-applicative
pretty
process
sel
temporary
text
transformers
unliftio
uuid
validation-selective
vector
];
Expand Down
8 changes: 7 additions & 1 deletion nix/cvss.nix
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@
mkDerivation {
pname = "cvss";
version = "0.1";
sha256 = "b9e1b3caa1f22662244c2379f277d7689ed35368cd9de57584a380068a598d0a";
src = fetchgit {
url = "https://github.com/haskell/security-advisories.git";
sha256 = "090rlbnlval16xh5ajpn7c3mymqpxhwjfmsbkxvcrk7zn28p6w7p";
rev = "061e2e466565de1167b5a116cdc1d621f70af268";
fetchSubmodules = true;
};
postUnpack = "sourceRoot+=/code/cvss/; echo source root reset to $sourceRoot";
libraryHaskellDepends = [base text];
testHaskellDepends = [base tasty tasty-hunit text];
description = "Common Vulnerability Scoring System";
Expand Down
6 changes: 3 additions & 3 deletions nix/haskell-overlay.nix
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
_: hfinal: hprev: {
{hlib, ...}: hfinal: hprev: {
cabal-audit = hfinal.callPackage ./cabal-audit.nix {};
osv = hfinal.callPackage ./osv.nix {};
hsec-core = hfinal.callPackage ./hsec-core.nix {};
Expand All @@ -7,6 +7,6 @@ _: hfinal: hprev: {

toml-parser = hprev.toml-parser_2_0_0_0;
Cabal-syntax = hprev.Cabal-syntax_3_10_3_0;
Cabal = hprev.Cabal_3_10_3_0;
extensions = hprev.extensions.override {inherit (hfinal) Cabal;};

sel = hlib.doJailbreak (hlib.markUnbroken hprev.sel);
}
8 changes: 7 additions & 1 deletion nix/hsec-core.nix
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@
mkDerivation {
pname = "hsec-core";
version = "0.1.0.0";
sha256 = "036d33f56b0de81e85031eb2bb5357b4f36eaf3c50b22b5214258f1d76dbc679";
src = fetchgit {
url = "https://github.com/haskell/security-advisories.git";
sha256 = "090rlbnlval16xh5ajpn7c3mymqpxhwjfmsbkxvcrk7zn28p6w7p";
rev = "061e2e466565de1167b5a116cdc1d621f70af268";
fetchSubmodules = true;
};
postUnpack = "sourceRoot+=/code/hsec-core/; echo source root reset to $sourceRoot";
libraryHaskellDepends = [
base
Cabal-syntax
Expand Down
4 changes: 2 additions & 2 deletions nix/hsec-tools.nix
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ mkDerivation {
version = "0.2.0.0";
src = fetchgit {
url = "https://github.com/haskell/security-advisories.git";
sha256 = "0j7q8mmlgi5rw82h75rz3nfh6d36qw805hwwy9d5mmchdpsgpall";
rev = "8e8b11e08d8026af91f4487391935dcdc8833c75";
sha256 = "090rlbnlval16xh5ajpn7c3mymqpxhwjfmsbkxvcrk7zn28p6w7p";
rev = "061e2e466565de1167b5a116cdc1d621f70af268";
fetchSubmodules = true;
};
postUnpack = "sourceRoot+=/code/hsec-tools/; echo source root reset to $sourceRoot";
Expand Down
10 changes: 8 additions & 2 deletions nix/osv.nix
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@
}:
mkDerivation {
pname = "osv";
version = "0.1.0.0";
sha256 = "0dd33c59a202a060fa295f488be8d89870e2e73521fc57be719a7417cde98287";
version = "0.1.0.1";
src = fetchgit {
url = "https://github.com/haskell/security-advisories.git";
sha256 = "090rlbnlval16xh5ajpn7c3mymqpxhwjfmsbkxvcrk7zn28p6w7p";
rev = "061e2e466565de1167b5a116cdc1d621f70af268";
fetchSubmodules = true;
};
postUnpack = "sourceRoot+=/code/osv/; echo source root reset to $sourceRoot";
libraryHaskellDepends = [aeson base cvss text time];
testHaskellDepends = [base tasty tasty-hunit];
description = "Open Source Vulnerability format";
Expand Down
15 changes: 3 additions & 12 deletions src/Distribution/Audit.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import Data.ByteString.Lazy qualified as BSL
import Data.Coerce (coerce)
import Data.Foldable (for_)
import Data.Functor.Identity (Identity (runIdentity))
import Data.List qualified as List
import Data.Map qualified as M
import Data.String (IsString (fromString))
import Data.Text (Text)
Expand All @@ -39,13 +38,14 @@ import Distribution.Client.ProjectPlanning (rebuildInstallPlan)
import Distribution.Client.Setup (defaultGlobalFlags)
import Distribution.Types.PackageName (PackageName, unPackageName)
import Distribution.Verbosity qualified as Verbosity
import Distribution.Version (Version, versionNumbers)
import Distribution.Version (Version)
import GHC.Generics (Generic)
import Options.Applicative
import Security.Advisories (Advisory (..), Keyword (..), ParseAdvisoryError (..), printHsecId)
import Security.Advisories.Cabal (ElaboratedPackageInfoAdvised, ElaboratedPackageInfoWith (..), matchAdvisoriesForPlan)
import Security.Advisories.Convert.OSV qualified as OSV
import Security.Advisories.Filesystem (listAdvisories)
import Security.Advisories.SBom.Types (prettyVersion)
import System.Exit (exitFailure)
import System.IO (Handle, IOMode (WriteMode), stdout, withFile)
import System.Process (callProcess)
Expand Down Expand Up @@ -186,16 +186,7 @@ osvHandler mkHandle mp =
]
]

-- | pretty-prints a 'Version'
--
-- >>> import Distribution.Version
-- >>> prettyVersion $ mkVersion [0, 1, 0, 0]
-- "0.1.0.0"
prettyVersion :: IsString s => Version -> s
prettyVersion = fromString . List.intercalate "." . map show . versionNumbers
{-# INLINE prettyVersion #-}

prettyAdvisory :: Advisory -> Maybe Version -> Vector ([Text], Text)
prettyAdvisory :: Advisory -> Maybe Version -> Text
prettyAdvisory Advisory {advisoryId, advisoryPublished, advisoryKeywords, advisorySummary} mfv =
let hsecId = T.pack (printHsecId advisoryId)
indentLine line = [([], " ")] <> line <> [([], "\n")]
Expand Down
44 changes: 44 additions & 0 deletions src/Security/Advisories/SBom/CycloneDX.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
module Security.Advisories.SBom.CycloneDX where

import Chronos (Datetime, encodeIso8601)
import Data.Aeson
import Data.UUID qualified as UUID
import Distribution.SPDX (License (..))
import Numeric.Natural (Natural)
import Security.Advisories.SBom.Types

data CycloneDXInfo = MkCycloneDXInfo
{ cyclonedx'sbomVersion :: Natural
-- ^ the generation of the sbom, this should be the generation of the last generated sbom + 1
, cyclonedx'freshUUID :: UUID.UUID
-- ^ the uri of the new cyclone dx SBom
, cyclonedx'currentTime :: Datetime
-- ^ the uri of the new cyclone dx SBom
}

-- | serializeds some SBomMeta to a Value; e.g. for a library of a cabal package
serializeToCycloneDX :: CycloneDXInfo -> SBomMeta -> Value
serializeToCycloneDX info meta =
object
[ "bomFormat" .= String "CycloneDX"
, "specVersion" .= String "1.6"
, "serialNumber" .= String ("urn:uuid:" <> UUID.toText info.cyclonedx'freshUUID)
, "version" .= Number (fromIntegral info.cyclonedx'sbomVersion)
, "metadata"
.= object
[ "timestamp" .= String (encodeIso8601 info.cyclonedx'currentTime)
, "component"
.= object
[ "name" .= String meta.sbom'componentName
, "type" .= String (prettyComponentType meta.sbom'componentType)
, "bom-ref" .= String (mkBomRef meta.sbom'supplierName meta.sbom'componentName meta.sbom'componentType meta.sbom'componentVersion)
, "authors" .= Array [object ["name" .= meta.sbom'componentAuthor]]
, "version" .= String (prettyVersion meta.sbom'componentVersion)
, "description" .= String meta.sbom'componentDescription
, "licenses" .= Array case meta.sbom'componentLicense of NONE -> []; License expr -> [String (prettyLicense expr)]
-- FUTUREWORK(mangoiv): it is possible to add externalReferences and render the SourceRepo information of the cabal package heere
-- https://hackage.haskell.org/package/Cabal-syntax-3.12.0.0/docs/Distribution-Types-SourceRepo.html#t:SourceRepo
]
]
, "dependencies" .= Array []
]
116 changes: 116 additions & 0 deletions src/Security/Advisories/SBom/Types.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
{-# LANGUAGE StrictData #-}

module Security.Advisories.SBom.Types (SBomMeta (..), ComponentType (..), prettyComponentType, prettyVersion, prettyLicense, mkPurl, mkBomRef) where

import Data.List qualified as List
import Data.Maybe (fromMaybe)
import Data.String (IsString (fromString))
import Data.Text (Text)
import Data.Text qualified as T
import Data.Text.Lazy qualified as TL
import Data.Text.Lazy.Builder qualified as TLB
import Data.Vector (Vector)
import Distribution.Pretty (Pretty (pretty))
import Distribution.SPDX qualified as SPDX
import Distribution.Version (Version, versionNumbers)
import GHC.Generics (Generic)
import Text.PrettyPrint.HughesPJ (Mode (OneLineMode), TextDetails (..), fullRender)

-- | a component type; cabal applications, tests and benchmarks are going to be applications, cabal libraries
-- are going to be libraries
data ComponentType
= Application
| Library
deriving stock (Eq, Ord, Show, Generic)

{-
- https://hackage.haskell.org/package/Cabal-syntax-3.12.0.0/docs/Distribution-Types-PackageDescription.html
- https://hackage.haskell.org/package/Cabal-syntax-3.12.0.0/docs/Distribution-Types-PackageId.html#t:PackageIdentifier
- https://hackage.haskell.org/package/Cabal-syntax-3.12.0.0/docs/Distribution-Types-Library.html#t:Library
- -}

-- | the component to describe
data SBomMeta = MKSBomMeta
{ sbom'componentName :: Text
-- ^ the name of the component
, sbom'componentVersion :: Version
-- ^ the version of the component
, sbom'componentType :: ComponentType
-- ^ the type of the component. If this is not present, then this is no
-- component but
, sbom'componentAuthor :: Text
-- ^ the authors of the component
, sbom'componentDescription :: Text
-- ^ the component's description
, sbom'componentLicense :: SPDX.License
-- ^ the component's license
, sbom'supplierName :: Maybe Text
-- ^ the name of the supplier if present, e.g. github/haskell
, sbom'componentDependencies :: Vector ComponentDependency
-- ^ all the dependencies of a component
}
deriving stock (Eq, Ord, Show, Generic)

data ComponentDependency = MkComponentDependency
{ dep'type :: ComponentType
, dep'repo :: Maybe Text
, dep'name :: Text
, dep'version :: Version
, dep'license :: Maybe SPDX.License
, dep'description :: Text
}
deriving stock (Eq, Ord, Show, Generic)

mkPurl
:: Text
-- ^ repo
-> Text
-- ^ name
-> Maybe Version
-- ^ version
-> Text
mkPurl repo name version = mconcat ["pkg:", repo, "/", name, maybe "" (("@" <>) . prettyVersion) version]
{-# INLINE mkPurl #-}

-- | pretty-prints a `Version`
--
-- >>> import Distribution.Version
-- >>> prettyVersion $ mkVersion [0, 1, 0, 0]
-- "0.1.0.0"
prettyVersion :: IsString s => Version -> s
prettyVersion = fromString . List.intercalate "." . map show . versionNumbers
{-# INLINE prettyVersion #-}

-- | pretty print an SPDX license expression
--
-- >>> import Distribution.SPDX
-- >>> prettyLicense (EAnd (ELicense (ELicenseId AGPL_1_0) Nothing) (ELicense (ELicenseId Beerware) Nothing))
-- "AGPL-1.0 AND Beerware"
prettyLicense :: SPDX.LicenseExpression -> Text
prettyLicense = TL.toStrict . TLB.toLazyText . fullRender OneLineMode 0 0 appendDoc "" . pretty
where
appendDoc =
(<>) . \case
Chr c -> TLB.singleton c
Str s -> TLB.fromString s
PStr s -> TLB.fromString s
{-# INLINE prettyLicense #-}

prettyComponentType :: IsString s => ComponentType -> s
prettyComponentType = \case
Library -> "library"
Application -> "application"
{-# INLINE prettyComponentType #-}

-- | builds a bom ref for a component
mkBomRef
:: Maybe Text
-- ^ supplier such as a github repository (@github/haskell@), if not specified, @haskell@ is assumed
-> Text
-- ^ name of the component
-> ComponentType
-- ^ component type
-> Version
-- ^ version of the component
-> Text
mkBomRef msupplier name typ version = T.intercalate ":" [fromMaybe "haskell" msupplier, name, prettyComponentType typ, prettyVersion version]

0 comments on commit ac44da7

Please sign in to comment.