Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for custom tooling image #330

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
)

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 @@
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 @@
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 @@
)

// 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 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 @@

// 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 @@
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 @@
}

// 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 @@
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 @@
}

// 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 @@
}

// 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 @@
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 @@
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 ...
```