Skip to content

Commit 20f59d4

Browse files
committed
ci: enforce the DGXC Go proxy in the verification workflow
Flips the main-only verification workflow from `routed` to `enforced`: GOPROXY carries no `,direct` fallback, so a module Artifactory does not serve is a hard failure rather than a silent fetch from the public internet. No build, release, or PR is affected — the workflow verifies a property and ships nothing. `routed` falls through on 404/410, so a module Artifactory did not carry resolved publicly and the check still went green. We knew nothing was rejected; we did not know everything came from Artifactory. Removing the fallback makes the workflow its own coverage measurement, because with no fallback and a cold cache there is nowhere else a module can come from. That only holds if the job actually fetches, which the first six attempts did not establish. Five ways it could report success without measuring anything were found in review and closed: - A restored 608 MB setup-go cache satisfied `go mod download` with zero fetches. Fixed with cache: false plus an explicit cold-cache assertion. - The `,direct` check was narrower than the property: an `<artifactory>,<other-proxy>` value still falls through on 404. Now requires an exact match on the full expected URL, repo key included, which also closes the -cdn variants that can hand off to another origin. - `go mod download -x` logs the URL Go requested and follows redirects inside http.Client.Do, so a 3xx to another host was invisible to the counters. Added a curl canary measuring the final destination. - That canary then had the same defect one layer down: without a status check, a 401 or 404 reports zero redirects from the Artifactory host. Now requires HTTP 200 first. - `read < <(curl ...)` returns read's status and discards curl's, so a body truncated after a 200 header still passed. Now uses command substitution with an explicit status capture. Declined a network-layer outbound-host allowlist: disproportionate for a workflow that ships nothing, and the canary covers the realistic slice. Verified on a cold cache with a single-entry GOPROXY: 224 module zips served 200 by Artifactory — matching the 224 modules in vendor/modules.txt — across requests that reached no other host, with the regenerated vendor/ byte-identical to what is committed. The file header records that this workflow is temporary and names the three things that migrate when #2374 deletes it: mode: enforced, id-token: write plus the teardown, and the GOPROXY assertion. Everything else exists only because this job builds nothing; a real build fails on its own when the proxy cannot serve it. Closes #2375 Signed-off-by: Mark Chmarny <mark@chmarny.com>
1 parent 32f80f8 commit 20f59d4

1 file changed

Lines changed: 161 additions & 12 deletions

File tree

.github/workflows/dgxc-goproxy-probe.yaml

Lines changed: 161 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,48 @@
2525
# `refs/pull/N/merge` instead and cannot validate the identity mapping the
2626
# exchange actually matches on.
2727
#
28-
# What a green run proves: the OIDC exchange mints a token, GOPROXY is ordered
29-
# with Artifactory first, GOSUMDB verification is intact, and a real module graph
30-
# resolves. What it does NOT prove: coverage. Under `routed` the resolver falls
31-
# through to `direct` on 404/410, so a module Artifactory does not carry is
32-
# fetched from the public internet and the build still goes green. Only
33-
# `enforced` closes that, and only once the graph is known to be served.
28+
# Runs in `enforced` mode: GOPROXY carries no `,direct` fallback, so a module
29+
# Artifactory does not serve is a hard failure rather than a silent fetch from
30+
# the public internet.
31+
#
32+
# That also makes this workflow its own coverage measurement. Under `routed` a
33+
# green run proved only that nothing was *rejected*; a 404 fell through and the
34+
# build still passed, so coverage was unknown. With the fallback gone, a green
35+
# run means every module in the resolved graph came from Artifactory — there is
36+
# nowhere else it could have come from. No separate reporting step is needed:
37+
# the failure mode names the unserved module directly.
38+
#
39+
# Safe to enforce here first because nothing depends on this workflow. It
40+
# verifies a property; it does not build or ship anything. If Artifactory turns
41+
# out not to serve some module, the blast radius is one red check on main.
42+
#
43+
# ---------------------------------------------------------------------------
44+
# THIS FILE IS TEMPORARY. Delete it as part of #2374.
45+
#
46+
# Almost every assertion below exists because this workflow builds nothing:
47+
# `cache: false`, the cold-cache check, the download count, and the archive
48+
# canary all answer "did anything actually happen?" A real build answers that
49+
# by existing — if Artifactory cannot serve a module, `go build` fails. The
50+
# build is the assertion.
51+
#
52+
# When #2374 lands and real jobs resolve through the proxy, carry over only:
53+
#
54+
# - `mode: enforced` on the action
55+
# - `id-token: write` and the credential teardown step
56+
# - the GOPROXY assertion (exact match, single entry)
57+
#
58+
# That last one is the only guard worth keeping, because it catches what a
59+
# build cannot: the action silently no-opping, or a revert to `routed`. Either
60+
# looks perfectly healthy while routing nothing.
61+
#
62+
# Everything else dies with this file. The vendor-equivalence and
63+
# resolver-posture steps in particular exist only because the repo vendors,
64+
# which is the thing #2374 removes.
65+
#
66+
# If a proxy canary is still wanted afterwards, write a new ~20-line
67+
# dispatch-only one. Its job is separating "the proxy is broken" from "the code
68+
# is broken" when a build fails — an ops tool, not this measurement rig.
69+
# ---------------------------------------------------------------------------
3470
name: DGXC Go Proxy Probe
3571

