Skip to content

Commit 3e4d35e

Browse files
authored
config: add support for extra TLS configuration (#6378)
1 parent 45e0b8b commit 3e4d35e

9 files changed

+223
-55
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
88

99
## [Unreleased]
1010

11+
### Added
12+
13+
- Add support for configuring `ClientCertificate` and `ClientKey` field for OTLP exporters in `go.opentelemetry.io/contrib/config`. (#6378)
14+
1115
### Fixed
1216

1317
- Use `context.Background()` as default context instead of nil in `go.opentelemetry.io/contrib/bridges/otellogr`. (#6527)

config/v0.3.0/config.go

+24-13
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"crypto/tls"
99
"crypto/x509"
1010
"errors"
11+
"fmt"
1112
"os"
1213

1314
"gopkg.in/yaml.v3"
@@ -159,19 +160,29 @@ func toStringMap(pairs []NameStringValuePair) map[string]string {
159160
return output
160161
}
161162

162-
// createTLSConfig creates a tls.Config from a raw certificate bytes
163-
// to verify a server certificate.
164-
func createTLSConfig(certFile string) (*tls.Config, error) {
165-
b, err := os.ReadFile(certFile)
166-
if err != nil {
167-
return nil, err
163+
// createTLSConfig creates a tls.Config from certificate files.
164+
func createTLSConfig(caCertFile *string, clientCertFile *string, clientKeyFile *string) (*tls.Config, error) {
165+
tlsConfig := &tls.Config{}
166+
if caCertFile != nil {
167+
caText, err := os.ReadFile(*caCertFile)
168+
if err != nil {
169+
return nil, err
170+
}
171+
certPool := x509.NewCertPool()
172+
if !certPool.AppendCertsFromPEM(caText) {
173+
return nil, errors.New("could not create certificate authority chain from certificate")
174+
}
175+
tlsConfig.RootCAs = certPool
168176
}
169-
cp := x509.NewCertPool()
170-
if ok := cp.AppendCertsFromPEM(b); !ok {
171-
return nil, errors.New("failed to append certificate to the cert pool")
177+
if clientCertFile != nil {
178+
if clientKeyFile == nil {
179+
return nil, errors.New("client certificate was provided but no client key was provided")
180+
}
181+
clientCert, err := tls.LoadX509KeyPair(*clientCertFile, *clientKeyFile)
182+
if err != nil {
183+
return nil, fmt.Errorf("could not use client certificate: %w", err)
184+
}
185+
tlsConfig.Certificates = []tls.Certificate{clientCert}
172186
}
173-
174-
return &tls.Config{
175-
RootCAs: cp,
176-
}, nil
187+
return tlsConfig, nil
177188
}

config/v0.3.0/config_test.go

+57
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package config
55

66
import (
77
"context"
8+
"crypto/tls"
89
"encoding/json"
910
"errors"
1011
"os"
@@ -511,6 +512,62 @@ func TestSerializeJSON(t *testing.T) {
511512
}
512513
}
513514

515+
func TestCreateTLSConfig(t *testing.T) {
516+
tests := []struct {
517+
name string
518+
caCertFile *string
519+
clientCertFile *string
520+
clientKeyFile *string
521+
wantErrContains string
522+
want func(*tls.Config, *testing.T)
523+
}{
524+
{
525+
name: "no-input",
526+
want: func(result *tls.Config, t *testing.T) {
527+
require.Nil(t, result.Certificates)
528+
require.Nil(t, result.RootCAs)
529+
},
530+
},
531+
{
532+
name: "only-cacert-provided",
533+
caCertFile: ptr(filepath.Join("..", "testdata", "ca.crt")),
534+
want: func(result *tls.Config, t *testing.T) {
535+
require.Nil(t, result.Certificates)
536+
require.NotNil(t, result.RootCAs)
537+
},
538+
},
539+
{
540+
name: "nonexistent-cacert-file",
541+
caCertFile: ptr("nowhere.crt"),
542+
wantErrContains: "open nowhere.crt:",
543+
},
544+
{
545+
name: "nonexistent-clientcert-file",
546+
clientCertFile: ptr("nowhere.crt"),
547+
clientKeyFile: ptr("nowhere.crt"),
548+
wantErrContains: "could not use client certificate: open nowhere.crt:",
549+
},
550+
{
551+
name: "bad-cacert-file",
552+
caCertFile: ptr(filepath.Join("..", "testdata", "bad_cert.crt")),
553+
wantErrContains: "could not create certificate authority chain from certificate",
554+
},
555+
}
556+
557+
for _, tt := range tests {
558+
t.Run(tt.name, func(t *testing.T) {
559+
got, err := createTLSConfig(tt.caCertFile, tt.clientCertFile, tt.clientKeyFile)
560+
561+
if tt.wantErrContains != "" {
562+
require.Contains(t, err.Error(), tt.wantErrContains)
563+
} else {
564+
require.NoError(t, err)
565+
tt.want(got, t)
566+
}
567+
})
568+
}
569+
}
570+
514571
func ptr[T any](v T) *T {
515572
return &v
516573
}

config/v0.3.0/log.go

+8-12
Original file line numberDiff line numberDiff line change
@@ -156,13 +156,11 @@ func otlpHTTPLogExporter(ctx context.Context, otlpConfig *OTLP) (sdklog.Exporter
156156
opts = append(opts, otlploghttp.WithHeaders(toStringMap(otlpConfig.Headers)))
157157
}
158158

159-
if otlpConfig.Certificate != nil {
160-
creds, err := createTLSConfig(*otlpConfig.Certificate)
161-
if err != nil {
162-
return nil, fmt.Errorf("could not create client tls credentials: %w", err)
163-
}
164-
opts = append(opts, otlploghttp.WithTLSClientConfig(creds))
159+
tlsConfig, err := createTLSConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey)
160+
if err != nil {
161+
return nil, err
165162
}
163+
opts = append(opts, otlploghttp.WithTLSClientConfig(tlsConfig))
166164

167165
return otlploghttp.New(ctx, opts...)
168166
}
@@ -206,13 +204,11 @@ func otlpGRPCLogExporter(ctx context.Context, otlpConfig *OTLP) (sdklog.Exporter
206204
opts = append(opts, otlploggrpc.WithHeaders(toStringMap(otlpConfig.Headers)))
207205
}
208206

209-
if otlpConfig.Certificate != nil {
210-
creds, err := credentials.NewClientTLSFromFile(*otlpConfig.Certificate, "")
211-
if err != nil {
212-
return nil, fmt.Errorf("could not create client tls credentials: %w", err)
213-
}
214-
opts = append(opts, otlploggrpc.WithTLSCredentials(creds))
207+
tlsConfig, err := createTLSConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey)
208+
if err != nil {
209+
return nil, err
215210
}
211+
opts = append(opts, otlploggrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig)))
216212

217213
return otlploggrpc.New(ctx, opts...)
218214
}

config/v0.3.0/log_test.go

+38-2
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,25 @@ func TestLogProcessor(t *testing.T) {
278278
},
279279
},
280280
},
281-
wantErr: fmt.Errorf("could not create client tls credentials: %w", errors.New("credentials: failed to append certificates")),
281+
wantErr: fmt.Errorf("could not create certificate authority chain from certificate"),
282+
},
283+
{
284+
name: "batch/otlp-grpc-bad-client-certificate",
285+
processor: LogRecordProcessor{
286+
Batch: &BatchLogRecordProcessor{
287+
Exporter: LogRecordExporter{
288+
OTLP: &OTLP{
289+
Protocol: ptr("grpc"),
290+
Endpoint: ptr("localhost:4317"),
291+
Compression: ptr("gzip"),
292+
Timeout: ptr(1000),
293+
ClientCertificate: ptr(filepath.Join("..", "testdata", "bad_cert.crt")),
294+
ClientKey: ptr(filepath.Join("..", "testdata", "bad_cert.crt")),
295+
},
296+
},
297+
},
298+
},
299+
wantErr: fmt.Errorf("could not use client certificate: %w", errors.New("tls: failed to find any PEM data in certificate input")),
282300
},
283301
{
284302
name: "batch/otlp-grpc-exporter-no-scheme",
@@ -404,7 +422,25 @@ func TestLogProcessor(t *testing.T) {
404422
},
405423
},
406424
},
407-
wantErr: fmt.Errorf("could not create client tls credentials: %w", errors.New("failed to append certificate to the cert pool")),
425+
wantErr: fmt.Errorf("could not create certificate authority chain from certificate"),
426+
},
427+
{
428+
name: "batch/otlp-http-bad-client-certificate",
429+
processor: LogRecordProcessor{
430+
Batch: &BatchLogRecordProcessor{
431+
Exporter: LogRecordExporter{
432+
OTLP: &OTLP{
433+
Protocol: ptr("http/protobuf"),
434+
Endpoint: ptr("localhost:4317"),
435+
Compression: ptr("gzip"),
436+
Timeout: ptr(1000),
437+
ClientCertificate: ptr(filepath.Join("..", "testdata", "bad_cert.crt")),
438+
ClientKey: ptr(filepath.Join("..", "testdata", "bad_cert.crt")),
439+
},
440+
},
441+
},
442+
},
443+
wantErr: fmt.Errorf("could not use client certificate: %w", errors.New("tls: failed to find any PEM data in certificate input")),
408444
},
409445
{
410446
name: "batch/otlp-http-exporter-with-path",

config/v0.3.0/metric.go

+8-12
Original file line numberDiff line numberDiff line change
@@ -182,13 +182,11 @@ func otlpHTTPMetricExporter(ctx context.Context, otlpConfig *OTLPMetric) (sdkmet
182182
}
183183
}
184184

