Skip boringcrypto when Go provides the native FIPS module

What does this MR do?

Guards the FIPS_MODE=1 boringcrypto activation in Makefile.build.mk so it is skipped when the Go toolchain provides the native Go Cryptographic Module (GOFIPS140, Go 1.24+, CMVP certificate 5247).

Root cause

The existing probe is too weak:

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

go version never initializes build or FIPS configuration, so this probe succeeds on every toolchain, including ones whose go.env bakes in GOFIPS140=v1.0.0 (as the CNG FIPS images now do, see gitlab-org/build/CNG!3010 (merged)). The build target then inlines $(GO_BUILD_ENV) into the go install invocation (note: in this repo GO_BUILD_ENV is an inline recipe prefix, not an export as in gitaly/gitlab-shell/workhorse) and fails:

go: cannot use GOFIPS140 with GOEXPERIMENT=boringcrypto

Failed CNG FIPS job: https://gitlab.com/gitlab-org/build/CNG/-/jobs/15234386707

The fix

Only run the probe (and set GO_BUILD_ENV=GOEXPERIMENT=boringcrypto) when go env GOFIPS140 reports empty or off:

  • A toolchain that selects the native module reports its version (e.g. v1.0.0) — boringcrypto must be skipped.
  • off must be treated as "legacy toolchain": golang-fips 1.24+ toolchains (still used by omnibus-gitlab) report off, never empty. A strictly-empty check would silently drop the OpenSSL backend from Omnibus FIPS builds. Empty covers golang-fips toolchains older than Go 1.24, which don't know the variable at all.

The GO_BUILD_TAGS := $(GO_BUILD_TAGS),fips line is unchanged, and the post-build self-check (go tool nm $(BINDIR)/gitlab-pages | grep FIPS) is retained as-is: the native module's symbols live under crypto/internal/fips140* and satisfy the same grep (verified empirically below).

Known follow-up (out of scope here)

With the Makefile conflict fixed, a native-module FIPS build of gitlab-pages still cannot link yet: the fips build tag selects gitlab.com/gitlab-org/labkit/fips/fips.go, which imports crypto/boring — a stdlib package gated on //go:build boringcrypto — so compilation fails with imports crypto/boring: build constraints exclude all Go files .... labkit needs a GOFIPS140-aware implementation (e.g. based on crypto/fips140.Enabled()). This MR removes the Makefile-level blocker; the labkit change is tracked as part of https://gitlab.com/gitlab-org/distribution/team-tasks/-/work_items/1706. The empirical native-chain build below was validated with a locally patched labkit (crypto/boring → crypto/fips140) via an uncommitted go.mod replace, to prove the Makefile guard and the self-check end to end.

Empirical testing

All on macOS arm64 with the repo-pinned Go 1.26.4 (mise exec). The native chain is simulated with GOFIPS140=v1.0.0 in the environment; unset, this toolchain reports exactly what a golang-fips 1.24+ toolchain reports:

$ GOFIPS140=v1.0.0 go env GOFIPS140
v1.0.0
$ go env GOFIPS140
off

Pre-patch failure reproduced (matches the CNG job):

$ GOFIPS140=v1.0.0 FIPS_MODE=1 make gitlab-pages
GOBIN=.../bin GOEXPERIMENT=boringcrypto go install -v ... -tags "continuous_profiler_stackdriver,fips" ...
go: cannot use GOFIPS140 with GOEXPERIMENT=boringcrypto
make: *** [build] Error 1

Post-patch, native sim — GO_BUILD_ENV is now empty (no GOEXPERIMENT in the recipe), and with the labkit shim in place the build succeeds and the line-27 self-check passes on native-module symbols:

$ GOFIPS140=v1.0.0 FIPS_MODE=1 make gitlab-pages
GOBIN=.../bin  go install -v ... -tags "continuous_profiler_stackdriver,fips" -buildmode exe gitlab.com/gitlab-org/gitlab-pages
...
go tool nm .../bin/gitlab-pages | grep FIPS >/dev/null && echo "binary is correctly built in FIPS mode" || ...
binary is correctly built in FIPS mode

$ go version -m bin/gitlab-pages | grep GOFIPS140
	build	GOFIPS140=v1.0.0-c2097c7c

$ go tool nm bin/gitlab-pages | grep -c 'crypto/internal/fips140'
1164

(With stock labkit the build proceeds past the old GOFIPS140/boringcrypto conflict and stops only at the labkit crypto/boring import described above.)

Post-patch, legacy sim (GOFIPS140 unset → off) — behavior is byte-identical to pre-patch: the probe runs, GOEXPERIMENT=boringcrypto is applied, the build succeeds and the self-check passes:

$ FIPS_MODE=1 make gitlab-pages
GOBIN=.../bin GOEXPERIMENT=boringcrypto go install -v ... -tags "continuous_profiler_stackdriver,fips" ...
binary is correctly built in FIPS mode

(On darwin the boringcrypto experiment is accepted but the backend is inert; the nm self-check passed both pre- and post-patch here because Go ≥ 1.24 binaries carry crypto/internal/fips140* symbols regardless.)

No-FIPS regression check:

$ make gitlab-pages     # builds clean, no FIPS flags anywhere
$ make unit-test
...
DONE 900 tests in 12.094s

TODO

🤖 Generated with Claude Code

Related to #1200 (closed)

Edited by Jason Plum

Merge request reports

Loading
Loading