Skip to content

Commit 118f8b4

Browse files
committed
chore(deps,security): drop archived deps, verify update checksums, redact DSNs
- migrate executors/rabbitmq from streadway/amqp (archived) to rabbitmq/amqp091-go (drop-in fork) - remove direct dependency on mattn/go-sqlite3; consolidate on modernc.org/sqlite with sqlite3 alias for backward compatibility - venom update: download checksums.txt from the GitHub release and verify SHA256 of the binary before applying; refuse update if checksums absent - add RedactURI helper and apply it to SQL, dbfixtures and mongo connection logs to prevent credential leaks via venom.log Signed-off-by: Yvonnick Esnault <yvonnick.esnault@ovhcloud.com>
1 parent 7e803bb commit 118f8b4

14 files changed

Lines changed: 232 additions & 37 deletions

File tree

Makefile

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ $(ALL_RESULTS_TARGETS):
4343
$(info copying $(call get_results_from_target, $@) to $@)
4444
@cp -f $(call get_results_from_target, $@) $@
4545

46-
.PHONY: build lint clean testrun test dist test-xunit package
46+
.PHONY: build lint clean testrun test dist test-xunit package vulncheck checksums
47+
48+
vulncheck: ## scan dependencies for known CVEs (uses golang.org/x/vuln/cmd/govulncheck)
49+
@which govulncheck > /dev/null 2>&1 || go install golang.org/x/vuln/cmd/govulncheck@latest
50+
govulncheck ./...
4751

4852
build: ## build all components and push them into dist directory
4953
$(info Building Component venom)
@@ -59,6 +63,10 @@ plugins: ## build all components and push them into dist directory
5963

6064
dist: $(ALL_DIST_TARGETS)
6165

66+
checksums: dist ## generate checksums.txt with SHA256 of all dist binaries (sha256sum format)
67+
@cd $(DIST_DIR) && sha256sum venom.* > checksums.txt
68+
$(info checksums.txt generated in $(DIST_DIR))
69+
6270
run: ## build binary for current OS only and run it. For development purpose only
6371
OS=${UNAME_LOWERCASE} $(MAKE) build -C cmd/venom
6472
@cmd/venom/dist/venom.${UNAME_LOWERCASE}-amd64

cmd/venom/update/cmd.go

Lines changed: 109 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
package update
22

