Skip to content

gmpctl hardening#1971

Open
bwplotka wants to merge 7 commits into
mainfrom
gmpctlhardening
Open

gmpctl hardening#1971
bwplotka wants to merge 7 commits into
mainfrom
gmpctlhardening

Conversation

@bwplotka

Copy link
Copy Markdown
Collaborator

See commits for the logical change and rationales.

@bwplotka bwplotka mentioned this pull request Jun 26, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request enhances the gmpctl release and vulnerability fixing tools by automating OpenTelemetry schema conflict resolution, detecting Go minor versions from Dockerfiles, creating pull requests via the GitHub CLI, and adding comprehensive unit tests. The review feedback highlights several key areas for improvement: restructuring the release flow to prompt the user before triggering local GPG tag creation, correcting the command arguments in mustPush to ensure proper execution, optimizing dependency installation in gmpctl.sh to skip already-installed binaries, using os.CreateTemp for safer temporary file handling, suppressing noisy stderr output in the schema detector, and splitting the ignored CVE string once outside the loop to improve performance.

Comment thread ops/gmpctl/cmd_release.go
Comment on lines +86 to 100
mustCreateOrRecreateTag(dir, tag)
if !mustIsRemoteUpToDate(dir, branch) {
if confirmf("About to git push state from %q to \"origin/%v\" for %q tag; are you sure?", dir, branch, tag) {
// We are in detached state, so use the HEAD reference.
mustPush(dir, fmt.Sprintf("HEAD:%v", branch))
if confirmf("About to create a signed tag %q and git push state (HEAD:%v) and tag %q from %q to \"origin\"; are you sure?", tag, branch, tag, dir) {
mustPush(dir, fmt.Sprintf("HEAD:%v", branch), tag)
} else {
return errors.New("aborting")
}
}

// TODO(bwplotka): Check if tag exists.
mustCreateSignedTag(dir, tag)
if confirmf("About to git push %q tag from %q to \"origin/%v\"; are you sure?", tag, dir, branch) {
mustPush(dir, tag)
} else {
return errors.New("aborting")
// Retagging only. This can happen if someone wants to continue the script.
if confirmf("About to create a signed tag %q and push it from %q to \"origin\"; are you sure?", tag, dir) {
mustPush(dir, tag)
} else {
return errors.New("aborting")
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Calling mustCreateOrRecreateTag before the confirmation prompt creates or recreates the signed tag locally (which may prompt the user for their GPG passphrase) even if they ultimately choose to abort the release. Additionally, the confirmation prompt says "About to create a signed tag..." when the tag has already been created. Restructuring the flow to prompt the user first avoids these side-effects and misleading messages.

Suggested change
mustCreateOrRecreateTag(dir, tag)
if !mustIsRemoteUpToDate(dir, branch) {
if confirmf("About to git push state from %q to \"origin/%v\" for %q tag; are you sure?", dir, branch, tag) {
// We are in detached state, so use the HEAD reference.
mustPush(dir, fmt.Sprintf("HEAD:%v", branch))
if confirmf("About to create a signed tag %q and git push state (HEAD:%v) and tag %q from %q to \"origin\"; are you sure?", tag, branch, tag, dir) {
mustPush(dir, fmt.Sprintf("HEAD:%v", branch), tag)
} else {
return errors.New("aborting")
}
}
// TODO(bwplotka): Check if tag exists.
mustCreateSignedTag(dir, tag)
if confirmf("About to git push %q tag from %q to \"origin/%v\"; are you sure?", tag, dir, branch) {
mustPush(dir, tag)
} else {
return errors.New("aborting")
// Retagging only. This can happen if someone wants to continue the script.
if confirmf("About to create a signed tag %q and push it from %q to \"origin\"; are you sure?", tag, dir) {
mustPush(dir, tag)
} else {
return errors.New("aborting")
}
}
if !mustIsRemoteUpToDate(dir, branch) {
if confirmf("About to create a signed tag %q and git push state (HEAD:%v) and tag %q from %q to \"origin\"; are you sure?", tag, branch, tag, dir) {
mustCreateOrRecreateTag(dir, tag)
mustPush(dir, fmt.Sprintf("HEAD:%v", branch), tag)
} else {
return errors.New("aborting")
}
} else {
// Retagging only. This can happen if someone wants to continue the script.
if confirmf("About to create a signed tag %q and push it from %q to \"origin\"; are you sure?", tag, dir) {
mustCreateOrRecreateTag(dir, tag)
mustPush(dir, tag)
} else {
return errors.New("aborting")
}
}

Comment thread ops/gmpctl/git.go
Comment on lines +134 to 140
func mustPush(dir string, what ...string) {
logf("Pushing %v...", strings.Join(what, " "))
args := append([]string{"git", "push", "origin"}, what...)
if _, err := runCommand(&cmdOpts{Dir: dir}, args...); err != nil {
panicf("failed to push: %v", err)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If the signature of runCommand is func runCommand(opts *cmdOpts, name string, args ...string), passing args... directly where args contains the command name ("git") will fail to compile because Go does not allow a slice to satisfy both a regular parameter and a variadic parameter in a single call. Passing the executable name "git" explicitly as the second argument ensures compilation succeeds regardless of whether runCommand takes (opts, name, args...) or (opts, cmdAndArgs...).

Suggested change
func mustPush(dir string, what ...string) {
logf("Pushing %v...", strings.Join(what, " "))
args := append([]string{"git", "push", "origin"}, what...)
if _, err := runCommand(&cmdOpts{Dir: dir}, args...); err != nil {
panicf("failed to push: %v", err)
}
}
func mustPush(dir string, what ...string) {
logf("Pushing %v...", strings.Join(what, " "))
args := append([]string{"push", "origin"}, what...)
if _, err := runCommand(&cmdOpts{Dir: dir}, "git", args...); err != nil {
panicf("failed to push: %v", err)
}
}

Comment thread ops/gmpctl.sh
Comment on lines +29 to +35
# Install dependencies in the current context. We switch Go toolchain versions so using go tool might not work.
go install github.com/google/go-containerregistry/cmd/gcrane@latest
go install github.com/mikefarah/yq/v4@latest
go install helm.sh/helm/v3/cmd/helm@latest
go install github.com/google/addlicense@latest

export PATH="$(go env GOPATH)/bin:${PATH}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Running go install on every execution of gmpctl.sh is highly inefficient and can significantly slow down the script, especially in offline environments or when dependencies are already installed. Consider checking if the binaries are already available in the PATH or GOPATH/bin before attempting to install them.

Suggested change
# Install dependencies in the current context. We switch Go toolchain versions so using go tool might not work.
go install github.com/google/go-containerregistry/cmd/gcrane@latest
go install github.com/mikefarah/yq/v4@latest
go install helm.sh/helm/v3/cmd/helm@latest
go install github.com/google/addlicense@latest
export PATH="$(go env GOPATH)/bin:${PATH}"
export PATH="$(go env GOPATH)/bin:${PATH}"
for cmd in gcrane yq helm addlicense; do
if ! command -v "$cmd" &>/dev/null; then
case "$cmd" in
gcrane) go install github.com/google/go-containerregistry/cmd/gcrane@latest ;;
yq) go install github.com/mikefarah/yq/v4@latest ;;
helm) go install helm.sh/helm/v3/cmd/helm@latest ;;
addlicense) go install github.com/google/addlicense@latest ;;
esac
fi
done

Comment thread ops/gmpctl/cmd_vulnfix.go
Comment on lines +205 to +221
tmpFile := filepath.Join(dir, "gmpctl_tmp_schema.go")
tmpCode := `package main

import (
"fmt"
"go.opentelemetry.io/otel/sdk/resource"
)

func main() {
r := resource.Default()
fmt.Print(r.SchemaURL())
}
`
if err := os.WriteFile(tmpFile, []byte(tmpCode), 0o644); err != nil {
return "", fmt.Errorf("failed to write temp file: %w", err)
}
defer os.Remove(tmpFile)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using a hardcoded temporary filename gmpctl_tmp_schema.go inside the project directory can lead to accidental file overwrites or deletion if a file with that name already exists. Using os.CreateTemp with a pattern ensures a unique filename is used.

	tmpFile, err := os.CreateTemp(dir, "gmpctl_tmp_schema_*.go")
	if err != nil {
		return "", fmt.Errorf("failed to create temp file: %w", err)
	}
	tmpPath := tmpFile.Name()
	defer os.Remove(tmpPath)
	defer tmpFile.Close()

	tmpCode := `package main

import (
	"fmt"
	"go.opentelemetry.io/otel/sdk/resource"
)

func main() {
	r := resource.Default()
	fmt.Print(r.SchemaURL())
}
`
	if _, err := tmpFile.Write([]byte(tmpCode)); err != nil {
		return "", fmt.Errorf("failed to write temp file: %w", err)
	}
	_ = tmpFile.Close()

Comment thread ops/gmpctl/cmd_vulnfix.go
Comment on lines +223 to +226
cmd := exec.Command("go", "run", "gmpctl_tmp_schema.go")
cmd.Dir = dir
cmd.Stderr = os.Stderr
out, err := cmd.Output()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Setting cmd.Stderr = os.Stderr will print noisy compilation errors directly to the console if the project does not have OpenTelemetry dependencies or fails to compile. Since the error is explicitly caught and logged as a warning, it is better to let cmd.Output() capture the stderr automatically (by leaving cmd.Stderr as nil) and only log it if an error occurs.

Suggested change
cmd := exec.Command("go", "run", "gmpctl_tmp_schema.go")
cmd.Dir = dir
cmd.Stderr = os.Stderr
out, err := cmd.Output()
cmd := exec.Command("go", "run", filepath.Base(tmpPath))
cmd.Dir = dir
out, err := cmd.Output()

Comment on lines +91 to +111
func isIgnoredCVE(osvID string, aliases []string, cveID string, module string) (string, bool) {
if module == "github.com/prometheus/prometheus" {
return "Not affected; going to be fixed with the unfork", true
}
for _, id := range append([]string{osvID, cveID}, aliases...) {
if id == "" {
continue
}
if reason, ok := ignoredCVEs[id]; ok {
return fmt.Sprintf("%s (%s)", id, reason), true
}
if *ignoreCVEs != "" {
for _, userIgnored := range strings.Split(*ignoreCVEs, ",") {
if strings.TrimSpace(userIgnored) == id {
return fmt.Sprintf("%s (ignored via command line)", id), true
}
}
}
}
return "", false
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Splitting *ignoreCVEs by comma inside the nested loop for every id is inefficient. Splitting the string once at the beginning of isIgnoredCVE is cleaner and more performant.

func isIgnoredCVE(osvID string, aliases []string, cveID string, module string) (string, bool) {
	if module == "github.com/prometheus/prometheus" {
		return "Not affected; going to be fixed with the unfork", true
	}
	var userIgnoredList []string
	if *ignoreCVEs != "" {
		userIgnoredList = strings.Split(*ignoreCVEs, ",")
	}
	for _, id := range append([]string{osvID, cveID}, aliases...) {
		if id == "" {
			continue
		}
		if reason, ok := ignoredCVEs[id]; ok {
			return fmt.Sprintf("%s (%s)", id, reason), true
		}
		for _, userIgnored := range userIgnoredList {
			if strings.TrimSpace(userIgnored) == id {
				return fmt.Sprintf("%s (ignored via command line)", id), true
			}
		}
	}
	return "", false
}

bwplotka added 7 commits June 26, 2026 15:27
Signed-off-by: bwplotka <bwplotka@gmail.com>
Signed-off-by: bwplotka <bwplotka@gmail.com>
Signed-off-by: bwplotka <bwplotka@gmail.com>
…l toolchain

Signed-off-by: bwplotka <bwplotka@gmail.com>
…acement corruptions

Signed-off-by: bwplotka <bwplotka@gmail.com>
…list

Signed-off-by: bwplotka <bwplotka@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant