Skip to content

Commit e8789f7

Browse files
author
Matthew Sainsbury
committed
config: add client certificate and client key functionality
1 parent 37befd7 commit e8789f7

File tree

8 files changed

+293
-129
lines changed

8 files changed

+293
-129
lines changed

config/config.go

Lines changed: 24 additions & 13 deletions
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/config_test.go

Lines changed: 57 additions & 0 deletions
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"
@@ -489,6 +490,62 @@ func TestSerializeJSON(t *testing.T) {
489490
}
490491
}
491492

493+
func TestCreateTLSConfig(t *testing.T) {
494+
tests := []struct {
495+
name string
496+
caCertFile *string
497+
clientCertFile *string
498+
clientKeyFile *string
499+
wantErr error
500+
want func(*tls.Config, *testing.T)
501+
}{
502+
{
503+
name: "no-input",
504+
want: func(result *tls.Config, t *testing.T) {
505+
require.Nil(t, result.Certificates)
506+
require.Nil(t, result.RootCAs)
507+
},
508+
},
509+
{
510+
name: "only-cacert-provided",
511+
caCertFile: ptr(filepath.Join("testdata", "ca.crt")),
512+
want: func(result *tls.Config, t *testing.T) {
513+
require.Nil(t, result.Certificates)
514+
require.NotNil(t, result.RootCAs)
515+
},
516+
},
517+
{
518+
name: "nonexistent-cacert-file",
519+
caCertFile: ptr("nowhere.crt"),
520+
wantErr: errors.New("open nowhere.crt: no such file or directory"),
521+
},
522+
{
523+
name: "nonexistent-clientcert-file",
524+
clientCertFile: ptr("nowhere.crt"),
525+
clientKeyFile: ptr("nowhere.crt"),
526+
wantErr: errors.New("could not use client certificate: open nowhere.crt: no such file or directory"),
527+
},
528+
{
529+
name: "bad-cacert-file",
530+
caCertFile: ptr(filepath.Join("testdata", "bad_cert.crt")),
531+
wantErr: errors.New("could not create certificate authority chain from certificate"),
532+
},
533+
}
534+
535+
for _, tt := range tests {
536+
t.Run(tt.name, func(t *testing.T) {
537+
got, err := createTLSConfig(tt.caCertFile, tt.clientCertFile, tt.clientKeyFile)
538+
539+
if tt.wantErr != nil {
540+
require.Equal(t, tt.wantErr.Error(), err.Error())
541+
} else {
542+
require.NoError(t, err)
543+
tt.want(got, t)
544+
}
545+
})
546+
}
547+
}
548+
492549
func ptr[T any](v T) *T {
493550
return &v
494551
}

config/log.go

Lines changed: 8 additions & 12 deletions
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/log_test.go

