From 8b087652030f0bf433948da7cff32d2b5557a78d Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Mon, 10 Aug 2026 10:10:59 +0000 Subject: [PATCH] Run acceptance tests against a FIPS build in CI Co-authored-by: Isaac --- .github/workflows/push.yml | 50 +++++++++++++++++++++++++++++++++++ acceptance/fips_test.go | 53 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 acceptance/fips_test.go diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index b4c5238326b..9c86e8a0341 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -195,6 +195,56 @@ jobs: exit 1 fi + # Go picks its cryptographic module at build time via GOFIPS140, so the module + # a binary uses is a property of the build and not of the code. The regular + # test job builds without it, which leaves the FIPS module unexercised: FIPS + # mode narrows the cipher suites the TLS client offers, and nothing else here + # would notice if that broke a handshake or a hashing call. Build the CLI with + # the validated module and run the acceptance suite against it. + # + # Linux only: the module is platform-independent, so a second OS would spend + # the wall-clock time to re-test the same crypto. + test-fips: + needs: + - cleanups + - testmask + + if: ${{ contains(fromJSON(needs.testmask.outputs.targets), 'test') }} + name: "task test (linux, fips)" + runs-on: + group: databricks-protected-runner-group-large + labels: linux-ubuntu-latest-large + + defaults: + run: + shell: bash + + permissions: + id-token: write + contents: read + + steps: + - name: Checkout repository and submodules + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Setup build environment + uses: ./.github/actions/setup-build-environment + with: + cache-key: test-fips + + - name: Run tests + env: + # Pinned to a frozen module version that has completed CMVP validation. + # "latest" also enables FIPS mode, but tracks the in-tree source and so + # moves with every Go release, leaving no fixed artifact to cite. + GOFIPS140: v1.0.0 + ENVFILTER: DATABRICKS_BUNDLE_ENGINE=direct + run: go tool -modfile=tools/task/go.mod task test + + - name: Summarize failed tests + if: ${{ failure() }} + run: uv run tools/summarize_failed_tests.py test-output.json | tee -a "$GITHUB_STEP_SUMMARY" + test-exp-aitools: needs: - cleanups diff --git a/acceptance/fips_test.go b/acceptance/fips_test.go new file mode 100644 index 00000000000..20bd1889edc --- /dev/null +++ b/acceptance/fips_test.go @@ -0,0 +1,53 @@ +package acceptance_test + +import ( + "os" + "os/exec" + "runtime" + "testing" + + "github.com/stretchr/testify/require" +) + +// approvedFIPSModule is the frozen Go Cryptographic Module version that has +// completed CMVP validation. GOFIPS140=latest also enables FIPS mode, but tracks +// the in-tree source and moves with every Go release, so it leaves no fixed +// artifact to cite; only a frozen version is accepted here. +const approvedFIPSModule = "v1.0.0" + +// TestCLIBuiltWithFIPSModule asserts that the binary the acceptance suite just +// exercised really was built against the validated module. +// +// Without this, the FIPS CI job would still pass if GOFIPS140 silently stopped +// reaching the compiler -- the suite would run happily against an ordinary +// build and report that FIPS "works". The setting is only observable in the +// binary's own build info, so read it back from there. +// +// Skipped unless GOFIPS140 is set, so the default build is unaffected. +func TestCLIBuiltWithFIPSModule(t *testing.T) { + if os.Getenv("GOFIPS140") == "" { + t.Skip("not a FIPS build") + } + + cwd, err := os.Getwd() + require.NoError(t, err) + + execPath := BuildCLI(t, getBuildDir(t, cwd, runtime.GOOS, runtime.GOARCH), "", runtime.GOOS, runtime.GOARCH) + + out, err := exec.Command("go", "version", "-m", execPath).Output() + require.NoError(t, err) + buildInfo := string(out) + + // The stamped value carries a hash suffix (v1.0.0-) that moves with the + // toolchain, so match the version prefix rather than the whole string. + require.Contains(t, buildInfo, "GOFIPS140="+approvedFIPSModule, + "binary was not built against the approved FIPS module") + + // GOFIPS140 links the module and defaults FIPS mode on. Both matter: a binary + // that links the module but leaves the mode off behaves like a plain build. + require.Contains(t, buildInfo, "DefaultGODEBUG=fips140=on", + "FIPS module is linked but FIPS mode is not enabled by default") + + require.NotContains(t, buildInfo, "GOFIPS140=latest", + "binary was built with an unvalidated module version") +}