185-
if otlpConfig.Certificate != nil {
186-
creds, err := createTLSConfig(*otlpConfig.Certificate)
187-
if err != nil {
188-
return nil, fmt.Errorf("could not create client tls credentials: %w", err)
189-
}
190-
opts = append(opts, otlpmetrichttp.WithTLSClientConfig(creds))
185+
tlsConfig, err := createTLSConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey)
186+
if err != nil {
187+
return nil, err
191188
}
189+
opts = append(opts, otlpmetrichttp.WithTLSClientConfig(tlsConfig))
192190

193191
return otlpmetrichttp.New(ctx, opts...)
194192
}
@@ -245,13 +243,11 @@ func otlpGRPCMetricExporter(ctx context.Context, otlpConfig *OTLPMetric) (sdkmet
245243
}
246244
}
247245

248-
if otlpConfig.Certificate != nil {
249-
creds, err := credentials.NewClientTLSFromFile(*otlpConfig.Certificate, "")
250-
if err != nil {
251-
return nil, fmt.Errorf("could not create client tls credentials: %w", err)
252-
}
253-
opts = append(opts, otlpmetricgrpc.WithTLSCredentials(creds))
246+
tlsConfig, err := createTLSConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey)
247+
if err != nil {
248+
return nil, err
254249
}
250+
opts = append(opts, otlpmetricgrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig)))
255251

