diff --git a/BUILD.bazel b/BUILD.bazel index 51c7d4351f..64a745a1aa 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -130,6 +130,7 @@ go_config( export_stdlib = "//go/config:export_stdlib", gc_goopts = "//go/config:gc_goopts", gc_linkopts = "//go/config:gc_linkopts", + gofips140 = "//go/config:gofips140", gotags = "//go/config:tags", linkmode = "//go/config:linkmode", msan = "//go/config:msan", diff --git a/docs/go/core/rules.bzl b/docs/go/core/rules.bzl index 8fdb90e2b2..6c15afb5bc 100644 --- a/docs/go/core/rules.bzl +++ b/docs/go/core/rules.bzl @@ -11,6 +11,7 @@ [config_setting]: https://docs.bazel.build/versions/master/be/general.html#config_setting [data dependencies]: https://bazel.build/concepts/dependencies#data-dependencies [goarch]: /go/modes.rst#goarch + [gofips140]: /go/modes.rst#gofips140 [goos]: /go/modes.rst#goos [mode attributes]: /go/modes.rst#mode-attributes [nogo]: /go/nogo.rst#nogo @@ -58,6 +59,7 @@ sufficient to match the capabilities of the normal go tools. - [config_setting] - [data dependencies] - [goarch] +- [gofips140] - [goos] - [mode attributes] - [nogo] diff --git a/docs/go/core/rules.md b/docs/go/core/rules.md index f084cc81d0..504b08352b 100644 --- a/docs/go/core/rules.md +++ b/docs/go/core/rules.md @@ -13,6 +13,7 @@ [config_setting]: https://docs.bazel.build/versions/master/be/general.html#config_setting [data dependencies]: https://bazel.build/concepts/dependencies#data-dependencies [goarch]: /go/modes.rst#goarch + [gofips140]: /go/modes.rst#gofips140 [goos]: /go/modes.rst#goos [mode attributes]: /go/modes.rst#mode-attributes [nogo]: /go/nogo.rst#nogo @@ -60,6 +61,7 @@ sufficient to match the capabilities of the normal go tools. - [config_setting] - [data dependencies] - [goarch] +- [gofips140] - [goos] - [mode attributes] - [nogo] @@ -124,8 +126,8 @@ Rules
 go_binary(name, basename, cdeps, cgo, clinkopts, copts, cppopts, cxxopts, data, deps, embed,
-          embedsrcs, env, gc_goopts, gc_linkopts, goarch, goos, gotags, importpath, linkmode, msan,
-          out, pgoprofile, pure, race, srcs, static, x_defs)
+          embedsrcs, env, gc_goopts, gc_linkopts, goarch, gofips140, goos, gotags, importpath,
+          linkmode, msan, out, pgoprofile, pure, race, srcs, static, x_defs)
 
This builds an executable from a set of source files, @@ -136,7 +138,7 @@ This builds an executable from a set of source files, - + ### **Attributes** @@ -159,6 +161,7 @@ This builds an executable from a set of source files, | gc_goopts | List of flags to add to the Go compilation command when using the gc compiler. Subject to ["Make variable"] substitution and [Bourne shell tokenization]. | List of strings | optional | [] | | gc_linkopts | List of flags to add to the Go link command when using the gc compiler. Subject to ["Make variable"] substitution and [Bourne shell tokenization]. | List of strings | optional | [] | | goarch | Forces a binary to be cross-compiled for a specific architecture. It's usually better to control this on the command line with --platforms.

This disables cgo by default, since a cross-compiling C/C++ toolchain is rarely available. To force cgo, set pure = off.

See [Cross compilation] for more information. | String | optional | "auto" | +| gofips140 | Controls the GOFIPS140 environment variable. May be any string value. Common values include "off" (default), "latest", and specific versions like "v1.0.0". See [mode attributes], specifically [gofips140]. | String | optional | "off" | | goos | Forces a binary to be cross-compiled for a specific operating system. It's usually better to control this on the command line with --platforms.

This disables cgo by default, since a cross-compiling C/C++ toolchain is rarely available. To force cgo, set pure = off.