3672
on:
@@ -87,14 +123,25 @@ jobs:
87123
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
88124
with:
89125
go-version-file: go.mod
126+
# Required for this workflow to measure anything. setup-go caches
127+
# GOMODCACHE and GOCACHE by default, and the proxy action sets
128+
# GOFLAGS=-mod=readonly so Go reads the module cache rather than
129+
# vendor/. A cache restored from an earlier run therefore satisfies
130+
# `go mod download` without contacting Artifactory at all, and the
131+
# job goes green having proven nothing.
132+
#
133+
# This is not hypothetical: the first enforced run here hit a 608 MB
134+
# cache and emitted zero `go: downloading` lines. Every other job in
135+
# the repo should keep caching; this one exists to force real fetches.
136+
cache: false
90137

91138
# No tooling is installed in this job, so rule 4 ("install tooling before
92139
# configuring the proxy") is satisfied trivially. That is not true of
93140
# go-test, which is why wiring it is a separate, later step.
94141
- name: Configure the DGXC Go proxy
95142
uses: ./.github/actions/setup-dgxc-goproxy
96143
with:
97-
mode: routed
144+
mode: enforced
98145

99146
# Non-optional. GOPROXY is an ordered list and Go consults later entries
100147
# only on 404/410, so `proxy.golang.org,<artifactory>,direct` looks routed,
@@ -109,12 +156,94 @@ jobs:
109156
case "${proxy}" in
110157
*"|"*) echo "GOPROXY uses '|', which falls through on 401/403. Use ','."; exit 1 ;;
111158
esac
112-
case "${proxy%%,*}" in
159+
# Exact match, not a host prefix. The `-cdn` repository variants are
160+
# the one documented path on which Artifactory hands a client off to
161+
# another origin (Direct Download), so pinning the repo key and not
162+
# just the host closes it. Upstream F-009 records that Direct
163+
# Download never engages for Go anyway — no module archive
164+
# approaches its 100 MB threshold — but the assertion costs nothing.
165+
expected="https://edge.urm.nvidia.com/artifactory/api/go/dgxc-go-virtual"
166+
[ "${proxy}" = "${expected}" ] \
167+
|| { echo "GOPROXY is '${proxy}', expected exactly '${expected}'"; exit 1; }
168+
[ "$(go env GOSUMDB)" != off ] || { echo "GOSUMDB=off"; exit 1; }
169+
# Enforced means no fallback at all. Reject any comma rather than
170+
# just `,direct`: Go consults later entries on 404/410 whatever they
171+
# are, so `<artifactory>,https://proxy.golang.org` falls through to
172+
# the public internet exactly like `,direct` would.
173+
case "${proxy}" in
174+
*,*) echo "GOPROXY has more than one entry ('${proxy}'); this is not enforced"; exit 1 ;;
175+
esac
176+
echo "OK: Artifactory is the only GOPROXY entry and GOSUMDB is intact"
177+
178+
# `go mod download -x` traces the URL Go *requested*, and http.Client.Do
179+
# follows redirects inside that call, logging the final status against
180+
# the original URL. So the download counters below cannot see a 3xx that
181+
# hands the transfer to another origin: `served` would increment and
182+
# `other` stay zero while the bytes came from elsewhere.
183+
#
184+
# Redirect behavior is a repository-level setting rather than per-module,
185+
# so one request is a representative canary. The credential comes from
186+
# the GOAUTH helper via curl --config so the token never reaches argv,
187+
# where a process listing would expose it.
188+
- name: Assert Artifactory serves bytes rather than redirecting
189+
shell: bash
190+
run: |
191+
url="https://edge.urm.nvidia.com/artifactory/api/go/dgxc-go-virtual/gopkg.in/yaml.v2/@v/v2.4.0.zip"
192+
auth="$("${GOAUTH}" | grep -i '^Authorization:')"
193+
[ -n "${auth}" ] || { echo "::error::GOAUTH helper produced no Authorization header"; exit 1; }
194+
195+
# Command substitution with an explicit status capture, not process
196+
# substitution. `read < <(...)` returns read's own status and
197+
# discards curl's, so a transfer that fails *after* the response
198+
# headers — a truncated body, exit 18 or 56 — still emits
199+
# `%{http_code} 200` and every assertion below passes on a fetch
200+
# that did not complete. Verified: a simulated exit 56 after output
201+
# is silently accepted under process substitution and caught here.
202+
#
203+
# `|| rc=$?` keeps the assignment from tripping `set -e` before the
204+
# status can be inspected, and the here-string below supplies the
205+
# trailing newline `read` needs to avoid returning non-zero at EOF.
206+
rc=0
207+
out="$(
208+
printf 'header = "%s"\nurl = "%s"\n' "${auth}" "${url}" \
209+
| curl --config - -sSL --max-time 60 -o /dev/null \
210+
-w '%{http_code} %{num_redirects} %{url_effective}'
211+
)" || rc=$?
212+
[ "${rc}" -eq 0 ] \
213+
|| { echo "::error::curl exited ${rc}; the transfer did not complete"; exit 1; }
214+
read -r status redirects effective <<<"${out}"
215+
echo "status: ${status}"
216+
echo "redirects: ${redirects}"
217+
echo "final URL host: $(printf '%s' "${effective}" | sed -E 's#^https?://([^/]+)/.*#\1#')"
218+
219+
# Status first. Without it a 401 or 404 also reports zero redirects
220+
# from the Artifactory host, so the two checks below would pass on a
221+
# response that carried no archive at all — the same shape of false
222+
# pass this canary was added to close.
223+
[ "${status}" = "200" ] \
224+
|| { echo "::error::archive probe returned HTTP ${status}, not 200; nothing was served"; exit 1; }
225+
[ "${redirects}" -eq 0 ] \
226+
|| { echo "::error::Artifactory redirected ${redirects} time(s) to ${effective}"; exit 1; }
227+
case "${effective}" in
113228
https://edge.urm.nvidia.com/*) ;;
114-
*) echo "first GOPROXY entry is '${proxy%%,*}', not Artifactory"; exit 1 ;;
229+
*) echo "::error::bytes came from ${effective}, not Artifactory"; exit 1 ;;
115230
esac
116-
[ "$(go env GOSUMDB)" != off ] || { echo "GOSUMDB=off"; exit 1; }
117-
echo "OK: Artifactory is first in GOPROXY and GOSUMDB is intact"
231+
echo "OK: Artifactory served the archive directly (HTTP 200, no redirect to another origin)"
232+
233+
# Guards the guard. Both assertions above are satisfied by a job that
234+
# never fetches anything, which is how the first enforced run passed on a
235+
# restored module cache. Coverage is only demonstrated if modules were
236+
# actually pulled through Artifactory.
237+
- name: Assert the module cache is cold
238+
shell: bash
239+
run: |
240+
modcache="$(go env GOMODCACHE)"
241+
if [ -d "${modcache}/cache/download" ] \
242+
&& [ -n "$(find "${modcache}/cache/download" -name '*.zip' -print -quit 2>/dev/null)" ]; then
243+
echo "::error::module cache is already populated at ${modcache}; this run cannot demonstrate proxy coverage"
244+
exit 1
245+
fi
246+
echo "OK: module cache is cold, so anything resolved below came over the wire"
118247
119248
# The real work. `go mod download` resolves from go.mod/go.sum rather
120249
# than reading vendor/, so it is a genuine application-dependency fetch
@@ -132,7 +261,27 @@ jobs:
132261
# needs.
133262
- name: Resolve the module graph through the proxy
134263
shell: bash
135-
run: go mod download
264+
run: |
265+
go mod download -x 2>&1 | tee /tmp/godownload.log
266+
267+
# `-x` traces every request as `# get <url>` and its response as
268+
# `# get <url>: <status>`. With a cold cache and a single-entry
269+
# GOPROXY, counting module zips Artifactory served 200 *is* the
270+
# coverage measurement — there is no other source they could have
271+
# come from.
272+
served="$(grep -cE '# get https://edge\.urm\.nvidia\.com/.*\.zip: 200 OK' /tmp/godownload.log || true)"
273+
other="$(grep -oE '# get https://[a-z0-9.-]+/' /tmp/godownload.log \
274+
| grep -vc 'edge\.urm\.nvidia\.com' || true)"
275+
echo "module zips served by Artifactory: ${served}"
276+
echo "requests to other hosts: ${other}"
277+
278+
# Zero fetches is the signature of the false pass this workflow
279+
# exists to prevent: every assertion above is satisfied by a job
280+
# that never contacted anything.
281+
[ "${served}" -gt 0 ] \
282+
|| { echo "::error::no module zips were fetched; this run proves nothing about coverage"; exit 1; }
283+
[ "${other}" -eq 0 ] \
284+
|| { echo "::error::${other} request(s) went to a host other than Artifactory"; exit 1; }
136285
137286
# The action exports GOFLAGS=-mod=readonly to GITHUB_ENV, which is
138287
# job-wide and cannot be scoped to one step. This repo commits vendor/,

0 commit comments

Comments
 (0)