Skip to content

Commit

Permalink
feat: add support for custom tooling image
Browse files Browse the repository at this point in the history
Signed-off-by: Sertac Ozercan <[email protected]>
  • Loading branch information
sozercan committed Sep 27, 2023
1 parent 28191f1 commit e7bf564
Show file tree
Hide file tree
Showing 9 changed files with 59 additions and 26 deletions.
21 changes: 12 additions & 9 deletions pkg/patch/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,16 @@ import (
)

type patchArgs struct {
appImage string
reportFile string
patchedTag string
workingFolder string
timeout time.Duration
ignoreError bool
format string
output string
bkOpts buildkit.Opts
appImage string
reportFile string
patchedTag string
workingFolder string
timeout time.Duration
ignoreError bool
format string
output string
customToolingImage string
bkOpts buildkit.Opts
}

func NewPatchCmd() *cobra.Command {
Expand All @@ -53,6 +54,7 @@ func NewPatchCmd() *cobra.Command {
ua.workingFolder,
ua.format,
ua.output,
ua.customToolingImage,

Check warning on line 57 in pkg/patch/cmd.go

View check run for this annotation

Codecov / codecov/patch

pkg/patch/cmd.go#L57

Added line #L57 was not covered by tests
ua.ignoreError,
bkopts)
},
Expand All @@ -70,6 +72,7 @@ func NewPatchCmd() *cobra.Command {
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")
flags.StringVarP(&ua.customToolingImage, "custom-tooling-image", "", "", "[EXPERIMENTAL] Custom tooling image to use for patching")

if err := patchCmd.MarkFlagRequired("image"); err != nil {
panic(err)
Expand Down
8 changes: 4 additions & 4 deletions pkg/patch/patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, format, output, customToolingImage string, ignoreError bool, bkOpts buildkit.Opts) error {

Check warning on line 32 in pkg/patch/patch.go

View check run for this annotation

Codecov / codecov/patch

pkg/patch/patch.go#L32

Added line #L32 was not covered by tests
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, format, output, customToolingImage, ignoreError, bkOpts)

Check warning on line 38 in pkg/patch/patch.go

View check run for this annotation

Codecov / codecov/patch

pkg/patch/patch.go#L38

Added line #L38 was not covered by tests
}()

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, format, output, customToolingImage string, ignoreError bool, bkOpts buildkit.Opts) error {

Check warning on line 63 in pkg/patch/patch.go

View check run for this annotation

Codecov / codecov/patch

pkg/patch/patch.go#L63

Added line #L63 was not covered by tests
imageName, err := ref.ParseNamed(image)
if err != nil {
return err
Expand Down Expand Up @@ -133,7 +133,7 @@ func patchWithContext(ctx context.Context, image, reportFile, patchedTag, workin

// Export the patched image state to Docker
// TODO: Add support for other output modes as buildctl does.
patchedImageState, errPkgs, err := pkgmgr.InstallUpdates(ctx, updates, ignoreError)
patchedImageState, errPkgs, err := pkgmgr.InstallUpdates(ctx, updates, ignoreError, customToolingImage)

Check warning on line 136 in pkg/patch/patch.go

View check run for this annotation

Codecov / codecov/patch

pkg/patch/patch.go#L136

Added line #L136 was not covered by tests
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/pkgmgr/apk.go
Original file line number Diff line number Diff line change
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 *types.UpdateManifest, ignoreErrors bool, _ string) (*llb.State, []string, error) {

Check warning on line 127 in pkg/pkgmgr/apk.go

View check run for this annotation

Codecov / codecov/patch

pkg/pkgmgr/apk.go#L127

Added line #L127 was not covered by tests
// Resolve set of unique packages to update
apkComparer := VersionComparer{isValidAPKVersion, isLessThanAPKVersion}
updates, err := GetUniqueLatestUpdates(manifest.Updates, apkComparer, ignoreErrors)
Expand Down
10 changes: 7 additions & 3 deletions pkg/pkgmgr/dpkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,11 @@ func isLessThanDebianVersion(v1, v2 string) bool {
}

// Map the target image OSType & OSVersion to an appropriate tooling image.
func getAPTImageName(manifest *types.UpdateManifest) string {
func getAPTImageName(manifest *types.UpdateManifest, customToolingImage string) string {
if customToolingImage != "" {
return customToolingImage
}

version := manifest.OSVersion
if manifest.OSType == "debian" {
version = strings.Split(manifest.OSVersion, ".")[0] + "-slim"
Expand All @@ -98,7 +102,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 *types.UpdateManifest, ignoreErrors bool, customToolingImage string) (*llb.State, []string, error) {

Check warning on line 105 in pkg/pkgmgr/dpkg.go

View check run for this annotation

Codecov / codecov/patch

pkg/pkgmgr/dpkg.go#L105

Added line #L105 was not covered by tests
// Validate and extract unique updates listed in input manifest
debComparer := VersionComparer{isValidDebianVersion, isLessThanDebianVersion}
updates, err := GetUniqueLatestUpdates(manifest.Updates, debComparer, ignoreErrors)
Expand All @@ -111,7 +115,7 @@ func (dm *dpkgManager) InstallUpdates(ctx context.Context, manifest *types.Updat
}

// Probe for additional information to execute the appropriate update install graphs
toolImageName := getAPTImageName(manifest)
toolImageName := getAPTImageName(manifest, customToolingImage)

Check warning on line 118 in pkg/pkgmgr/dpkg.go

View check run for this annotation

Codecov / codecov/patch

pkg/pkgmgr/dpkg.go#L118

Added line #L118 was not covered by tests
if err := dm.probeDPKGStatus(ctx, toolImageName); err != nil {
return nil, nil, err
}
Expand Down
8 changes: 7 additions & 1 deletion pkg/pkgmgr/dpkg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,16 @@ func TestGetAPTImageName(t *testing.T) {
// Run test cases
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got := getAPTImageName(tc.manifest)
got := getAPTImageName(tc.manifest, "")
if got != tc.want {
t.Errorf("getAPTImageName() = %v, want %v", got, tc.want)
}

// Test with custom tooling image
got = getAPTImageName(tc.manifest, "docker.io/foo:bar")
if got != "docker.io/foo:bar" {
t.Errorf("getAPTImageName() = %v, want %v", got, "docker.io/foo:bar")
}
})
}
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/pkgmgr/pkgmgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const (
)

type PackageManager interface {
InstallUpdates(context.Context, *types.UpdateManifest, bool) (*llb.State, []string, error)
InstallUpdates(context.Context, *types.UpdateManifest, bool, string) (*llb.State, []string, error)
GetPackageType() string
}

Expand Down
10 changes: 7 additions & 3 deletions pkg/pkgmgr/rpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,11 @@ func isLessThanRPMVersion(v1, v2 string) bool {
}

// Map the target image OSType & OSVersion to an appropriate tooling image.
func getRPMImageName(manifest *types.UpdateManifest) string {
func getRPMImageName(manifest *types.UpdateManifest, customToolingImage string) string {
if customToolingImage != "" {
return customToolingImage
}

// Standardize on mariner as tooling image base as redhat/ubi does not provide
// static busybox binary
image := "mcr.microsoft.com/cbl-mariner/base/core"
Expand Down Expand Up @@ -159,7 +163,7 @@ func getRPMDBType(dir string) rpmDBType {
return out
}

func (rm *rpmManager) InstallUpdates(ctx context.Context, manifest *types.UpdateManifest, ignoreErrors bool) (*llb.State, []string, error) {
func (rm *rpmManager) InstallUpdates(ctx context.Context, manifest *types.UpdateManifest, ignoreErrors bool, customToolingImage string) (*llb.State, []string, error) {

Check warning on line 166 in pkg/pkgmgr/rpm.go

View check run for this annotation

Codecov / codecov/patch

pkg/pkgmgr/rpm.go#L166

Added line #L166 was not covered by tests
// Resolve set of unique packages to update
rpmComparer := VersionComparer{isValidRPMVersion, isLessThanRPMVersion}
updates, err := GetUniqueLatestUpdates(manifest.Updates, rpmComparer, ignoreErrors)
Expand All @@ -173,7 +177,7 @@ func (rm *rpmManager) InstallUpdates(ctx context.Context, manifest *types.Update
log.Debugf("latest unique RPMs: %v", updates)

// Probe RPM status for available tooling on the target image
toolImageName := getRPMImageName(manifest)
toolImageName := getRPMImageName(manifest, customToolingImage)

Check warning on line 180 in pkg/pkgmgr/rpm.go

View check run for this annotation

Codecov / codecov/patch

pkg/pkgmgr/rpm.go#L180

Added line #L180 was not covered by tests
if err := rm.probeRPMStatus(ctx, toolImageName); err != nil {
return nil, nil, err
}
Expand Down
8 changes: 5 additions & 3 deletions pkg/pkgmgr/rpm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,12 @@ func TestGetRPMImageName(t *testing.T) {
// Loop over test cases and run getRPMImageName function with each input manifest
for _, tc := range testCases {
t.Run(tc.image, func(t *testing.T) {
image := getRPMImageName(tc.manifest)

// Use testify package to assert that the output image name matches the expected one
image := getRPMImageName(tc.manifest, "")
assert.Equal(t, tc.image, image)

// Test with custom tooling image
image = getRPMImageName(tc.manifest, "docker.io/foo:bar")
assert.Equal(t, "docker.io/foo:bar", image)
})
}
}
Expand Down
16 changes: 15 additions & 1 deletion website/docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,18 @@ title: FAQ
---

## What kind of vulnerabilities can Copa patch?
Copa is capable of patching "OS level" vulnerabilities. This includes packages (like `openssl`) in the image that are managed by a package manager such as `apt` or `yum`. Copa is not currently capable of patching vulnerabilities at the "application level" such as Python packages or Go modules.
Copa is capable of patching "OS level" vulnerabilities. This includes packages (like `openssl`) in the image that are managed by a package manager such as `apt` or `yum`. Copa is not currently capable of patching vulnerabilities at the "application level" such as Python packages or Go modules.

## Can I replace the package repositories in the image with my own?

:::caution

Experimental: This feature might change without preserving backwards compatibility.

:::

Copa does not support replacing the repositories in the package managers with alternatives. Images must already use the intended package repositories. For example, for debian, updating `/etc/apt/sources.list` from `http://archive.ubuntu.com/ubuntu/` to a mirror, such as `https://mirrors.wikimedia.org/ubuntu/`. If you need the tooling image to use a different package repository, you can create a custom tooling image with the desired package repository and specify it using `--custom-tooling-image` flag.

```shell
copa patch --image docker.io/myorg/image:tag --custom-tooling-image docker.io/myorg/tooling-base:tag ...
```

0 comments on commit e7bf564

Please sign in to comment.