Skip to content

Commit

Permalink
feat: add modular scanners (#261)
Browse files Browse the repository at this point in the history
Signed-off-by: Anubhav Gupta <mail.anubhav06@gmail.com>
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Co-authored-by: Sertac Ozercan <sozercan@gmail.com>
  • Loading branch information
anubhav06 and sozercan committed Oct 16, 2023
1 parent d514256 commit 4eacf06
Show file tree
Hide file tree
Showing 23 changed files with 384 additions and 129 deletions.
8 changes: 5 additions & 3 deletions integration/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ import (
)

var (
buildkitAddr string
copaPath string
ignoreErrors bool
buildkitAddr string
copaPath string
ignoreErrors bool
scannerPlugin string
)

func TestMain(m *testing.M) {
flag.StringVar(&buildkitAddr, "addr", "", "buildkit address to pass through to copa binary")
flag.StringVar(&copaPath, "copa", "./copa", "path to copa binary")
flag.StringVar(&scannerPlugin, "scanner", "trivy", "Scanner used to generate the report")
flag.BoolVar(&ignoreErrors, "ignore-errors", false, "Ignore errors and continue patching")
flag.Parse()

Expand Down
1 change: 1 addition & 0 deletions integration/patch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ func patch(t *testing.T, ref, patchedTag, path string, ignoreErrors bool) {
"-i="+ref,
"-t="+patchedTag,
"-r="+path+"/scan.json",
"-s="+scannerPlugin,
"--timeout=20m",
addrFl,
"--ignore-errors="+strconv.FormatBool(ignoreErrors),
Expand Down
6 changes: 3 additions & 3 deletions pkg/buildkit/buildkit.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (
"github.com/moby/buildkit/version"
"github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/project-copacetic/copacetic/pkg/types"
"github.com/project-copacetic/copacetic/pkg/types/unversioned"
"github.com/project-copacetic/copacetic/pkg/utils"
log "github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
Expand Down Expand Up @@ -109,13 +109,13 @@ func resolveImageConfig(ctx context.Context, ref string, platform *ispec.Platfor
return dgst, config, nil
}

func InitializeBuildkitConfig(ctx context.Context, client *client.Client, image string, manifest *types.UpdateManifest) (*Config, error) {
func InitializeBuildkitConfig(ctx context.Context, client *client.Client, image string, manifest *unversioned.UpdateManifest) (*Config, error) {
// Initialize buildkit config for the target image
config := Config{
ImageName: image,
Platform: ispec.Platform{
OS: "linux",
Architecture: manifest.Arch,
Architecture: manifest.Metadata.Config.Arch,
},
}

Expand Down
3 changes: 3 additions & 0 deletions pkg/patch/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type patchArgs struct {
patchedTag string
workingFolder string
timeout time.Duration
scanner string
ignoreError bool
format string
output string
Expand All @@ -51,6 +52,7 @@ func NewPatchCmd() *cobra.Command {
ua.reportFile,
ua.patchedTag,
ua.workingFolder,
ua.scanner,
ua.format,
ua.output,
ua.ignoreError,
Expand All @@ -67,6 +69,7 @@ func NewPatchCmd() *cobra.Command {
flags.StringVarP(&ua.bkOpts.CertPath, "cert", "", "", "Absolute path to buildkit client certificate")
flags.StringVarP(&ua.bkOpts.KeyPath, "key", "", "", "Absolute path to buildkit client key")
flags.DurationVar(&ua.timeout, "timeout", 5*time.Minute, "Timeout for the operation, defaults to '5m'")
flags.StringVarP(&ua.scanner, "scanner", "s", "trivy", "Scanner used to generate the report, defaults to 'trivy'")
flags.BoolVar(&ua.ignoreError, "ignore-errors", false, "Ignore errors and continue patching")
flags.StringVarP(&ua.format, "format", "f", "openvex", "Output format, defaults to 'openvex'")
flags.StringVarP(&ua.output, "output", "o", "", "Output file path")
Expand Down
28 changes: 17 additions & 11 deletions pkg/patch/patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
"github.com/project-copacetic/copacetic/pkg/buildkit"
"github.com/project-copacetic/copacetic/pkg/pkgmgr"
"github.com/project-copacetic/copacetic/pkg/report"
"github.com/project-copacetic/copacetic/pkg/types"
"github.com/project-copacetic/copacetic/pkg/types/unversioned"
"github.com/project-copacetic/copacetic/pkg/utils"
"github.com/project-copacetic/copacetic/pkg/vex"
)
Expand All @@ -29,13 +29,13 @@ const (
)

// Patch command applies package updates to an OCI image given a vulnerability report.
func Patch(ctx context.Context, timeout time.Duration, image, reportFile, patchedTag, workingFolder, format, output string, ignoreError bool, bkOpts buildkit.Opts) error {
func Patch(ctx context.Context, timeout time.Duration, image, reportFile, patchedTag, workingFolder, scanner, format, output string, ignoreError bool, bkOpts buildkit.Opts) error {
timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()

ch := make(chan error)
go func() {
ch <- patchWithContext(timeoutCtx, image, reportFile, patchedTag, workingFolder, format, output, ignoreError, bkOpts)
ch <- patchWithContext(timeoutCtx, image, reportFile, patchedTag, workingFolder, scanner, format, output, ignoreError, bkOpts)
}()

select {
Expand All @@ -60,7 +60,7 @@ func removeIfNotDebug(workingFolder string) {
}
}

func patchWithContext(ctx context.Context, image, reportFile, patchedTag, workingFolder, format, output string, ignoreError bool, bkOpts buildkit.Opts) error {
func patchWithContext(ctx context.Context, image, reportFile, patchedTag, workingFolder, scanner, format, output string, ignoreError bool, bkOpts buildkit.Opts) error {
imageName, err := reference.ParseNamed(image)
if err != nil {
return err
Expand Down Expand Up @@ -107,7 +107,7 @@ func patchWithContext(ctx context.Context, image, reportFile, patchedTag, workin
}

// Parse report for update packages
updates, err := report.TryParseScanReport(reportFile)
updates, err := report.TryParseScanReport(reportFile, scanner)
if err != nil {
return err
}
Expand All @@ -126,7 +126,7 @@ func patchWithContext(ctx context.Context, image, reportFile, patchedTag, workin
}

// Create package manager helper
pkgmgr, err := pkgmgr.GetPackageManager(updates.OSType, config, workingFolder)
pkgmgr, err := pkgmgr.GetPackageManager(updates.Metadata.OS.Type, config, workingFolder)
if err != nil {
return err
}
Expand All @@ -143,11 +143,17 @@ func patchWithContext(ctx context.Context, image, reportFile, patchedTag, workin
}

// create a new manifest with the successfully patched packages
validatedManifest := &types.UpdateManifest{
OSType: updates.OSType,
OSVersion: updates.OSVersion,
Arch: updates.Arch,
Updates: []types.UpdatePackage{},
validatedManifest := &unversioned.UpdateManifest{
Metadata: unversioned.Metadata{
OS: unversioned.OS{
Type: updates.Metadata.OS.Type,
Version: updates.Metadata.OS.Version,
},
Config: unversioned.Config{
Arch: updates.Metadata.Config.Arch,
},
},
Updates: []unversioned.UpdatePackage{},
}
for _, update := range updates.Updates {
if !slices.Contains(errPkgs, update.Name) {
Expand Down
8 changes: 4 additions & 4 deletions pkg/pkgmgr/apk.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
apkVer "github.com/knqyf263/go-apk-version"
"github.com/moby/buildkit/client/llb"
"github.com/project-copacetic/copacetic/pkg/buildkit"
"github.com/project-copacetic/copacetic/pkg/types"
"github.com/project-copacetic/copacetic/pkg/types/unversioned"
"github.com/project-copacetic/copacetic/pkg/utils"
log "github.com/sirupsen/logrus"
)
Expand Down Expand Up @@ -56,7 +56,7 @@ func apkReadResultsManifest(path string) ([]string, error) {
return lines, nil
}

func validateAPKPackageVersions(updates types.UpdatePackages, cmp VersionComparer, resultsPath string, ignoreErrors bool) ([]string, error) {
func validateAPKPackageVersions(updates unversioned.UpdatePackages, cmp VersionComparer, resultsPath string, ignoreErrors bool) ([]string, error) {
lines, err := apkReadResultsManifest(resultsPath)
if err != nil {
return nil, err
Expand Down Expand Up @@ -124,7 +124,7 @@ func validateAPKPackageVersions(updates types.UpdatePackages, cmp VersionCompare
return errorPkgs, allErrors.ErrorOrNil()
}

func (am *apkManager) InstallUpdates(ctx context.Context, manifest *types.UpdateManifest, ignoreErrors bool) (*llb.State, []string, error) {
func (am *apkManager) InstallUpdates(ctx context.Context, manifest *unversioned.UpdateManifest, ignoreErrors bool) (*llb.State, []string, error) {
// Resolve set of unique packages to update
apkComparer := VersionComparer{isValidAPKVersion, isLessThanAPKVersion}
updates, err := GetUniqueLatestUpdates(manifest.Updates, apkComparer, ignoreErrors)
Expand Down Expand Up @@ -159,7 +159,7 @@ func (am *apkManager) InstallUpdates(ctx context.Context, manifest *types.Update
// TODO: support "distroless" Alpine images (e.g. APKO images)
// Still assumes that APK exists in the target image and is pathed, which can be addressed by
// mounting a copy of apk-tools-static into the image and invoking apk-static directly.
func (am *apkManager) upgradePackages(ctx context.Context, updates types.UpdatePackages) (*llb.State, error) {
func (am *apkManager) upgradePackages(ctx context.Context, updates unversioned.UpdatePackages) (*llb.State, error) {
// TODO: Add support for custom APK config
apkUpdated := am.config.ImageState.Run(llb.Shlex("apk update"), llb.WithProxy(utils.GetProxy())).Root()

Expand Down
12 changes: 6 additions & 6 deletions pkg/pkgmgr/apk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"testing"

"github.com/project-copacetic/copacetic/pkg/buildkit"
"github.com/project-copacetic/copacetic/pkg/types"
"github.com/project-copacetic/copacetic/pkg/types/unversioned"
)

// TestApkReadResultsManifest tests the apkReadResultsManifest function.
Expand Down Expand Up @@ -104,7 +104,7 @@ func TestValidateAPKPackageVersions(t *testing.T) {
// Define some test cases with inputs and expected outputs
testCases := []struct {
name string
updates types.UpdatePackages
updates unversioned.UpdatePackages
cmp VersionComparer
resultsPath string
ignoreErrors bool
Expand All @@ -113,14 +113,14 @@ func TestValidateAPKPackageVersions(t *testing.T) {
}{
{
name: "valid updates",
updates: []types.UpdatePackage{{Name: "apk-tools", FixedVersion: "2.12.7-r0"}, {Name: "busybox", FixedVersion: "1.33.1-r8"}},
updates: []unversioned.UpdatePackage{{Name: "apk-tools", FixedVersion: "2.12.7-r0"}, {Name: "busybox", FixedVersion: "1.33.1-r8"}},
cmp: apkComparer,
resultsPath: "testdata/apk_valid.txt",
ignoreErrors: false,
},
{
name: "invalid version",
updates: []types.UpdatePackage{{Name: "apk-tools", FixedVersion: "1.0"}, {Name: "busybox", FixedVersion: "2.0"}},
updates: []unversioned.UpdatePackage{{Name: "apk-tools", FixedVersion: "1.0"}, {Name: "busybox", FixedVersion: "2.0"}},
cmp: apkComparer,
resultsPath: "testdata/apk_invalid.txt",
ignoreErrors: false,
Expand All @@ -130,14 +130,14 @@ func TestValidateAPKPackageVersions(t *testing.T) {
},
{
name: "invalid version with ignore errors",
updates: []types.UpdatePackage{{Name: "apk-tools", FixedVersion: "1.0"}, {Name: "busybox", FixedVersion: "2.0"}},
updates: []unversioned.UpdatePackage{{Name: "apk-tools", FixedVersion: "1.0"}, {Name: "busybox", FixedVersion: "2.0"}},
cmp: apkComparer,
resultsPath: "testdata/apk_valid.txt",
ignoreErrors: true,
},
{
name: "expected 1 updates, installed 2",
updates: []types.UpdatePackage{{Name: "apk-tools", FixedVersion: "2.12.7-r0"}},
updates: []unversioned.UpdatePackage{{Name: "apk-tools", FixedVersion: "2.12.7-r0"}},
cmp: apkComparer,
resultsPath: "testdata/apk_valid.txt",
ignoreErrors: false,
Expand Down
22 changes: 11 additions & 11 deletions pkg/pkgmgr/dpkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
debVer "github.com/knqyf263/go-deb-version"
"github.com/moby/buildkit/client/llb"
"github.com/project-copacetic/copacetic/pkg/buildkit"
"github.com/project-copacetic/copacetic/pkg/types"
"github.com/project-copacetic/copacetic/pkg/types/unversioned"
"github.com/project-copacetic/copacetic/pkg/utils"
log "github.com/sirupsen/logrus"
)
Expand Down Expand Up @@ -72,15 +72,15 @@ func isLessThanDebianVersion(v1, v2 string) bool {
}

// Map the target image OSType & OSVersion to an appropriate tooling image.
func getAPTImageName(manifest *types.UpdateManifest) string {
version := manifest.OSVersion
if manifest.OSType == "debian" {
version = strings.Split(manifest.OSVersion, ".")[0] + "-slim"
func getAPTImageName(manifest *unversioned.UpdateManifest) string {
version := manifest.Metadata.OS.Version
if manifest.Metadata.OS.Type == "debian" {
version = strings.Split(manifest.Metadata.OS.Version, ".")[0] + "-slim"
}

// TODO: support qualifying image name with designated repository
log.Debugf("Using %s:%s as basis for tooling image", manifest.OSType, version)
return fmt.Sprintf("%s:%s", manifest.OSType, version)
log.Debugf("Using %s:%s as basis for tooling image", manifest.Metadata.OS.Type, version)
return fmt.Sprintf("%s:%s", manifest.Metadata.OS.Type, version)
}

func getDPKGStatusType(dir string) dpkgStatusType {
Expand All @@ -98,7 +98,7 @@ func getDPKGStatusType(dir string) dpkgStatusType {
return out
}

func (dm *dpkgManager) InstallUpdates(ctx context.Context, manifest *types.UpdateManifest, ignoreErrors bool) (*llb.State, []string, error) {
func (dm *dpkgManager) InstallUpdates(ctx context.Context, manifest *unversioned.UpdateManifest, ignoreErrors bool) (*llb.State, []string, error) {
// Validate and extract unique updates listed in input manifest
debComparer := VersionComparer{isValidDebianVersion, isLessThanDebianVersion}
updates, err := GetUniqueLatestUpdates(manifest.Updates, debComparer, ignoreErrors)
Expand Down Expand Up @@ -197,7 +197,7 @@ func (dm *dpkgManager) probeDPKGStatus(ctx context.Context, toolImage string) er
//
// TODO: Support Debian images with valid dpkg status but missing tools. No current examples exist in test set
// i.e. extra RunOption to mount a copy of busybox-static or full apt install into the image and invoking that.
func (dm *dpkgManager) installUpdates(ctx context.Context, updates types.UpdatePackages) (*llb.State, error) {
func (dm *dpkgManager) installUpdates(ctx context.Context, updates unversioned.UpdatePackages) (*llb.State, error) {
// TODO: Add support for custom APT config and gpg key injection
// Since this takes place in the target container, it can interfere with install actions
// such as the installation of the updated debian-archive-keyring package, so it's probably best
Expand Down Expand Up @@ -236,7 +236,7 @@ func (dm *dpkgManager) installUpdates(ctx context.Context, updates types.UpdateP
return &patchMerge, nil
}

func (dm *dpkgManager) unpackAndMergeUpdates(ctx context.Context, updates types.UpdatePackages, toolImage string) (*llb.State, error) {
func (dm *dpkgManager) unpackAndMergeUpdates(ctx context.Context, updates unversioned.UpdatePackages, toolImage string) (*llb.State, error) {
// Spin up a build tooling container to fetch and unpack packages to create patch layer.
// Pull family:version -> need to create version to base image map
toolingBase := llb.Image(toolImage,
Expand Down Expand Up @@ -369,7 +369,7 @@ func dpkgParseResultsManifest(path string) (map[string]string, error) {
return updateMap, nil
}

func validateDebianPackageVersions(updates types.UpdatePackages, cmp VersionComparer, resultsPath string, ignoreErrors bool) ([]string, error) {
func validateDebianPackageVersions(updates unversioned.UpdatePackages, cmp VersionComparer, resultsPath string, ignoreErrors bool) ([]string, error) {
// Load file into map[string]string for package:version lookup
updateMap, err := dpkgParseResultsManifest(resultsPath)
if err != nil {
Expand Down

0 comments on commit 4eacf06

Please sign in to comment.