From 825e357e48cfd71d6918de1a7498bf2f17f61ab3 Mon Sep 17 00:00:00 2001 From: Dominik Rosiek Date: Wed, 8 Jul 2026 16:22:47 +0200 Subject: [PATCH 1/3] Bump elastic-agent-libs and enforce fips140=on at runtime for FIPS builds elastic-agent-libs v0.46.0 enforces FIPS 140-3 peer-cert key-type checks across all TLS verification modes, closing a gap where FIPS builds could accept non-compliant TLS certificates. Also embed DefaultGODEBUG=fips140=on in FIPS binaries via a requirefips-gated go:debug directive, and have checkFIPSBinary verify it's present so FIPS binaries actually enforce FIPS mode at runtime, not just compile with the module available. Co-Authored-By: Claude Sonnet 5 --- NOTICE-fips.txt | 4 +-- NOTICE.txt | 4 +-- ...t-libs-for-FIPS-peer-cert-enforcement.yaml | 36 +++++++++++++++++++ go.mod | 2 +- go.sum | 4 +-- godebug_fips.go | 10 ++++++ magefile.go | 9 ++++- testing/go.mod | 2 +- testing/go.sum | 4 +-- 9 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 changelog/fragments/1783518046-Bump-elastic-agent-libs-for-FIPS-peer-cert-enforcement.yaml create mode 100644 godebug_fips.go diff --git a/NOTICE-fips.txt b/NOTICE-fips.txt index 4c0b15fbbe..93af8bf75e 100644 --- a/NOTICE-fips.txt +++ b/NOTICE-fips.txt @@ -661,11 +661,11 @@ SOFTWARE -------------------------------------------------------------------------------- Dependency : github.com/elastic/elastic-agent-libs -Version: v0.44.0 +Version: v0.46.0 Licence type (autodetected): Apache-2.0 -------------------------------------------------------------------------------- -Contents of probable licence file $GOMODCACHE/github.com/elastic/elastic-agent-libs@v0.44.0/LICENSE: +Contents of probable licence file $GOMODCACHE/github.com/elastic/elastic-agent-libs@v0.46.0/LICENSE: Apache License Version 2.0, January 2004 diff --git a/NOTICE.txt b/NOTICE.txt index d956db5804..68558b79a6 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -661,11 +661,11 @@ SOFTWARE -------------------------------------------------------------------------------- Dependency : github.com/elastic/elastic-agent-libs -Version: v0.44.0 +Version: v0.46.0 Licence type (autodetected): Apache-2.0 -------------------------------------------------------------------------------- -Contents of probable licence file $GOMODCACHE/github.com/elastic/elastic-agent-libs@v0.44.0/LICENSE: +Contents of probable licence file $GOMODCACHE/github.com/elastic/elastic-agent-libs@v0.46.0/LICENSE: Apache License Version 2.0, January 2004 diff --git a/changelog/fragments/1783518046-Bump-elastic-agent-libs-for-FIPS-peer-cert-enforcement.yaml b/changelog/fragments/1783518046-Bump-elastic-agent-libs-for-FIPS-peer-cert-enforcement.yaml new file mode 100644 index 0000000000..730f80364d --- /dev/null +++ b/changelog/fragments/1783518046-Bump-elastic-agent-libs-for-FIPS-peer-cert-enforcement.yaml @@ -0,0 +1,36 @@ +# Kind can be one of: +# - breaking-change: a change to previously-documented behavior +# - deprecation: functionality that is being removed in a later release +# - bug-fix: fixes a problem in a previous version +# - enhancement: extends functionality but does not break or fix existing behavior +# - feature: new functionality +# - known-issue: problems that we are aware of in a given version +# - security: impacts on the security of a product or a user’s deployment. +# - upgrade: important information for someone upgrading from a prior version +# - other: does not fit into any of the other categories +kind: enhancement + +# Change summary; a 80ish characters long description of the change. +summary: Bump elastic-agent-libs to v0.46.0 for FIPS 140-3 peer cert key-type enforcement + +# Long description; in case the summary is not enough to describe the change +# this field accommodate a description without length limits. +# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment. +description: | + Bump elastic-agent-libs to v0.46.0 which enforces FIPS 140-3 compliant peer + certificate key types across all TLS verification modes. Also set the + fips140=on GODEBUG default in the FIPS binary so it enforces FIPS mode at + runtime, and verify this in the binary FIPS marker check. + +# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc. +component: fleet-server + +# PR URL; optional; the PR number that added the changeset. +# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. +# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. +# Please provide it if you are adding a fragment for a different PR. +#pr: https://github.com/owner/repo/1234 + +# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). +# If not present is automatically filled by the tooling with the issue linked to the PR number. +#issue: https://github.com/owner/repo/1234 diff --git a/go.mod b/go.mod index ae9e2556be..8abcb8dd93 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/dgraph-io/ristretto v0.2.0 github.com/docker/go-units v0.5.0 github.com/elastic/elastic-agent-client/v7 v7.18.1 - github.com/elastic/elastic-agent-libs v0.44.0 + github.com/elastic/elastic-agent-libs v0.46.0 github.com/elastic/elastic-agent-system-metrics v0.14.4 github.com/elastic/go-elasticsearch/v8 v8.19.6 github.com/elastic/go-ucfg v0.9.1 diff --git a/go.sum b/go.sum index 2accbb956a..6897844c3d 100644 --- a/go.sum +++ b/go.sum @@ -37,8 +37,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/elastic/elastic-agent-client/v7 v7.18.1 h1:WnM53JjaukeysrAuiTyrhDPmFxJG07ZAByc2TrkcKcs= github.com/elastic/elastic-agent-client/v7 v7.18.1/go.mod h1:uDpSGZ+YCKgqgtkwCA0qjwX0gU/wmixDsVbPjY3GkPs= -github.com/elastic/elastic-agent-libs v0.44.0 h1:dONaZsY5G0rVGULP+j5xKyT51rK7nsBNEGywIXcCrg4= -github.com/elastic/elastic-agent-libs v0.44.0/go.mod h1:axkpqDCCzAky6G4D/cklgtZQa3jTNGAMVHVkU0u6YbU= +github.com/elastic/elastic-agent-libs v0.46.0 h1:4otnN3M34k2sngeeeSEza1YS+XKY1zZ8sBoSVhu2Tew= +github.com/elastic/elastic-agent-libs v0.46.0/go.mod h1:axkpqDCCzAky6G4D/cklgtZQa3jTNGAMVHVkU0u6YbU= github.com/elastic/elastic-agent-system-metrics v0.14.4 h1:XGGepNVOxhtfJmanQsgqCAZESIJtyDIFOKErOaVzFEw= github.com/elastic/elastic-agent-system-metrics v0.14.4/go.mod h1:NyNMrdqMfznb/Zy8EmIAHlJpX6HuRlNW+bBL9UN7WQY= github.com/elastic/elastic-transport-go/v8 v8.9.0 h1:KeT/2P54F0xS0S8Y3Pf+tFDg4HmBgReQMB+BMz8dDAs= diff --git a/godebug_fips.go b/godebug_fips.go new file mode 100644 index 0000000000..cd715efe0e --- /dev/null +++ b/godebug_fips.go @@ -0,0 +1,10 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License 2.0; +// you may not use this file except in compliance with the Elastic License 2.0. + +//go:build requirefips + +// Enforce FIPS 140-3 mode at runtime so the binary rejects non-compliant crypto. +//go:debug fips140=on + +package main diff --git a/magefile.go b/magefile.go index e7d66c3a09..2ffa652c77 100644 --- a/magefile.go +++ b/magefile.go @@ -1640,7 +1640,7 @@ func checkFIPSBinary(path string) error { if err != nil { return fmt.Errorf("unable to read buildinfo: %w", err) } - var foundTags, foundFIPS140 bool + var foundTags, foundFIPS140, foundFIPSDefault bool for _, setting := range info.Settings { switch setting.Key { @@ -1668,6 +1668,10 @@ func checkFIPSBinary(path string) error { if setting.Value == "" { return fmt.Errorf("GOFIPS140 is empty") } + case "DefaultGODEBUG": + if strings.Contains(setting.Value, "fips140=on") { + foundFIPSDefault = true + } } } @@ -1677,6 +1681,9 @@ func checkFIPSBinary(path string) error { if !foundFIPS140 { return fmt.Errorf("did not find GOFIPS140 in build settings") } + if !foundFIPSDefault { + return fmt.Errorf("did not find fips140=on in DefaultGODEBUG — binary will not enforce FIPS mode at runtime") + } return nil } diff --git a/testing/go.mod b/testing/go.mod index 8cff82208e..116da87e48 100644 --- a/testing/go.mod +++ b/testing/go.mod @@ -10,7 +10,7 @@ replace ( require ( github.com/Shopify/toxiproxy/v2 v2.12.0 github.com/elastic/elastic-agent-client/v7 v7.18.1 - github.com/elastic/elastic-agent-libs v0.45.0 + github.com/elastic/elastic-agent-libs v0.46.0 github.com/elastic/fleet-server/pkg/api v0.0.0-00010101000000-000000000000 github.com/elastic/fleet-server/v7 v7.0.0-00010101000000-000000000000 github.com/gofrs/uuid/v5 v5.4.0 diff --git a/testing/go.sum b/testing/go.sum index 302f51e536..1f058ebe17 100644 --- a/testing/go.sum +++ b/testing/go.sum @@ -45,8 +45,8 @@ github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/elastic/elastic-agent-client/v7 v7.18.1 h1:WnM53JjaukeysrAuiTyrhDPmFxJG07ZAByc2TrkcKcs= github.com/elastic/elastic-agent-client/v7 v7.18.1/go.mod h1:uDpSGZ+YCKgqgtkwCA0qjwX0gU/wmixDsVbPjY3GkPs= -github.com/elastic/elastic-agent-libs v0.45.0 h1:ioCrNcd03tJVxrnAdZ19MCmea1SgOO+QeFtt+KvcJC0= -github.com/elastic/elastic-agent-libs v0.45.0/go.mod h1:axkpqDCCzAky6G4D/cklgtZQa3jTNGAMVHVkU0u6YbU= +github.com/elastic/elastic-agent-libs v0.46.0 h1:4otnN3M34k2sngeeeSEza1YS+XKY1zZ8sBoSVhu2Tew= +github.com/elastic/elastic-agent-libs v0.46.0/go.mod h1:axkpqDCCzAky6G4D/cklgtZQa3jTNGAMVHVkU0u6YbU= github.com/elastic/go-sysinfo v1.15.1 h1:zBmTnFEXxIQ3iwcQuk7MzaUotmKRp3OabbbWM8TdzIQ= github.com/elastic/go-sysinfo v1.15.1/go.mod h1:jPSuTgXG+dhhh0GKIyI2Cso+w5lPJ5PvVqKlL8LV/Hk= github.com/elastic/go-ucfg v0.9.1 h1:OwbVLC9pAmHqlBDq5owRC7HbfldsLuqPqrwg23n17BQ= From 6197ec19bc94cf995070794f6847d7cc15fbc852 Mon Sep 17 00:00:00 2001 From: Dominik Rosiek Date: Thu, 9 Jul 2026 09:03:04 +0200 Subject: [PATCH 2/3] Fix DefaultGODEBUG check to not match fips140=only as fips140=on strings.Contains(value, "fips140=on") also matched "fips140=only" since "on" is a prefix of "only", so a fips140=only binary would incorrectly pass the check meant to verify fips140=on enforcement. Parse comma separated key=value entries and compare exactly instead. Co-Authored-By: Claude Sonnet 5 --- magefile.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/magefile.go b/magefile.go index 2ffa652c77..02c5f7a2fc 100644 --- a/magefile.go +++ b/magefile.go @@ -1669,8 +1669,11 @@ func checkFIPSBinary(path string) error { return fmt.Errorf("GOFIPS140 is empty") } case "DefaultGODEBUG": - if strings.Contains(setting.Value, "fips140=on") { - foundFIPSDefault = true + for _, entry := range strings.Split(setting.Value, ",") { + if key, val, ok := strings.Cut(entry, "="); ok && key == "fips140" && val == "on" { + foundFIPSDefault = true + break + } } } } From 78833292484f0d148d44b7f97aac575678576a4f Mon Sep 17 00:00:00 2001 From: Dominik Rosiek Date: Thu, 9 Jul 2026 09:43:09 +0200 Subject: [PATCH 3/3] Fix FIPS unit tests for elastic-agent-libs TLS peer-cert enforcement elastic-agent-libs v0.46.0 now installs a VerifyConnection callback on FIPS builds regardless of GODEBUG=fips140=on vs only, since the check is gated by the requirefips build tag rather than runtime enforcement level. Update the affected tests accordingly: - TestToESConfig: ignore VerifyConnection when comparing TLS configs, same as the existing handling for Proxy. - TestConnectionTLS: switch from fips140.Enforced() (true only under fips140=only) to fips140.Enabled() (true under fips140=on or only), matching when the library's FIPS enforcement is actually active. Co-Authored-By: Claude Sonnet 5 --- internal/pkg/config/output_test.go | 6 +++++- internal/pkg/es/client_test.go | 18 ++++++------------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/internal/pkg/config/output_test.go b/internal/pkg/config/output_test.go index 6a7b6130d7..566340fbbd 100644 --- a/internal/pkg/config/output_test.go +++ b/internal/pkg/config/output_test.go @@ -205,7 +205,11 @@ func TestToESConfig(t *testing.T) { require.NoError(t, err) // cmp.Diff can't handle function pointers. - res.Transport.(*http.Transport).Proxy = nil + transport := res.Transport.(*http.Transport) + transport.Proxy = nil + if transport.TLSClientConfig != nil { + transport.TLSClientConfig.VerifyConnection = nil + } test.result.Header.Set("X-elastic-product-origin", "fleet") assert.True(t, cmp.Equal(test.result, res, copts...), "mismatch (-want +got)\n%s", cmp.Diff(test.result, res, copts...)) diff --git a/internal/pkg/es/client_test.go b/internal/pkg/es/client_test.go index 06feb708df..c677a5dec4 100644 --- a/internal/pkg/es/client_test.go +++ b/internal/pkg/es/client_test.go @@ -174,12 +174,11 @@ func TestClientCerts(t *testing.T) { // TestConnectionTLS tries to connect to a test HTTPS server (pretending // to be an Elasticsearch cluster), that deliberately presents TLS options // that are not FIPS-compliant. -// - If the test is running with a FIPS-capable build, the client, being FIPS- -// capable, should fail the TLS handshake. Concretely, the conn.Connect() method -// should return an error. -// - If the test is not running with a FIPS-capable build, the client should -// complete the TLS handshake successfully. Concretely, the conn.Connect() method -// should not return an error. +// - If FIPS crypto is enabled, the client should fail the TLS handshake. +// Concretely, the conn.Connect() method should return an error. +// - If FIPS crypto is not enabled, the client should complete the TLS +// handshake successfully. Concretely, the conn.Connect() method should not +// return an error. func TestConnectionTLS(t *testing.T) { server := startTLSServer(t) defer server.Close() @@ -205,12 +204,7 @@ func TestConnectionTLS(t *testing.T) { _, err = FetchESVersion(ctx, client) - if fips140.Enforced() { - // When FIPS 140 is enforced (GODEBUG=fips140=only), Go's crypto - // stack rejects signing with a 1024-bit RSA key. Note: fips140=on - // with microsoft/go's systemcrypto backend silently falls back to - // stdlib in test binaries (via UnreachableExceptTests), so only - // fips140=only reliably enforces this. + if fips140.Enabled() { require.Error(t, err) } else { require.NoError(t, err)