Skip boringcrypto when Go's native FIPS module is selected

What does this MR do?

Guards the Makefile's boringcrypto activation under FIPS_MODE=1 so it is skipped when the Go toolchain selects Go's native FIPS 140-3 module (the Go Cryptographic Module, CMVP certificate 5247) via GOFIPS140.

Root cause

The existing probe is too weak:

BORINGCRYPTO_SUPPORT := $(shell GOEXPERIMENT=boringcrypto go version > /dev/null 2>&1; echo $$?)

go version never initializes the build or FIPS configuration, so the probe succeeds on any modern toolchain — including toolchains that enable the native module through GOFIPS140 in go.env. The Makefile then exports GOEXPERIMENT=boringcrypto, and the real build fails:

go: cannot use GOFIPS140 with GOEXPERIMENT=boringcrypto

This breaks CNG FIPS image builds since gitlab-org/build/CNG!3010 (merged) moved to upstream Go with GOFIPS140=v1.0.0 baked into the toolchain's go.env. Failed job (at Makefile:111: bin/gitlab-shell): https://gitlab.com/gitlab-org/build/CNG/-/jobs/15232583916

The fix

Consult go env GOFIPS140 before activating boringcrypto, and skip the probe + export when it reports anything other than empty or off.

Why empty or off, not just empty: golang-fips 1.24+ toolchains (still used by omnibus-gitlab FIPS builds) report off, never empty. A strictly-empty check would silently drop the OpenSSL backend from Omnibus FIPS builds. Distinguishing on off keeps both toolchain families working:

Toolchain go env GOFIPS140 Result
Upstream Go + GOFIPS140=v1.0.0 (CNG) v1.0.0 boringcrypto skipped
golang-fips 1.24+ (Omnibus) off boringcrypto exported (unchanged)
Go < 1.24 / golang-fips < 1.24 empty boringcrypto exported (unchanged)

Explicit non-goals

GO_TAGS += fips and export CGO_ENABLED=1 are intentionally untouched. CGO under the native FIPS module is no longer mandated by the toolchain, but CGO also enables the gssapi build tag in this Makefile — whether gitlab-shell wants to keep requiring CGO/gssapi in FIPS mode is gitlab-shell's own decision and out of scope here. Maintainers can follow up separately if desired.

gitlab-shell selects SSH algorithms via labkit's fips.Enabled(), which currently relies on crypto/boring and needs a parallel labkit change to report the native module (crypto/fips140). See https://gitlab.com/gitlab-org/distribution/team-tasks/-/work_items/1706#note_3538243712. Until that lands, a FIPS_MODE=1 build on a native-FIPS toolchain fails compiling gitlab.com/gitlab-org/labkit/fips (see testing below); this MR fixes the Makefile half of the problem.

Work item: https://gitlab.com/gitlab-org/distribution/team-tasks/-/work_items/1706

Empirical testing

All on macOS arm64, Go 1.25.9 (pinned by .tool-versions, via mise). Simulation basis: GOFIPS140=v1.0.0 go env GOFIPS140 → v1.0.0 (native toolchain, as CNG bakes into go.env); unset → off, which is exactly what a golang-fips 1.24+ toolchain reports.

Pre-patch failure reproduction (native sim):

$ GOFIPS140=v1.0.0 FIPS_MODE=1 make build     # on main, pre-patch
go build ... -tags "... fips gssapi" -mod=mod -o .../bin ./cmd/...
go: cannot use GOFIPS140 with GOEXPERIMENT=boringcrypto
make: *** [bin/gitlab-shell] Error 1

Post-patch, native sim — GOEXPERIMENT no longer exported:

$ GOFIPS140=v1.0.0 FIPS_MODE=1 make -np build | grep -E '^(export )?GOEXPERIMENT'
(no output)

Full build succeeds once labkit's fips package compiles under the native module (tested with a temporary local replace pointing at a labkit copy patched crypto/boring → crypto/fips140, matching the pending labkit change; the replace is not part of this MR):

$ GOFIPS140=v1.0.0 FIPS_MODE=1 make build
$ go version -m bin/gitlab-shell | grep -E 'GOEXPERIMENT|GOFIPS'
	build	GOFIPS140=v1.0.0-c2097c7c
$ go version -m bin/gitlab-sshd | grep -E 'GOEXPERIMENT|GOFIPS'
	build	GOFIPS140=v1.0.0-c2097c7c

GOFIPS140=v1.0.0 recorded, no GOEXPERIMENT line.

Post-patch, legacy sim (GOFIPS140 unset → off) — identical to pre-patch:

$ FIPS_MODE=1 make -np build | grep -E '^(export )?GOEXPERIMENT'   # pre- and post-patch output diffed: identical
GOEXPERIMENT = boringcrypto

$ FIPS_MODE=1 make build            # stock go.mod, no replace
$ go version -m bin/gitlab-shell | grep -E 'GOEXPERIMENT|GOFIPS'
	build	GOEXPERIMENT=boringcrypto

(On darwin the boring backend doesn't activate at runtime; the exported variable and recorded build setting are what matter.)

No-FIPS regression check:

$ make build      # OK; binary has no GOFIPS140/GOEXPERIMENT settings
$ make verify     # OK
$ make test       # OK, all packages pass

🤖 Generated with Claude Code

Related to #865 (closed)

Related https://gitlab.com/gitlab-org/release/tasks/-/work_items/28133+s

Edited by Robert Marshall

Merge request reports

Loading
Loading