Skip to content

Conversation

@blazingbbq
Copy link
Contributor

What type of PR is this?

Feature

What does this PR do? Why is it needed?

This PR adds support for the GOFIPS140 environment variable can be used with go build, go install, and go test to select the version of the Go Cryptographic Module to be linked into the executable program.

See: https://go.dev/doc/security/fips140

Which issues(s) does this PR fix?

Fixes #4293

Other notes for review

None.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary of Changes

Hello @blazingbbq, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request integrates the GOFIPS140 environment variable into the Go build system, enabling users to manage the Go Cryptographic Module version directly through Bazel rules. This enhancement provides crucial control for applications requiring FIPS 140 compliance, ensuring that the correct cryptographic implementations are used during the build and test phases of Go projects.

Highlights

  • GOFIPS140 Environment Variable Support: This pull request introduces comprehensive support for the GOFIPS140 environment variable within Bazel's Go rules, allowing users to specify the version of the Go Cryptographic Module to be linked into executables.
  • New gofips140 Attribute: A new gofips140 attribute has been added to go_binary and go_test rules, providing direct control over the GOFIPS140 setting. This attribute accepts values like "off", "latest", or specific versions such as "v1.0.0".
  • Configuration and Documentation Updates: The go_config definition, build settings, and internal mode handling have been updated to recognize and propagate the gofips140 setting. Corresponding documentation in rules.bzl, rules.md, core.rst, and modes.rst has been added or modified to reflect this new feature.
  • New Test Cases: New test cases have been added to tests/core/go_binary/ and tests/core/transition/ to verify that the gofips140 attribute and the --@io_bazel_rules_go//go/config:gofips140 flag correctly control the GOFIPS140 environment variable and its impact on fips140.Enabled().
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for the GOFIPS140 build mode, which is a valuable addition for users requiring FIPS 140 compliance in their Go applications. The changes are comprehensive, touching the build configuration, rule definitions, documentation, and adding new tests. While the overall approach is sound, I've identified a couple of critical issues in the implementation that could lead to incorrect build behavior and caching problems. Additionally, there are some minor documentation inconsistencies that could be improved for clarity. My review includes detailed suggestions to address these points.

@blazingbbq blazingbbq marked this pull request as draft September 18, 2025 13:43
Copy link
Member

@fmeum fmeum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks pretty good, thanks for picking this up. Left a comment that may require some changes.

if tags:
settings["//go/config:tags"] = _deduped_and_sorted(tags)

gofips140 = getattr(attr, "gofips140", "off")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this mean that it's impossible to disable fips for a binary if it is enabled globally?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This option just determines what gets built into the crypto module (see: https://go.dev/doc/security/fips140#the-gofips140-environment-variable). Essentially:

  • "off": Use the fips140 crypto from the standard library tree in use, but does not enable FIPS mode at runtime by default. You would need the GODEBUG runtime option to enable fips140 (e.g. GODEBUG=fips140=on or GODEBUG=fips140=only)
  • "latest": Same as off, but enables FIPS mode without needing the runtime flag.
  • "vX.Y.Z": Uses a specific Go crypto module, and enabled FIPS mode by default.

@fmeum fmeum requested a review from jayconrod September 18, 2025 20:41
@blazingbbq
Copy link
Contributor Author

Trying to get the tests working, will mark as Ready for Review once those are passing.

Copy link
Collaborator

@jayconrod jayconrod left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for picking this up, this looks really good. I'm not really familiar with the GOFIPS140 functionality, but from reading the docs, it sounds like all we need to do is set the GOFIPS140 environment variable when building the standard library and when compiling and linking. The go tool itself may be doing some module magic, but setting the environment variable should cover it.

It seems like a build setting and a go_binary / go_test attribute is the correct way to express that, though I regret that go_binary has so many of these attributes now. Oh well, it's consistent.

@rickystewart
Copy link
Contributor

Hi, a gentle ping on this PR -- I'm wondering how we can get this merged. It would be great to have support for FIPS mode in rules_go. There are workarounds like using --action_env, but it unnecessarily busts the cache on non-Go code.

@fmeum
Copy link
Member

fmeum commented Oct 16, 2025

This generally looks good to me, we just have to get CI green. You may have to configure a more recent SDK foot the integration test.

rickystewart pushed a commit to cockroachdb/rules_go that referenced this pull request Oct 16, 2025
This captures code currently in the form of a draft PR at
bazel-contrib#4449. When this PR is merged, this
commit can be omitted from the cherry-picks onto our
`crl-release-*` branches.
@rickystewart
Copy link
Contributor

rickystewart commented Oct 17, 2025

Having spent some time trying to get this working, it seems at least the following patches are required if you are using GOFIPS140=v1.0.0:

