From 8acbcf47fa3b94f8bd8b837fdb5313eea32a84ca Mon Sep 17 00:00:00 2001 From: Aaron Yee Date: Thu, 5 Dec 2024 22:50:33 -1000 Subject: [PATCH] remediate some vulnerabilities found by semgrep and grype --- .github/workflows/manual-delivery.yaml | 6 ++++-- Dockerfile | 7 ++++++- cmd/portage/cli/v1/run-task.go | 10 ++++++++-- pkg/tasks/image-build.go | 8 ++++++++ pkg/tasks/image-push.go | 18 ++++++++++++++++-- pkg/tasks/image-save.go | 23 ++++++++++++++++++++++- 6 files changed, 64 insertions(+), 8 deletions(-) diff --git a/.github/workflows/manual-delivery.yaml b/.github/workflows/manual-delivery.yaml index d6b6469..5578625 100644 --- a/.github/workflows/manual-delivery.yaml +++ b/.github/workflows/manual-delivery.yaml @@ -54,5 +54,7 @@ jobs: run: | echo "## Docker Action Image Build and Push Summary" >> $GITHUB_STEP_SUMMARY echo ":white_check_mark: Docker Action Image Build and Push" >> $GITHUB_STEP_SUMMARY - echo ":white_check_mark: Image (Docker CLI): ghcr.io/easy-up/portage:${{ steps.vars.outputs.tag_name }}" >> $GITHUB_STEP_SUMMARY - echo ":white_check_mark: Image (Podman CLI): ghcr.io/easy-up/portage:podman-${{ steps.vars.outputs.tag_name }}" >> $GITHUB_STEP_SUMMARY + # echo ":white_check_mark: Image (Docker CLI): ghcr.io/easy-up/portage:${{ steps.vars.outputs.tag_name }}" >> $GITHUB_STEP_SUMMARY + # echo ":white_check_mark: Image (Podman CLI): ghcr.io/easy-up/portage:podman-${{ steps.vars.outputs.tag_name }}" >> $GITHUB_STEP_SUMMARY + echo ":white_check_mark: Image (Docker CLI): ghcr.io/easy-up/portage:${GITHUB_SHA::8}" >> $GITHUB_STEP_SUMMARY + echo ":white_check_mark: Image (Podman CLI): ghcr.io/easy-up/portage:podman-${GITHUB_SHA::8}" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 520707f..824edcd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -108,6 +108,10 @@ WORKDIR /app ENV PORTAGE_CODE_SCAN_SEMGREP_EXPERIMENTAL="true" +# Create non-root user and group +RUN addgroup -S portage && adduser -S portage -G portage +USER portage + ENTRYPOINT ["portage"] LABEL org.opencontainers.image.title="portage-docker" @@ -144,7 +148,8 @@ LABEL org.opencontainers.image.title="portage-podman" FROM portage-base -# Install docker CLI +USER root RUN apk update && apk add --no-cache docker-cli-buildx +USER portage LABEL org.opencontainers.image.title="portage-docker" diff --git a/cmd/portage/cli/v1/run-task.go b/cmd/portage/cli/v1/run-task.go index dcc3a9f..bda10ef 100644 --- a/cmd/portage/cli/v1/run-task.go +++ b/cmd/portage/cli/v1/run-task.go @@ -189,10 +189,16 @@ var runImagePushTask = &cobra.Command{ PreRunE: configPreRunE, RunE: func(cmd *cobra.Command, args []string) error { if *flagPodmanInterface { - task := tasks.NewGenericImagePushTask("podman", config.ImageTag) + task, err := tasks.NewGenericImagePushTask("podman", config.ImageTag) + if err != nil { + return err + } return task.Run(cmd.Context(), cmd.ErrOrStderr()) } - task := tasks.NewGenericImagePushTask("docker", config.ImageTag) + task, err := tasks.NewGenericImagePushTask("docker", config.ImageTag) + if err != nil { + return err + } return task.Run(cmd.Context(), cmd.ErrOrStderr()) }, } diff --git a/pkg/tasks/image-build.go b/pkg/tasks/image-build.go index 12596af..538a495 100644 --- a/pkg/tasks/image-build.go +++ b/pkg/tasks/image-build.go @@ -43,6 +43,10 @@ func NewImageBuildTask(cliInterface string, opts ...taskOptionFunc) ImageBuildTa } func NewGenericImageBuildTask(cmdString string) *GenericImageBuildTask { + if cmdString != "docker" && cmdString != "podman" { + panic("cmdString must be either 'docker' or 'podman'") + } + return &GenericImageBuildTask{ cmdString: cmdString, args: make([]string, 0), @@ -112,6 +116,10 @@ func (t *GenericImageBuildTask) Run(ctx context.Context, stderr io.Writer) error return err } + if t.cmdString != "docker" && t.cmdString != "podman" { + return fmt.Errorf("invalid command string: must be either 'docker' or 'podman'") + } + buildCmd := exec.CommandContext(ctx, t.cmdString, t.args...) return StreamStderr(buildCmd, stderr, fmt.Sprintf("%s build", t.cmdString)) } diff --git a/pkg/tasks/image-push.go b/pkg/tasks/image-push.go index fc45fa4..40cda16 100644 --- a/pkg/tasks/image-push.go +++ b/pkg/tasks/image-push.go @@ -2,21 +2,35 @@ package tasks import ( "context" + "errors" "fmt" "io" "os/exec" ) +// Add a whitelist of allowed CLI tools +var allowedCLITools = map[string]bool{ + "docker": true, + "podman": true, + "buildah": true, + // Add other valid CLI tools as needed +} + type GenericImagePushTask struct { TagName string cmdName string } -func NewGenericImagePushTask(cliInterface string, tagName string) *GenericImagePushTask { +func NewGenericImagePushTask(cliInterface string, tagName string) (*GenericImagePushTask, error) { + // Validate CLI interface against whitelist + if !allowedCLITools[cliInterface] { + return nil, errors.New("invalid CLI interface specified") + } + return &GenericImagePushTask{ TagName: tagName, cmdName: cliInterface, - } + }, nil } func (t *GenericImagePushTask) Run(ctx context.Context, stderr io.Writer) error { diff --git a/pkg/tasks/image-save.go b/pkg/tasks/image-save.go index 0fb69b5..558af42 100644 --- a/pkg/tasks/image-save.go +++ b/pkg/tasks/image-save.go @@ -37,11 +37,32 @@ type GenericImageSaveTask struct { cmdName string } +func isValidImageName(name string) bool { + // Only allow characters typically used in Docker image names: + // - Lowercase letters, numbers + // - Allowed special chars: period, underscore, hyphen + // - Forward slash for image paths (e.g., "repository/image") + // - Colon for tags (e.g., "image:tag") + for _, char := range name { + if !((char >= 'a' && char <= 'z') || + (char >= '0' && char <= '9') || + char == '.' || char == '_' || char == '-' || + char == '/' || char == ':') { + return false + } + } + return len(name) > 0 +} + func (t *GenericImageSaveTask) Run(ctx context.Context, stderr io.Writer) error { if strings.EqualFold(t.opts.ImageName, "") { return errors.New("image name is required") } + if !isValidImageName(t.opts.ImageName) { + return errors.New("invalid image name: must contain only lowercase letters, numbers, and allowed special characters (./_-/:)") + } + // let the open file command handle any invalid filename errors // run this first just incase to fail early if something goes wrong imageSaveFile, err := os.OpenFile(t.opts.ImageTarFilename, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0o644) @@ -60,7 +81,7 @@ func (t *GenericImageSaveTask) Run(ctx context.Context, stderr io.Writer) error mw := io.MultiWriter(imageSaveFile, smWriter) - imageSaveCmd := exec.CommandContext(ctx, t.cmdName, "save", t.opts.ImageName) + imageSaveCmd := exec.CommandContext(ctx, t.cmdName, []string{"save", t.opts.ImageName}...) imageSaveCmd.Stdout = mw err = StreamStderr(imageSaveCmd, stderr, "image save")