Lines changed: 66 additions & 30 deletions
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",
@@ -350,41 +368,52 @@ func TestLogProcessor(t *testing.T) {
350368
wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter),
351369
},
352370
{
353-
name: "batch/otlp-http-good-ca-certificate",
371+
name: "batch/otlp-http-exporter-with-path",
354372
processor: LogRecordProcessor{
355373
Batch: &BatchLogRecordProcessor{
374+
MaxExportBatchSize: ptr(0),
375+
ExportTimeout: ptr(0),
376+
MaxQueueSize: ptr(0),
377+
ScheduleDelay: ptr(0),
356378
Exporter: LogRecordExporter{
357379
OTLP: &OTLP{
358380
Protocol: ptr("http/protobuf"),
359-
Endpoint: ptr("localhost:4317"),
360-
Compression: ptr("gzip"),
381+
Endpoint: ptr("http://localhost:4318/path/123"),
382+
Compression: ptr("none"),
361383
Timeout: ptr(1000),
362-
Certificate: ptr(filepath.Join("testdata", "ca.crt")),
384+
Headers: []NameStringValuePair{
385+
{Name: "test", Value: ptr("test1")},
386+
},
363387
},
364388
},
365389
},
366390
},
367391
wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter),
368392
},
369393
{
370-
name: "batch/otlp-http-bad-ca-certificate",
394+
name: "batch/otlp-http-exporter-no-endpoint",
371395
processor: LogRecordProcessor{
372396
Batch: &BatchLogRecordProcessor{
397+
MaxExportBatchSize: ptr(0),
398+
ExportTimeout: ptr(0),
399+
MaxQueueSize: ptr(0),
400+
ScheduleDelay: ptr(0),
373401
Exporter: LogRecordExporter{
374402
OTLP: &OTLP{
375403
Protocol: ptr("http/protobuf"),
376-
Endpoint: ptr("localhost:4317"),
377404
Compression: ptr("gzip"),
378405
Timeout: ptr(1000),
379-
Certificate: ptr(filepath.Join("testdata", "bad_cert.crt")),
406+
Headers: []NameStringValuePair{
407+
{Name: "test", Value: ptr("test1")},
408+
},
380409
},
381410
},
382411
},
383412
},
384-
wantErr: fmt.Errorf("could not create client tls credentials: %w", errors.New("failed to append certificate to the cert pool")),
413+
wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter),
385414
},
386415
{
387-
name: "batch/otlp-http-exporter-with-path",
416+
name: "batch/otlp-http-exporter-no-scheme",
388417
processor: LogRecordProcessor{
389418
Batch: &BatchLogRecordProcessor{
390419
MaxExportBatchSize: ptr(0),
@@ -394,8 +423,8 @@ func TestLogProcessor(t *testing.T) {
394423
Exporter: LogRecordExporter{
395424
OTLP: &OTLP{
396425
Protocol: ptr("http/protobuf"),
397-
Endpoint: ptr("http://localhost:4318/path/123"),
398-
Compression: ptr("none"),
426+
Endpoint: ptr("localhost:4318"),
427+
Compression: ptr("gzip"),
399428
Timeout: ptr(1000),
400429
Headers: []NameStringValuePair{
401430
{Name: "test", Value: ptr("test1")},
@@ -407,49 +436,56 @@ func TestLogProcessor(t *testing.T) {
407436
wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter),
408437
},
409438
{
410-
name: "batch/otlp-http-exporter-no-endpoint",
439+
name: "batch/otlp-http-good-ca-certificate",
411440
processor: LogRecordProcessor{
412441
Batch: &BatchLogRecordProcessor{
413-
MaxExportBatchSize: ptr(0),
414-
ExportTimeout: ptr(0),
415-
MaxQueueSize: ptr(0),
416-
ScheduleDelay: ptr(0),
417442
Exporter: LogRecordExporter{
418443
OTLP: &OTLP{
419444
Protocol: ptr("http/protobuf"),
445+
Endpoint: ptr("localhost:4317"),
420446
Compression: ptr("gzip"),
421447
Timeout: ptr(1000),
422-
Headers: []NameStringValuePair{
423-
{Name: "test", Value: ptr("test1")},
424-
},
448+
Certificate: ptr(filepath.Join("testdata", "ca.crt")),
425449
},
426450
},
427451
},
428452
},
429453
wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter),
430454
},
431455
{
432-
name: "batch/otlp-http-exporter-no-scheme",
456+
name: "batch/otlp-http-bad-ca-certificate",
433457
processor: LogRecordProcessor{
434458
Batch: &BatchLogRecordProcessor{
435-
MaxExportBatchSize: ptr(0),
436-
ExportTimeout: ptr(0),
437-
MaxQueueSize: ptr(0),
438-
ScheduleDelay: ptr(0),
439459
Exporter: LogRecordExporter{
440460
OTLP: &OTLP{
441461
Protocol: ptr("http/protobuf"),
442-
Endpoint: ptr("localhost:4318"),
462+
Endpoint: ptr("localhost:4317"),
443463
Compression: ptr("gzip"),
444464
Timeout: ptr(1000),
445-
Headers: []NameStringValuePair{
446-
{Name: "test", Value: ptr("test1")},
447-
},
465+
Certificate: ptr(filepath.Join("testdata", "bad_cert.crt")),
448466
},
449467
},
450468
},
451469
},
452-
wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter),
470+
wantErr: fmt.Errorf("could not create certificate authority chain from certificate"),
471+
},
472+
{
473+
name: "batch/otlp-http-bad-client-certificate",
474+
processor: LogRecordProcessor{
475+
Batch: &BatchLogRecordProcessor{
476+
Exporter: LogRecordExporter{
477+
OTLP: &OTLP{
478+
Protocol: ptr("http/protobuf"),
479+
Endpoint: ptr("localhost:4317"),
480+
Compression: ptr("gzip"),
481+
Timeout: ptr(1000),
482+
ClientCertificate: ptr(filepath.Join("testdata", "bad_cert.crt")),
483+
ClientKey: ptr(filepath.Join("testdata", "bad_cert.crt")),
484+
},
485+
},
486+
},
487+
},
488+
wantErr: fmt.Errorf("could not use client certificate: %w", errors.New("tls: failed to find any PEM data in certificate input")),
453489
},
454490
{
455491
name: "batch/otlp-http-invalid-protocol",

config/metric.go

Lines changed: 8 additions & 12 deletions
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
}

0 commit comments

Comments
 (0)