This repository has been archived by the owner on Apr 1, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7
Container scan (Analyze/Test w/ upload) #173
Merged
Merged
Changes from 15 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
6036ae0
Rewire binary embedding
skilly-lily 0094ba9
Update vendor download script
skilly-lily cc1c0ed
Move issue type
skilly-lily b5d56b3
Add container analysis
skilly-lily b898cb0
Add container test scan
skilly-lily e0ed3ab
Add container commands
skilly-lily 7454801
add dump-binaries command
skilly-lily a8cada6
Fix vendoring pipeline
skilly-lily fa14637
Fuck you github, let me make files!
skilly-lily 1222abe
FUCK YOU GITHUB!!!!
skilly-lily faaf78f
Damn you, Steve Jobs.
skilly-lily bd1bd14
Move tests to match issues move
skilly-lily 2a162ff
Fix dumb constructor
skilly-lily 0205b1a
Remove comment
skilly-lily deece4f
Use updated test method
skilly-lily bb14667
Hide container scanning and disable
skilly-lily File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -86,6 +86,7 @@ jobs: | |
run: | | ||
mkdir vendor | ||
touch vendor/wiggins | ||
touch vendor/syft | ||
|
||
- name: Build | ||
run: | | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
{-# LANGUAGE QuasiQuotes #-} | ||
{-# LANGUAGE RecordWildCards #-} | ||
{-# LANGUAGE TypeApplications #-} | ||
|
||
module App.Fossa.Container | ||
( ImageText (..), | ||
imageTextArg, | ||
Locator (..), | ||
SyftResponse (..), | ||
ResponseArtifact (..), | ||
ResponseSource (..), | ||
ResponseDistro (..), | ||
SourceTarget (..), | ||
ContainerScan (..), | ||
ContainerImage (..), | ||
ContainerArtifact (..), | ||
runSyft, | ||
toContainerScan, | ||
extractRevision, | ||
) | ||
where | ||
|
||
import App.Fossa.EmbeddedBinary (BinaryPaths, toExecutablePath, withSyftBinary) | ||
import App.Types (ProjectRevision (..), OverrideProject (..)) | ||
import Control.Effect.Diagnostics hiding (fromMaybe) | ||
import Control.Effect.Lift (Lift) | ||
import Control.Monad.IO.Class | ||
import Data.Aeson | ||
import qualified Data.Map.Lazy as LMap | ||
import Data.Map.Strict (Map) | ||
import Data.Maybe (listToMaybe, fromMaybe) | ||
import Data.Text (Text, pack) | ||
import Data.Text.Extra (breakOnAndRemove) | ||
import Effect.Exec (AllowErr (Never), Command (..), execJson, runExecIO) | ||
import Options.Applicative (Parser, argument, help, metavar, str) | ||
import Path | ||
|
||
newtype ImageText = ImageText {unImageText :: Text} deriving (Show, Eq, Ord) | ||
|
||
imageTextArg :: Parser ImageText | ||
imageTextArg = ImageText . pack <$> argument str (metavar "IMAGE" <> help "The image to scan") | ||
|
||
newtype Locator = Locator {unLocator :: Text} deriving (Eq, Ord, Show) | ||
|
||
-- | The output of the syft binary | ||
data SyftResponse | ||
= SyftResponse | ||
{ responseArtifacts :: [ResponseArtifact], | ||
responseSource :: ResponseSource, | ||
responseDistro :: ResponseDistro | ||
} | ||
|
||
instance FromJSON SyftResponse where | ||
parseJSON = withObject "SyftResponse" $ \obj -> | ||
SyftResponse <$> obj .: "artifacts" | ||
<*> obj .: "source" | ||
<*> obj .: "distro" | ||
|
||
data ResponseArtifact | ||
= ResponseArtifact | ||
{ artifactName :: Text, | ||
artifactVersion :: Text, | ||
artifactType :: Text, | ||
artifactPkgUrl :: Text, | ||
artifactMetadataType :: Text, | ||
artifactMetadata :: Map Text Value | ||
} | ||
|
||
instance FromJSON ResponseArtifact where | ||
parseJSON = withObject "ResponseArtifact" $ \obj -> | ||
ResponseArtifact <$> obj .: "name" | ||
<*> obj .: "version" | ||
<*> obj .: "type" | ||
<*> obj .: "purl" | ||
<*> obj .: "metadataType" | ||
-- We delete "files" as early as possible, which reduces | ||
-- the size by over 95% in many cases. | ||
-- We use Lazy delete to avoid evaluating the innards of | ||
-- the field, since Aeson will try to avoid evaluating it | ||
-- as well. | ||
<*> (LMap.delete "files" <$> obj .: "metadata") | ||
|
||
newtype ResponseSource | ||
= ResponseSource | ||
{sourceTarget :: SourceTarget} | ||
|
||
instance FromJSON ResponseSource where | ||
parseJSON = withObject "ResponseSource" $ \obj -> | ||
ResponseSource <$> obj .: "target" | ||
|
||
data ResponseDistro | ||
= ResponseDistro | ||
{ distroName :: Text, | ||
distroVersion :: Text | ||
} | ||
|
||
instance FromJSON ResponseDistro where | ||
parseJSON = withObject "ResponseDistro" $ \obj -> | ||
ResponseDistro <$> obj .: "name" | ||
<*> obj .: "version" | ||
|
||
data SourceTarget | ||
= SourceTarget | ||
{ targetDigest :: Text, | ||
targetTags :: [Text] | ||
} | ||
|
||
instance FromJSON SourceTarget where | ||
parseJSON = withObject "SourceTarget" $ \obj -> | ||
SourceTarget <$> obj .: "digest" | ||
<*> obj .: "tags" | ||
|
||
-- | The reorganized output of syft into a slightly different format | ||
data ContainerScan | ||
= ContainerScan | ||
{ imageData :: ContainerImage, | ||
imageTag :: Text, | ||
imageDigest :: Text | ||
} | ||
|
||
instance ToJSON ContainerScan where | ||
toJSON scan = object ["image" .= imageData scan] | ||
|
||
data ContainerImage | ||
= ContainerImage | ||
{ imageArtifacts :: [ContainerArtifact], | ||
imageOs :: Text, | ||
imageOsRelease :: Text | ||
} | ||
|
||
instance ToJSON ContainerImage where | ||
toJSON ContainerImage {..} = | ||
object | ||
[ "os" .= imageOs, | ||
"osRelease" .= imageOsRelease, | ||
"artifacts" .= imageArtifacts | ||
] | ||
|
||
data ContainerArtifact | ||
= ContainerArtifact | ||
{ conArtifactName :: Text, | ||
conArtifactVersion :: Text, | ||
conArtifactType :: Text, | ||
conArtifactPkgUrl :: Text, | ||
conArtifactMetadataType :: Text, | ||
conArtifactMetadata :: Map Text Value | ||
} | ||
|
||
instance ToJSON ContainerArtifact where | ||
toJSON ContainerArtifact {..} = | ||
object | ||
[ "name" .= conArtifactName, | ||
"fullVersion" .= conArtifactVersion, | ||
"type" .= conArtifactType, | ||
"purl" .= conArtifactPkgUrl, | ||
"metadataType" .= conArtifactMetadataType, | ||
"metadata" .= LMap.delete "files" conArtifactMetadata | ||
] | ||
|
||
extractRevision :: OverrideProject -> ContainerScan -> ProjectRevision | ||
extractRevision OverrideProject {..} ContainerScan {..} = ProjectRevision name revision Nothing | ||
where | ||
name = fromMaybe imageTag overrideName | ||
revision = fromMaybe imageDigest overrideRevision | ||
|
||
|
||
toContainerScan :: Has Diagnostics sig m => SyftResponse -> m ContainerScan | ||
toContainerScan SyftResponse {..} = do | ||
let newArts = map convertArtifact responseArtifacts | ||
image = ContainerImage newArts (distroName responseDistro) (distroVersion responseDistro) | ||
target = sourceTarget responseSource | ||
tag <- extractTag $ targetTags target | ||
pure . ContainerScan image tag $ targetDigest target | ||
|
||
convertArtifact :: ResponseArtifact -> ContainerArtifact | ||
convertArtifact ResponseArtifact {..} = | ||
ContainerArtifact | ||
{ conArtifactName = artifactName, | ||
conArtifactVersion = artifactVersion, | ||
conArtifactType = artifactType, | ||
conArtifactPkgUrl = artifactPkgUrl, | ||
conArtifactMetadataType = artifactMetadataType, | ||
conArtifactMetadata = artifactMetadata | ||
} | ||
|
||
extractTag :: Has Diagnostics sig m => [Text] -> m Text | ||
extractTag tags = do | ||
firstTag <- fromMaybeText "No image tags found" $ listToMaybe tags | ||
tagTuple <- fromMaybeText "Image was not in the format name:tag" $ breakOnAndRemove ":" firstTag | ||
pure $ fst tagTuple | ||
|
||
runSyft :: | ||
( Has Diagnostics sig m, | ||
Has (Lift IO) sig m, | ||
MonadIO m | ||
) => | ||
ImageText -> | ||
m SyftResponse | ||
runSyft image = runExecIO . withSyftBinary $ \syftBin -> do | ||
execJson @SyftResponse [reldir|.|] $ syftCommand syftBin image | ||
|
||
syftCommand :: BinaryPaths -> ImageText -> Command | ||
syftCommand bin (ImageText image) = | ||
Command | ||
{ cmdName = pack . toFilePath $ toExecutablePath bin, | ||
cmdArgs = ["-o", "json", image], | ||
cmdAllowErr = Never | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
|
||
module App.Fossa.Container.Analyze | ||
( analyzeMain, | ||
) | ||
where | ||
|
||
import App.Fossa.Analyze (ScanDestination (..), fossaProjectUrl) | ||
import App.Fossa.Container (ImageText (..), runSyft, toContainerScan, extractRevision) | ||
import App.Fossa.FossaAPIV1 (uploadContainerScan) | ||
import App.Types (OverrideProject (..), ProjectRevision (..)) | ||
import Control.Carrier.Diagnostics | ||
import Control.Effect.Lift (Lift) | ||
import Control.Monad.IO.Class (MonadIO) | ||
import Data.Aeson | ||
import Data.Text.Lazy.Encoding (decodeUtf8) | ||
import Effect.Logger | ||
import Fossa.API.Types (ApiOpts (..)) | ||
|
||
analyzeMain :: ScanDestination -> Severity -> OverrideProject -> ImageText -> IO () | ||
analyzeMain scanDestination logSeverity override image = withLogger logSeverity $ do | ||
result <- runDiagnostics $ analyze scanDestination override image | ||
case result of | ||
Left err -> logError (renderFailureBundle err) | ||
Right _ -> pure () | ||
|
||
analyze :: | ||
( Has Diagnostics sig m, | ||
Has (Lift IO) sig m, | ||
Has Logger sig m, | ||
MonadIO m | ||
) => | ||
ScanDestination -> | ||
OverrideProject -> | ||
ImageText -> | ||
m () | ||
analyze scanDestination override image = do | ||
logDebug "Running embedded syft binary" | ||
containerScan <- runSyft image >>= toContainerScan | ||
case scanDestination of | ||
OutputStdout -> logStdout . pretty . decodeUtf8 $ encode containerScan | ||
UploadScan apiOpts projectMeta -> do | ||
let revision = extractRevision override containerScan | ||
logInfo ("Using project name: `" <> pretty (projectName revision) <> "`") | ||
logInfo ("Using project revision: `" <> pretty (projectRevision revision) <> "`") | ||
locator <- uploadContainerScan apiOpts projectMeta containerScan | ||
logInfo "Container Analysis successfully uploaded!" | ||
logInfo "View FOSSA Report:" | ||
logInfo (" " <> pretty (fossaProjectUrl (apiOptsUri apiOpts) locator revision)) | ||
pure () | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you elaborate on what a file is? I get why we need a comment here but I don't really understand what it is saying. I'm guessing this may make sense to me as I read further.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's some output from the
syft
binary that is under the nested json keyartifacts[n].metadata.files
.Since this value is unnecessary AND huge, we take steps to prevent loading it into memory as much as possible. Since haskell lazily evaluates data by default, and the
Data.Map
andData.Map.Lazy
internal map structure are strict in keys, but lazy in values, we can potentially remove the files key from this map without ever trying to hold the entire array in memory at once.The comment is saying "we retain the json-like map structure, but we want to ignore the 'files' field to save a ton of memory."