Skip to content

Commit 136b0a3

Browse files
author
Matthew Sainsbury
committed
config: add client certificate and client key functionality open-telemetry#6378
1 parent 00786cc commit 136b0a3

9 files changed

+221
-56
lines changed

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
2323
- Added SNS instrumentation in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws`. (#6388)
2424
- Use a `sync.Pool` for metric options in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#6394)
2525
- Added support for configuring `Certificate` field when configuring OTLP exporters in `go.opentelemetry.io/contrib/config`. (#6376)
26+
- Added support for configuring `ClientCertificate` and `ClientKey` field when exporting OTLP over gRPC in `go.opentelemetry.io/contrib/config`. (#6378)
2627

2728
### Changed
2829

@@ -56,7 +57,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
5657
- Use `baggagecopy.NewLogProcessor` when configuring a Log Provider.
5758
- `NewLogProcessor` accepts a `Filter` function type that selects which baggage members are added to the log record.
5859

59-
### Changed
60+
### Changed
6061

6162
- Transform raw (`slog.KindAny`) attribute values to matching `log.Value` types.
6263
For example, `[]string{"foo", "bar"}` attribute value is now transformed to `log.SliceValue(log.StringValue("foo"), log.StringValue("bar"))` instead of `log.String("[foo bar"])`. (#6254)

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+
wantErr error
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+
wantErr: errors.New("open nowhere.crt: no such file or directory"),
543+
},
544+
{
545+
name: "nonexistent-clientcert-file",
546+
clientCertFile: ptr("nowhere.crt"),
547+
clientKeyFile: ptr("nowhere.crt"),
548+
wantErr: errors.New("could not use client certificate: open nowhere.crt: no such file or directory"),
549+
},
550+
{
551+
name: "bad-cacert-file",
552+
caCertFile: ptr(filepath.Join("testdata", "bad_cert.crt")),
553+
wantErr: errors.New("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.wantErr != nil {
562+
require.Equal(t, tt.wantErr.Error(), err.Error())
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
@@ -255,7 +255,25 @@ func TestLogProcessor(t *testing.T) {
255255
},
256256
},
257257
},
258-
wantErr: fmt.Errorf("could not create client tls credentials: %w", errors.New("credentials: failed to append certificates")),
258+
wantErr: fmt.Errorf("could not create certificate authority chain from certificate"),
259+
},
260+
{
261+
name: "batch/otlp-grpc-bad-client-certificate",
262+
processor: LogRecordProcessor{
263+
Batch: &BatchLogRecordProcessor{
264+
Exporter: LogRecordExporter{
265+
OTLP: &OTLP{
266+
Protocol: ptr("grpc"),
267+
Endpoint: ptr("localhost:4317"),
268+
Compression: ptr("gzip"),
269+
Timeout: ptr(1000),
270+
ClientCertificate: ptr(filepath.Join("..", "testdata", "bad_cert.crt")),
271+
ClientKey: ptr(filepath.Join("..", "testdata", "bad_cert.crt")),
272+
},
273+
},
274+
},
275+
},
276+
wantErr: fmt.Errorf("could not use client certificate: %w", errors.New("tls: failed to find any PEM data in certificate input")),
259277
},
260278
{
261279
name: "batch/otlp-grpc-exporter-no-scheme",
@@ -381,7 +399,25 @@ func TestLogProcessor(t *testing.T) {
381399
},
382400
},
383401
},
384-
wantErr: fmt.Errorf("could not create client tls credentials: %w", errors.New("failed to append certificate to the cert pool")),
402+
wantErr: fmt.Errorf("could not create certificate authority chain from certificate"),
403+
},
404+
{
405+
name: "batch/otlp-http-bad-client-certificate",
406+
processor: LogRecordProcessor{
407+
Batch: &BatchLogRecordProcessor{
408+
Exporter: LogRecordExporter{
409+
OTLP: &OTLP{
410+
Protocol: ptr("http/protobuf"),
411+
Endpoint: ptr("localhost:4317"),
412+
Compression: ptr("gzip"),
413+
Timeout: ptr(1000),
414+
ClientCertificate: ptr(filepath.Join("..", "testdata", "bad_cert.crt")),
415+
ClientKey: ptr(filepath.Join("..", "testdata", "bad_cert.crt")),
416+
},
417+
},
418+
},
419+
},
420+
wantErr: fmt.Errorf("could not use client certificate: %w", errors.New("tls: failed to find any PEM data in certificate input")),
385421
},
386422
{
387423
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",
@@ -475,7 +493,25 @@ func TestReader(t *testing.T) {
475493
},
476494
},
477495
},
478-
wantErr: fmt.Errorf("could not create client tls credentials: %w", errors.New("failed to append certificate to the cert pool")),
496+
wantErr: errors.New("could not create certificate authority chain from certificate"),
497+
},
498+
{
499+
name: "periodic/otlp-http-bad-client-certificate",
500+
reader: MetricReader{
501+
Periodic: &PeriodicMetricReader{
502+
Exporter: PushMetricExporter{
503+
OTLP: &OTLPMetric{
504+
Protocol: ptr("http/protobuf"),
505+
Endpoint: ptr("localhost:4317"),
506+
Compression: ptr("gzip"),
507+
Timeout: ptr(1000),
508+
ClientCertificate: ptr(filepath.Join("..", "testdata", "bad_cert.crt")),
509+
ClientKey: ptr(filepath.Join("..", "testdata", "bad_cert.crt")),
510+
},
511+
},
512+
},
513+
},
514+
wantErr: fmt.Errorf("could not use client certificate: %w", errors.New("tls: failed to find any PEM data in certificate input")),
479515
},
480516
{
481517
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)