mirror of
https://github.com/pgsty/minio.git
synced 2026-08-09 15:53:28 +03:00
fix: harden healthcheck and distroless lanes per adversarial review
Findings from an adversarial review (Codex, gpt-5.6-sol at max effort) of2ff594f4band4c34d2309, each independently verified before fixing: - SBOM generation: buildx attaches a provenance attestation, so every per-arch digest names an OCI index; Syft's platform default on an amd64 runner cannot resolve an arm64-only index and the step dies. Pass --platform explicitly on all four Syft calls (the two classic lanes had the same latent defect - the renamed workflow has not run yet, which is why it never fired). - Release ordering: the HEALTHCHECK survival check now runs against the pushed architecture image before the versioned and rolling multi-arch manifests are created, so a broken health config blocks their promotion; the comment now states honestly that the arch-suffixed tags are already public at that point. - Gate assertions: tar's member-argument mode exits non-zero on any missing name, which under pipefail masked a found forbidden file when exactly one of them existed; -tv prints symlinks as 'name -> target', defeating $-anchored greps; and the licenses check proved only one-of-three. Export the rootfs once and assert every required and forbidden entry individually (busybox/sh and usr/bin/mc[li] now covered), and match the image healthcheck as an exact array instead of a substring. - Probe target vs CLI-configured servers: a probe process cannot see PID 1's argv, so --url gains EnvVar MINIO_HEALTHCHECK_URL as the documented way to point the baked-in HEALTHCHECK at a server whose address/TLS comes from command-line arguments (verified end to end: server on --address :9010, env var alone turns the container healthy). Baseline regenerated for the new env token. - IPv6 zone identifiers: serialize probe URLs via url.URL.String() so [fe80::1%eth0]:9000 becomes a valid %25-escaped URL (tests added). - Boolean flags: read --json/--quiet via Bool() so --json=false is false, instead of IsSet() which treats any occurrence as true. - Docker's HEALTHCHECK timeout raised to 10s: an outer deadline equal to the probe's own 5s always SIGKILLed the probe before it could print its diagnostic line. - test-release path filter now also triggers on cmd/healthcheck-main.go and cmd/main.go, so subcommand regressions run the image gate. Not adopted: require_text's comment-insensitivity in verify-rebrand.sh (snapshot-tripwire by design, consistent with its other assertions - the semantic check lives in the CI gate now), and full staging-then-promote tag publishing (a workflow-wide redesign shared with the classic lanes, tracked as follow-up). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -61,8 +61,9 @@ var healthcheckFlags = []cli.Flag{
|
||||
EnvVar: "MINIO_ADDRESS",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "url",
|
||||
Usage: "probe this base URL (http[s]://HOST:PORT) instead of deriving one from --address and the certs directory",
|
||||
Name: "url",
|
||||
Usage: "probe this base URL (http[s]://HOST:PORT) instead of deriving one from --address and the certs directory",
|
||||
EnvVar: "MINIO_HEALTHCHECK_URL",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "maintenance",
|
||||
@@ -152,7 +153,8 @@ func (r healthcheckResult) line() string {
|
||||
|
||||
// healthcheckTarget derives the base URL to probe. An explicit rawURL wins;
|
||||
// otherwise the address' host:port is used, with the scheme decided by the
|
||||
// same certificate presence check the server performs at startup.
|
||||
// same certificate presence check the server performs at startup. URLs are
|
||||
// serialized via url.URL so IPv6 zone identifiers survive as %25-escapes.
|
||||
func healthcheckTarget(rawURL, address, certsDir string) (string, error) {
|
||||
if rawURL != "" {
|
||||
u, err := url.Parse(rawURL)
|
||||
@@ -162,7 +164,7 @@ func healthcheckTarget(rawURL, address, certsDir string) (string, error) {
|
||||
if (u.Scheme != "http" && u.Scheme != "https") || u.Host == "" {
|
||||
return "", fmt.Errorf("invalid --url %q: expected http[s]://HOST:PORT", rawURL)
|
||||
}
|
||||
return u.Scheme + "://" + u.Host, nil
|
||||
return (&url.URL{Scheme: u.Scheme, Host: u.Host}).String(), nil
|
||||
}
|
||||
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
@@ -176,7 +178,7 @@ func healthcheckTarget(rawURL, address, certsDir string) (string, error) {
|
||||
if isFile(filepath.Join(certsDir, publicCertFile)) && isFile(filepath.Join(certsDir, privateKeyFile)) {
|
||||
scheme = "https"
|
||||
}
|
||||
return scheme + "://" + net.JoinHostPort(host, port), nil
|
||||
return (&url.URL{Scheme: scheme, Host: net.JoinHostPort(host, port)}).String(), nil
|
||||
}
|
||||
|
||||
// probeHealth performs one bounded, strictly anonymous GET against the
|
||||
@@ -282,8 +284,8 @@ func healthcheckMain(ctx *cli.Context) {
|
||||
|
||||
res := probeHealth(baseURL, check, ctx.Bool("maintenance"), timeout)
|
||||
|
||||
quiet := ctx.IsSet("quiet") || ctx.GlobalIsSet("quiet")
|
||||
if ctx.IsSet("json") || ctx.GlobalIsSet("json") {
|
||||
quiet := ctx.Bool("quiet") || ctx.GlobalBool("quiet")
|
||||
if ctx.Bool("json") || ctx.GlobalBool("json") {
|
||||
buf, jerr := json.Marshal(res)
|
||||
if jerr != nil {
|
||||
fail("%v", jerr)
|
||||
|
||||
@@ -54,6 +54,9 @@ func TestHealthcheckTarget(t *testing.T) {
|
||||
}{
|
||||
{name: "default address", address: ":9000", certsDir: plainDir, want: "http://127.0.0.1:9000"},
|
||||
{name: "explicit host", address: "10.0.0.7:9010", certsDir: plainDir, want: "http://10.0.0.7:9010"},
|
||||
{name: "ipv6 address", address: "[::1]:9000", certsDir: plainDir, want: "http://[::1]:9000"},
|
||||
{name: "ipv6 zone is escaped", address: "[fe80::1%eth0]:9000", certsDir: plainDir, want: "http://[fe80::1%25eth0]:9000"},
|
||||
{name: "ipv6 zone in url", rawURL: "http://[fe80::1%25eth0]:9000", certsDir: plainDir, want: "http://[fe80::1%25eth0]:9000"},
|
||||
{name: "tls certs present", address: ":9000", certsDir: tlsDir, want: "https://127.0.0.1:9000"},
|
||||
{name: "cert without key stays http", address: ":9000", certsDir: halfDir, want: "http://127.0.0.1:9000"},
|
||||
{name: "url override wins", rawURL: "https://silo.internal:9000", address: ":9000", certsDir: plainDir, want: "https://silo.internal:9000"},
|
||||
|
||||
Reference in New Issue
Block a user