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=boringcryptoThis 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.
Related work: labkit
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 1Post-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-c2097c7cGOFIPS140=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 passRelated to #865 (closed)
Related https://gitlab.com/gitlab-org/release/tasks/-/work_items/28133+s