Skip to content

Commit be4502a

Browse files
[9.2](backport #47199) [OTel] Add TLS test to Logstash Exporter (#47230)
* [OTel] Add TLS test to Logstash Exporter (#47199) This commit verifies that key TLS features in Logstash Exporter still work as expected Add TLS tests - TLS connections - Self-signed certificates - Keypass support Add E2E TestLogstashExporterProxyURL to ensure proxy_url config is respected --------- Co-authored-by: Tiago Queiroz <[email protected]> (cherry picked from commit 55f5403)
1 parent 6d1c276 commit be4502a

File tree

7 files changed

+468
-76
lines changed

7 files changed

+468
-76
lines changed

NOTICE.txt

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14562,6 +14562,36 @@ Contents of probable licence file $GOMODCACHE/github.com/elastic/opentelemetry-c
1456214562
limitations under the License.
1456314563

1456414564

14565+
--------------------------------------------------------------------------------
14566+
Dependency : github.com/elastic/pkcs8
14567+
Version: v1.0.0
14568+
Licence type (autodetected): MIT
14569+
--------------------------------------------------------------------------------
14570+
14571+
Contents of probable licence file $GOMODCACHE/github.com/elastic/[email protected]/LICENSE:
14572+
14573+
The MIT License (MIT)
14574+
14575+
Copyright (c) 2014 youmark
14576+
14577+
Permission is hereby granted, free of charge, to any person obtaining a copy
14578+
of this software and associated documentation files (the "Software"), to deal
14579+
in the Software without restriction, including without limitation the rights
14580+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14581+
copies of the Software, and to permit persons to whom the Software is
14582+
furnished to do so, subject to the following conditions:
14583+
14584+
The above copyright notice and this permission notice shall be included in all
14585+
copies or substantial portions of the Software.
14586+
14587+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14588+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14589+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14590+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
14591+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
14592+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
14593+
SOFTWARE.
14594+
1456514595
--------------------------------------------------------------------------------
1456614596
Dependency : github.com/elastic/sarama
1456714597
Version: v1.19.1-0.20250603175145-7672917f26b6
@@ -45872,36 +45902,6 @@ Contents of probable licence file $GOMODCACHE/github.com/elastic/[email protected]
4587245902
limitations under the License.
4587345903

4587445904

45875-
--------------------------------------------------------------------------------
45876-
Dependency : github.com/elastic/pkcs8
45877-
Version: v1.0.0
45878-
Licence type (autodetected): MIT
45879-
--------------------------------------------------------------------------------
45880-
45881-
Contents of probable licence file $GOMODCACHE/github.com/elastic/[email protected]/LICENSE:
45882-
45883-
The MIT License (MIT)
45884-
45885-
Copyright (c) 2014 youmark
45886-
45887-
Permission is hereby granted, free of charge, to any person obtaining a copy
45888-
of this software and associated documentation files (the "Software"), to deal
45889-
in the Software without restriction, including without limitation the rights
45890-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
45891-
copies of the Software, and to permit persons to whom the Software is
45892-
furnished to do so, subject to the following conditions:
45893-
45894-
The above copyright notice and this permission notice shall be included in all
45895-
copies or substantial portions of the Software.
45896-
45897-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
45898-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
45899-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
45900-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
45901-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
45902-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
45903-
SOFTWARE.
45904-
4590545905
--------------------------------------------------------------------------------
4590645906
Dependency : github.com/elazarl/goproxy
4590745907
Version: v0.0.0-20240909085733-6741dbfc16a1

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ require (
237237
require (
238238
github.com/apache/arrow-go/v18 v18.4.1
239239
github.com/cilium/ebpf v0.19.0
240+
github.com/elastic/pkcs8 v1.0.0
240241
go.opentelemetry.io/collector/client v1.43.0
241242
go.opentelemetry.io/collector/component/componenttest v0.137.0
242243
go.opentelemetry.io/collector/confmap/xconfmap v0.137.0
@@ -320,7 +321,6 @@ require (
320321
github.com/elastic/elastic-transport-go/v8 v8.7.0 // indirect
321322
github.com/elastic/go-docappender/v2 v2.11.2 // indirect
322323
github.com/elastic/go-windows v1.0.2 // indirect
323-
github.com/elastic/pkcs8 v1.0.0 // indirect
324324
github.com/elazarl/goproxy v0.0.0-20240909085733-6741dbfc16a1 // indirect
325325
github.com/elazarl/goproxy/ext v0.0.0-20240909085733-6741dbfc16a1 // indirect
326326
github.com/emicklei/go-restful/v3 v3.11.0 // indirect

libbeat/otelbeat/oteltest/beatsauth_test.go

Lines changed: 11 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ func TestMTLS(t *testing.T) {
9797
}
9898

9999
// get client certificates paths
100-
clientCertificate, clientKey := getClientCerts(t, caCert)
100+
clientCertificate, clientKey := GetClientCerts(t, caCert, "")
101101

102102
// start test server with given server and root certs
103103
certPool := x509.NewCertPool()
@@ -113,11 +113,11 @@ func TestMTLS(t *testing.T) {
113113

114114
inputConfig := `
115115
receivers:
116-
filebeatreceiver:
116+
filebeatreceiver:
117117
output:
118118
elasticsearch:
119119
hosts: {{ .Host }}
120-
ssl:
120+
ssl:
121121
enabled: true
122122
certificate_authorities:
123123
- {{ .CACertificate }}
@@ -183,13 +183,13 @@ func TestCATrustedFingerPrint(t *testing.T) {
183183

184184
inputConfig := `
185185
receivers:
186-
filebeatreceiver:
186+
filebeatreceiver:
187187
output:
188188
elasticsearch:
189189
hosts: {{ .Host }}
190-
ssl:
190+
ssl:
191191
enabled: true
192-
ca_trusted_fingerprint: {{ .CATrustedFingerPrint }}
192+
ca_trusted_fingerprint: {{ .CATrustedFingerPrint }}
193193
`
194194

195195
var otelConfigBuffer bytes.Buffer
@@ -361,11 +361,11 @@ func TestVerificationMode(t *testing.T) {
361361

362362
inputConfig := `
363363
receivers:
364-
filebeatreceiver:
364+
filebeatreceiver:
365365
output:
366366
elasticsearch:
367367
hosts: {{ .Host }}
368-
ssl:
368+
ssl:
369369
enabled: true
370370
certificate_authorities:
371371
- {{ .CACertificate }}
@@ -457,7 +457,7 @@ func TestProxyHTTP(t *testing.T) {
457457
proxytest.WithRequestLog("https", t.Logf)},
458458
inputConfig: `
459459
receivers:
460-
filebeatreceiver:
460+
filebeatreceiver:
461461
output:
462462
elasticsearch:
463463
hosts: {{ .Host }}
@@ -490,7 +490,7 @@ receivers:
490490
})},
491491
inputConfig: `
492492
receivers:
493-
filebeatreceiver:
493+
filebeatreceiver:
494494
output:
495495
elasticsearch:
496496
hosts: {{ .Host }}
@@ -508,7 +508,7 @@ receivers:
508508
proxytest.WithRequestLog("https", t.Logf)},
509509
inputConfig: `
510510
receivers:
511-
filebeatreceiver:
511+
filebeatreceiver:
512512
output:
513513
elasticsearch:
514514
hosts: {{ .Host }}
@@ -683,40 +683,6 @@ func mustSendLogs(t *testing.T, exporter exporter.Logs, logs plog.Logs) error {
683683
return err
684684
}
685685

686-
// getClientCerts creates client certificates, writes them to a file and return the path of certificate and key
687-
func getClientCerts(t *testing.T, caCert tls.Certificate) (certificate string, key string) {
688-
// create client certificates
689-
clientCerts, err := tlscommontest.GenSignedCert(caCert, x509.KeyUsageCertSign, false, "client", []string{"localhost"}, []net.IP{net.IPv4(127, 0, 0, 1)}, false)
690-
if err != nil {
691-
t.Fatalf("could not generate certificates: %s", err)
692-
}
693-
694-
clientKey, err := x509.MarshalPKCS8PrivateKey(clientCerts.PrivateKey)
695-
if err != nil {
696-
t.Fatalf("could not marshal private key: %v", err)
697-
}
698-
699-
tempDir := t.TempDir()
700-
clientCertPath := filepath.Join(tempDir, "client-cert.pem")
701-
clientKeyPath := filepath.Join(tempDir, "client-key.pem")
702-
703-
if err = os.WriteFile(clientCertPath, pem.EncodeToMemory(&pem.Block{
704-
Type: "CERTIFICATE",
705-
Bytes: clientCerts.Leaf.Raw,
706-
}), 0o777); err != nil {
707-
t.Fatalf("could not write client certificate to file")
708-
}
709-
710-
if err = os.WriteFile(clientKeyPath, pem.EncodeToMemory(&pem.Block{
711-
Type: "RSA PRIVATE KEY",
712-
Bytes: clientKey,
713-
}), 0o777); err != nil {
714-
t.Fatalf("could not write client key to file")
715-
}
716-
717-
return clientCertPath, clientKeyPath
718-
}
719-
720686
func getTranslatedConf(t *testing.T, input []byte) *confmap.Conf {
721687
c := beatconverter.Converter{}
722688

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Licensed to Elasticsearch B.V. under one or more contributor
2+
// license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright
4+
// ownership. Elasticsearch B.V. licenses this file to you under
5+
// the Apache License, Version 2.0 (the "License"); you may
6+
// not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package oteltest
19+
20+
import (
21+
"crypto/tls"
22+
"crypto/x509"
23+
"encoding/pem"
24+
"net"
25+
"os"
26+
"path/filepath"
27+
"testing"
28+
29+
"github.com/elastic/elastic-agent-libs/transport/tlscommontest"
30+
"github.com/elastic/pkcs8"
31+
)
32+
33+
// GetClientCerts creates client certificates, writes them to a file and return the path of certificate and key
34+
// if passphrase is passed, it is used to encrypt the key file
35+
func GetClientCerts(t *testing.T, caCert tls.Certificate, passphrase string) (certificate string, key string) {
36+
// create client certificates
37+
clientCerts, err := tlscommontest.GenSignedCert(caCert, x509.KeyUsageCertSign, false, "client", []string{"localhost"}, []net.IP{net.IPv4(127, 0, 0, 1)}, false)
38+
if err != nil {
39+
t.Fatalf("could not generate certificates: %s", err)
40+
}
41+
42+
tempDir := t.TempDir()
43+
clientCertPath := filepath.Join(tempDir, "client-cert.pem")
44+
clientKeyPath := filepath.Join(tempDir, "client-key.pem")
45+
46+
if passphrase != "" {
47+
clientKey, err := pkcs8.MarshalPrivateKey(clientCerts.PrivateKey, []byte(passphrase), pkcs8.DefaultOpts)
48+
if err != nil {
49+
t.Fatalf("could not marshal private key: %v", err)
50+
}
51+
52+
if err = os.WriteFile(clientKeyPath, pem.EncodeToMemory(&pem.Block{
53+
Type: "ENCRYPTED PRIVATE KEY",
54+
Bytes: clientKey,
55+
}), 0400); err != nil {
56+
t.Fatalf("could not write client key to file")
57+
}
58+
} else {
59+
clientKey, err := x509.MarshalPKCS8PrivateKey(clientCerts.PrivateKey)
60+
if err != nil {
61+
t.Fatalf("could not marshal private key: %v", err)
62+
}
63+
if err = os.WriteFile(clientKeyPath, pem.EncodeToMemory(&pem.Block{
64+
Type: "RSA PRIVATE KEY",
65+
Bytes: clientKey,
66+
}), 0400); err != nil {
67+
t.Fatalf("could not write client key to file")
68+
}
69+
}
70+
71+
if err = os.WriteFile(clientCertPath, pem.EncodeToMemory(&pem.Block{
72+
Type: "CERTIFICATE",
73+
Bytes: clientCerts.Leaf.Raw,
74+
}), 0400); err != nil {
75+
t.Fatalf("could not write client certificate to file")
76+
}
77+
78+
return clientCertPath, clientKeyPath
79+
}

x-pack/filebeat/docker-compose.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ services:
5858
- --addr=:8090
5959
- --config=/files/config.yaml
6060

61+
socks5-proxy:
62+
image: serjs/go-socks5-proxy:latest
63+
container_name: socks5-proxy
64+
ports:
65+
- "1080:1080"
66+
environment:
67+
PROXY_USER: elastic
68+
PROXY_PASSWORD: changeme
6169
init-logstash:
6270
image: busybox:latest
6371
user: "0:0"
@@ -68,6 +76,7 @@ services:
6876
logstash:
6977
depends_on:
7078
- init-logstash
79+
- socks5-proxy
7180
extends:
7281
file: ${ES_BEATS}/testing/environments/${STACK_ENVIRONMENT}.yml
7382
service: logstash

x-pack/filebeat/tests/integration/otel_lsexporter_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,81 @@ processors:
149149
compareOutputFiles(t, fbFilePath, otelFilePath, ignoredFields)
150150
}
151151

152+
// TestLogstashExporterProxyURL verifies that Filebeat OTel mode can send data to Logstash via a SOCKS5 proxy.
153+
// Filebeat otel mode sends events to "logstash" via a socks5-proxy container running on localhost:1080
154+
func TestLogstashExporterProxyURL(t *testing.T) {
155+
// ensure the size of events is big enough
156+
numEvents := 3
157+
158+
var beatsCfgFile = `
159+
filebeat.inputs:
160+
- type: filestream
161+
id: filestream-input-id
162+
enabled: true
163+
paths:
164+
- %s
165+
output.logstash:
166+
hosts:
167+
- "logstash:%s"
168+
pipelining: 0
169+
worker: 1
170+
proxy_url: "socks5://elastic:changeme@localhost:1080"
171+
queue.mem.flush.timeout: 0s
172+
processors:
173+
- add_host_metadata: ~
174+
- add_fields:
175+
target: ""
176+
fields:
177+
testcase: %s
178+
`
179+
testCaseName := uuid.Must(uuid.NewV4()).String()
180+
events := generateEvents(numEvents)
181+
182+
// start filebeat in otel mode
183+
fbOTel := integration.NewBeat(
184+
t,
185+
"filebeat-otel",
186+
"../../filebeat.test",
187+
"otel",
188+
)
189+
190+
inputFilePath := filepath.Join(fbOTel.TempDir(), "event.json")
191+
writeEvents(t, inputFilePath, events)
192+
193+
fbOTel.WriteConfigFile(fmt.Sprintf(beatsCfgFile, inputFilePath, "5055", testCaseName))
194+
fbOTel.Start()
195+
defer fbOTel.Stop()
196+
197+
// Nginx endpoint URLs
198+
baseURL := "http://localhost:8082"
199+
outOTelFileURL := fmt.Sprintf("%s/%s_otel.json", baseURL, testCaseName)
200+
201+
// Logstash is outputting to a file inside its container, to access
202+
// this file we use Nginx to serve the output folder via HTTP
203+
// (see docker-compose.yml for Logstash and Nginx configuration).
204+
// Wait to ensure the file can be downloaded via HTTP, the file
205+
// being available indicates that Filebeat successfully sent data
206+
// to Logstash
207+
require.EventuallyWithTf(t,
208+
func(ct *assert.CollectT) {
209+
for _, url := range []string{outOTelFileURL} {
210+
checkURLHasContent(ct, url)
211+
}
212+
},
213+
30*time.Second,
214+
1*time.Second,
215+
"did not find Logstash output file served via Nginx")
216+
217+
// download files from Nginx
218+
otelFilePath := downloadToTestData(t, outOTelFileURL, fmt.Sprintf("%s_otel.json", testCaseName))
219+
otelEvents, err := readAllEvents(otelFilePath)
220+
221+
require.NoError(t, err, "failed to read otel events")
222+
require.Equal(t, numEvents, len(otelEvents),
223+
"different number of events: sent=%d, get=%d", numEvents, len(otelEvents))
224+
225+
}
226+
152227
func generateEvents(numEvents int) []string {
153228
gofakeit.Seed(time.Now().UnixNano())
154229

0 commit comments

Comments
 (0)