fix: harden healthcheck and distroless lanes per adversarial review

Findings from an adversarial review (Codex, gpt-5.6-sol at max effort)
of 2ff594f4b and 4c34d2309, 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:
Feng Ruohang
2026-08-06 17:27:22 +08:00
parent 4c34d23099
commit b6d47b739c
7 changed files with 68 additions and 36 deletions
+9 -7
View File
@@ -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)
+3
View File
@@ -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"},