See [Cross compilation] for more information. | String | optional | "auto" | | gotags | Enables a list of build tags when evaluating [build constraints]. Useful for conditional compilation. | List of strings | optional | [] | | importpath | The import path of this binary. Binaries can't actually be imported, but this may be used by [go_path] and other tools to report the location of source files. This may be inferred from embedded libraries. | String | optional | "" | @@ -191,7 +194,7 @@ This wraps an executable built by `go_binary` to cross compile it - + ### **Attributes** @@ -226,7 +229,7 @@ This builds a Go library from a set of source files that are all part of
  • [GoInfo]
  • [GoArchive]
  • - + ### **Attributes** @@ -269,7 +272,7 @@ go_path(name, data,
  • [GoInfo]
  • - + ### **Attributes** @@ -363,8 +366,8 @@ This declares a set of source files and related dependencies that can be embedde
     go_test(name, cdeps, cgo, clinkopts, copts, cppopts, cxxopts, data, deps, embed, embedsrcs, env,
    -        env_inherit, gc_goopts, gc_linkopts, goarch, goos, gotags, importpath, linkmode, msan, pure,
    -        race, rundir, srcs, static, x_defs)
    +        env_inherit, gc_goopts, gc_linkopts, goarch, gofips140, goos, gotags, importpath, linkmode,
    +        msan, pure, race, rundir, srcs, static, x_defs)
     
    This builds a set of tests that can be run with `bazel test`.

    @@ -396,7 +399,7 @@ This builds a set of tests that can be run with `bazel test`.

    the name based on the last component of the path. For example, a test in `//foo/bar` is named `bar_test`, and uses internal and external sources. - + ### **Attributes** @@ -419,6 +422,7 @@ This builds a set of tests that can be run with `bazel test`.

    | gc_goopts | List of flags to add to the Go compilation command when using the gc compiler. Subject to ["Make variable"] substitution and [Bourne shell tokenization]. | List of strings | optional | [] | | gc_linkopts | List of flags to add to the Go link command when using the gc compiler. Subject to ["Make variable"] substitution and [Bourne shell tokenization]. | List of strings | optional | [] | | goarch | Forces a binary to be cross-compiled for a specific architecture. It's usually better to control this on the command line with --platforms.

    This disables cgo by default, since a cross-compiling C/C++ toolchain is rarely available. To force cgo, set pure = off.

    See [Cross compilation] for more information. | String | optional | "auto" | +| gofips140 | Controls the GOFIPS140 environment variable. May be any string value. Common values include "off" (default), "latest", and specific versions like "v1.0.0". See [mode attributes], specifically [gofips140]. | String | optional | "off" | | goos | Forces a binary to be cross-compiled for a specific operating system. It's usually better to control this on the command line with --platforms.

    This disables cgo by default, since a cross-compiling C/C++ toolchain is rarely available. To force cgo, set pure = off.

    See [Cross compilation] for more information. | String | optional | "auto" | | gotags | Enables a list of build tags when evaluating [build constraints]. Useful for conditional compilation. | List of strings | optional | [] | | importpath | The import path of this test. Tests can't actually be imported, but this may be used by [go_path] and other tools to report the location of source files. This may be inferred from embedded libraries. | String | optional | "" | diff --git a/go/config/BUILD.bazel b/go/config/BUILD.bazel index 5b1ccc0087..d19f801b38 100644 --- a/go/config/BUILD.bazel +++ b/go/config/BUILD.bazel @@ -33,6 +33,12 @@ bool_flag( visibility = ["//visibility:public"], ) +string_flag( + name = "gofips140", + build_setting_default = "off", + visibility = ["//visibility:public"], +) + bool_flag( name = "debug", build_setting_default = False, diff --git a/go/core.rst b/go/core.rst index 03fddb4d02..89f2bf183f 100644 --- a/go/core.rst +++ b/go/core.rst @@ -13,6 +13,7 @@ Core Go rules .. _config_setting: https://docs.bazel.build/versions/master/be/general.html#config_setting .. _data dependencies: https://bazel.build/concepts/dependencies#data-dependencies .. _goarch: modes.rst#goarch +.. _gofips140: modes.rst#gofips140 .. _goos: modes.rst#goos .. _mode attributes: modes.rst#mode-attributes .. _nogo: nogo.rst#nogo diff --git a/go/modes.rst b/go/modes.rst index 044d2eb19f..55b1e04e9b 100644 --- a/go/modes.rst +++ b/go/modes.rst @@ -69,6 +69,13 @@ or using `Bazel configuration transitions`_. | ``CGO_ENABLED=0``). Packages that contain cgo code may still be built, but | | the cgo code will be filtered out, and the ``cgo`` build tag will be false. | +------------------------+---------------------+-------------------------------+ +| :param:`gofips140` | :type:`string` | :value:`"off"` | ++------------------------+---------------------+-------------------------------+ +| Controls the ``GOFIPS140`` environment variable used by Go 1.24+ to select | +| the version of the Go Cryptographic Module. Can be set to ``"off"`` | +| (default), ``"latest"``, or a specific version like ``"v1.0.0"``. | +| See the `Go 1.24 FIPS 140-3 documentation`_ for more details. | ++------------------------+---------------------+-------------------------------+ | :param:`debug` | :type:`bool` | :value:`false` | +------------------------+---------------------+-------------------------------+ | Includes debugging information in compiled packages (using the ``-N`` and | diff --git a/go/private/context.bzl b/go/private/context.bzl index f12fd0070b..6b463cc87c 100644 --- a/go/private/context.bzl +++ b/go/private/context.bzl @@ -452,6 +452,7 @@ default_go_config_info = GoConfigInfo( race = False, msan = False, pure = False, + gofips140 = "off", strip = False, debug = False, linkmode = LINKMODE_NORMAL, @@ -541,6 +542,7 @@ def go_context( "GOROOT": goroot, "GOROOT_FINAL": "GOROOT", "CGO_ENABLED": "0" if mode.pure else "1", + "GOFIPS140": mode.gofips140, # If we use --action_env=GOPATH, or in other cases where environment # variables are passed through to this builder, the SDK build will try @@ -988,6 +990,7 @@ def _go_config_impl(ctx): race = race, msan = msan, pure = ctx.attr.pure[BuildSettingInfo].value, + gofips140 = ctx.attr.gofips140[BuildSettingInfo].value, strip = ctx.attr.strip, debug = ctx.attr.debug[BuildSettingInfo].value, linkmode = ctx.attr.linkmode[BuildSettingInfo].value, @@ -1024,6 +1027,10 @@ go_config = rule( mandatory = True, providers = [BuildSettingInfo], ), + "gofips140": attr.label( + mandatory = True, + providers = [BuildSettingInfo], + ), "strip": attr.bool(mandatory = True), "debug": attr.label( mandatory = True, diff --git a/go/private/mode.bzl b/go/private/mode.bzl index 19e288dd50..b1368fcda2 100644 --- a/go/private/mode.bzl +++ b/go/private/mode.bzl @@ -48,6 +48,8 @@ def mode_string(mode): result.append("msan") if mode.pure: result.append("pure") + if mode.gofips140 != "off": + result.append("gofips140=" + mode.gofips140) if mode.debug: result.append("debug") if mode.strip: diff --git a/go/private/rules/binary.bzl b/go/private/rules/binary.bzl index 0ec4b26c4a..e197fec6d2 100644 --- a/go/private/rules/binary.bzl +++ b/go/private/rules/binary.bzl @@ -373,6 +373,13 @@ def _go_binary_kwargs(go_cc_aspects = []): [pure]. """, ), + "gofips140": attr.string( + default = "off", + doc = """Controls the GOFIPS140 environment variable. May be any string value. + Common values include `"off"` (default), `"latest"`, and specific versions like `"v1.0.0"`. + See [mode attributes], specifically [gofips140]. + """, + ), "static": attr.string( default = "auto", doc = """Controls whether a binary is statically linked. May be one of `on`, diff --git a/go/private/rules/test.bzl b/go/private/rules/test.bzl index 46ae669633..997b935582 100644 --- a/go/private/rules/test.bzl +++ b/go/private/rules/test.bzl @@ -407,6 +407,13 @@ _go_test_kwargs = { [pure]. """, ), + "gofips140": attr.string( + default = "off", + doc = """Controls the GOFIPS140 environment variable. May be any string value. + Common values include `"off"` (default), `"latest"`, and specific versions like `"v1.0.0"`. + See [mode attributes], specifically [gofips140]. + """, + ), "static": attr.string( default = "auto", doc = """Controls whether a binary is statically linked. May be one of `on`, diff --git a/go/private/rules/transition.bzl b/go/private/rules/transition.bzl index 83909ec53c..7816d5e66f 100644 --- a/go/private/rules/transition.bzl +++ b/go/private/rules/transition.bzl @@ -40,6 +40,7 @@ TRANSITIONED_GO_SETTING_KEYS = [ "//go/config:msan", "//go/config:race", "//go/config:pure", + "//go/config:gofips140", "//go/config:linkmode", "//go/config:tags", "//go/config:pgoprofile", @@ -109,6 +110,10 @@ def _go_transition_impl(settings, attr): if tags: settings["//go/config:tags"] = _deduped_and_sorted(tags) + gofips140 = getattr(attr, "gofips140", "off") + if gofips140 != "off": + settings["//go/config:gofips140"] = gofips140 + linkmode = getattr(attr, "linkmode", "auto") if linkmode != "auto": if linkmode not in LINKMODES: @@ -198,6 +203,7 @@ _common_reset_transition_dict = dict({ "//go/config:msan": False, "//go/config:race": False, "//go/config:pure": False, + "//go/config:gofips140": "off", "//go/config:debug": False, "//go/config:linkmode": LINKMODE_NORMAL, "//go/config:tags": [], @@ -214,6 +220,7 @@ _stdlib_keep_keys = sorted([ "//go/config:msan", "//go/config:race", "//go/config:pure", + "//go/config:gofips140", "//go/config:linkmode", "//go/config:tags", "//go/config:pgoprofile", diff --git a/tests/core/go_binary/BUILD.bazel b/tests/core/go_binary/BUILD.bazel index d437868350..f1db80d2e9 100644 --- a/tests/core/go_binary/BUILD.bazel +++ b/tests/core/go_binary/BUILD.bazel @@ -233,6 +233,11 @@ go_bazel_test( embedsrcs = ["pgo.pprof"], ) +go_bazel_test( + name = "gofips140_test", + srcs = ["gofips140_test.go"], +) + # Tests using .syso files in go_binary both transitively and directly. go_binary( name = "meaning", diff --git a/tests/core/go_binary/gofips140_test.go b/tests/core/go_binary/gofips140_test.go new file mode 100644 index 0000000000..b5d4381e0e --- /dev/null +++ b/tests/core/go_binary/gofips140_test.go @@ -0,0 +1,87 @@ +// Copyright 2025 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gofips140_test + +import ( + "bytes" + "testing" + + "github.com/bazelbuild/rules_go/go/tools/bazel_testing" +) + +func TestMain(m *testing.M) { + bazel_testing.TestMain(m, bazel_testing.Args{ + Main: ` +-- BUILD.bazel -- +load("@io_bazel_rules_go//go:def.bzl", "go_binary") + +go_binary( + name = "gofips140_off", + srcs = ["gofips140.go"], + gofips140 = "off", +) + +go_binary( + name = "gofips140_latest", + srcs = ["gofips140.go"], + gofips140 = "latest", +) + +go_binary( + name = "gofips140_version", + srcs = ["gofips140.go"], + gofips140 = "v1.0.0", +) + +-- gofips140.go -- +package main + +import ( + "crypto/fips140" + "fmt" +) + +func main() { + fmt.Printf("%t", fips140.Enabled()) +} +`, + }) +} + +// TestGOFIPS140Attribute checks that the gofips140 attribute on go_binary +// controls the GOFIPS140 environment variable. +func TestGOFIPS140Attribute(t *testing.T) { + tests := []struct { + target string + want string + }{ + {"//:gofips140_off", "false"}, + {"//:gofips140_latest", "true"}, + {"//:gofips140_version", "true"}, + } + + for _, tt := range tests { + t.Run(tt.target, func(t *testing.T) { + out, err := bazel_testing.BazelOutput("run", tt.target) + if err != nil { + t.Fatalf("running %s: %v", tt.target, err) + } + got := string(bytes.TrimSpace(out)) + if got != tt.want { + t.Fatalf("got %q; want %q", got, tt.want) + } + }) + } +} diff --git a/tests/core/transition/cmdline_test.go b/tests/core/transition/cmdline_test.go index 1de0b3f593..69f52e3db6 100644 --- a/tests/core/transition/cmdline_test.go +++ b/tests/core/transition/cmdline_test.go @@ -35,6 +35,11 @@ go_binary( ], ) +go_binary( + name = "gofips140_test", + srcs = ["gofips140.go"], +) + -- not_pure.go -- // +build cgo @@ -56,6 +61,18 @@ import "fmt" func main() { fmt.Println("pure") } + +-- gofips140.go -- +package main + +import ( + "crypto/fips140" + "fmt" +) + +func main() { + fmt.Printf("%t", fips140.Enabled()) +} `, }) } @@ -82,3 +99,37 @@ func TestPure(t *testing.T) { t.Fatalf("got %q; want %q", got, want) } } + +// TestGOFIPS140 checks that the --@io_bazel_rules_go//go/config:gofips140 flag +// controls the GOFIPS140 environment variable. +func TestGOFIPS140(t *testing.T) { + // Test default value (should be "off") + out, err := bazel_testing.BazelOutput("run", "//:gofips140_test") + if err != nil { + t.Fatalf("running //:gofips140_test without flag: %v", err) + } + got := string(bytes.TrimSpace(out)) + if want := "false"; got != want { + t.Fatalf("got %q; want %q", got, want) + } + + // Test with "latest" value + out, err = bazel_testing.BazelOutput("run", "--@io_bazel_rules_go//go/config:gofips140=latest", "//:gofips140_test") + if err != nil { + t.Fatalf("running //:gofips140_test with gofips140=latest: %v", err) + } + got = string(bytes.TrimSpace(out)) + if want := "true"; got != want { + t.Fatalf("got %q; want %q", got, want) + } + + // Test with specific version + out, err = bazel_testing.BazelOutput("run", "--@io_bazel_rules_go//go/config:gofips140=v1.0.0", "//:gofips140_test") + if err != nil { + t.Fatalf("running //:gofips140_test with gofips140=v1.0.0: %v", err) + } + got = string(bytes.TrimSpace(out)) + if want := "true"; got != want { + t.Fatalf("got %q; want %q", got, want) + } +}