diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 2439c9dd73..778c0de969 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -3,6 +3,7 @@ env: DOCKER_COMPOSE_VERSION: "1.25.5" TERRAFORM_VERSION: "1.6.4" + IMAGE_UBUNTU_X86_64_FIPS: "platform-ingest-fleet-server-ubuntu-2204-fips-1751684469" # This section is used to define the plugins that will be used in the pipeline. # See https://buildkite.com/docs/pipelines/integrations/plugins/using#using-yaml-anchors-with-plugins @@ -114,24 +115,31 @@ steps: - build/*.xml - build/coverage*.out - - label: ":smartbear-testexecute: Run unit tests with requirefips build tag" + - label: ":smartbear-testexecute: Run unit tests with requirefips build tag and FIPS provider" key: unit-test-fips-tag command: ".buildkite/scripts/unit_test.sh" env: FIPS: "true" + GOEXPERIMENT: "systemcrypto" + GO_DISTRO: "microsoft" agents: - provider: "gcp" + provider: "aws" + image: "${IMAGE_UBUNTU_X86_64_FIPS}" + instanceType: "m5.xlarge" artifact_paths: - build/*.xml - build/coverage*.out - - label: ":smartbear-testexecute: Run fips140=only unit tests" + - label: ":smartbear-testexecute: Run fips140=only unit tests with FIPS provider" key: unit-test-fips140-only command: ".buildkite/scripts/unit_test_fipsonly.sh" env: FIPS: "true" + GO_DISTRO: "stdlib" agents: - provider: "gcp" + provider: "aws" + image: "${IMAGE_UBUNTU_X86_64_FIPS}" + instanceType: "m5.xlarge" artifact_paths: - build/*.xml - build/coverage*.out diff --git a/.buildkite/scripts/common.sh b/.buildkite/scripts/common.sh index 04961401a3..84927104aa 100755 --- a/.buildkite/scripts/common.sh +++ b/.buildkite/scripts/common.sh @@ -55,9 +55,12 @@ with_msft_go() { echo "Setting up microsoft/go" create_workspace check_platform_architeture + + # Use a temporary folder to house the Go SDK downloaded from Microsoft + tempfolder=$(mktemp -d) MSFT_DOWNLOAD_URL=https://aka.ms/golang/release/latest/go$(cat .go-version).${platform_type}-${arch_type}.tar.gz - retry 5 $(curl -sL -o - $MSFT_DOWNLOAD_URL | tar -xz -f - -C ${WORKSPACE}) - export PATH="${PATH}:${WORKSPACE}/go/bin" + retry 5 $(curl -sL -o - $MSFT_DOWNLOAD_URL | tar -xz -f - -C ${tempfolder}/) + export PATH="${PATH}:${tempfolder}/go/bin" go version which go export PATH="${PATH}:$(go env GOPATH)/bin" diff --git a/.buildkite/scripts/unit_test.sh b/.buildkite/scripts/unit_test.sh index 1e664af92c..d262fddbe9 100755 --- a/.buildkite/scripts/unit_test.sh +++ b/.buildkite/scripts/unit_test.sh @@ -6,7 +6,11 @@ source .buildkite/scripts/common.sh add_bin_path -with_go +if [[ ${FIPS:-false} == "true" && ${GO_DISTRO:-stdlib} == "microsoft" ]]; then + with_msft_go +else + with_go +fi with_mage diff --git a/.buildkite/scripts/unit_test_fipsonly.sh b/.buildkite/scripts/unit_test_fipsonly.sh index 0c6d4dea58..20a2c84a3f 100755 --- a/.buildkite/scripts/unit_test_fipsonly.sh +++ b/.buildkite/scripts/unit_test_fipsonly.sh @@ -6,7 +6,11 @@ source .buildkite/scripts/common.sh add_bin_path -with_go +if [[ ${FIPS:-false} == "true" && ${GO_DISTRO:-stdlib} == "microsoft" ]]; then + with_msft_go +else + with_go +fi with_mage diff --git a/internal/pkg/es/client_test.go b/internal/pkg/es/client_test.go index 3d2eff13e4..6e48a0603e 100644 --- a/internal/pkg/es/client_test.go +++ b/internal/pkg/es/client_test.go @@ -8,12 +8,15 @@ import ( "context" "crypto/tls" "crypto/x509" + _ "embed" "fmt" "net/http" "net/http/httptest" "testing" + "time" "github.com/elastic/elastic-agent-libs/transport/tlscommon" + "github.com/elastic/fleet-server/v7/internal/pkg/build" "github.com/elastic/fleet-server/v7/internal/pkg/config" "github.com/elastic/fleet-server/v7/internal/pkg/testing/certs" "github.com/stretchr/testify/require" @@ -42,7 +45,7 @@ func TestClientCerts(t *testing.T) { defer server.Close() // client does not use client certs - client, err := NewClient(context.Background(), &config.Config{ + client, err := NewClient(t.Context(), &config.Config{ Output: config.Output{ Elasticsearch: config.Elasticsearch{ Protocol: "https", @@ -56,7 +59,7 @@ func TestClientCerts(t *testing.T) { }, false) require.NoError(t, err) - req, err := http.NewRequestWithContext(context.Background(), "GET", server.URL, nil) + req, err := http.NewRequestWithContext(t.Context(), "GET", server.URL, nil) require.NoError(t, err) resp, err := client.Perform(req) @@ -87,7 +90,7 @@ func TestClientCerts(t *testing.T) { cert := certs.GenCert(t, ca) // client uses valid, matching certs - client, err := NewClient(context.Background(), &config.Config{ + client, err := NewClient(t.Context(), &config.Config{ Output: config.Output{ Elasticsearch: config.Elasticsearch{ Protocol: "https", @@ -105,7 +108,7 @@ func TestClientCerts(t *testing.T) { }, false) require.NoError(t, err) - req, err := http.NewRequestWithContext(context.Background(), "GET", server.URL, nil) + req, err := http.NewRequestWithContext(t.Context(), "GET", server.URL, nil) require.NoError(t, err) resp, err := client.Perform(req) @@ -137,7 +140,7 @@ func TestClientCerts(t *testing.T) { cert := certs.GenCert(t, certCA) // client uses certs that are signed by a different CA - client, err := NewClient(context.Background(), &config.Config{ + client, err := NewClient(t.Context(), &config.Config{ Output: config.Output{ Elasticsearch: config.Elasticsearch{ Protocol: "https", @@ -155,10 +158,93 @@ func TestClientCerts(t *testing.T) { }, false) require.NoError(t, err) - req, err := http.NewRequestWithContext(context.Background(), "GET", server.URL, nil) + req, err := http.NewRequestWithContext(t.Context(), "GET", server.URL, nil) require.NoError(t, err) _, err = client.Perform(req) //nolint:bodyclose // no response is expected require.Error(t, err) }) } + +// 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. +func TestConnectionTLS(t *testing.T) { + server := startTLSServer(t) + defer server.Close() + + cfg := &config.Config{ + Output: config.Output{ + Elasticsearch: config.Elasticsearch{ + Protocol: "https", + Hosts: []string{server.URL}, + TLS: &tlscommon.Config{ + Enabled: &enabled, + CAs: []string{string(caCertPEM)}, + }, + }, + }, + } + + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) + defer cancel() + + client, err := NewClient(ctx, cfg, false) + require.NoError(t, err) + + _, err = FetchESVersion(ctx, client) + + if build.FIPSDistribution { + require.ErrorContains(t, err, "tls: internal error") + } else { + require.NoError(t, err) + } +} + +//go:embed testdata/ca.crt +var caCertPEM []byte + +//go:embed testdata/fips_invalid.key +var serverKeyPEM []byte // RSA key with length = 1024 bits + +//go:embed testdata/fips_invalid.crt +var serverCertPEM []byte + +//go:embed testdata/es_ping_response.json +var esPingResponse []byte + +func startTLSServer(t *testing.T) *httptest.Server { + // Configure server and start it + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCertPEM) + + // Create HTTPS server + server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("X-Elastic-Product", "Elasticsearch") + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, err := w.Write(esPingResponse) + require.NoError(t, err) + })) + + serverCert, err := tls.X509KeyPair(serverCertPEM, serverKeyPEM) + require.NoError(t, err) + + server.TLS = &tls.Config{ + MinVersion: tls.VersionTLS12, + RootCAs: caCertPool, + Certificates: []tls.Certificate{serverCert}, + ClientCAs: caCertPool, + ClientAuth: tls.NoClientCert, + } + + server.StartTLS() + + return server +} diff --git a/internal/pkg/es/testdata/ca.crt b/internal/pkg/es/testdata/ca.crt new file mode 100644 index 0000000000..3137ac7c5e --- /dev/null +++ b/internal/pkg/es/testdata/ca.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIUA9Gphn0fTO3Vuo7ePJpfebnebtgwDQYJKoZIhvcNAQEL +BQAwZDELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMSEwHwYDVQQKDBhJ +bnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxCzAJBgNVBAsMAkNBMRIwEAYDVQQDDAls +b2NhbGhvc3QwHhcNMjUwNDIyMjIwMTU2WhcNMzAwNDIxMjIwMTU2WjBkMQswCQYD +VQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsxITAfBgNVBAoMGEludGVybmV0IFdp +ZGdpdHMgUHR5IEx0ZDELMAkGA1UECwwCQ0ExEjAQBgNVBAMMCWxvY2FsaG9zdDCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIaR6W/pAoEFE5Hc6kgH2UTZ +cd0LOT5hp3xtomKfnNONS5WgXDZbOqCSUY1+ZrG6NDrzG64vDC+AdtW7Zji7s+VA +2hZ2DESbq+JBosAAyZbwzqosTCpp24on1VWXS+h8NT1nMGkvkkrKnM0fBK4Q9DVI +H9QAtKysPnLwbfyWrnAHtjMd0bIrBPlt26g16l1nJklTwm2clD0ixE4MKw7lPZWE +eJN+sK1CvA+r65huC7vDbNrL2OC+eNAiKtCH+AQR4HcB76kG9Qy/9+qfCGhizBlt +mwceLDhz6FWgxKSgXwSfmorZLc1ecBfuWjqr9rfaUhOd4oLkmfbaEPqNu2V/rw0C +AwEAAaNvMG0wHQYDVR0OBBYEFGzBvXdyHsVEY4bOAIiI3m4w7JfcMB8GA1UdIwQY +MBaAFGzBvXdyHsVEY4bOAIiI3m4w7JfcMA8GA1UdEwEB/wQFMAMBAf8wGgYDVR0R +BBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQCEbCFPgfT4 +DUkl/LozK8zUPEUV6mh53rTGQLhMbPfu7l1f6aSjvb1bIzYmrEFhlv/3yke+2/BC +lGPYZrzdy2S9Xqv2ZthBoqE7cUrUGcq6U4y9helsM4gMfokpgBuNqwFVOGtSAlYy +otUTRuIJeCLqAUV51wYROe9dOnY//ICEVrnRmLN4uXl64LMlBWbx76PS2s9dktr1 +5oWeF8whEhzg41FGsd6QPulKgT9h8+RR10hc3F4IFCVjtnp11E22x0/YYONbuAEH +ZxL++PbvQRAvFGpTEmxH/AIq8yGQ90V94+HB7ocqz+3y0Nl93iNoanMOAJush3uL +oIhHS8L9ENUv +-----END CERTIFICATE----- diff --git a/internal/pkg/es/testdata/es_ping_response.json b/internal/pkg/es/testdata/es_ping_response.json new file mode 100644 index 0000000000..5a0cf0b2a8 --- /dev/null +++ b/internal/pkg/es/testdata/es_ping_response.json @@ -0,0 +1,17 @@ +{ + "name": "instance-0000000000", + "cluster_name": "e8647d9cfc9e4e77a83554c5e6d5ee25", + "cluster_uuid": "25mwr5sGTZyXIGwWMcSGmQ", + "version": { + "number": "9.2.0-SNAPSHOT", + "build_flavor": "default", + "build_type": "docker", + "build_hash": "a6dfe646524f42869fc4820a67fffec2d76890dc", + "build_date": "2025-07-01T22:12:52.207781139Z", + "build_snapshot": true, + "lucene_version": "10.2.2", + "minimum_wire_compatibility_version": "8.19.0", + "minimum_index_compatibility_version": "8.0.0" + }, + "tagline": "You Know, for Search" +} diff --git a/internal/pkg/es/testdata/fips_invalid.crt b/internal/pkg/es/testdata/fips_invalid.crt new file mode 100644 index 0000000000..8457dd6ec9 --- /dev/null +++ b/internal/pkg/es/testdata/fips_invalid.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDLzCCAhegAwIBAgIUIUHef0rqBRe0SWOxT/OwncexFiwwDQYJKoZIhvcNAQEL +BQAwZDELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMSEwHwYDVQQKDBhJ +bnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxCzAJBgNVBAsMAkNBMRIwEAYDVQQDDAls +b2NhbGhvc3QwHhcNMjUwNDIyMjIwNjM1WhcNMzAwNDIxMjIwNjM1WjBYMQswCQYD +VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEQMA4GA1UECgwHRWxhc3RpYzEO +MAwGA1UECwwFQWdlbnQxEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0B +AQEFAAOBjQAwgYkCgYEAo87+PNnpbu+hNkjPigLVKSmlDStd0OqcOmUlsegElMEk +CArXkS+nDkD+p6o6QgGZ65mevmbJ9AxTV4tHQZ8YE695BgVa/MixzWNa2CjZu4OY +CXJd1q8LfvkExXjp8+RVWuAh+FY5bZYGIlw2yLSHGbrE5k3F8YlfoL6ADkAf43kC +AwEAAaNpMGcwHwYDVR0jBBgwFoAUbMG9d3IexURjhs4AiIjebjDsl9wwCQYDVR0T +BAIwADAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwHQYDVR0OBBYEFFTZsQwH +ipBtdR1XTu9x3yUg7H9uMA0GCSqGSIb3DQEBCwUAA4IBAQAH4gJMyjTvGUBUuih9 +VDcKsxxIGPhcBaoDRN7YX/qI5DapRA7/bP+1AIAoByrs6YTbMZYNB4hDEEywf59T +pHNFt+3IsWOO3RCP1IeJscKO79Ga5WeKYJyV5HeNRpgNMsjslh+shzz29Qm4voiE +Ab+x/CZYJu9Yw5JPENb035KWVAMCiN34afi3jgNcQVKYlyUwm27qVOLxTuUZU23a +RzsFIc3/DpxIUZ7hD0qgR00jOXWAynhRpptKW7/tJmnoUk5nZuJUz3XDvE4UPvHj +0KTf4RFfnNJbmO3ZVqj8QI9FdhOUYr/rJnrufyQBnCDEHmo9KIeIVynaijoBtem5 +mPgS +-----END CERTIFICATE----- diff --git a/internal/pkg/es/testdata/fips_invalid.key b/internal/pkg/es/testdata/fips_invalid.key new file mode 100644 index 0000000000..bf201566e4 --- /dev/null +++ b/internal/pkg/es/testdata/fips_invalid.key @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKPO/jzZ6W7voTZI +z4oC1SkppQ0rXdDqnDplJbHoBJTBJAgK15Evpw5A/qeqOkIBmeuZnr5myfQMU1eL +R0GfGBOveQYFWvzIsc1jWtgo2buDmAlyXdavC375BMV46fPkVVrgIfhWOW2WBiJc +Nsi0hxm6xOZNxfGJX6C+gA5AH+N5AgMBAAECgYAD5fw6Zyge6Aeu4FGSTy7mC3WM +ydv+8DLR99nRx6V1qeyK3yfIiHxDn4CbLYoOJTyPZRqOtuj5iVvbTO3GbIwg09tW +4MMyS2AABIaO8Ke2MdXseI1Dt9sY7TnAPs8tz6KOEPksWXYOroqrzqXXmlG/yEei +Fk9Z/UZB3ue33oq+IQJBAMm53Bi7ck2A7O4ueaeX9SkC5XpFpaUAimY5h8m+J0aB +vXUjEX0BzEj7/+ocX+KaEXE0gfvQZHl2PUY5qCwnbdkCQQDP4YUknD+1lFH7l8tJ +1whZfPEKn7MYAAS9wI5q11CTeaSkvq3z+5gX0EwLBn7WSqfcR3vQOBmyz0uzZhws +e36hAkAP+Z4Kf12v8ZPR0PBla01I8CfIJRfXF1HegpPUUDDADqo4Soyp/6hz5zD/ +Ezwsr9LNykC49mnejJSRqSM+S+kRAkEAhKEZBmueBia0S7XkIJ9OF3Isg5+ybwyL ++dihxK7NHNpOXkG90F1kA0WFTr99KxGEmXkOGKHCW6AAZ1wte3/rIQJASAjYaX8z +cvqU4hUIsh0A1fQDD4j1HYrkOdsThrAoWRPu2mBDsD6IWnVmHzRB8coTZllP+mBA +JL2QoEeSSdfniw== +-----END PRIVATE KEY-----