Skip to content

Commit 1c233b8

Browse files
[FIPS] Test that ES client will not connect to ES with invalid TLS certificate (#5088) (#5141)
* Adding unit test for connecting to FIPS-incapable ES * Make linter happy * Reordering imports * Run FIPS unit tests on FIPS VM * Install Microsoft Go if FIPS=true * Debugging * Use fleet server FIPS VM image * Debugging: extracting microsoft/go outside of fleet-server folder * Explicitly specify Go distribution for tests * Use temporary folder for microsoft/go SDK * Don't pass GOEXPERIMENT=systemcrypto when running tests with Go stdlib * Remove debugging statements * Reduce VM size (cherry picked from commit c0ae099) Co-authored-by: Shaunak Kashyap <[email protected]>
1 parent 25e8c8c commit 1c233b8

File tree

9 files changed

+195
-14
lines changed

9 files changed

+195
-14
lines changed

.buildkite/pipeline.yml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
env:
44
DOCKER_COMPOSE_VERSION: "1.25.5"
55
TERRAFORM_VERSION: "1.6.4"
6+
IMAGE_UBUNTU_X86_64_FIPS: "platform-ingest-fleet-server-ubuntu-2204-fips-1751684469"
67

78
steps:
89
- group: "Check and build"
@@ -81,24 +82,31 @@ steps:
8182
- build/*.xml
8283
- build/coverage*.out
8384

84-
- label: ":smartbear-testexecute: Run unit tests with requirefips build tag"
85+
- label: ":smartbear-testexecute: Run unit tests with requirefips build tag and FIPS provider"
8586
key: unit-test-fips-tag
8687
command: ".buildkite/scripts/unit_test.sh"
8788
env:
8889
FIPS: "true"
90+
GOEXPERIMENT: "systemcrypto"
91+
GO_DISTRO: "microsoft"
8992
agents:
90-
provider: "gcp"
93+
provider: "aws"
94+
image: "${IMAGE_UBUNTU_X86_64_FIPS}"
95+
instanceType: "m5.xlarge"
9196
artifact_paths:
9297
- build/*.xml
9398
- build/coverage*.out
9499

95-
- label: ":smartbear-testexecute: Run fips140=only unit tests"
100+
- label: ":smartbear-testexecute: Run fips140=only unit tests with FIPS provider"
96101
key: unit-test-fips140-only
97102
command: ".buildkite/scripts/unit_test_fipsonly.sh"
98103
env:
99104
FIPS: "true"
105+
GO_DISTRO: "stdlib"
100106
agents:
101-
provider: "gcp"
107+
provider: "aws"
108+
image: "${IMAGE_UBUNTU_X86_64_FIPS}"
109+
instanceType: "m5.xlarge"
102110
artifact_paths:
103111
- build/*.xml
104112
- build/coverage*.out

.buildkite/scripts/common.sh

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,12 @@ with_msft_go() {
5555
echo "Setting up microsoft/go"
5656
create_workspace
5757
check_platform_architeture
58+
59+
# Use a temporary folder to house the Go SDK downloaded from Microsoft
60+
tempfolder=$(mktemp -d)
5861
MSFT_DOWNLOAD_URL=https://aka.ms/golang/release/latest/go$(cat .go-version).${platform_type}-${arch_type}.tar.gz
59-
retry 5 $(curl -sL -o - $MSFT_DOWNLOAD_URL | tar -xz -f - -C ${WORKSPACE})
60-
export PATH="${PATH}:${WORKSPACE}/go/bin"
62+
retry 5 $(curl -sL -o - $MSFT_DOWNLOAD_URL | tar -xz -f - -C ${tempfolder}/)
63+
export PATH="${PATH}:${tempfolder}/go/bin"
6164
go version
6265
which go
6366
export PATH="${PATH}:$(go env GOPATH)/bin"

.buildkite/scripts/unit_test.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ source .buildkite/scripts/common.sh
66

77
add_bin_path
88

9-
with_go
9+
if [[ ${FIPS:-false} == "true" && ${GO_DISTRO:-stdlib} == "microsoft" ]]; then
10+
with_msft_go
11+
else
12+
with_go
13+
fi
1014

1115
with_mage
1216

.buildkite/scripts/unit_test_fipsonly.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ source .buildkite/scripts/common.sh
66

77
add_bin_path
88

9-
with_go
9+
if [[ ${FIPS:-false} == "true" && ${GO_DISTRO:-stdlib} == "microsoft" ]]; then
10+
with_msft_go
11+
else
12+
with_go
13+
fi
1014

1115
with_mage
1216

internal/pkg/es/client_test.go

Lines changed: 92 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@ import (
88
"context"
99
"crypto/tls"
1010
"crypto/x509"
11+
_ "embed"
1112
"fmt"
1213
"net/http"
1314
"net/http/httptest"
1415
"testing"
16+
"time"
1517

1618
"github.com/elastic/elastic-agent-libs/transport/tlscommon"
19+
"github.com/elastic/fleet-server/v7/internal/pkg/build"
1720
"github.com/elastic/fleet-server/v7/internal/pkg/config"
1821
"github.com/elastic/fleet-server/v7/internal/pkg/testing/certs"
1922
"github.com/stretchr/testify/require"
@@ -42,7 +45,7 @@ func TestClientCerts(t *testing.T) {
4245
defer server.Close()
4346

4447
// client does not use client certs
45-
client, err := NewClient(context.Background(), &config.Config{
48+
client, err := NewClient(t.Context(), &config.Config{
4649
Output: config.Output{
4750
Elasticsearch: config.Elasticsearch{
4851
Protocol: "https",
@@ -56,7 +59,7 @@ func TestClientCerts(t *testing.T) {
5659
}, false)
5760
require.NoError(t, err)
5861

59-
req, err := http.NewRequestWithContext(context.Background(), "GET", server.URL, nil)
62+
req, err := http.NewRequestWithContext(t.Context(), "GET", server.URL, nil)
6063
require.NoError(t, err)
6164

6265
resp, err := client.Perform(req)
@@ -87,7 +90,7 @@ func TestClientCerts(t *testing.T) {
8790
cert := certs.GenCert(t, ca)
8891

8992
// client uses valid, matching certs
90-
client, err := NewClient(context.Background(), &config.Config{
93+
client, err := NewClient(t.Context(), &config.Config{
9194
Output: config.Output{
9295
Elasticsearch: config.Elasticsearch{
9396
Protocol: "https",
@@ -105,7 +108,7 @@ func TestClientCerts(t *testing.T) {
105108
}, false)
106109
require.NoError(t, err)
107110

108-
req, err := http.NewRequestWithContext(context.Background(), "GET", server.URL, nil)
111+
req, err := http.NewRequestWithContext(t.Context(), "GET", server.URL, nil)
109112
require.NoError(t, err)
110113

111114
resp, err := client.Perform(req)
@@ -137,7 +140,7 @@ func TestClientCerts(t *testing.T) {
137140
cert := certs.GenCert(t, certCA)
138141

139142
// client uses certs that are signed by a different CA
140-
client, err := NewClient(context.Background(), &config.Config{
143+
client, err := NewClient(t.Context(), &config.Config{
141144
Output: config.Output{
142145
Elasticsearch: config.Elasticsearch{
143146
Protocol: "https",
@@ -155,10 +158,93 @@ func TestClientCerts(t *testing.T) {
155158
}, false)
156159
require.NoError(t, err)
157160

158-
req, err := http.NewRequestWithContext(context.Background(), "GET", server.URL, nil)
161+
req, err := http.NewRequestWithContext(t.Context(), "GET", server.URL, nil)
159162
require.NoError(t, err)
160163

161164
_, err = client.Perform(req) //nolint:bodyclose // no response is expected
162165
require.Error(t, err)
163166
})
164167
}
168+
169+
// TestConnectionTLS tries to connect to a test HTTPS server (pretending
170+
// to be an Elasticsearch cluster), that deliberately presents TLS options
171+
// that are not FIPS-compliant.
172+
// - If the test is running with a FIPS-capable build, the client, being FIPS-
173+
// capable, should fail the TLS handshake. Concretely, the conn.Connect() method
174+
// should return an error.
175+
// - If the test is not running with a FIPS-capable build, the client should
176+
// complete the TLS handshake successfully. Concretely, the conn.Connect() method
177+
// should not return an error.
178+
func TestConnectionTLS(t *testing.T) {
179+
server := startTLSServer(t)
180+
defer server.Close()
181+
182+
cfg := &config.Config{
183+
Output: config.Output{
184+
Elasticsearch: config.Elasticsearch{
185+
Protocol: "https",
186+
Hosts: []string{server.URL},
187+
TLS: &tlscommon.Config{
188+
Enabled: &enabled,
189+
CAs: []string{string(caCertPEM)},
190+
},
191+
},
192+
},
193+
}
194+
195+
ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second)
196+
defer cancel()
197+
198+
client, err := NewClient(ctx, cfg, false)
199+
require.NoError(t, err)
200+
201+
_, err = FetchESVersion(ctx, client)
202+
203+
if build.FIPSDistribution {
204+
require.ErrorContains(t, err, "tls: internal error")
205+
} else {
206+
require.NoError(t, err)
207+
}
208+
}
209+
210+
//go:embed testdata/ca.crt
211+
var caCertPEM []byte
212+
213+
//go:embed testdata/fips_invalid.key
214+
var serverKeyPEM []byte // RSA key with length = 1024 bits
215+
216+
//go:embed testdata/fips_invalid.crt
217+
var serverCertPEM []byte
218+
219+
//go:embed testdata/es_ping_response.json
220+
var esPingResponse []byte
221+
222+
func startTLSServer(t *testing.T) *httptest.Server {
223+
// Configure server and start it
224+
caCertPool := x509.NewCertPool()
225+
caCertPool.AppendCertsFromPEM(caCertPEM)
226+
227+
// Create HTTPS server
228+
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
229+
w.Header().Set("X-Elastic-Product", "Elasticsearch")
230+
w.Header().Set("Content-Type", "application/json")
231+
w.WriteHeader(http.StatusOK)
232+
_, err := w.Write(esPingResponse)
233+
require.NoError(t, err)
234+
}))
235+
236+
serverCert, err := tls.X509KeyPair(serverCertPEM, serverKeyPEM)
237+
require.NoError(t, err)
238+
239+
server.TLS = &tls.Config{
240+
MinVersion: tls.VersionTLS12,
241+
RootCAs: caCertPool,
242+
Certificates: []tls.Certificate{serverCert},
243+
ClientCAs: caCertPool,
244+
ClientAuth: tls.NoClientCert,
245+
}
246+
247+
server.StartTLS()
248+
249+
return server
250+
}

internal/pkg/es/testdata/ca.crt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDxTCCAq2gAwIBAgIUA9Gphn0fTO3Vuo7ePJpfebnebtgwDQYJKoZIhvcNAQEL
3+
BQAwZDELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMSEwHwYDVQQKDBhJ
4+
bnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxCzAJBgNVBAsMAkNBMRIwEAYDVQQDDAls
5+
b2NhbGhvc3QwHhcNMjUwNDIyMjIwMTU2WhcNMzAwNDIxMjIwMTU2WjBkMQswCQYD
6+
VQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsxITAfBgNVBAoMGEludGVybmV0IFdp
7+
ZGdpdHMgUHR5IEx0ZDELMAkGA1UECwwCQ0ExEjAQBgNVBAMMCWxvY2FsaG9zdDCC
8+
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIaR6W/pAoEFE5Hc6kgH2UTZ
9+
cd0LOT5hp3xtomKfnNONS5WgXDZbOqCSUY1+ZrG6NDrzG64vDC+AdtW7Zji7s+VA
10+
2hZ2DESbq+JBosAAyZbwzqosTCpp24on1VWXS+h8NT1nMGkvkkrKnM0fBK4Q9DVI
11+
H9QAtKysPnLwbfyWrnAHtjMd0bIrBPlt26g16l1nJklTwm2clD0ixE4MKw7lPZWE
12+
eJN+sK1CvA+r65huC7vDbNrL2OC+eNAiKtCH+AQR4HcB76kG9Qy/9+qfCGhizBlt
13+
mwceLDhz6FWgxKSgXwSfmorZLc1ecBfuWjqr9rfaUhOd4oLkmfbaEPqNu2V/rw0C
14+
AwEAAaNvMG0wHQYDVR0OBBYEFGzBvXdyHsVEY4bOAIiI3m4w7JfcMB8GA1UdIwQY
15+
MBaAFGzBvXdyHsVEY4bOAIiI3m4w7JfcMA8GA1UdEwEB/wQFMAMBAf8wGgYDVR0R
16+
BBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQCEbCFPgfT4
17+
DUkl/LozK8zUPEUV6mh53rTGQLhMbPfu7l1f6aSjvb1bIzYmrEFhlv/3yke+2/BC
18+
lGPYZrzdy2S9Xqv2ZthBoqE7cUrUGcq6U4y9helsM4gMfokpgBuNqwFVOGtSAlYy
19+
otUTRuIJeCLqAUV51wYROe9dOnY//ICEVrnRmLN4uXl64LMlBWbx76PS2s9dktr1
20+
5oWeF8whEhzg41FGsd6QPulKgT9h8+RR10hc3F4IFCVjtnp11E22x0/YYONbuAEH
21+
ZxL++PbvQRAvFGpTEmxH/AIq8yGQ90V94+HB7ocqz+3y0Nl93iNoanMOAJush3uL
22+
oIhHS8L9ENUv
23+
-----END CERTIFICATE-----
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "instance-0000000000",
3+
"cluster_name": "e8647d9cfc9e4e77a83554c5e6d5ee25",
4+
"cluster_uuid": "25mwr5sGTZyXIGwWMcSGmQ",
5+
"version": {
6+
"number": "9.2.0-SNAPSHOT",
7+
"build_flavor": "default",
8+
"build_type": "docker",
9+
"build_hash": "a6dfe646524f42869fc4820a67fffec2d76890dc",
10+
"build_date": "2025-07-01T22:12:52.207781139Z",
11+
"build_snapshot": true,
12+
"lucene_version": "10.2.2",
13+
"minimum_wire_compatibility_version": "8.19.0",
14+
"minimum_index_compatibility_version": "8.0.0"
15+
},
16+
"tagline": "You Know, for Search"
17+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDLzCCAhegAwIBAgIUIUHef0rqBRe0SWOxT/OwncexFiwwDQYJKoZIhvcNAQEL
3+
BQAwZDELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMSEwHwYDVQQKDBhJ
4+
bnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxCzAJBgNVBAsMAkNBMRIwEAYDVQQDDAls
5+
b2NhbGhvc3QwHhcNMjUwNDIyMjIwNjM1WhcNMzAwNDIxMjIwNjM1WjBYMQswCQYD
6+
VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEQMA4GA1UECgwHRWxhc3RpYzEO
7+
MAwGA1UECwwFQWdlbnQxEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0B
8+
AQEFAAOBjQAwgYkCgYEAo87+PNnpbu+hNkjPigLVKSmlDStd0OqcOmUlsegElMEk
9+
CArXkS+nDkD+p6o6QgGZ65mevmbJ9AxTV4tHQZ8YE695BgVa/MixzWNa2CjZu4OY
10+
CXJd1q8LfvkExXjp8+RVWuAh+FY5bZYGIlw2yLSHGbrE5k3F8YlfoL6ADkAf43kC
11+
AwEAAaNpMGcwHwYDVR0jBBgwFoAUbMG9d3IexURjhs4AiIjebjDsl9wwCQYDVR0T
12+
BAIwADAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwHQYDVR0OBBYEFFTZsQwH
13+
ipBtdR1XTu9x3yUg7H9uMA0GCSqGSIb3DQEBCwUAA4IBAQAH4gJMyjTvGUBUuih9
14+
VDcKsxxIGPhcBaoDRN7YX/qI5DapRA7/bP+1AIAoByrs6YTbMZYNB4hDEEywf59T
15+
pHNFt+3IsWOO3RCP1IeJscKO79Ga5WeKYJyV5HeNRpgNMsjslh+shzz29Qm4voiE
16+
Ab+x/CZYJu9Yw5JPENb035KWVAMCiN34afi3jgNcQVKYlyUwm27qVOLxTuUZU23a
17+
RzsFIc3/DpxIUZ7hD0qgR00jOXWAynhRpptKW7/tJmnoUk5nZuJUz3XDvE4UPvHj
18+
0KTf4RFfnNJbmO3ZVqj8QI9FdhOUYr/rJnrufyQBnCDEHmo9KIeIVynaijoBtem5
19+
mPgS
20+
-----END CERTIFICATE-----
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKPO/jzZ6W7voTZI
3+
z4oC1SkppQ0rXdDqnDplJbHoBJTBJAgK15Evpw5A/qeqOkIBmeuZnr5myfQMU1eL
4+
R0GfGBOveQYFWvzIsc1jWtgo2buDmAlyXdavC375BMV46fPkVVrgIfhWOW2WBiJc
5+
Nsi0hxm6xOZNxfGJX6C+gA5AH+N5AgMBAAECgYAD5fw6Zyge6Aeu4FGSTy7mC3WM
6+
ydv+8DLR99nRx6V1qeyK3yfIiHxDn4CbLYoOJTyPZRqOtuj5iVvbTO3GbIwg09tW
7+
4MMyS2AABIaO8Ke2MdXseI1Dt9sY7TnAPs8tz6KOEPksWXYOroqrzqXXmlG/yEei
8+
Fk9Z/UZB3ue33oq+IQJBAMm53Bi7ck2A7O4ueaeX9SkC5XpFpaUAimY5h8m+J0aB
9+
vXUjEX0BzEj7/+ocX+KaEXE0gfvQZHl2PUY5qCwnbdkCQQDP4YUknD+1lFH7l8tJ
10+
1whZfPEKn7MYAAS9wI5q11CTeaSkvq3z+5gX0EwLBn7WSqfcR3vQOBmyz0uzZhws
11+
e36hAkAP+Z4Kf12v8ZPR0PBla01I8CfIJRfXF1HegpPUUDDADqo4Soyp/6hz5zD/
12+
Ezwsr9LNykC49mnejJSRqSM+S+kRAkEAhKEZBmueBia0S7XkIJ9OF3Isg5+ybwyL
13+
+dihxK7NHNpOXkG90F1kA0WFTr99KxGEmXkOGKHCW6AAZ1wte3/rIQJASAjYaX8z
14+
cvqU4hUIsh0A1fQDD4j1HYrkOdsThrAoWRPu2mBDsD6IWnVmHzRB8coTZllP+mBA
15+
JL2QoEeSSdfniw==
16+
-----END PRIVATE KEY-----

0 commit comments

Comments
 (0)