diff --git a/.envrc b/.envrc deleted file mode 100644 index c0594df0..00000000 --- a/.envrc +++ /dev/null @@ -1,5 +0,0 @@ -use flake - -if [ -f .env ]; then - . .env -fi diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 6a3949e3..0a9df3ba 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -20,8 +20,14 @@ jobs: - uses: DeterminateSystems/magic-nix-cache-action@main - name: Check Nix flake inputs uses: DeterminateSystems/flake-checker-action@v4 + - name: Check formatting and lint + run: nix -L flake check - name: Build executable (hsec-tools) run: nix -L build + - name: Build executable (hsec-sync) + run: nix -L build '.#hsec-sync' + - name: Build executable (cabal-audit) + run: nix -L build '.#cabal-audit' - name: Build docker image run: nix build -L '.#packages.x86_64-linux.hsec-tools-image' - run: mkdir -p ~/.local/dockerImages diff --git a/.gitignore b/.gitignore index 89117f34..35bad6d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *~ dist-newstyle/ -result +result* .direnv .env +.pre-commit-config.yaml diff --git a/README.md b/README.md index a1c857b1..61334982 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,29 @@ please open a PR listing it here. To report a new vulnerability, open a pull request using the template below. See [CONTRIBUTING.md] for more information. +## Checking your project for vulnerabilities + +For cabal projects, the security-advisories repository offers a way to inspect your project +for vulnerabilities. + +To do so +1. install the `cabal-audit` executable, for example with nix by running `nix shell github:haskell/security-advisories#cabal-audit` +2. navigate to your cabal project +3. run `cabal-audit` + +The `cabal-audit` executable will then go on and +1. git clone the newest version of https://github.com/haskell/security-advisories +2. solve your project +3. query the created plan against the advisory database + +Run `cabal-audit --help` to see available options. + +What to do if `cabal-audit` reported a vulnerability (in that order): +1. check if you can upgrade to at least the proposed fix version +2. check if you can upgrade to a fix version that is not shown (find more information at the provided URL) +3. check if there is a not vulnerable dependency that you can switch to +4. check if the vulnerable behaviour applies to your project (find more information at the provided URL) + ## Advisory Format See [EXAMPLE_ADVISORY.md] for a template. diff --git a/cabal.project b/cabal.project deleted file mode 100644 index 3f2ec970..00000000 --- a/cabal.project +++ /dev/null @@ -1,6 +0,0 @@ -packages: code/*/*.cabal - -package hsec-core -package hsec-tools -package cvss -package osv diff --git a/code/.envrc b/code/.envrc new file mode 100644 index 00000000..4743c14a --- /dev/null +++ b/code/.envrc @@ -0,0 +1 @@ +use flake .. -Lv diff --git a/code/cabal-audit/app/Main.hs b/code/cabal-audit/app/Main.hs new file mode 100644 index 00000000..acf6dfb5 --- /dev/null +++ b/code/cabal-audit/app/Main.hs @@ -0,0 +1,6 @@ +module Main (main) where + +import Distribution.Audit (auditMain) + +main :: IO () +main = auditMain diff --git a/code/cabal-audit/cabal-audit.cabal b/code/cabal-audit/cabal-audit.cabal new file mode 100644 index 00000000..6c944c41 --- /dev/null +++ b/code/cabal-audit/cabal-audit.cabal @@ -0,0 +1,100 @@ +cabal-version: 2.4 +name: cabal-audit +version: 1.0.0.0 + +-- A short (one-line) description of the package. +synopsis: Checking a cabal project for security advisories + +-- A longer description of the package. +description: + Tools for querying the haskell security advisories database against cabal projects. + +-- A URL where users can report bugs. +-- bug-reports: + +-- The license under which the package is released. +license: BSD-3-Clause +author: @MangoIV +maintainer: contact@mangoiv.com + +-- A copyright notice. +-- copyright: +category: Data +extra-doc-files: +extra-source-files: +tested-with: + GHC ==8.10.7 || ==9.0.2 || ==9.2.8 || ==9.4.8 || ==9.6.3 || ==9.8.1 + +common common-all + ghc-options: + -Wall -Wcompat -Widentities -Wincomplete-record-updates + -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints + -fmax-relevant-binds=0 -fno-show-valid-hole-fits + + if impl(ghc >=9.6.1) + ghc-options: -fno-show-error-context + + default-extensions: + BlockArguments + DeriveGeneric + DerivingStrategies + EmptyCase + ImportQualifiedPost + LambdaCase + NamedFieldPuns + NoStarIsType + OverloadedStrings + PartialTypeSignatures + ScopedTypeVariables + StandaloneDeriving + StandaloneKindSignatures + TypeApplications + +library + import: common-all + exposed-modules: + Distribution.Audit + Security.Advisories.Cabal + + build-depends: + , base <5 + , Cabal + , cabal-install + , colourista + , containers + , filepath + , hsec-core + , hsec-tools + , http-client + , optparse-applicative + , process + , temporary + , text + , validation-selective + + hs-source-dirs: src + default-language: Haskell2010 + +executable cabal-audit + import: common-all + hs-source-dirs: app + main-is: Main.hs + other-modules: + build-depends: + , base <5 + , cabal-audit + + default-language: Haskell2010 + +test-suite spec + import: common-all + type: exitcode-stdio-1.0 + hs-source-dirs: test + main-is: Main.hs + other-modules: Spec + build-depends: + , base <5 + , cabal-audit + , hspec + + default-language: Haskell2010 diff --git a/code/cabal-audit/fourmolu.yaml b/code/cabal-audit/fourmolu.yaml new file mode 100644 index 00000000..3d7a01a2 --- /dev/null +++ b/code/cabal-audit/fourmolu.yaml @@ -0,0 +1,14 @@ +comma-style: leading +function-arrows: leading +haddock-style: single-line +import-export-style: leading +in-style: right-align +indent-wheres: false +indentation: 2 +let-style: inline +newlines-between-decls: 1 +record-brace-space: true +respectful: false +single-constraint-parens: never +single-deriving-parens: never +unicode: never diff --git a/code/cabal-audit/src/Distribution/Audit.hs b/code/cabal-audit/src/Distribution/Audit.hs new file mode 100644 index 00000000..6544273d --- /dev/null +++ b/code/cabal-audit/src/Distribution/Audit.hs @@ -0,0 +1,215 @@ +-- | provides the @cabal-audit@ plugin which works as follows: +-- +-- 1. parse command line arguments to pass on to cabal to build +-- an install plan and parse the advisories database +-- 2. lookup all dependencies in the elaborated plan within the +-- database +-- 3. summarise the found vulnerabilities as a humand readable or +-- otherwise formatted output +module Distribution.Audit (auditMain, buildAdvisories, AuditConfig (..), AuditException (..)) where + +import Colourista.Pure (blue, bold, formatWith, green, red, yellow) +import Control.Exception (Exception (displayException), SomeException (SomeException), catch, throwIO) +import Control.Monad (when) +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) +import Data.Text qualified as T +import Data.Text.IO qualified as T +import Distribution.Client.NixStyleOptions (NixStyleFlags, defaultNixStyleFlags) +import Distribution.Client.ProjectConfig (ProjectConfig) +import Distribution.Client.ProjectOrchestration + ( CurrentCommand (OtherCommand) + , ProjectBaseContext (ProjectBaseContext, cabalDirLayout, distDirLayout, localPackages, projectConfig) + , commandLineFlagsToProjectConfig + , establishProjectBaseContext + ) +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 GHC.Generics (Generic) +import Options.Applicative +import Security.Advisories (Advisory (..), Keyword (..), ParseAdvisoryError, printHsecId) +import Security.Advisories.Cabal (ElaboratedPackageInfoAdvised, ElaboratedPackageInfoWith (elaboratedPackageVersion, packageAdvisories), matchAdvisoriesForPlan) +import Security.Advisories.Filesystem (listAdvisories) +import System.Exit (exitFailure) +import System.IO.Temp (withSystemTempDirectory) +import System.Process (callProcess) +import Validation (validation) +import System.IO (stderr, hPutStrLn) + +data AuditException + = -- | parsing the advisory database failed + ListAdvisoryValidationError FilePath [ParseAdvisoryError] + | -- | to rethrow exceptions thrown by cabal during plan elaboration + CabalException String SomeException + deriving stock (Show, Generic) + +instance Exception AuditException where + displayException = \case + ListAdvisoryValidationError dir errs -> + mconcat + [ "Listing the advisories in directory " + , dir + , " failed with: \n" + , show errs + ] + CabalException ctx (SomeException ex) -> + "cabal failed while " + <> ctx + <> ":\n" + <> displayException ex + +-- | configuration that is specific to the cabal audit command +data AuditConfig = MkAuditConfig + { advisoriesPathOrURL :: Either FilePath String + -- ^ path or URL to the advisories + , verbosity :: Verbosity.Verbosity + -- ^ verbosity of cabal + } + +-- | the main action to invoke +auditMain :: IO () +auditMain = + do + handleBuiltAdvisories + =<< uncurry buildAdvisories + =<< customExecParser (prefs showHelpOnEmpty) do + info + do helper <*> auditCommandParser + do + mconcat + [ fullDesc + , progDesc (formatWith [blue] "audit your cabal projects for vulnerabilities") + , header (formatWith [bold, blue] "Welcome to cabal audit") + ] + `catch` \(SomeException ex) -> do + hPutStrLn stderr $ + unlines + [ formatWith [red, bold] "cabal-audit failed:" + , formatWith [red] $ displayException ex + ] + exitFailure + +buildAdvisories :: AuditConfig -> NixStyleFlags () -> IO (M.Map PackageName ElaboratedPackageInfoAdvised) +buildAdvisories MkAuditConfig {advisoriesPathOrURL, verbosity} flags = do + let cliConfig = projectConfigFromFlags flags + + ProjectBaseContext {distDirLayout, cabalDirLayout, projectConfig, localPackages} <- + establishProjectBaseContext + verbosity + cliConfig + OtherCommand + `catch` \ex -> throwIO $ CabalException "trying to establish project base context" ex + -- the two plans are + -- 1. the "improved plan" with packages replaced by in-store packages + -- 2. the "original" elaborated plan + -- + -- as far as I can tell, for our use case these should be indistinguishable + (_improvedPlan, plan, _, _, _) <- + rebuildInstallPlan verbosity distDirLayout cabalDirLayout projectConfig localPackages Nothing + `catch` \ex -> throwIO $ CabalException "elaborating the install-plan" ex + + when (verbosity > Verbosity.normal) do + hPutStrLn stderr (formatWith [blue] "Finished building the cabal install plan, looking for advisories...") + + advisories <- do + let k realPath = + listAdvisories realPath + >>= validation (throwIO . ListAdvisoryValidationError realPath) pure + case advisoriesPathOrURL of + Left fp -> k fp + Right url -> withSystemTempDirectory "cabal-audit" \tmp -> do + hPutStrLn stderr $ formatWith [blue] $ "trying to clone " <> url + callProcess "git" ["clone","--depth", "1",url, tmp] + k tmp + + pure $ matchAdvisoriesForPlan plan advisories + +-- | provides the built advisories in some consumable form, e.g. as human readable form +-- +-- FUTUREWORK(mangoiv): provide output as JSON +handleBuiltAdvisories :: M.Map PackageName ElaboratedPackageInfoAdvised -> IO () +handleBuiltAdvisories = humanReadableHandler . M.toList + +-- | 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 -> Text +prettyAdvisory Advisory {advisoryId, advisoryPublished, advisoryKeywords, advisorySummary} mfv = + T.unlines do + let hsecId = T.pack (printHsecId advisoryId) + map + (" " <>) + [ formatWith [bold, blue] hsecId <> " \"" <> advisorySummary <> "\"" + , "published: " <> formatWith [bold] (ps advisoryPublished) + , "https://haskell.github.io/security-advisories/advisory/" <> hsecId + , fixAvailable + , formatWith [blue] $ T.intercalate ", " (coerce advisoryKeywords) + ] + where + ps = T.pack . show + fixAvailable = case mfv of + Nothing -> formatWith [bold, red] "No fix version available" + Just fv -> formatWith [bold, green] "Fix available since version " <> formatWith [yellow] (prettyVersion fv) + +-- | this is handler is used when displaying to the user +humanReadableHandler :: [(PackageName, ElaboratedPackageInfoAdvised)] -> IO () +humanReadableHandler = \case + [] -> putStrLn (formatWith [green, bold] "No advisories found.") + avs -> do + putStrLn (formatWith [bold, red] "\n\nFound advisories:\n") + for_ avs \(pn, i) -> do + let verString = formatWith [yellow] $ prettyVersion $ elaboratedPackageVersion i + pkgName = formatWith [yellow] $ show $ unPackageName pn + putStrLn ("dependency " <> pkgName <> " at version " <> verString <> " is vulnerable for:") + for_ (runIdentity (packageAdvisories i)) (T.putStrLn . uncurry prettyAdvisory) + +projectConfigFromFlags :: NixStyleFlags a -> ProjectConfig +projectConfigFromFlags flags = commandLineFlagsToProjectConfig defaultGlobalFlags flags mempty + +auditCommandParser :: Parser (AuditConfig, NixStyleFlags ()) +auditCommandParser = + (,) + <$> do + MkAuditConfig + <$> do + Left + <$> strOption do + mconcat + [ long "file-path" + , short 'p' + , metavar "FILE_PATH" + , help "the path to the repository containing an advisories directory" + ] + <|> Right + <$> strOption do + mconcat + [ long "repository" + , short 'r' + , metavar "REPOSITORY" + , help "the url to the repository containing an advisories directory" + , value "https://github.com/haskell/security-advisories" + ] + <*> flip option (long "verbosity" <> value Verbosity.normal <> showDefaultWith (const "normal")) do + eitherReader \case + "silent" -> Right Verbosity.silent + "normal" -> Right Verbosity.normal + "verbose" -> Right Verbosity.verbose + "deafening" -> Right Verbosity.deafening + _ -> Left "verbosity has to be one of \"silent\", \"normal\", \"verbose\" or \"deafening\"" + -- FUTUREWORK(mangoiv): this will accept cabal flags as an additional argument with something like + -- --cabal-flags "--some-cabal-flag" and print a helper that just forwards the cabal help text + <*> pure (defaultNixStyleFlags ()) diff --git a/code/cabal-audit/src/Security/Advisories/Cabal.hs b/code/cabal-audit/src/Security/Advisories/Cabal.hs new file mode 100644 index 00000000..9ad3d435 --- /dev/null +++ b/code/cabal-audit/src/Security/Advisories/Cabal.hs @@ -0,0 +1,109 @@ +{-# LANGUAGE StrictData #-} +{-# LANGUAGE UndecidableInstances #-} + +module Security.Advisories.Cabal + ( matchAdvisoriesForPlan + , ElaboratedPackageInfoWith (..) + , ElaboratedPackageInfoAdvised + , ElaboratedPackageInfo + ) +where + +import Data.Functor.Identity (Identity (Identity)) +import Data.Kind (Type) +import Data.Map (Map, (!?)) +import Data.Map.Strict qualified as Map +import Data.Maybe (mapMaybe) +import Data.Monoid (Alt (Alt, getAlt), Any (Any, getAny)) +import Data.Proxy (Proxy (Proxy)) +import Data.Text qualified as T +import Distribution.Client.InstallPlan (foldPlanPackage) +import Distribution.Client.InstallPlan qualified as Plan +import Distribution.Client.ProjectPlanning (ElaboratedInstallPlan, elabPkgSourceId) +import Distribution.InstalledPackageInfo (sourcePackageId) +import Distribution.Package (PackageIdentifier (PackageIdentifier, pkgName, pkgVersion), PackageName, mkPackageName) +import Distribution.Version (Version) +import GHC.Generics (Generic) +import Security.Advisories + ( Advisory (advisoryAffected) + , Affected (Affected, affectedPackage, affectedVersions) + , AffectedVersionRange (affectedVersionRangeFixed, affectedVersionRangeIntroduced) + ) + +-- | for a given 'ElaboratedInstallPlan' and a list of advisories, construct a map of advisories +-- and packages within the install plan that are affected by them +matchAdvisoriesForPlan + :: ElaboratedInstallPlan + -- ^ the plan as created by cabal + -> [Advisory] + -- ^ the advisories as discovered in some advisory dir + -> Map PackageName ElaboratedPackageInfoAdvised +matchAdvisoriesForPlan plan = foldr advise Map.empty + where + advise :: Advisory -> Map PackageName ElaboratedPackageInfoAdvised -> Map PackageName ElaboratedPackageInfoAdvised + advise adv = do + let versionAffected :: Version -> [AffectedVersionRange] -> Bool + versionAffected v = + getAny . foldMap \av -> Any do + v >= affectedVersionRangeIntroduced av && maybe True (v <) (affectedVersionRangeFixed av) + + fixVersion :: [AffectedVersionRange] -> Maybe Version + fixVersion = getAlt . foldMap (Alt . affectedVersionRangeFixed) + + advPkgs :: [(PackageName, ElaboratedPackageInfoAdvised)] + advPkgs = flip mapMaybe (advisoryAffected adv) \Affected {affectedPackage, affectedVersions} -> do + let pkgn = mkPackageName (T.unpack affectedPackage) + MkElaboratedPackageInfoWith {elaboratedPackageVersion = elabv} <- installPlanToLookupTable plan !? pkgn + if versionAffected elabv affectedVersions + then Just (pkgn, MkElaboratedPackageInfoWith {elaboratedPackageVersion = elabv, packageAdvisories = Identity [(adv, fixVersion affectedVersions)]}) + else Nothing + + flip + do foldr . uncurry $ Map.insertWith combinedElaboratedPackageInfos + advPkgs + + combinedElaboratedPackageInfos + MkElaboratedPackageInfoWith {elaboratedPackageVersion = ver1, packageAdvisories = advs1} + MkElaboratedPackageInfoWith {packageAdvisories = advs2} = + MkElaboratedPackageInfoWith {elaboratedPackageVersion = ver1, packageAdvisories = advs1 <> advs2} + +type ElaboratedPackageInfoAdvised = ElaboratedPackageInfoWith Identity + +type ElaboratedPackageInfo = ElaboratedPackageInfoWith Proxy + +-- | information about the elaborated package that +-- is to be looked up that we want to add to the +-- information displayed in the advisory +type ElaboratedPackageInfoWith :: (Type -> Type) -> Type +data ElaboratedPackageInfoWith f = MkElaboratedPackageInfoWith + { elaboratedPackageVersion :: Version + -- ^ the version of the package that is installed + , packageAdvisories :: f [(Advisory, Maybe Version)] + -- ^ the advisories for some package; this is just the () type + -- (Proxy) as long as the advisories haven't been looked up and a + -- [Advisory] after looking up the advisories in the DB we also + -- want to attach the newest fixed version of a given Advisory + } + deriving stock (Generic) + +deriving stock instance Eq (f [(Advisory, Maybe Version)]) => (Eq (ElaboratedPackageInfoWith f)) + +deriving stock instance Ord (f [(Advisory, Maybe Version)]) => (Ord (ElaboratedPackageInfoWith f)) + +deriving stock instance Show (f [(Advisory, Maybe Version)]) => (Show (ElaboratedPackageInfoWith f)) + +-- FUTUREWORK(mangoiv): this could probably be done more intelligently by also +-- looking up via the version range but I don't know exacty how + +-- | 'Map' to lookup the package name in the install plan that returns information +-- about the package +installPlanToLookupTable :: ElaboratedInstallPlan -> Map PackageName ElaboratedPackageInfo +installPlanToLookupTable = Map.fromList . fmap planPkgToPackageInfo . Plan.toList + where + planPkgToPackageInfo pkg = do + let (PackageIdentifier {pkgName, pkgVersion}) = + foldPlanPackage + sourcePackageId + elabPkgSourceId + pkg + (pkgName, MkElaboratedPackageInfoWith {elaboratedPackageVersion = pkgVersion, packageAdvisories = Proxy}) diff --git a/code/cabal-audit/test/Main.hs b/code/cabal-audit/test/Main.hs new file mode 100644 index 00000000..2aa36112 --- /dev/null +++ b/code/cabal-audit/test/Main.hs @@ -0,0 +1,7 @@ +module Main where + +import Spec qualified (spec) +import Test.Hspec (hspec) + +main :: IO () +main = hspec Spec.spec diff --git a/code/cabal-audit/test/Spec.hs b/code/cabal-audit/test/Spec.hs new file mode 100644 index 00000000..9ccc729c --- /dev/null +++ b/code/cabal-audit/test/Spec.hs @@ -0,0 +1,6 @@ +module Spec (spec) where + +import Test.Hspec + +spec :: Spec +spec = pure () diff --git a/code/cabal-audit/test/assets/test-cabal-project/cabal.project b/code/cabal-audit/test/assets/test-cabal-project/cabal.project new file mode 100644 index 00000000..5ec22a28 --- /dev/null +++ b/code/cabal-audit/test/assets/test-cabal-project/cabal.project @@ -0,0 +1,4 @@ +packages: + ./test-a +index-state: hackage.haskell.org 2023-01-01T00:00:00Z +active-repositories: hackage.haskell.org diff --git a/code/cabal-audit/test/assets/test-cabal-project/cabal.project.local b/code/cabal-audit/test/assets/test-cabal-project/cabal.project.local new file mode 100644 index 00000000..649f6888 --- /dev/null +++ b/code/cabal-audit/test/assets/test-cabal-project/cabal.project.local @@ -0,0 +1,2 @@ +sysconfdir: . +ignore-project: False diff --git a/code/cabal-audit/test/assets/test-cabal-project/flake.lock b/code/cabal-audit/test/assets/test-cabal-project/flake.lock new file mode 100644 index 00000000..37bb26bf --- /dev/null +++ b/code/cabal-audit/test/assets/test-cabal-project/flake.lock @@ -0,0 +1,80 @@ +{ + "nodes": { + "haskell-flake": { + "locked": { + "lastModified": 1707835791, + "narHash": "sha256-oQbDPHtver9DO8IJCBMq/TVbscCkxuw9tIfBBti71Yk=", + "owner": "srid", + "repo": "haskell-flake", + "rev": "5113f700d6e92199fbe0574f7d12c775bb169702", + "type": "github" + }, + "original": { + "owner": "srid", + "repo": "haskell-flake", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1708093448, + "narHash": "sha256-gohEm3/NVyu7WINFhRf83yJH8UM2ie/KY9Iw3VN6fiE=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "c7763249f02b7786b4ca36e13a4d7365cfba162f", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "dir": "lib", + "lastModified": 1706550542, + "narHash": "sha256-UcsnCG6wx++23yeER4Hg18CXWbgNpqNXcHIo5/1Y+hc=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "97b17f32362e475016f942bbdfda4a4a72a8a652", + "type": "github" + }, + "original": { + "dir": "lib", + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1706830856, + "narHash": "sha256-a0NYyp+h9hlb7ddVz4LUn1vT/PLwqfrWYcHMvFB1xYg=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "b253292d9c0a5ead9bc98c4e9a26c6312e27d69f", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "root": { + "inputs": { + "haskell-flake": "haskell-flake", + "nixpkgs": "nixpkgs", + "parts": "parts" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/code/cabal-audit/test/assets/test-cabal-project/flake.nix b/code/cabal-audit/test/assets/test-cabal-project/flake.nix new file mode 100644 index 00000000..b56ce668 --- /dev/null +++ b/code/cabal-audit/test/assets/test-cabal-project/flake.nix @@ -0,0 +1,27 @@ +{ + nixConfig.allow-import-from-derivation = true; + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + parts.url = "github:hercules-ci/flake-parts"; + haskell-flake.url = "github:srid/haskell-flake"; + }; + outputs = inputs: + inputs.parts.lib.mkFlake { inherit inputs; } { + systems = [ "x86_64-linux" ]; + imports = [ + inputs.haskell-flake.flakeModule + ]; + + perSystem = + { + haskellProjects.default = { + defaults.devShell.tools = ps: { inherit (ps) cabal-install; }; + packages = { + toml-reader.source = "0.1.0.0"; + megaparsec.source = "9.2.0"; + }; + settings = { }; + }; + }; + }; +} diff --git a/code/cabal-audit/test/assets/test-cabal-project/test-a/src/MyLib.hs b/code/cabal-audit/test/assets/test-cabal-project/test-a/src/MyLib.hs new file mode 100644 index 00000000..e657c440 --- /dev/null +++ b/code/cabal-audit/test/assets/test-cabal-project/test-a/src/MyLib.hs @@ -0,0 +1,4 @@ +module MyLib (someFunc) where + +someFunc :: IO () +someFunc = putStrLn "someFunc" diff --git a/code/cabal-audit/test/assets/test-cabal-project/test-a/test-a.cabal b/code/cabal-audit/test/assets/test-cabal-project/test-a/test-a.cabal new file mode 100644 index 00000000..90a393d8 --- /dev/null +++ b/code/cabal-audit/test/assets/test-cabal-project/test-a/test-a.cabal @@ -0,0 +1,25 @@ +cabal-version: 3.0 +name: test-a +version: 0.1.0.0 +license: MIT +license-file: LICENSE +author: mustermann +maintainer: mustermann@example.com +category: Codec +build-type: Simple +extra-doc-files: CHANGELOG.md + +common warnings + ghc-options: -Wall + +library + import: warnings + exposed-modules: MyLib + + -- hakyll depends on pandoc which has a security report + build-depends: + , base + , toml-reader ==0.1.0.0 + + hs-source-dirs: src + default-language: Haskell2010 diff --git a/code/cabal.project b/code/cabal.project new file mode 100644 index 00000000..b90828bf --- /dev/null +++ b/code/cabal.project @@ -0,0 +1,9 @@ +packages: + ./hsec-core + ./hsec-tools + ./cabal-audit + ./hsec-sync + ./cvss + ./osv + +test-show-details: direct diff --git a/code/cvss/cvss.cabal b/code/cvss/cvss.cabal index f9d1fbb1..1890bae6 100644 --- a/code/cvss/cvss.cabal +++ b/code/cvss/cvss.cabal @@ -10,7 +10,8 @@ author: Tristan de Cacqueray maintainer: tdecacqu@redhat.com category: Data extra-doc-files: CHANGELOG.md -tested-with: GHC ==8.10.7 || ==9.0.2 || ==9.2.8 || ==9.4.8 || ==9.6.3 || ==9.8.1 +tested-with: + GHC ==8.10.7 || ==9.0.2 || ==9.2.8 || ==9.4.8 || ==9.6.3 || ==9.8.1 library exposed-modules: Security.CVSS diff --git a/code/hsec-core/hsec-core.cabal b/code/hsec-core/hsec-core.cabal index 2039abbc..a458c3c0 100644 --- a/code/hsec-core/hsec-core.cabal +++ b/code/hsec-core/hsec-core.cabal @@ -1,26 +1,25 @@ -cabal-version: 2.4 -name: hsec-core -version: 0.1.0.0 +cabal-version: 2.4 +name: hsec-core +version: 0.1.0.0 -- A short (one-line) description of the package. -synopsis: Core package representing Haskell advisories +synopsis: Core package representing Haskell advisories -- A longer description of the package. -description: Core package representing Haskell advisories. +description: Core package representing Haskell advisories. -- A URL where users can report bugs. -- bug-reports: -- The license under which the package is released. -license: BSD-3-Clause -author: David Christiansen -maintainer: david@davidchristiansen.dk +license: BSD-3-Clause +author: David Christiansen +maintainer: david@davidchristiansen.dk -- A copyright notice. -- copyright: -category: Data -extra-doc-files: CHANGELOG.md - +category: Data +extra-doc-files: CHANGELOG.md tested-with: GHC ==8.10.7 || ==9.0.2 || ==9.2.8 || ==9.4.8 || ==9.6.3 || ==9.8.1 diff --git a/code/hsec-core/src/Security/Advisories/Core/Advisory.hs b/code/hsec-core/src/Security/Advisories/Core/Advisory.hs index c2a4d3c7..97f453b9 100644 --- a/code/hsec-core/src/Security/Advisories/Core/Advisory.hs +++ b/code/hsec-core/src/Security/Advisories/Core/Advisory.hs @@ -100,7 +100,7 @@ data OS | OpenBSD deriving stock (Show) -newtype Keyword = Keyword Text +newtype Keyword = Keyword {getKeyWord :: Text} deriving stock (Eq, Ord) deriving (Show) via Text diff --git a/code/hsec-sync/app/Main.hs b/code/hsec-sync/app/Main.hs index 858875d9..cda7e60c 100644 --- a/code/hsec-sync/app/Main.hs +++ b/code/hsec-sync/app/Main.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} module Main where diff --git a/code/hsec-sync/hsec-sync.cabal b/code/hsec-sync/hsec-sync.cabal index 81c93eb1..f3a4ba8c 100644 --- a/code/hsec-sync/hsec-sync.cabal +++ b/code/hsec-sync/hsec-sync.cabal @@ -30,19 +30,19 @@ library Security.Advisories.Sync.Git build-depends: - , base >=4.14 && <4.20 - , directory >=1.3 && <1.4 - , extra >=1.7 && <1.8 - , feed >=1.3 && <1.4 - , filepath >=1.4 && <1.5 + , base >=4.14 && <4.20 + , directory >=1.3 && <1.4 + , extra >=1.7 && <1.8 + , feed >=1.3 && <1.4 + , filepath >=1.4 && <1.5 , hsec-core - , http-client >=0.7.0 && <0.8 - , lens >=5.1 && <5.3 - , process >=1.6 && <1.7 - , text >=1.2 && <3 - , time >=1.9 && <1.14 - , transformers >=0.5 && <0.7 - , wreq >=0.5 && <0.6 + , http-client >=0.7.0 && <0.8 + , lens >=5.1 && <5.3 + , process >=1.6 && <1.7 + , text >=1.2 && <3 + , time >=1.9 && <1.14 + , transformers >=0.5 && <0.7 + , wreq >=0.5 && <0.6 hs-source-dirs: src default-language: Haskell2010 @@ -81,12 +81,12 @@ test-suite spec build-depends: , base <5 , directory - , hsec-sync , filepath + , hsec-sync , process , tasty <1.5 , tasty-hunit <0.11 - , temporary ==1.* + , temporary >=1 && <2 , text , time diff --git a/code/hsec-sync/test/Spec/SyncSpec.hs b/code/hsec-sync/test/Spec/SyncSpec.hs index 93c64679..9574dba5 100644 --- a/code/hsec-sync/test/Spec/SyncSpec.hs +++ b/code/hsec-sync/test/Spec/SyncSpec.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} module Spec.SyncSpec (spec) where diff --git a/code/hsec-tools/hsec-tools.cabal b/code/hsec-tools/hsec-tools.cabal index 531d55c9..3f50172e 100644 --- a/code/hsec-tools/hsec-tools.cabal +++ b/code/hsec-tools/hsec-tools.cabal @@ -49,9 +49,9 @@ library , cvss , directory <2 , extra ^>=1.7.5 + , feed >=1.3 && <1.4 , filepath >=1.4 && <1.5 , hsec-core - , feed ==1.3.* , lucid >=2.9.0 , mtl >=2.2 && <2.4 , osv diff --git a/code/osv/osv.cabal b/code/osv/osv.cabal index 5c5eea3f..c371814e 100644 --- a/code/osv/osv.cabal +++ b/code/osv/osv.cabal @@ -1,41 +1,36 @@ -cabal-version: 2.4 -name: osv -version: 0.1.0.0 +cabal-version: 2.4 +name: osv +version: 0.1.0.0 -- A short (one-line) description of the package. -synopsis: - Open Source Vulnerability format +synopsis: Open Source Vulnerability format -- A longer description of the package. -description: - Open Source Vulnerability format. +description: Open Source Vulnerability format. -- A URL where users can report bugs. -- bug-reports: -- The license under which the package is released. -license: BSD-3-Clause -author: David Christiansen -maintainer: david@davidchristiansen.dk +license: BSD-3-Clause +author: David Christiansen +maintainer: david@davidchristiansen.dk -- A copyright notice. -- copyright: -category: Data -extra-doc-files: CHANGELOG.md - +category: Data +extra-doc-files: CHANGELOG.md tested-with: GHC ==8.10.7 || ==9.0.2 || ==9.2.8 || ==9.4.8 || ==9.6.3 || ==9.8.1 library - exposed-modules: - Security.OSV - + exposed-modules: Security.OSV build-depends: - , aeson >=2.0.1.0 && <3 - , base >=4.14 && <4.20 + , aeson >=2.0.1.0 && <3 + , base >=4.14 && <4.20 , cvss - , text >=1.2 && <3 - , time >=1.9 && <1.14 + , text >=1.2 && <3 + , time >=1.9 && <1.14 hs-source-dirs: src default-language: Haskell2010 @@ -48,10 +43,10 @@ test-suite spec hs-source-dirs: test main-is: Spec.hs build-depends: - , base <5 + , base <5 , osv - , tasty <1.5 - , tasty-hunit <0.11 + , tasty <1.5 + , tasty-hunit <0.11 default-language: Haskell2010 ghc-options: diff --git a/flake.lock b/flake.lock index 783f2afb..b5f39a51 100644 --- a/flake.lock +++ b/flake.lock @@ -1,15 +1,50 @@ { "nodes": { + "devshell": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1711099426, + "narHash": "sha256-HzpgM/wc3aqpnHJJ2oDqPBkNsqWbW0WfWUO8lKu8nGk=", + "owner": "numtide", + "repo": "devshell", + "rev": "2d45b54ca4a183f2fdcf4b19c895b64fbf620ee8", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "devshell", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, "flake-utils": { "inputs": { "systems": "systems" }, "locked": { - "lastModified": 1689068808, - "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", "owner": "numtide", "repo": "flake-utils", - "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", "type": "github" }, "original": { @@ -18,26 +53,189 @@ "type": "github" } }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1703887061, + "narHash": "sha256-gGPa9qWNc6eCXT/+Z5/zMkyYOuRZqeFZBDbopNZQkuY=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "43e1aa1308018f37118e34d3a9cb4f5e75dc11d5", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "haskell-flake": { + "locked": { + "lastModified": 1707242163, + "narHash": "sha256-w+cBynh7yqnpVtFdu1SEZxPgtlz/nWnv47D5crnPXHM=", + "owner": "srid", + "repo": "haskell-flake", + "rev": "f9d17c3aa68e65529f424816c8b9346ae602d1de", + "type": "github" + }, + "original": { + "owner": "srid", + "repo": "haskell-flake", + "type": "github" + } + }, "nixpkgs": { "locked": { - "lastModified": 1689679375, - "narHash": "sha256-LHUC52WvyVDi9PwyL1QCpaxYWBqp4ir4iL6zgOkmcb8=", + "lastModified": 1704161960, + "narHash": "sha256-QGua89Pmq+FBAro8NriTuoO/wNaUtugt29/qqA8zeeM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "684c17c429c42515bafb3ad775d2a710947f3d67", + "rev": "63143ac2c9186be6d9da6035fa22620018c85932", "type": "github" }, "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "dir": "lib", + "lastModified": 1706550542, + "narHash": "sha256-UcsnCG6wx++23yeER4Hg18CXWbgNpqNXcHIo5/1Y+hc=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "97b17f32362e475016f942bbdfda4a4a72a8a652", + "type": "github" + }, + "original": { + "dir": "lib", "owner": "NixOS", "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1704874635, + "narHash": "sha256-YWuCrtsty5vVZvu+7BchAxmcYzTMfolSPP5io8+WYCg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "3dc440faeee9e889fe2d1b4d25ad0f430d449356", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1707907779, + "narHash": "sha256-dtktfFJn+36yBkZ1mnQGdiDsqnzC9pXt/Ecpsui0hiY=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "c5e9528855e4e6feda2b16dec28de880ce774b93", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { + "locked": { + "lastModified": 1704842529, + "narHash": "sha256-OTeQA+F8d/Evad33JMfuXC89VMetQbsU4qcaePchGr4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "eabe8d3eface69f5bb16c18f8662a702f50c20d5", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1706830856, + "narHash": "sha256-a0NYyp+h9hlb7ddVz4LUn1vT/PLwqfrWYcHMvFB1xYg=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "b253292d9c0a5ead9bc98c4e9a26c6312e27d69f", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils_2", + "gitignore": "gitignore", + "nixpkgs": "nixpkgs_3", + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1706424699, + "narHash": "sha256-Q3RBuOpZNH2eFA1e+IHgZLAOqDD9SKhJ/sszrL8bQD4=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "7c54e08a689b53c8a1e5d70169f2ec9e2a68ffaf", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, "root": { "inputs": { - "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs", + "devshell": "devshell", + "haskell-flake": "haskell-flake", + "nixpkgs": "nixpkgs_2", + "parts": "parts", + "pre-commit-hooks": "pre-commit-hooks", "toml-parser": "toml-parser" } }, @@ -56,22 +254,33 @@ "type": "github" } }, - "toml-parser": { - "flake": false, + "systems_2": { "locked": { - "lastModified": 1708792062, - "narHash": "sha256-RiRBBnDriQi9jH76JVr72ygYWtGF963iv/XoPBuMA3U=", - "owner": "glguy", - "repo": "toml-parser", - "rev": "4bcf07dc403a0882e9ce5a423473306cf1863f2f", + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", "type": "github" }, "original": { - "owner": "glguy", - "ref": "toml-parser-2.0.0.0", - "repo": "toml-parser", + "owner": "nix-systems", + "repo": "default", "type": "github" } + }, + "toml-parser": { + "flake": false, + "locked": { + "lastModified": 1000000000, + "narHash": "sha256-ORXJIeAEb2fTAqLVhOurubDrz7Ddh7weZNEDkTkNP4k=", + "type": "tarball", + "url": "https://hackage.haskell.org/package/toml-parser-2.0.0.0/toml-parser-2.0.0.0.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://hackage.haskell.org/package/toml-parser-2.0.0.0/toml-parser-2.0.0.0.tar.gz" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 08036306..fb9a5047 100644 --- a/flake.nix +++ b/flake.nix @@ -1,114 +1,151 @@ { - description = "hsec-tools"; - + nixConfig.allow-import-from-derivation = true; + description = "hsec-flake"; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - flake-utils.url = "github:numtide/flake-utils"; - toml-parser = { - url = "github:glguy/toml-parser/toml-parser-2.0.0.0"; - flake = false; - }; + # flake inputs + nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + + # flake parts + parts.url = "github:hercules-ci/flake-parts"; + haskell-flake.url = "github:srid/haskell-flake"; + pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix"; + devshell.url = "github:numtide/devshell"; + # end flake parts + # end flake inputs + + # non-flake inputs + toml-parser.url = "https://hackage.haskell.org/package/toml-parser-2.0.0.0/toml-parser-2.0.0.0.tar.gz"; + toml-parser.flake = false; + # end non-flake inputs }; + outputs = inputs: + inputs.parts.lib.mkFlake { inherit inputs; } { + systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; + imports = [ + inputs.haskell-flake.flakeModule + inputs.pre-commit-hooks.flakeModule + inputs.devshell.flakeModule + ]; - outputs = { self, nixpkgs, flake-utils, toml-parser }: - flake-utils.lib.eachDefaultSystem (system: - let - overlays = [ ]; - pkgs = - import nixpkgs { inherit system overlays; config.allowBroken = true; }; - jailbreakUnbreak = pkg: - pkgs.haskell.lib.doJailbreak (pkgs.haskell.lib.dontCheck (pkgs.haskell.lib.unmarkBroken pkg)); + perSystem = + { config + , pkgs + , ... + }: { + # this flake module adds two things + # 1. the pre-commit script which is automatically run when committing + # which checks formatting and lints of both Haskell and nix files + # the automatically run check can be bypassed with -n or --no-verify + # 2. an attribute in the checks. attrset which can be run with + # nix flake check which checks the same lints as the pre-commit hook + pre-commit = { + check.enable = true; + settings.hooks = { + cabal-fmt.enable = true; + hlint.enable = true; - cvss = pkgs.haskellPackages.callCabal2nix "cvss" ./code/cvss { }; - osv = pkgs.haskellPackages.callCabal2nix "osv" ./code/osv { inherit cvss; }; - hsec-core = pkgs.haskellPackages.callCabal2nix "hsec-core" ./code/hsec-core { - inherit cvss osv; - Cabal-syntax = pkgs.haskellPackages.Cabal-syntax_3_8_1_0; - }; - hsec-tools = returnShellEnv: - pkgs.haskellPackages.developPackage { - inherit returnShellEnv; - name = "hsec-tools"; - root = ./code/hsec-tools; - withHoogle = false; - overrides = self: super: { - inherit cvss hsec-core osv; - Cabal-syntax = super.Cabal-syntax_3_8_1_0; - toml-parser = jailbreakUnbreak (super.callCabal2nix "toml-parser" toml-parser { }); + nixpkgs-fmt.enable = true; + statix.enable = true; + deadnix.enable = true; }; - - modifier = drv: - if returnShellEnv - then - pkgs.haskell.lib.addBuildTools drv - (with pkgs.haskellPackages; - [ - cabal-fmt - cabal-install - ghcid - haskell-language-server - pkgs.nixpkgs-fmt - ]) - else drv; }; - hsec-sync = - pkgs.haskell.lib.dontCheck - (pkgs.haskellPackages.callCabal2nix - "hsec-sync" - ./code/hsec-sync - { inherit hsec-core; }); - gitconfig = - pkgs.writeTextFile { - name = ".gitconfig"; - text = '' - [safe] - directory = * - ''; - destination = "/.gitconfig"; # should match 'config.WorkDir' + # this flake module adds a Haskell project by + # 1. parsing the packages in the cabal.project file + # 2. calling out to callCabal2nix to generate nix package definitions + # 3. applying overrides to the nix package set from the nixpkgs input used + # 4. populating the devShells.. (in this case "default") with + # a devShell that contains a built package-db suitable for building + # the cabal project's components with cabal-install; this is later reused to build the + # default devShell + # 5. populating the packages.. with a derivation that + # builds the package with name $packageName + # 6. populating the apps.. with executables as defined by + # the corresponding stanza in the *.cabal files within the cabal project + # 7. populating the outputs with a haskellFlakeProjectModules attribute-set that + # can be used to easily reuse the generated package definitions in another project + # using haskell-flake + # + # For more information, refer to the official documentation https://flake.parts/options/haskell-flake + # and run nix --allow-import-from-derivation flake show in the repository (or as usual + # by providing a flake url) + haskellProjects.default = { + packages = { + Cabal-syntax.source = "3.10.2.0"; + toml-parser.source = inputs.toml-parser; + }; + settings = { + cabal-audit.justStaticExecutables = true; + hsec-sync.check = false; + hsec-sync.justStaticExecutables = true; + }; + projectRoot = ./code; + autoWire = [ "packages" "checks" "apps" ]; }; - in - { - packages.cvss = cvss; - packages.osv = osv; - packages.hsec-core = hsec-core; - packages.hsec-tools = pkgs.haskell.lib.justStaticExecutables (hsec-tools false); - packages.hsec-sync = hsec-sync; - packages.hsec-tools-image = - pkgs.dockerTools.buildImage { - name = "haskell/hsec-tools"; - tag = "latest"; - copyToRoot = pkgs.buildEnv { - name = "image-root"; - paths = [ - self.packages.${system}.hsec-tools - pkgs.gitMinimal.out - gitconfig - ]; - pathsToLink = [ "/bin" "/" ]; - }; - runAsRoot = "rm -Rf /share"; - config = { - Cmd = [ "/bin/hsec-tools" ]; - Env = [ - "LOCALE_ARCHIVE=${pkgs.glibcLocalesUtf8}/lib/locale/locale-archive" - "LC_TIME=en_US.UTF-8" - "LANG=en_US.UTF-8" - "LANGUAGE=en" - "LC_ALL=en_US.UTF-8" - "GIT_DISCOVERY_ACROSS_FILESYSTEM=1" - ]; - Volumes = { - "/repo" = { }; - }; - WorkDir = "/"; + # the default devshell; this has a couple of advantages to using stdenv.mkShell; refer to + # https://flake.parts/options/devshell for more information; one of the advantages is + # the beautiful menu this provides where one can add commands that are offered and loaded + # as part of the devShell + devshells.default = { + commands = [ + { + name = "lint"; + help = "run formatting and linting of haskell and nix files in the entire repository"; + command = "pre-commit run --all"; + } + ]; + devshell = { + name = "security-advisories-haskell"; + packagesFrom = [ config.haskellProjects.default.outputs.devShell ]; + startup.pre-commit.text = config.pre-commit.installationScript; }; }; - # Used by `nix build` & `nix run` (prod exe) - defaultPackage = self.packages.${system}.hsec-tools; - # Used by `nix develop` (dev shell) - devShell = hsec-tools true; - }); + packages.default = config.packages.hsec-tools; + + packages.hsec-tools-image = + let + gitconfig = + pkgs.writeTextFile { + name = ".gitconfig"; + text = '' + [safe] + directory = * + ''; + destination = "/.gitconfig"; # should match 'config.WorkDir' + }; + in + pkgs.dockerTools.buildImage { + name = "haskell/hsec-tools"; + tag = "latest"; + + copyToRoot = pkgs.buildEnv { + name = "image-root"; + paths = [ + config.packages.hsec-tools + pkgs.gitMinimal.out + gitconfig + ]; + pathsToLink = [ "/bin" "/" ]; + }; + runAsRoot = "rm -Rf /share"; + config = { + Cmd = [ "/bin/hsec-tools" ]; + Env = [ + "LOCALE_ARCHIVE=${pkgs.glibcLocalesUtf8}/lib/locale/locale-archive" + "LC_TIME=en_US.UTF-8" + "LANG=en_US.UTF-8" + "LANGUAGE=en" + "LC_ALL=en_US.UTF-8" + "GIT_DISCOVERY_ACROSS_FILESYSTEM=1" + ]; + Volumes = { + "/repo" = { }; + }; + WorkDir = "/"; + }; + }; + }; + }; }