diff --git a/Makefile b/Makefile index f774012f3e6..417e2eb3253 100644 --- a/Makefile +++ b/Makefile @@ -546,11 +546,12 @@ helm-template-%: clean-charts charts-integration ./hack/bin/helm-template.sh $(*) # Ask the security team for the `DEPENDENCY_TRACK_API_KEY` (if you need it) +# changing the directory is necessary because of some quirkiness of how +# runhaskell / ghci behaves (it doesn't find modules that aren't in the same +# directory as the script that is being executed) .PHONY: upload-bombon upload-bombon: - nix build -f nix wireServer.allLocalPackagesBom -o "bill-of-materials.$(HELM_SEMVER).json" - ./hack/bin/bombon.hs -- \ - --bom-filepath "./bill-of-materials.$(HELM_SEMVER).json" \ + cd ./hack/bin && ./bombon.hs -- \ --project-version $(HELM_SEMVER) \ --api-key $(DEPENDENCY_TRACK_API_KEY) \ --auto-create diff --git a/changelog.d/5-internal/SEC-596 b/changelog.d/5-internal/SEC-596 new file mode 100644 index 00000000000..e7af3b64def --- /dev/null +++ b/changelog.d/5-internal/SEC-596 @@ -0,0 +1 @@ +Create a new script (`Sbom.hs`) to generate the wire-server sbom (bill of material) file. diff --git a/hack/bin/Sbom.hs b/hack/bin/Sbom.hs new file mode 100644 index 00000000000..7226a19885d --- /dev/null +++ b/hack/bin/Sbom.hs @@ -0,0 +1,376 @@ +{-# LANGUAGE BlockArguments #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedLists #-} +{-# LANGUAGE OverloadedRecordDot #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE StrictData #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE ViewPatterns #-} +{-# OPTIONS_GHC -Wall #-} +{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} + +{- +- the only place that has the data we need about the package is the evaluated nix code, i.e. before + writing the derivation; this is where we have `meta` and friends to get the data we need +- say we now want to build a dependency tree; the issue is to find all dependencies of the derivation. + this is hard because + - there are normal input attrs that the builder will have a look at but also + - string contexts like + ```nix + x = /* bash */ '' + cp ${pkgs.bla}/bin $out + ''; + ``` + would ignore dependencies on `pkgs.bla` +- we can build the dependency graph independently (without knowing about the meta) but we somehow need + to obtain the meta itself +- people don't always have a complete package set but more commonly are hand assembling things; we need + to give the possibility to build meta "databases" from package sets +- we need to trace which dependencies are missing when querying the meta database against them +- collecting the meta also poses some issue + - nixpkgs is not a tree, but a more general graph + - it also not a DAG but it has loops + - this means more specifically that we cannot without care recurse into it + - even if we only recurse very shallowly, we soon start running out of memory, this means we probably need + to do some on the fly filtering by "actual" dependencies + - this is similarly an issue, because it means that for every package we have to evaluate the entirety + of the package set instead of being able to keep and persist the database + - a more clean solution would probably be to at each time we recurse, a derivation that does the evaluation + and outputs a JSON that can later be read + +how this relates to bombon: +- bombon uses a more coarse grained approach +- this builds a metadata "database" i.e. is two pass +- see the corresponding nix code in ./nix +-} + +module Sbom where + +import Control.Arrow ((&&&)) +import Data.Aeson +import Data.Aeson.Key qualified as KM +import Data.Aeson.KeyMap qualified as KM +import Data.Aeson.Types (typeMismatch) +import Data.Bifunctor (first) +import Data.Bitraversable (bitraverse) +import Data.ByteString (ByteString) +import Data.ByteString.Char8 qualified as C8 +import Data.ByteString.Lazy (LazyByteString) +import Data.ByteString.Lazy qualified as BSL +import Data.ByteString.Lazy.Char8 qualified as C8L +import Data.Containers.ListUtils (nubOrd, nubOrdOn) +import Data.Functor.Identity +import Data.Map (Map) +import Data.Map qualified as M +import Data.Maybe +import Data.Proxy +import Data.Text (Text) +import Data.Text qualified as T +import Data.Text.IO qualified as T +import Data.Time.Clock.POSIX +import Data.Traversable (for) +import Data.Tree +import Data.UUID qualified as UUID +import Data.UUID.V4 qualified as V4 +import Debug.Trace +import GHC.Generics hiding (Meta) +import GHC.IsList (IsList (fromList, toList)) +import Numeric.Natural (Natural) +import Options.Applicative (customExecParser, fullDesc, help, long, prefs, progDesc, showHelpOnEmpty, strOption, value) +import Options.Applicative qualified as Opt +import System.Directory +import System.Process + +data License = MkLicense + { id :: Maybe Text, + name :: Maybe Text + } + deriving stock (Eq, Ord, Show, Generic) + deriving anyclass (FromJSON, ToJSON) + +sadSbomMeta :: Text -> Text -> [Text] -> SBomMeta Identity +sadSbomMeta drvPath outPath directDeps = + MkSBomMeta + { drvPath = drvPath, + outPath = Identity outPath, + directDeps = Identity directDeps, + description = Nothing, + homepage = Nothing, + licenseSpdxId = [], + name = Nothing, + typ = Nothing, + urls = [], + version = Nothing + } + +data SBomMeta f = MkSBomMeta + { drvPath :: Text, + description :: Maybe Text, + homepage :: Maybe Text, + licenseSpdxId :: [Maybe License], + name :: Maybe Text, + typ :: Maybe Text, + urls :: [Maybe Text], + version :: Maybe Text, + outPath :: f Text, + directDeps :: f [Text] + } + +deriving stock instance (Eq (f [Text]), Eq (f Text)) => Eq (SBomMeta f) + +deriving stock instance (Ord (f [Text]), Ord (f Text)) => Ord (SBomMeta f) + +deriving stock instance (Show (f [Text]), Show (f Text)) => Show (SBomMeta f) + +type Meta = SBomMeta Proxy + +instance FromJSON Meta where + parseJSON (Object val) = + MkSBomMeta + <$> do val .: "drvPath" + <*> do val .: "description" + <*> do val .: "homepage" + <*> do val .: "licenseSpdxId" + <*> do val .: "name" + <*> do val .: "type" + <*> do val .: "urls" + <*> do val .: "version" + <*> pure Proxy + <*> pure Proxy + parseJSON invalid = typeMismatch "Object" invalid + +type SBom = Map Text (SBomMeta Identity) + +type MetaDB = Map Text (SBomMeta Proxy) + +type ClosureInfo = Tree ByteString + +type PathInfo = [(Text, (Text, [Text]))] + +data Visit a = Seen a | Unseen a + deriving stock (Eq, Ord, Show) + +data SerializeSBom = MkSerializeSBom + { -- | the version of the SBom; this is version of the old SBom + 1 + sbom'version :: Natural, + -- | name of the component the SBom is generated for + sbom'component :: Text, + -- | the creator of the component the SBom is generated for + sbom'manufacture :: Text, + -- | the supplier (manufacturer or repackager or distributor) + sbom'supplier :: Maybe Text, + -- | (spdxids of) licenses of the product + sbom'licenses :: [Text] + } + +defaultSerializeSBom :: SerializeSBom +defaultSerializeSBom = + MkSerializeSBom + { sbom'version = 1, + sbom'component = "wire-server", + sbom'manufacture = "wire", + sbom'supplier = Nothing, + sbom'licenses = ["AGPL-3.0-or-later"] + } + +-- FUTUREWORK(mangoiv): we can also have +-- +-- - qualifiers: extra qualifying data for a package such as an OS, architecture, a distro, etc. Optional and type-specific. +-- - subpath: extra subpath within a package, relative to the package root. Optional. +-- - use heuristics based approach to finding original repositories for packages, e.g. pkg:hackage.... +mkPurl :: SBomMeta Identity -> Text +mkPurl meta = + mconcat + [ "pkg:", + repo, + "/", + fromMaybe (runIdentity meta.outPath) meta.name, + maybe "" ("@" <>) meta.version + ] + where + repo + | any (maybe False (T.isInfixOf "hackage.haskell.org")) meta.urls = "hackage" + | otherwise = "nixpkgs" + +-- | serializes an SBom to JSON format +-- conventions: +-- - bomRef == outPath +serializeSBom :: SerializeSBom -> SBom -> IO LazyByteString +serializeSBom settings bom = do + uuid <- V4.nextRandom + curTime <- getCurrentTime + -- FUTUREWORK(mangoiv): "tools" (the tools used in the creation of the bom) + let mkDependencies :: SBomMeta Identity -> Array + mkDependencies meta = do + let d = + object + [ "ref" .= meta.outPath, + "dependsOn" .= runIdentity meta.directDeps + ] + [d] + mkComponents :: SBomMeta Identity -> Array + mkComponents meta = do + let c :: Value + c = + -- FUTUREWORK(mangoiv): swid? https://www.iso.org/standard/65666.html + -- FUTUREWORK(mangoiv): CPE? + -- FUTUREWORK(mangoiv): more information in the supplier section + object + [ "type" .= meta.typ, + "bom-ref" .= String (runIdentity meta.outPath), + "supplier" .= object ["url" .= nubOrd (maybeToList meta.homepage <> catMaybes meta.urls)], + "name" .= String (fromMaybe (st'name $ splitStorePath $ runIdentity meta.outPath) meta.name), + "version" .= meta.version, + "description" .= meta.description, + "scope" .= String "required", + "licenses" .= ((\ln -> object ["license" .= ln]) <$> filter (isJust . (>>= (.id))) meta.licenseSpdxId), + "purl" .= mkPurl meta + ] + [c] + (dependencies, components) = foldMap (mkDependencies &&& mkComponents) bom + + pure $ + encode @Value $ + object + [ "bomFormat" .= String "CycloneDX", + "specVersion" .= String "1.5", + "serialNumber" .= String ("urn:uuid:" <> UUID.toText uuid), + "version" .= Number (fromIntegral settings.sbom'version), + "metadata" + .= object + [ "timestamp" .= String (T.pack (show curTime)), + "component" + .= object + [ "name" .= String settings.sbom'component, + "type" .= String "application" + -- FUTUREWORK(mangoiv): this should be a choice in the settings above + ], + -- FUTUREWORK(mangoiv): "manufacture" can also have url + "manufacture" .= object ["name" .= String settings.sbom'manufacture], + "supplier" .= object ["name" .= String (fromMaybe settings.sbom'manufacture settings.sbom'supplier)], + "licenses" .= Array (fromList $ object . (\n -> ["id" .= n]) . String <$> settings.sbom'licenses) + ], + "components" .= Array components, + -- FUTUREWORK(mangoiv): services: allow to tell the program the name of the services like brig, galley, ... + "dependencies" .= Array dependencies + ] + +buildMetaDB :: [Meta] -> MetaDB +buildMetaDB = foldMap \MkSBomMeta {..} -> [(drvPath, MkSBomMeta {..})] + +discoverSBom :: FilePath -> MetaDB -> IO SBom +discoverSBom outP metaDb = do + canonicalOutP <- canonicalizePath =<< getSymbolicLinkTarget outP + info <- pathInfo canonicalOutP + let go :: PathInfo -> IO SBom -> IO SBom + go (k, (deriver, deps)) = do + let proxyToIdentity :: SBomMeta Proxy -> SBomMeta Identity + proxyToIdentity (MkSBomMeta {..}) = MkSBomMeta {directDeps = Identity deps, outPath = Identity k, ..} + case M.lookup deriver metaDb of + Nothing -> \x -> do + T.putStrLn ("no meta found for drv: " <> deriver <> "\ntrying approximate match") + x >>= maybe + do + \m -> do + T.putStrLn ("no approximate match found for: " <> deriver) + pure $ M.insert k (sadSbomMeta deriver k deps) m + do \match -> pure . M.insert k (proxyToIdentity match) + do approximateMatch deriver metaDb + Just pmeta -> fmap $ M.insert k $ proxyToIdentity pmeta + + foldr go mempty info + +data StorePath = MkStorePath + { st'hash :: Text, + st'name :: Text, + st'original :: Text + } + deriving stock (Eq, Ord, Show) + +-- >>> splitStorePath "/nix/store/m306sk6syihxp80zrr9xs8hi5mjricgh-sop-core-0.5.0.2" +-- MkStorePath {st'hash = "m306sk6syihxp80zrr9xs8hi5mjricgh", st'name = "sop-core-0.5.0.2", st'original = "/nix/store/m306sk6syihxp80zrr9xs8hi5mjricgh-sop-core-0.5.0.2"} +splitStorePath :: Text -> StorePath +splitStorePath stp = do + let rest = T.drop (T.length "/nix/store/") stp + (hash, T.drop 1 -> name) = T.breakOn "-" rest + MkStorePath {st'original = stp, st'hash = hash, st'name = name} + +approximateMatch :: Text -> MetaDB -> Maybe (SBomMeta Proxy) +approximateMatch stp db = + let goal = splitStorePath stp + metas = first splitStorePath <$> M.toList db + in case filter (\(m, _) -> m.st'name == goal.st'name) metas of + [(_stp, meta)] -> pure meta + _ -> Nothing + +parse :: IO (String, String) +parse = customExecParser (prefs showHelpOnEmpty) do + Opt.info + do drvAndTlParser + do + mconcat + [ fullDesc, + progDesc "build an sbom from a derivation and a package set" + ] + +drvAndTlParser :: Opt.Parser (String, String) +drvAndTlParser = + (,) + <$> strOption (long "drv" <> help "outpath of the derivation to build the sbom for" <> value "result") + <*> strOption do + long "tldfp" + <> help "path to the derivation containing the output of the allLocalPackages drv" + <> value "wire-server" + +main :: IO () +main = parse >>= mainNoParse >>= BSL.writeFile "sbom.json" + +-- | by not always parsing, we have an easy time to call directly from haskell +mainNoParse :: (String, String) -> IO LazyByteString +mainNoParse (tldFp, drv) = do + let mkMeta :: LazyByteString -> Maybe Meta + mkMeta = decodeStrict . BSL.toStrict + metaDB <- buildMetaDB . mapMaybe mkMeta . C8L.lines <$> BSL.readFile tldFp + sbom <- discoverSBom drv metaDB + serializeSBom defaultSerializeSBom sbom + +pathInfo :: FilePath -> IO PathInfo +pathInfo path = do + let nixPathInfo = proc "nix" ["path-info", path, "--json", "--recursive"] + withCreateProcess nixPathInfo {std_out = CreatePipe} \_in (Just out) _err _ph -> do + Just refs' <- decodeStrict @Value <$> C8.hGetContents out + let failureBecauseNixHasZeroContracts = fail "unexpected format: this may be due to the output of `nix path-info` having changed randomly lol" + tryFindOutpath :: Value -> IO (Key, Value) + tryFindOutpath val + | Object pc <- val, + Just (String k) <- KM.lookup "path" pc = + pure (KM.fromText k, val) + tryFindOutpath _ = failureBecauseNixHasZeroContracts + refs <- case refs' of + Object refs -> pure $ KM.toList refs + Array refs -> traverse tryFindOutpath $ toList refs + _ -> failureBecauseNixHasZeroContracts + + let parseObj :: Value -> Maybe (Text, [Text]) + parseObj info + | Object mp <- info, + Just (Array rs) <- KM.lookup "references" mp, + Just (String deriver) <- KM.lookup "deriver" mp, + Just rs' <- for rs \case + String s -> Just s + _ -> Nothing = + Just (deriver, toList rs') + parseObj _ = trace "could not parse object" Nothing + -- some heuristics based filtering + pure + -- remove derivations with the same deriver + . nubOrdOn (fst . snd) + -- remove derivations that are just docs + . filter ((/= "doc") . T.takeEnd 3 . fst) + . mapMaybe (bitraverse (pure . KM.toText) parseObj) + $ refs diff --git a/hack/bin/bombon.hs b/hack/bin/bombon.hs index 0c01c4cf80f..d4bc7fdec0b 100755 --- a/hack/bin/bombon.hs +++ b/hack/bin/bombon.hs @@ -1,9 +1,11 @@ -#!/usr/bin/env -S nix -Lv run github:wireapp/ghc-flakr/99fe5a331fdd37d52043f14e5c565ac29a30bcb4 +#!/usr/bin/env -S nix -Lv run github:wireapp/ghc-flakr/6311bb166bf835d4a587fe1661b86c9a1426f212 {-# LANGUAGE DataKinds #-} +{-# LANGUAGE LambdaCase #-} +{-# OPTIONS_GHC -Wall #-} import Data.Aeson import qualified Data.ByteString.Base64.Lazy as Base64 -import qualified Data.ByteString.Lazy.Char8 as BL +import Data.ByteString.Lazy import Data.Proxy import Data.Text.Lazy import Data.Text.Lazy.Encoding @@ -11,8 +13,11 @@ import GHC.Generics import qualified Network.HTTP.Client as HTTP import Network.HTTP.Client.TLS (tlsManagerSettings) import Options.Applicative +import Sbom hiding (main) import Servant.API import Servant.Client +import System.Exit +import System.Process data Payload = Payload { bom :: Text, @@ -46,8 +51,7 @@ putBOM :: Payload -> Maybe String -> ClientM ApiResponse putBOM = client api data CliOptions = CliOptions - { opBomPath :: String, - opProjectName :: String, + { opProjectName :: String, opProjectVersion :: String, opAutoCreate :: Bool, opApiKey :: String @@ -58,12 +62,6 @@ cliParser :: Parser CliOptions cliParser = CliOptions <$> ( strOption - ( long "bom-filepath" - <> short 'f' - <> metavar "FILENAME" - ) - ) - <*> ( strOption ( long "project-name" <> short 'p' <> metavar "PROJECT_NAME" @@ -100,7 +98,16 @@ main :: IO () main = do options <- execParser fullCliParser manager' <- HTTP.newManager tlsManagerSettings - bom <- readFile $ opBomPath options + buildWire <- spawnCommand "nix -Lv build -f ../../nix wireServer.allLocalPackages -o wire-server" + buildMeta <- spawnCommand "nix -Lv build -f ../../nix wireServer.toplevel-derivations --impure -o meta" + waitForProcess buildWire >>= \case + ExitFailure _ -> fail "process for building wire failed" + ExitSuccess -> putStrLn "finished building Wire" + waitForProcess buildMeta >>= \case + ExitFailure _ -> fail "process for building meta for wire failed" + ExitSuccess -> putStrLn "finished building meta" + + bom <- mainNoParse ("./meta", "./wire-server") let payload = Payload { bom = toBase64Text bom, @@ -114,7 +121,7 @@ main = do (mkClientEnv manager' (BaseUrl Https "deptrack.wire.link" 443 "")) case res of Left err -> print $ "Error: " ++ show err - Right res -> print res + Right res' -> print res' -toBase64Text :: String -> Text -toBase64Text = decodeUtf8 . Base64.encode . BL.pack +toBase64Text :: LazyByteString -> Text +toBase64Text = decodeUtf8 . Base64.encode diff --git a/nix/all-toplevel-derivations.nix b/nix/all-toplevel-derivations.nix new file mode 100644 index 00000000000..4c7954352f4 --- /dev/null +++ b/nix/all-toplevel-derivations.nix @@ -0,0 +1,62 @@ +# this tries to recurse into pkgs to collect metadata about packages within nixpkgs +# it needs a recusionDepth, because pkgs is actually not a tree but a graph so you +# will go around in circles; also it helps bounding the memory needed to build this +# we also pass a keyFilter to ignore certain package names +# else, this just goes through the packages, tries to evaluate them, if that succeeds +# it goes on and remembers their metadata +# there's a lot of obfuscation caused by the fact that everything needs to be tryEval'd +# reason being that there's not a single thing in nixpkgs that is reliably evaluatable +{ lib +, pkgSet +, fn +, recursionDepth +, keyFilter +, ... +}: +let + go = depth: set': + let + evaluateableSet = builtins.tryEval set'; + in + if evaluateableSet.success && builtins.isAttrs evaluateableSet.value + then + let + set = evaluateableSet.value; + in + ( + if (builtins.tryEval (lib.isDerivation set)).value + then + let + meta = builtins.tryEval (fn set); + in + builtins.deepSeq meta ( + builtins.trace ("reached leaf: " + toString set) + ( + if meta.success + then [ meta.value ] + else builtins.trace "package didn't evaluate" [ ] + ) + ) + else if depth >= recursionDepth + then builtins.trace ("max depth of " + toString recursionDepth + " reached") [ ] + else + let + attrVals = builtins.tryEval (builtins.attrValues (lib.filterAttrs (k: _v: keyFilter k) set)); + go' = d: s: + let + gone' = builtins.tryEval (go d s); + in + if gone'.success + then gone'.value + else builtins.trace "could not recurse because of eval error" [ ]; + in + if attrVals.success + then + (builtins.concatMap + (go' (builtins.trace ("depth was: " + toString depth) (depth + 1))) + attrVals.value) + else builtins.trace "could not evaluate attr values because of eval error" [ ] + ) + else builtins.trace "could not evaluate package or package was not an attrset" [ ]; +in +go 0 pkgSet diff --git a/nix/default.nix b/nix/default.nix index f77d8de6fc1..02c0d9e01a7 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -7,7 +7,6 @@ let # All wire-server specific packages (import ./overlay.nix) (import ./overlay-docs.nix) - (self: super: { lib = super.lib // (import sources.bombon).lib.${super.system}; }) ]; }; @@ -79,7 +78,6 @@ let pkgs.entr ] ++ docsPkgs; }; - mls-test-cli = pkgs.mls-test-cli; - rusty-jwt-tools = pkgs.rusty-jwt-tools; + inherit (pkgs) mls-test-cli; in { inherit pkgs profileEnv wireServer docs docsEnv mls-test-cli nginz nginz-disco; } diff --git a/nix/overlay.nix b/nix/overlay.nix index a6390ab1d38..08dd42c00d0 100644 --- a/nix/overlay.nix +++ b/nix/overlay.nix @@ -103,4 +103,6 @@ self: super: { }; rabbitmqadmin = super.callPackage ./pkgs/rabbitmqadmin { }; + + sbomqs = super.callPackage ./pkgs/sbomqs { }; } diff --git a/nix/pkg-info.nix b/nix/pkg-info.nix new file mode 100644 index 00000000000..9773bbaef9d --- /dev/null +++ b/nix/pkg-info.nix @@ -0,0 +1,60 @@ +# collects information about a single nixpkgs package +{ lib +, pkg +, ... +}: +with builtins; +assert lib.isDerivation pkg; let + # trace with reason + trc = info: pkg: trace (info + ": " + toString pkg); + + # if thing is a list, map the function, else apply f to thing and return a singleton of + # it + mapOrSingleton = f: x: + if isList x + then map f x + else [ (f x) ]; + + # things to save from the src attr (the derivation that was created by a fetcher) + srcInfo = { + urls = (pkg.src.urls or (trc "package didn't have src or url" pkg [ ])) ++ [ (pkg.src.url or null) ]; + }; + + dp = builtins.tryEval pkg.drvPath; + + # things to save from the meta attr + metaInfo = + let + m = pkg.meta or (trc "package didn't have meta" pkg { }); + in + { + homepage = m.homepage or (trc "package didn't have homepage" pkg null); + description = m.description or (trc "package didn't have description" pkg null); + licenseSpdxId = + mapOrSingleton + ( + l: { + id = l.spdxId or (trc "package license doesn't have a spdxId" pkg null); + name = l.fullName or (trc "package license doens't have a name" pkg null); + } + ) + (m.license or (trc "package does not have a license" pkg null)); + + # based on heuristics, figure out whether something is an application for now this only checks whether this + # componnent has a main program + type = + if m ? mainProgram + then "application" + else "library"; + + name = pkg.pname or pkg.name or (trc "name is missing" pkg null); + version = pkg.version or (trc "version is missing" pkg null); + }; +in +if dp.success +then + let + info = builtins.toJSON (srcInfo // metaInfo // { drvPath = builtins.unsafeDiscardStringContext dp.value; }); + in + info +else trc "drvPath of package could not be computed" pkg { } diff --git a/nix/pkgs/sbomqs/default.nix b/nix/pkgs/sbomqs/default.nix new file mode 100644 index 00000000000..d0e41ad5785 --- /dev/null +++ b/nix/pkgs/sbomqs/default.nix @@ -0,0 +1,21 @@ +{ buildGoModule, fetchFromGitHub, lib, ... }: +buildGoModule rec { + pname = "sbomqs"; + version = "0.0.30"; + + src = fetchFromGitHub { + owner = "interlynk-io"; + repo = "sbomqs"; + rev = "v${version}"; + hash = "sha256-+y7+xi+E8kjGUjhIRKNk6ogcQMP+Dp39LrL66B1XdrQ="; + }; + + vendorHash = "sha256-V6k7nF2ovyl4ELE8Cqe/xjpmPAKI0t5BNlssf41kd0Y="; + + meta = with lib; { + description = "SBOM quality score - Quality metrics for your sboms"; + homepage = "https://github.com/interlynk-io/sbomqs"; + license = licenses.asl20; + mainProgram = "sbomqs"; + }; +} diff --git a/nix/wire-server.nix b/nix/wire-server.nix index 8a7b85b5e91..9815de26869 100644 --- a/nix/wire-server.nix +++ b/nix/wire-server.nix @@ -462,9 +462,37 @@ let allLocalPackagesBom = lib.buildBom allLocalPackages { includeBuildtimeDependencies = true; }; + + haskellPackages = hPkgs localModsEnableAll; + haskellPackagesUnoptimizedNoDocs = hPkgs localModsOnlyTests; + + toplevel-derivations = + let + mk = pkg: + import ./pkg-info.nix { + inherit pkg; + inherit (pkgs) lib hostPlatform writeText; + }; + out = import ./all-toplevel-derivations.nix { + inherit (pkgs) lib; + fn = mk; + # more than two takes more than 32GB of RAM, so this is what + # we're limiting ourselves to + recursionDepth = 2; + keyFilter = k: k != "passthru"; + # only import the package sets we want; this makes the database + # less copmplete but makes it so that nix doesn't get OOMkilled + pkgSet = { + inherit pkgs; + inherit haskellPackages; + }; + }; + in + pkgs.writeText "all-toplevel.jsonl" (builtins.concatStringsSep "\n" out); in { - inherit ciImage hoogleImage allImages allLocalPackages allLocalPackagesBom; + inherit ciImage hoogleImage allImages allLocalPackages allLocalPackagesBom + toplevel-derivations haskellPackages haskellPackagesUnoptimizedNoDocs imagesList; images = images localModsEnableAll; imagesUnoptimizedNoDocs = images localModsOnlyTests; @@ -475,7 +503,6 @@ in enableTests = true; enableDocs = false; }; - inherit imagesList; devEnv = pkgs.buildEnv { name = "wire-server-dev-env"; @@ -508,6 +535,7 @@ in pkgs.yq pkgs.nginz pkgs.rabbitmqadmin + pkgs.sbomqs pkgs.cabal-install pkgs.nix-prefetch-git @@ -523,6 +551,4 @@ in }; inherit brig-templates; - haskellPackages = hPkgs localModsEnableAll; - haskellPackagesUnoptimizedNoDocs = hPkgs localModsOnlyTests; } // attrsets.genAttrs wireServerPackages (e: (hPkgs localModsEnableAll).${e})