diff --git a/go/private/BUILD.sdk.bazel b/go/private/BUILD.sdk.bazel
index 686b2c62..d6fca19e 100644
--- a/go/private/BUILD.sdk.bazel
+++ b/go/private/BUILD.sdk.bazel
@@ -24,7 +24,10 @@ filegroup(
 filegroup(
     name = "srcs",
     srcs = glob(
-        ["src/**/*"],
+        [
+            "lib/fips140/**",
+            "src/**/*",
+        ],
         exclude = [
             "src/**/*_test.go",
             "src/**/testdata/**",
diff --git a/go/tools/builders/stdlib.go b/go/tools/builders/stdlib.go
index cfcb9910..326a2d08 100644
--- a/go/tools/builders/stdlib.go
+++ b/go/tools/builders/stdlib.go
@@ -58,7 +58,7 @@ You may need to use the flags --cpu=x64_windows --compiler=mingw-gcc.`)
 	}
 
 	// Link in the bare minimum needed to the new GOROOT
-	if err := replicate(goroot, output, replicatePaths("src", "pkg/tool", "pkg/include")); err != nil {
+	if err := replicate(goroot, output, replicatePaths("src", "pkg/tool", "pkg/include", "lib")); err != nil {
 		return err
 	}
 

That doesn't seem to be entirely sufficient, and the build now fails for me with the error go: module cache not found: neither GOMODCACHE nor GOPATH is set. I'm still looking into that.

@rickystewart
Copy link
Contributor

rickystewart commented Oct 17, 2025

Still looking into the GOFIPS140=v1.0.0 use case. These patches seem to get the stdlib building (though I'm not sure if using a temporary GOMODCACHE is the correct thing to do in this case), but then you get a bunch of errors that look like cannot find package crypto/internal/fips140/v1.0.0-c2097c7c/aes (using -importcfg) -- I can't quite tell what the problem is there.

diff --git a/go/private/BUILD.sdk.bazel b/go/private/BUILD.sdk.bazel
index 686b2c62..d6fca19e 100644
--- a/go/private/BUILD.sdk.bazel
+++ b/go/private/BUILD.sdk.bazel
@@ -24,7 +24,10 @@ filegroup(
 filegroup(
     name = "srcs",
     srcs = glob(
-        ["src/**/*"],
+        [
+            "lib/fips140/**",
+            "src/**/*",
+        ],
         exclude = [
             "src/**/*_test.go",
             "src/**/testdata/**",
diff --git a/go/tools/builders/stdlib.go b/go/tools/builders/stdlib.go
index cfcb9910..ad79fa74 100644
--- a/go/tools/builders/stdlib.go
+++ b/go/tools/builders/stdlib.go
@@ -58,7 +58,7 @@ You may need to use the flags --cpu=x64_windows --compiler=mingw-gcc.`)
 	}
 
 	// Link in the bare minimum needed to the new GOROOT
-	if err := replicate(goroot, output, replicatePaths("src", "pkg/tool", "pkg/include")); err != nil {
+	if err := replicate(goroot, output, replicatePaths("src", "pkg/tool", "pkg/include", "lib")); err != nil {
 		return err
 	}
 
@@ -75,6 +75,11 @@ You may need to use the flags --cpu=x64_windows --compiler=mingw-gcc.`)
 	cachePath := filepath.Join(output, ".gocache")
 	os.Setenv("GOCACHE", cachePath)
 	defer os.RemoveAll(cachePath)
+	// Create a temporary modcache directory.
+	modCachePath := filepath.Join(output, ".gomodcache")
+	os.Setenv("GOMODCACHE", modCachePath)
+	defer os.RemoveAll(modCachePath)
+
 
 	// Disable modules for the 'go install' command. Depending on the sandboxing
 	// mode, there may be a go.mod file in a parent directory which will turn

@rickystewart
Copy link
Contributor

I'm no expert and am slightly out of my depth here, so it's very possible I'm missing something. But I think supporting GOFIPS140 values besides latest is going to be challenging and may require some more heavy lifting. You do need to apply the patches I provided above to expose the .zip file containing the FIPS libraries, but then that is not sufficient.

  • In the Go toolchain, first the lookup to find the .zip containing the FIPS binary occurs; and then
  • the toolchain will unpack the zip into the GOMODCACHE.

On the rules_go side, I believe what needs to happen is that we need to separately compile these .go files and capture them in the importcfg that we're passing down to go tool compile.

I hope I'm misreading the situation and it's much more straightforward than I think it is :)

@fmeum
Copy link
Member

fmeum commented Oct 18, 2025

Could we limit support to latest for now then with this PR? Is that already a useful mode for companies that need FIPS?

@jayconrod
Copy link
Collaborator

I wonder instead if fips40 should be an attribute on the toolchain rather than an attribute on go_binary. The go_sdk module extension could let users pick which version they want, if not latest, and we could build that as part of GoStdLib.

@rickystewart
Copy link
Contributor

Could we limit support to latest for now then with this PR?

Unfortunately I also see that building with GOFIPS140=latest does not work with this PR; while the build succeeds, the resultant binary does not have FIPS mode enabled (i.e. crypto/fips140.Enabled() returns false). I have not been able to figure out why. Setting GOFIPS140=latest in the environment when building the stdlib, running go tool compile, and then running go tool link does not seem to be sufficient to get the desired behavior of GOFIPS140=latest.

@rickystewart
Copy link
Contributor

rickystewart commented Nov 3, 2025

This gist captures my current version that I've been iterating on, derived from the original code in this PR. (It attempts to integrate jayconrod's advice to associate this configuration with the toolchain rather than the binary or test binary.) When attempting to build with --@io_bazel_rules_go//go/config:gofips140=latest, I see that the GoStdlib, GoCompilePkg, and GoLink all correctly run with GOFIPS140=latest, but still the result binary has crypto/fips140.Enabled() == false.

  • Setting the flag does something. I can tell because building the GoStdlib fails if you set the config to a nonsense string like @io_bazel_rules_go//go/config:gofips140=asdflkjsf (invalid GOFIPS140 error).
  • If you build with @io_bazel_rules_go//go/config:gofips140=latest, the build does "work", but I can't tell if the result GoStdlib is correct or not.

@rickystewart
Copy link
Contributor

rickystewart commented Nov 5, 2025

I found that the following diff is necessary to support the GOFIPS140=latest case:

diff --git a/go/tools/builders/link.go b/go/tools/builders/link.go
index 11dc0abf..3a94167d 100644
--- a/go/tools/builders/link.go
+++ b/go/tools/builders/link.go
@@ -136,6 +136,11 @@ func link(args []string) error {
                }
        }
 
+       gofips140 := os.Getenv("GOFIPS140")
+       if gofips140 != "off" {
+               goargs = append(goargs, "-X", "runtime.godebugDefault=fips140=on")
+       }
+
        if *buildmode != "" {
                goargs = append(goargs, "-buildmode", *buildmode)
        }

This ensures that the binary defaults to GODEBUG=fips140=on, which is the correct behavior.

GOFIPS140=v1.0.0 is hairier. Eventually, the build will fail with the following error:

cannot find package crypto/internal/fips140/v1.0.0-c2097c7c (using -importcfg)
cannot find package crypto/internal/fips140/v1.0.0-c2097c7c/check (using -importcfg)

The problem is that some of the built crypto packages (I believe, crypto/internal/fips140 and its subpackages) are "replaced" transparently by a versioned package like crypto/internal/fips140/v1.0.0-c2097c7c. This breaks a prior assumption in rules_go: that each package in the Go SDK has a simple, one-to-one mapping with its package name. This means that the logic used to compute the importcfg and/or the package_list needs to be augmented to include the real names of all stdlib packages, with their real corresponding .a files.

One option could be to use the gcexportdata package to read all the .a files from the stdlib and get their "real" package names, but it's not a low-touch change. For one, I'm not even sure where the .a archives for crypto/internal/fips140/v1.0.0-c2097c7c end up right now.

Edit: this gist captures my latest version of this, which supports GOFIPS140=latest but not GOFIPS140=v1.0.0.

rickystewart added a commit to cockroachdb/rules_go that referenced this pull request Nov 5, 2025
The previous version of the code did not correctly build with
`GOFIPS140=latest`, and namely did not link the binary such that FIPS
mode was on-by-default, which it should. This fixes that. We also revert
some misleading/incorrect changes from the prior in-progress commit
`7c5c55d859d1518f963a79abec87af34876cc2ee`.

See discussion [here](bazel-contrib#4449), and specifically
[this comment](bazel-contrib#4449 (comment)).
rickystewart added a commit to rickystewart/cockroach that referenced this pull request Nov 5, 2025
Prior to this change, the FIPS build did not have FIPS mode enabled by
default. This PR addresses this via a change to `rules_go`. We still
need to fix `rules_go` so that `GOFIPS140=v1.0.0` will work, but that
requires more tweaking (see [discussion](bazel-contrib/rules_go#4449 (comment))).

Release note: none
Epic: DEVINF-1477
rickystewart added a commit to rickystewart/cockroach that referenced this pull request Nov 6, 2025
Prior to this change, the FIPS build did not have FIPS mode enabled by
default. This PR addresses this via a change to `rules_go`. We still
need to fix `rules_go` so that `GOFIPS140=v1.0.0` will work, but that
requires more tweaking (see [discussion](bazel-contrib/rules_go#4449 (comment))).

Release note: none
Epic: DEVINF-1477
rickystewart added a commit to rickystewart/cockroach that referenced this pull request Nov 6, 2025
Prior to this change, the FIPS build did not have FIPS mode enabled by
default. This PR addresses this via a change to `rules_go`. We still
need to fix `rules_go` so that `GOFIPS140=v1.0.0` will work, but that
requires more tweaking (see [discussion](bazel-contrib/rules_go#4449 (comment))).

Release note: none
Epic: DEVINF-1477
craig bot pushed a commit to cockroachdb/cockroach that referenced this pull request Nov 6, 2025
156976: build: fix behavior of FIPS build r=rail a=rickystewart

Prior to this change, the FIPS build did not have FIPS mode enabled by default. This PR addresses this via a change to `rules_go`. We still need to fix `rules_go` so that `GOFIPS140=v1.0.0` will work, but that requires more tweaking (see [discussion](bazel-contrib/rules_go#4449 (comment))).

Release note: none
Epic: DEVINF-1477

Co-authored-by: Ricky Stewart <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support GOFIPS140 build time opt-in to FIPS

4 participants