Skip to content

Commit 5c4ec2e

Browse files
authored
Merge pull request #3185 from Nordix/lentzi90/govulncheck-patch
🌱 Patch govulncheck to support ignoring findings
2 parents b48a75c + 25d093e commit 5c4ec2e

3 files changed

Lines changed: 226 additions & 10 deletions

File tree

Makefile

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,10 @@ GOLANGCI_LINT_VER = $(shell cd hack/tools && go list -m -f '{{.Version}}' github
6262
GOLANGCI_LINT_PKG := github.com/golangci/golangci-lint/v2/cmd/golangci-lint
6363

6464
# govulncheck
65-
GOVULNCHECK_VER := v1.1.4
65+
GOVULNCHECK_VER := v1.3.0
6666
GOVULNCHECK_BIN := govulncheck
67-
GOVULNCHECK_PKG := golang.org/x/vuln/cmd/govulncheck
67+
GOVULNCHECK_DIR := hack/tools/govulncheck
68+
GOVULNCHECK_TMP_DIR ?= $(GOVULNCHECK_DIR)/govulncheck.tmp
6869

6970
TRIVY_VER := 0.69.3
7071

@@ -88,7 +89,7 @@ RELEASE_NOTES := $(TOOLS_BIN_DIR)/release-notes
8889
SETUP_ENVTEST := $(TOOLS_BIN_DIR)/setup-envtest
8990
GEN_CRD_API_REFERENCE_DOCS := $(TOOLS_BIN_DIR)/gen-crd-api-reference-docs
9091
GO_APIDIFF := $(TOOLS_BIN_DIR)/$(GO_APIDIFF_BIN)-$(GO_APIDIFF_VER)
91-
GOVULNCHECK := $(TOOLS_BIN_DIR)/$(GOVULNCHECK_BIN)-$(GOVULNCHECK_VER)
92+
GOVULNCHECK := $(abspath $(TOOLS_BIN_DIR)/$(GOVULNCHECK_BIN))
9293

9394
# Kubebuilder
9495
export KUBEBUILDER_ENVTEST_KUBERNETES_VERSION ?= 1.28.0
@@ -283,8 +284,26 @@ $(GO_APIDIFF): # Build go-apidiff.
283284
.PHONY: $(GOVULNCHECK_BIN)
284285
$(GOVULNCHECK_BIN): $(GOVULNCHECK) ## Build a local copy of govulncheck.
285286

286-
$(GOVULNCHECK): # Build govulncheck.
287-
GOBIN=$(abspath $(TOOLS_BIN_DIR)) $(GO_INSTALL) $(GOVULNCHECK_PKG) $(GOVULNCHECK_BIN) $(GOVULNCHECK_VER)
287+
$(GOVULNCHECK): # Build govulncheck from source with exclusion patch.
288+
@if [ -d "$(GOVULNCHECK_TMP_DIR)" ]; then \
289+
echo "$(GOVULNCHECK_TMP_DIR) exists, skipping clone"; \
290+
else \
291+
git clone "https://github.com/golang/vuln.git" "$(GOVULNCHECK_TMP_DIR)"; \
292+
cd "$(GOVULNCHECK_TMP_DIR)"; \
293+
git checkout "$(GOVULNCHECK_VER)"; \
294+
git apply "$(REPO_ROOT)/$(GOVULNCHECK_DIR)/govulncheck.patch"; \
295+
fi
296+
@cd "$(REPO_ROOT)/$(GOVULNCHECK_TMP_DIR)"; \
297+
if [ "$$(git describe --tag 2> /dev/null)" != "$(GOVULNCHECK_VER)" ]; then \
298+
echo "ERROR: checked out version does not match expected version $(GOVULNCHECK_VER)"; \
299+
exit 1; \
300+
fi
301+
@rm -f $(GOVULNCHECK)
302+
go build -C "$(REPO_ROOT)/$(GOVULNCHECK_TMP_DIR)" -o $(GOVULNCHECK) ./cmd/govulncheck
303+
304+
.PHONY: clean-govulncheck
305+
clean-govulncheck:
306+
rm -fr "$(GOVULNCHECK_TMP_DIR)"
288307

289308
.PHONY: $(GOLANGCI_LINT_BIN)
290309
$(GOLANGCI_LINT_BIN): $(GOLANGCI_LINT) ## Build a local copy of golangci-lint.
@@ -715,11 +734,7 @@ verify-container-images: ## Verify container images
715734

716735
.PHONY: verify-govulncheck
717736
verify-govulncheck: $(GOVULNCHECK) ## Verify code for vulnerabilities
718-
$(GOVULNCHECK) $(GOVULNCHECK_ARGS) ./... && R1=$$? || R1=$$?; \
719-
$(GOVULNCHECK) $(GOVULNCHECK_ARGS) -C "$(TOOLS_DIR)" ./... && R2=$$? || R2=$$?; \
720-
if [ "$$R1" -ne "0" ] || [ "$$R2" -ne "0" ]; then \
721-
exit 1; \
722-
fi
737+
$(GOVULNCHECK) $(GOVULNCHECK_ARGS) ./...
723738

724739
.PHONY: verify-security
725740
verify-security: ## Verify code and images for vulnerabilities

hack/tools/govulncheck/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
govulncheck.tmp/
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
diff --git a/README.md b/README.md
2+
index 0af935a..8db6157 100644
3+
--- a/README.md
4+
+++ b/README.md
5+
@@ -1,5 +1,23 @@
6+
# Go Vulnerability Management
7+
8+
+This is a fork of the govulncheck tool which adds the ability to exclude specific
9+
+vulnerebalities.
10+
+
11+
+It is configured through the `.govuln_exclude` file by default which takes the following format
12+
+```
13+
+# Some comment about why the vulnerability is excluded
14+
+GO-2026-4880
15+
+```
16+
+The file should contain the ID of 1 excluded vulnerability per line.
17+
+Only lines starting with `GO-` are considered vulnerability identifiers.
18+
+Any other lines will be discarded.
19+
+
20+
+If the file is missing or parsing otherwise fails, an empty excluded list will be used.
21+
+
22+
+This should make the tool a drop-in replacement for govulncheck.
23+
+
24+
+
25+
+## Original README
26+
[![Go Reference](https://pkg.go.dev/badge/golang.org/x/vuln.svg)](https://pkg.go.dev/golang.org/x/vuln)
27+
28+
Go's support for vulnerability management includes tooling for analyzing your
29+
diff --git a/internal/govulncheck/govulncheck.go b/internal/govulncheck/govulncheck.go
30+
index 377a378..aca9f41 100644
31+
--- a/internal/govulncheck/govulncheck.go
32+
+++ b/internal/govulncheck/govulncheck.go
33+
@@ -84,6 +84,10 @@ type Config struct {
34+
// what to do with it. Valid values are source, binary, query,
35+
// and extract.
36+
ScanMode ScanMode `json:"scan_mode,omitempty"`
37+
+
38+
+ // ExcludedVulnerabilites contains a set of go vuln identifiers that will be
39+
+ // ignored if they are present in the output
40+
+ ExcludedVulnerabilites map[string]struct{} `json:"excluded_vulnerabilities,omitempty"`
41+
}
42+
43+
// SBOM contains minimal information about the artifacts govulncheck is scanning.
44+
diff --git a/internal/govulncheck/jsonhandler.go b/internal/govulncheck/jsonhandler.go
45+
index b1586e0..51c2f02 100644
46+
--- a/internal/govulncheck/jsonhandler.go
47+
+++ b/internal/govulncheck/jsonhandler.go
48+
@@ -13,6 +13,7 @@ import (
49+
)
50+
51+
type jsonHandler struct {
52+
+ cfg *Config
53+
enc *json.Encoder
54+
}
55+
56+
@@ -25,6 +26,7 @@ func NewJSONHandler(w io.Writer) Handler {
57+
58+
// Config writes config block in JSON to the underlying writer.
59+
func (h *jsonHandler) Config(config *Config) error {
60+
+ h.cfg = config
61+
return h.enc.Encode(Message{Config: config})
62+
}
63+
64+
@@ -45,5 +47,9 @@ func (h *jsonHandler) OSV(entry *osv.Entry) error {
65+
66+
// Finding writes a finding in JSON to the underlying writer.
67+
func (h *jsonHandler) Finding(finding *Finding) error {
68+
+ // Return early if the finding is excluded
69+
+ if _, ok := h.cfg.ExcludedVulnerabilites[finding.OSV]; ok {
70+
+ return nil
71+
+ }
72+
return h.enc.Encode(Message{Finding: finding})
73+
}
74+
diff --git a/internal/openvex/handler.go b/internal/openvex/handler.go
75+
index a2d3282..5a5e80b 100644
76+
--- a/internal/openvex/handler.go
77+
+++ b/internal/openvex/handler.go
78+
@@ -109,6 +109,10 @@ func moreSpecific(f1, f2 *govulncheck.Finding) int {
79+
}
80+
81+
func (h *handler) Finding(f *govulncheck.Finding) error {
82+
+ // Return early if the finding is ignored
83+
+ if _, ok := h.cfg.ExcludedVulnerabilites[f.OSV]; ok {
84+
+ return nil
85+
+ }
86+
fs := h.findings[f.OSV]
87+
if len(fs) == 0 {
88+
fs = []*govulncheck.Finding{f}
89+
diff --git a/internal/sarif/handler.go b/internal/sarif/handler.go
90+
index d9e585b..b2d0dcd 100644
91+
--- a/internal/sarif/handler.go
92+
+++ b/internal/sarif/handler.go
93+
@@ -87,6 +87,10 @@ func moreSpecific(f1, f2 *govulncheck.Finding) int {
94+
}
95+
96+
func (h *handler) Finding(f *govulncheck.Finding) error {
97+
+ // Return early if the OSV is excluded
98+
+ if _, ok := h.cfg.ExcludedVulnerabilites[f.OSV]; ok {
99+
+ return nil
100+
+ }
101+
fs := h.findings[f.OSV]
102+
if len(fs) == 0 {
103+
fs = []*govulncheck.Finding{f}
104+
diff --git a/internal/scan/flags.go b/internal/scan/flags.go
105+
index e67c0a1..5c1871f 100644
106+
--- a/internal/scan/flags.go
107+
+++ b/internal/scan/flags.go
108+
@@ -27,6 +27,7 @@ type config struct {
109+
format FormatFlag
110+
version bool
111+
env []string
112+
+ exclude string
113+
}
114+
115+
func parseFlags(cfg *config, stderr io.Writer, args []string) error {
116+
@@ -46,6 +47,7 @@ func parseFlags(cfg *config, stderr io.Writer, args []string) error {
117+
flags.Var(&cfg.format, "format", "specify format output\nThe supported values are 'text', 'json', 'sarif', and 'openvex' (default 'text')")
118+
flags.BoolVar(&version, "version", false, "print the version information")
119+
flags.Var(&scanFlag, "scan", "set the scanning level desired, one of 'module', 'package', or 'symbol' (default 'symbol')")
120+
+ flags.StringVar(&cfg.exclude, "exclude", ".govuln_exclude", "path to a file containing vulnerabilities to exclude (default '.govuln_exclude')")
121+
122+
// We don't want to print the whole usage message on each flags
123+
// error, so we set to a no-op and do the printing ourselves.
124+
diff --git a/internal/scan/run.go b/internal/scan/run.go
125+
index 5f6a641..1ae1e0e 100644
126+
--- a/internal/scan/run.go
127+
+++ b/internal/scan/run.go
128+
@@ -5,9 +5,11 @@
129+
package scan
130+
131+
import (
132+
+ "bufio"
133+
"context"
134+
"fmt"
135+
"io"
136+
+ "os"
137+
"os/exec"
138+
"path"
139+
"path/filepath"
140+
@@ -104,6 +106,30 @@ func prepareConfig(ctx context.Context, cfg *config, client *client.Client) {
141+
if mod, err := client.LastModifiedTime(ctx); err == nil {
142+
cfg.DBLastModified = &mod
143+
}
144+
+
145+
+ cfg.ExcludedVulnerabilites = excludedVulns(cfg.exclude)
146+
+}
147+
+
148+
+// excludedVulns loads a list of vulnerability IDs to ignore from the given filepath
149+
+// If the file is not present, or cannot be parsed, an empty set will be returned
150+
+func excludedVulns(file string) map[string]struct{} {
151+
+ output := make(map[string]struct{})
152+
+ fd, err := os.Open(file)
153+
+ if err != nil {
154+
+ return output
155+
+ }
156+
+ scanner := bufio.NewScanner(fd)
157+
+ for scanner.Scan() {
158+
+ line := scanner.Text()
159+
+ line = strings.TrimSpace(line)
160+
+ if strings.HasPrefix(line, "GO-") {
161+
+ output[line] = struct{}{}
162+
+ }
163+
+ }
164+
+ if scanner.Err() != nil {
165+
+ return make(map[string]struct{})
166+
+ }
167+
+ return output
168+
}
169+
170+
// scannerVersion reconstructs the current version of
171+
diff --git a/internal/scan/text.go b/internal/scan/text.go
172+
index ab49bdd..6eb6270 100644
173+
--- a/internal/scan/text.go
174+
+++ b/internal/scan/text.go
175+
@@ -40,6 +40,7 @@ type TextHandler struct {
176+
findings []*findingSummary
177+
scanLevel govulncheck.ScanLevel
178+
scanMode govulncheck.ScanMode
179+
+ excluded map[string]struct{}
180+
181+
err error
182+
183+
@@ -91,6 +92,7 @@ func (h *TextHandler) Flush() error {
184+
func (h *TextHandler) Config(config *govulncheck.Config) error {
185+
h.scanLevel = config.ScanLevel
186+
h.scanMode = config.ScanMode
187+
+ h.excluded = config.ExcludedVulnerabilites
188+
189+
if !h.showVersion {
190+
return nil
191+
@@ -182,6 +184,9 @@ func (h *TextHandler) OSV(entry *osv.Entry) error {
192+
193+
// Finding gathers vulnerability findings to be written.
194+
func (h *TextHandler) Finding(finding *govulncheck.Finding) error {
195+
+ if _, ok := h.excluded[finding.OSV]; ok {
196+
+ return nil
197+
+ }
198+
if err := validateFindings(finding); err != nil {
199+
return err
200+
}

0 commit comments

Comments
 (0)