33
import (
4+
"bufio"
5+
"bytes"
46
"context"
7+
"crypto/sha256"
8+
"crypto/subtle"
9+
"encoding/hex"
510
"fmt"
11+
"io"
612
"net/http"
713
"runtime"
814
"strings"
@@ -17,6 +23,8 @@ import (
1723

1824
var urlGitubReleases = "https://github.com/ovh/venom/releases"
1925

26+
const checksumsAssetName = "checksums.txt"
27+
2028
// Cmd update
2129
var Cmd = &cobra.Command{
2230
Use: "update",
@@ -27,7 +35,9 @@ var Cmd = &cobra.Command{
2735
},
2836
}
2937

30-
func getURLArtifactFromGithub() string {
38+
// releaseAssets returns, for the latest release, the download URL of the
39+
// binary for the current OS/arch and the URL of the checksums.txt file.
40+
func releaseAssets() (binaryURL, checksumsURL, binaryAssetName string) {
3141
client := github.NewClient(nil)
3242
release, resp, err := client.Repositories.GetLatestRelease(context.TODO(), "ovh", "venom")
3343
if err != nil {
@@ -38,21 +48,89 @@ func getURLArtifactFromGithub() string {
3848
cmd.Exit("you already have the latest release: %s", *release.TagName)
3949
}
4050

41-
if len(release.Assets) > 0 {
42-
for _, asset := range release.Assets {
43-
assetName := strings.ReplaceAll(*asset.Name, ".", "-")
44-
current := fmt.Sprintf("venom-%s-%s", runtime.GOOS, runtime.GOARCH)
45-
if assetName == current {
46-
return *asset.BrowserDownloadURL
47-
}
51+
current := fmt.Sprintf("venom-%s-%s", runtime.GOOS, runtime.GOARCH)
52+
for _, asset := range release.Assets {
53+
normalised := strings.ReplaceAll(*asset.Name, ".", "-")
54+
if normalised == current {
55+
binaryURL = *asset.BrowserDownloadURL
56+
binaryAssetName = *asset.Name
57+
}
58+
if *asset.Name == checksumsAssetName {
59+
checksumsURL = *asset.BrowserDownloadURL
4860
}
4961
}
5062

51-
const text = `Invalid Artifacts on latest release. Please try again in few minutes.
63+
if binaryURL == "" {
64+
const text = `Invalid Artifacts on latest release. Please try again in few minutes.
5265
If the problem persists, please open an issue on https://github.com/ovh/venom/issues
5366
`
54-
cmd.Exit(text)
55-
return ""
67+
cmd.Exit(text)
68+
}
69+
return binaryURL, checksumsURL, binaryAssetName
70+
}
71+
72+
// fetchExpectedChecksum downloads checksums.txt from the release and
73+
// returns the expected SHA256 (hex) for assetName. The checksums.txt
74+
// format is the standard `sha256sum` output: "<hex> <filename>" lines.
75+
func fetchExpectedChecksum(checksumsURL, assetName string) (string, error) {
76+
if checksumsURL == "" {
77+
return "", fmt.Errorf("this release does not publish %s; refusing to update for security reasons", checksumsAssetName)
78+
}
79+
resp, err := http.Get(checksumsURL)
80+
if err != nil {
81+
return "", fmt.Errorf("failed to fetch %s: %w", checksumsAssetName, err)
82+
}
83+
defer resp.Body.Close()
84+
if resp.StatusCode != http.StatusOK {
85+
return "", fmt.Errorf("failed to fetch %s: HTTP %d", checksumsAssetName, resp.StatusCode)
86+
}
87+
88+
scanner := bufio.NewScanner(resp.Body)
89+
for scanner.Scan() {
90+
line := strings.TrimSpace(scanner.Text())
91+
if line == "" || strings.HasPrefix(line, "#") {
92+
continue
93+
}
94+
// Accept both "<hex> <name>" (two spaces) and "<hex> <name>"
95+
fields := strings.Fields(line)
96+
if len(fields) != 2 {
97+
continue
98+
}
99+
// The filename in checksums.txt may be a relative path; match on basename.
100+
name := fields[1]
101+
if idx := strings.LastIndexAny(name, "/\\"); idx >= 0 {
102+
name = name[idx+1:]
103+
}
104+
if name == assetName {
105+
return strings.ToLower(fields[0]), nil
106+
}
107+
}
108+
if err := scanner.Err(); err != nil {
109+
return "", fmt.Errorf("error parsing %s: %w", checksumsAssetName, err)
110+
}
111+
return "", fmt.Errorf("no checksum entry for asset %q in %s", assetName, checksumsAssetName)
112+
}
113+
114+
// downloadAndVerify reads the response body fully into memory, verifies its
115+
// SHA256 against expectedHex (constant-time compare), and returns a Reader
116+
// suitable for update.Apply. Memory cost is acceptable: venom binaries are
117+
// only a few tens of MB.
118+
func downloadAndVerify(body io.Reader, expectedHex string) (io.Reader, error) {
119+
hasher := sha256.New()
120+
var buf bytes.Buffer
121+
if _, err := io.Copy(io.MultiWriter(&buf, hasher), body); err != nil {
122+
return nil, fmt.Errorf("failed to download binary: %w", err)
123+
}
124+
got := hex.EncodeToString(hasher.Sum(nil))
125+
expected, err := hex.DecodeString(expectedHex)
126+
if err != nil {
127+
return nil, fmt.Errorf("invalid expected checksum %q: %w", expectedHex, err)
128+
}
129+
gotBytes := hasher.Sum(nil)
130+
if subtle.ConstantTimeCompare(gotBytes, expected) != 1 {
131+
return nil, fmt.Errorf("checksum mismatch: expected %s, got %s", expectedHex, got)
132+
}
133+
return &buf, nil
56134
}
57135

58136
func getContentType(resp *http.Response) string {
@@ -65,27 +143,37 @@ func getContentType(resp *http.Response) string {
65143
}
66144

67145
func doUpdate() {
68-
url := getURLArtifactFromGithub()
69-
fmt.Printf("Url to update venom: %s\n", url)
146+
binaryURL, checksumsURL, assetName := releaseAssets()
147+
fmt.Printf("Url to update venom: %s\n", binaryURL)
70148

71-
resp, err := http.Get(url)
149+
expected, err := fetchExpectedChecksum(checksumsURL, assetName)
72150
if err != nil {
73-
cmd.Exit("Error when downloading venom from url %s: %v\n", url, err)
151+
cmd.Exit("%s\nDownload the binary manually from %s if needed.\n", err.Error(), urlGitubReleases)
74152
}
75153

154+
resp, err := http.Get(binaryURL)
155+
if err != nil {
156+
cmd.Exit("Error when downloading venom from url %s: %v\n", binaryURL, err)
157+
}
158+
defer resp.Body.Close()
159+
76160
if contentType := getContentType(resp); contentType != "application/octet-stream" {
77-
fmt.Printf("Url: %s\n", url)
161+
fmt.Printf("Url: %s\n", binaryURL)
78162
cmd.Exit("Invalid Binary (Content-Type: %s). Please try again or download it manually from %s\n", contentType, urlGitubReleases)
79163
}
80164

81165
if resp.StatusCode != 200 {
82-
cmd.Exit("Error http code: %d, url called: %s\n", resp.StatusCode, url)
166+
cmd.Exit("Error http code: %d, url called: %s\n", resp.StatusCode, binaryURL)
83167
}
84168

85-
fmt.Printf("Getting latest release from: %s ...\n", url)
86-
defer resp.Body.Close()
87-
if err = update.Apply(resp.Body, update.Options{}); err != nil {
88-
cmd.Exit("Error when updating venom from url: %s err:%s\n", url, err.Error())
169+
fmt.Printf("Getting latest release from: %s ...\n", binaryURL)
170+
verified, err := downloadAndVerify(resp.Body, expected)
171+
if err != nil {
172+
cmd.Exit("Error when verifying venom binary from url: %s err:%s\n", binaryURL, err.Error())
173+
}
174+
175+
if err = update.Apply(verified, update.Options{}); err != nil {
176+
cmd.Exit("Error when updating venom from url: %s err:%s\n", binaryURL, err.Error())
89177
}
90178
fmt.Println("Update done.")
91179
}

executors/dbfixtures/dbfixtures.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
// SQL drivers.
1515
_ "github.com/go-sql-driver/mysql"
1616
_ "github.com/lib/pq"
17-
_ "github.com/mattn/go-sqlite3"
17+
_ "modernc.org/sqlite"
1818

1919
"github.com/ovh/venom"
2020
)
@@ -52,10 +52,16 @@ func (e Executor) Run(ctx context.Context, step venom.TestStep) (interface{}, er
5252
if err := mapstructure.Decode(step, &e); err != nil {
5353
return nil, err
5454
}
55+
// Map user-facing database name to the registered Go driver name.
56+
driverName := e.Database
57+
if driverName == "sqlite3" {
58+
driverName = "sqlite" // modernc.org/sqlite registers under "sqlite"
59+
}
60+
5561
// Connect to the database and ping it.
56-
venom.Debug(ctx, "connecting to database %s, %s\n", e.Database, e.DSN)
62+
venom.Debug(ctx, "connecting to database %s, %s\n", e.Database, venom.RedactURI(e.DSN))
5763

58-
db, err := sql.Open(e.Database, e.DSN)
64+
db, err := sql.Open(driverName, e.DSN)
5965
if err != nil {
6066
return nil, errors.Wrapf(err, "failed to connect to database")
6167
}
@@ -194,7 +200,10 @@ func getDialect(name string, skipResetSequences bool) func(*fixtures.Loader) err
194200
}
195201
case "mysql":
196202
return fixtures.Dialect("mysql")
197-
case "sqlite3":
203+
case "sqlite", "sqlite3":
204+
// Both names are accepted: "sqlite" matches the modernc.org/sqlite
205+
// driver name, "sqlite3" is kept for testsuite backward compatibility
206+
// (the testfixtures dialect identifier remains "sqlite3").
198207
return fixtures.Dialect("sqlite3")
199208
}
200209
return nil

executors/imap/imap.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ type Client struct {
275275

276276
type AuthConfig struct {
277277
WithTLS bool `json:"withtls,omitempty" yaml:"withtls,omitempty"`
278-
IgnoreVerifySSL bool `json:"ignore_verify_ssl,omitempty" yaml:"ignore_verify_ssl,omitempty"`
278+
IgnoreVerifySSL bool `json:"ignore_verify_ssl,omitempty" yaml:"ignore_verify_ssl,omitempty" mapstructure:"ignore_verify_ssl"`
279279
Host string `json:"host,omitempty" yaml:"host,omitempty"`
280280
Port string `json:"port,omitempty" yaml:"port,omitempty"`
281281
User string `json:"user,omitempty" yaml:"user,omitempty"`

executors/mongo/mongo.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func (e Executor) Run(ctx context.Context, step venom.TestStep) (any, error) {
4040
return nil, err
4141
}
4242

43-
venom.Debug(ctx, "connecting to database: %s\n", e.URI)
43+
venom.Debug(ctx, "connecting to database: %s\n", venom.RedactURI(e.URI))
4444
mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(e.URI))
4545
if err != nil {
4646
return nil, fmt.Errorf("failed to connect to database: %w", err)

executors/rabbitmq/rabbitmq.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010

1111
"github.com/ovh/venom"
1212

13-
"github.com/streadway/amqp"
13+
amqp "github.com/rabbitmq/amqp091-go"
1414
)
1515

1616
// Name of executor

executors/smtp/smtp.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func New() venom.Executor {
2525
// Executor represents a Test Exec
2626
type Executor struct {
2727
WithTLS bool `json:"withtls,omitempty" yaml:"withtls,omitempty"`
28-
IgnoreVerifySSL bool `json:"ignore_verify_ssl,omitempty" yaml:"ignore_verify_ssl,omitempty"`
28+
IgnoreVerifySSL bool `json:"ignore_verify_ssl,omitempty" yaml:"ignore_verify_ssl,omitempty" mapstructure:"ignore_verify_ssl"`
2929
Host string `json:"host,omitempty" yaml:"host,omitempty"`
3030
Port string `json:"port,omitempty" yaml:"port,omitempty"`
3131
User string `json:"user,omitempty" yaml:"user,omitempty"`

executors/sql/sql.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func (e Executor) Run(ctx context.Context, step venom.TestStep) (interface{}, er
6262
return nil, err
6363
}
6464
// Connect to the database and ping it.
65-
venom.Debug(ctx, "connecting to database %s, %s\n", e.Driver, e.DSN)
65+
venom.Debug(ctx, "connecting to database %s, %s\n", e.Driver, venom.RedactURI(e.DSN))
6666
db, err := sqlx.Connect(e.Driver, e.DSN)
6767
if err != nil {
6868
return nil, errors.Wrapf(err, "failed to connect to database")

executors/ssh/ssh.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ type Executor struct {
3939
PrivateKey string `json:"privatekey,omitempty" yaml:"privatekey,omitempty"`
4040
Sudo string `json:"sudo,omitempty" yaml:"sudo,omitempty"`
4141
SudoPassword string `json:"sudopassword,omitempty" yaml:"sudopassword,omitempty"`
42-
InsecureIgnoreHostKey bool `json:"insecure_ignore_host_key,omitempty" yaml:"insecure_ignore_host_key,omitempty"`
43-
Timeout int `json:"timeout,omitempty" yaml:"timeout,omitempty"` // connection timeout in seconds, default 30
42+
InsecureIgnoreHostKey bool `json:"insecure_ignore_host_key,omitempty" yaml:"insecure_ignore_host_key,omitempty" mapstructure:"insecure_ignore_host_key"`
43+
Timeout int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout"` // connection timeout in seconds, default 30
4444
}
4545

4646
const defaultSSHTimeoutSeconds = 30

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,20 @@ require (
2626
github.com/lib/pq v1.10.9
2727
github.com/linkedin/goavro/v2 v2.12.0
2828
github.com/mattn/go-shellwords v1.0.12
29-
github.com/mattn/go-sqlite3 v2.0.3+incompatible
3029
github.com/mattn/go-zglob v0.0.4
3130
github.com/mitchellh/go-homedir v1.1.0
3231
github.com/mitchellh/mapstructure v1.5.0
3332
github.com/mndrix/tap-go v0.0.0-20171203230836-629fa407e90b
3433
github.com/ovh/go-ovh v1.9.0
3534
github.com/pkg/errors v0.9.1
35+
github.com/rabbitmq/amqp091-go v1.11.0
3636
github.com/rockbears/yaml v0.4.0
3737
github.com/rubenv/sql-migrate v1.5.2
3838
github.com/sijms/go-ora v1.3.2
3939
github.com/sirupsen/logrus v1.9.3
4040
github.com/spf13/cast v1.5.1
4141
github.com/spf13/cobra v1.7.0
4242
github.com/spf13/pflag v1.0.5
43-
github.com/streadway/amqp v1.1.0
4443
github.com/stretchr/testify v1.11.1
4544
github.com/yesnault/go-imap v0.0.0-20160710142244-eb9bbb66bd7b
4645
go.mongodb.org/mongo-driver v1.12.1
@@ -62,6 +61,7 @@ require (
6261
github.com/go-logr/logr v1.4.3 // indirect
6362
github.com/go-logr/stdr v1.2.2 // indirect
6463
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
64+
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
6565
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
6666
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 // indirect
6767
go.opentelemetry.io/otel/metric v1.43.0 // indirect

0 commit comments

Comments
 (0)