256252
return otlpmetricgrpc.New(ctx, opts...)
257253
}

config/v0.3.0/metric_test.go

+38-2
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,25 @@ func TestReader(t *testing.T) {
248248
},
249249
},
250250
},
251-
wantErr: fmt.Errorf("could not create client tls credentials: %w", errors.New("credentials: failed to append certificates")),
251+
wantErr: errors.New("could not create certificate authority chain from certificate"),
252+
},
253+
{
254+
name: "periodic/otlp-grpc-bad-client-certificate",
255+
reader: MetricReader{
256+
Periodic: &PeriodicMetricReader{
257+
Exporter: PushMetricExporter{
258+
OTLP: &OTLPMetric{
259+
Protocol: ptr("grpc"),
260+
Endpoint: ptr("localhost:4317"),
261+
Compression: ptr("gzip"),
262+
Timeout: ptr(1000),
263+
ClientCertificate: ptr(filepath.Join("..", "testdata", "bad_cert.crt")),
264+
ClientKey: ptr(filepath.Join("..", "testdata", "bad_cert.crt")),
265+
},
266+
},
267+
},
268+
},
269+
wantErr: fmt.Errorf("could not use client certificate: %w", errors.New("tls: failed to find any PEM data in certificate input")),
252270
},
253271
{
254272
name: "periodic/otlp-grpc-exporter-no-endpoint",
@@ -494,7 +512,25 @@ func TestReader(t *testing.T) {
494512
},
495513
},
496514
},
497-
wantErr: fmt.Errorf("could not create client tls credentials: %w", errors.New("failed to append certificate to the cert pool")),
515+
wantErr: errors.New("could not create certificate authority chain from certificate"),
516+
},
517+
{
518+
name: "periodic/otlp-http-bad-client-certificate",
519+
reader: MetricReader{
520+
Periodic: &PeriodicMetricReader{
521+
Exporter: PushMetricExporter{
522+
OTLP: &OTLPMetric{
523+
Protocol: ptr("http/protobuf"),
524+
Endpoint: ptr("localhost:4317"),
525+
Compression: ptr("gzip"),
526+
Timeout: ptr(1000),
527+
ClientCertificate: ptr(filepath.Join("..", "testdata", "bad_cert.crt")),
528+
ClientKey: ptr(filepath.Join("..", "testdata", "bad_cert.crt")),
529+
},
530+
},
531+
},
532+
},
533+
wantErr: fmt.Errorf("could not use client certificate: %w", errors.New("tls: failed to find any PEM data in certificate input")),
498534
},
499535
{
500536
name: "periodic/otlp-http-exporter-with-path",

config/v0.3.0/trace.go

+8-12
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,11 @@ func otlpGRPCSpanExporter(ctx context.Context, otlpConfig *OTLP) (sdktrace.SpanE
129129
opts = append(opts, otlptracegrpc.WithHeaders(toStringMap(otlpConfig.Headers)))
130130
}
131131

132-
if otlpConfig.Certificate != nil {
133-
creds, err := credentials.NewClientTLSFromFile(*otlpConfig.Certificate, "")
134-
if err != nil {
135-
return nil, fmt.Errorf("could not create client tls credentials: %w", err)
136-
}
137-
opts = append(opts, otlptracegrpc.WithTLSCredentials(creds))
132+
tlsConfig, err := createTLSConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey)
133+
if err != nil {
134+
return nil, err
138135
}
136+
opts = append(opts, otlptracegrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig)))
139137

140138
return otlptracegrpc.New(ctx, opts...)
141139
}
@@ -174,13 +172,11 @@ func otlpHTTPSpanExporter(ctx context.Context, otlpConfig *OTLP) (sdktrace.SpanE
174172
opts = append(opts, otlptracehttp.WithHeaders(toStringMap(otlpConfig.Headers)))
175173
}
176174

177-
if otlpConfig.Certificate != nil {
178-
creds, err := createTLSConfig(*otlpConfig.Certificate)
179-
if err != nil {
180-
return nil, fmt.Errorf("could not create client tls credentials: %w", err)
181-
}
182-
opts = append(opts, otlptracehttp.WithTLSClientConfig(creds))
175+
tlsConfig, err := createTLSConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey)
176+
if err != nil {
177+
return nil, err
183178
}
179+
opts = append(opts, otlptracehttp.WithTLSClientConfig(tlsConfig))
184180

185181
return otlptracehttp.New(ctx, opts...)
186182
}

0 commit comments

Comments
 (0)