Skip to content

Commit

Permalink
config: add client certificate and client key functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
mattsains committed Dec 2, 2024
1 parent c100782 commit bca43ed
Show file tree
Hide file tree
Showing 8 changed files with 293 additions and 129 deletions.
37 changes: 24 additions & 13 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"os"

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

// createTLSConfig creates a tls.Config from a raw certificate bytes
// to verify a server certificate.
func createTLSConfig(certFile string) (*tls.Config, error) {
b, err := os.ReadFile(certFile)
if err != nil {
return nil, err
// createTLSConfig creates a tls.Config from certificate files.
func createTLSConfig(caCertFile *string, clientCertFile *string, clientKeyFile *string) (*tls.Config, error) {
tlsConfig := &tls.Config{}
if caCertFile != nil {
caText, err := os.ReadFile(*caCertFile)
if err != nil {
return nil, err
}
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(caText) {
return nil, errors.New("could not create certificate authority chain from certificate")
}
tlsConfig.RootCAs = certPool
}
cp := x509.NewCertPool()
if ok := cp.AppendCertsFromPEM(b); !ok {
return nil, errors.New("failed to append certificate to the cert pool")
if clientCertFile != nil {
if clientKeyFile == nil {
return nil, errors.New("client certificate was provided but no client key was provided")
}
clientCert, err := tls.LoadX509KeyPair(*clientCertFile, *clientKeyFile)
if err != nil {
return nil, fmt.Errorf("could not use client certificate: %w", err)
}
tlsConfig.Certificates = []tls.Certificate{clientCert}
}

return &tls.Config{
RootCAs: cp,
}, nil
return tlsConfig, nil
}
57 changes: 57 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package config

import (
"context"
"crypto/tls"
"encoding/json"
"errors"
"os"
Expand Down Expand Up @@ -489,6 +490,62 @@ func TestSerializeJSON(t *testing.T) {
}
}

func TestCreateTLSConfig(t *testing.T) {
tests := []struct {
name string
caCertFile *string
clientCertFile *string
clientKeyFile *string
wantErr error
want func(*tls.Config, *testing.T)
}{
{
name: "no-input",
want: func(result *tls.Config, t *testing.T) {
require.Nil(t, result.Certificates)
require.Nil(t, result.RootCAs)
},
},
{
name: "only-cacert-provided",
caCertFile: ptr(filepath.Join("testdata", "ca.crt")),
want: func(result *tls.Config, t *testing.T) {
require.Nil(t, result.Certificates)
require.NotNil(t, result.RootCAs)
},
},
{
name: "nonexistent-cacert-file",
caCertFile: ptr("nowhere.crt"),
wantErr: errors.New("open nowhere.crt: no such file or directory"),
},
{
name: "nonexistent-clientcert-file",
clientCertFile: ptr("nowhere.crt"),
clientKeyFile: ptr("nowhere.crt"),
wantErr: errors.New("could not use client certificate: open nowhere.crt: no such file or directory"),
},
{
name: "bad-cacert-file",
caCertFile: ptr(filepath.Join("testdata", "bad_cert.crt")),
wantErr: errors.New("could not create certificate authority chain from certificate"),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := createTLSConfig(tt.caCertFile, tt.clientCertFile, tt.clientKeyFile)

if tt.wantErr != nil {
require.Equal(t, tt.wantErr.Error(), err.Error())
} else {
require.NoError(t, err)
tt.want(got, t)
}
})
}
}

func ptr[T any](v T) *T {
return &v
}
20 changes: 8 additions & 12 deletions config/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,11 @@ func otlpHTTPLogExporter(ctx context.Context, otlpConfig *OTLP) (sdklog.Exporter
opts = append(opts, otlploghttp.WithHeaders(toStringMap(otlpConfig.Headers)))
}

if otlpConfig.Certificate != nil {
creds, err := createTLSConfig(*otlpConfig.Certificate)
if err != nil {
return nil, fmt.Errorf("could not create client tls credentials: %w", err)
}
opts = append(opts, otlploghttp.WithTLSClientConfig(creds))
tlsConfig, err := createTLSConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey)
if err != nil {
return nil, err
}
opts = append(opts, otlploghttp.WithTLSClientConfig(tlsConfig))

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

if otlpConfig.Certificate != nil {
creds, err := credentials.NewClientTLSFromFile(*otlpConfig.Certificate, "")
if err != nil {
return nil, fmt.Errorf("could not create client tls credentials: %w", err)
}
opts = append(opts, otlploggrpc.WithTLSCredentials(creds))
tlsConfig, err := createTLSConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey)
if err != nil {
return nil, err
}
opts = append(opts, otlploggrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig)))

return otlploggrpc.New(ctx, opts...)
}
96 changes: 66 additions & 30 deletions config/log_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,25 @@ func TestLogProcessor(t *testing.T) {
},
},
},
wantErr: fmt.Errorf("could not create client tls credentials: %w", errors.New("credentials: failed to append certificates")),
wantErr: fmt.Errorf("could not create certificate authority chain from certificate"),
},
{
name: "batch/otlp-grpc-bad-client-certificate",
processor: LogRecordProcessor{
Batch: &BatchLogRecordProcessor{
Exporter: LogRecordExporter{
OTLP: &OTLP{
Protocol: ptr("grpc"),
Endpoint: ptr("localhost:4317"),
Compression: ptr("gzip"),
Timeout: ptr(1000),
ClientCertificate: ptr(filepath.Join("testdata", "bad_cert.crt")),
ClientKey: ptr(filepath.Join("testdata", "bad_cert.crt")),
},
},
},
},
wantErr: fmt.Errorf("could not use client certificate: %w", errors.New("tls: failed to find any PEM data in certificate input")),
},
{
name: "batch/otlp-grpc-exporter-no-scheme",
Expand Down Expand Up @@ -350,41 +368,52 @@ func TestLogProcessor(t *testing.T) {
wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter),
},
{
name: "batch/otlp-http-good-ca-certificate",
name: "batch/otlp-http-exporter-with-path",
processor: LogRecordProcessor{
Batch: &BatchLogRecordProcessor{
MaxExportBatchSize: ptr(0),
ExportTimeout: ptr(0),
MaxQueueSize: ptr(0),
ScheduleDelay: ptr(0),
Exporter: LogRecordExporter{
OTLP: &OTLP{
Protocol: ptr("http/protobuf"),
Endpoint: ptr("localhost:4317"),
Compression: ptr("gzip"),
Endpoint: ptr("http://localhost:4318/path/123"),
Compression: ptr("none"),
Timeout: ptr(1000),
Certificate: ptr(filepath.Join("testdata", "ca.crt")),
Headers: []NameStringValuePair{
{Name: "test", Value: ptr("test1")},
},
},
},
},
},
wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter),
},
{
name: "batch/otlp-http-bad-ca-certificate",
name: "batch/otlp-http-exporter-no-endpoint",
processor: LogRecordProcessor{
Batch: &BatchLogRecordProcessor{
MaxExportBatchSize: ptr(0),
ExportTimeout: ptr(0),
MaxQueueSize: ptr(0),
ScheduleDelay: ptr(0),
Exporter: LogRecordExporter{
OTLP: &OTLP{
Protocol: ptr("http/protobuf"),
Endpoint: ptr("localhost:4317"),
Compression: ptr("gzip"),
Timeout: ptr(1000),
Certificate: ptr(filepath.Join("testdata", "bad_cert.crt")),
Headers: []NameStringValuePair{
{Name: "test", Value: ptr("test1")},
},
},
},
},
},
wantErr: fmt.Errorf("could not create client tls credentials: %w", errors.New("failed to append certificate to the cert pool")),
wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter),
},
{
name: "batch/otlp-http-exporter-with-path",
name: "batch/otlp-http-exporter-no-scheme",
processor: LogRecordProcessor{
Batch: &BatchLogRecordProcessor{
MaxExportBatchSize: ptr(0),
Expand All @@ -394,8 +423,8 @@ func TestLogProcessor(t *testing.T) {
Exporter: LogRecordExporter{
OTLP: &OTLP{
Protocol: ptr("http/protobuf"),
Endpoint: ptr("http://localhost:4318/path/123"),
Compression: ptr("none"),
Endpoint: ptr("localhost:4318"),
Compression: ptr("gzip"),
Timeout: ptr(1000),
Headers: []NameStringValuePair{
{Name: "test", Value: ptr("test1")},
Expand All @@ -407,49 +436,56 @@ func TestLogProcessor(t *testing.T) {
wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter),
},
{
name: "batch/otlp-http-exporter-no-endpoint",
name: "batch/otlp-http-good-ca-certificate",
processor: LogRecordProcessor{
Batch: &BatchLogRecordProcessor{
MaxExportBatchSize: ptr(0),
ExportTimeout: ptr(0),
MaxQueueSize: ptr(0),
ScheduleDelay: ptr(0),
Exporter: LogRecordExporter{
OTLP: &OTLP{
Protocol: ptr("http/protobuf"),
Endpoint: ptr("localhost:4317"),
Compression: ptr("gzip"),
Timeout: ptr(1000),
Headers: []NameStringValuePair{
{Name: "test", Value: ptr("test1")},
},
Certificate: ptr(filepath.Join("testdata", "ca.crt")),
},
},
},
},
wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter),
},
{
name: "batch/otlp-http-exporter-no-scheme",
name: "batch/otlp-http-bad-ca-certificate",
processor: LogRecordProcessor{
Batch: &BatchLogRecordProcessor{
MaxExportBatchSize: ptr(0),
ExportTimeout: ptr(0),
MaxQueueSize: ptr(0),
ScheduleDelay: ptr(0),
Exporter: LogRecordExporter{
OTLP: &OTLP{
Protocol: ptr("http/protobuf"),
Endpoint: ptr("localhost:4318"),
Endpoint: ptr("localhost:4317"),
Compression: ptr("gzip"),
Timeout: ptr(1000),
Headers: []NameStringValuePair{
{Name: "test", Value: ptr("test1")},
},
Certificate: ptr(filepath.Join("testdata", "bad_cert.crt")),
},
},
},
},
wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter),
wantErr: fmt.Errorf("could not create certificate authority chain from certificate"),
},
{
name: "batch/otlp-http-bad-client-certificate",
processor: LogRecordProcessor{
Batch: &BatchLogRecordProcessor{
Exporter: LogRecordExporter{
OTLP: &OTLP{
Protocol: ptr("http/protobuf"),
Endpoint: ptr("localhost:4317"),
Compression: ptr("gzip"),
Timeout: ptr(1000),
ClientCertificate: ptr(filepath.Join("testdata", "bad_cert.crt")),
ClientKey: ptr(filepath.Join("testdata", "bad_cert.crt")),
},
},
},
},
wantErr: fmt.Errorf("could not use client certificate: %w", errors.New("tls: failed to find any PEM data in certificate input")),
},
{
name: "batch/otlp-http-invalid-protocol",
Expand Down
20 changes: 8 additions & 12 deletions config/metric.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,13 +182,11 @@ func otlpHTTPMetricExporter(ctx context.Context, otlpConfig *OTLPMetric) (sdkmet
}
}

if otlpConfig.Certificate != nil {
creds, err := createTLSConfig(*otlpConfig.Certificate)
if err != nil {
return nil, fmt.Errorf("could not create client tls credentials: %w", err)
}
opts = append(opts, otlpmetrichttp.WithTLSClientConfig(creds))
tlsConfig, err := createTLSConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey)
if err != nil {
return nil, err
}
opts = append(opts, otlpmetrichttp.WithTLSClientConfig(tlsConfig))

return otlpmetrichttp.New(ctx, opts...)
}
Expand Down Expand Up @@ -245,13 +243,11 @@ func otlpGRPCMetricExporter(ctx context.Context, otlpConfig *OTLPMetric) (sdkmet
}
}

if otlpConfig.Certificate != nil {
creds, err := credentials.NewClientTLSFromFile(*otlpConfig.Certificate, "")
if err != nil {
return nil, fmt.Errorf("could not create client tls credentials: %w", err)
}
opts = append(opts, otlpmetricgrpc.WithTLSCredentials(creds))
tlsConfig, err := createTLSConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey)
if err != nil {
return nil, err
}
opts = append(opts, otlpmetricgrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig)))

return otlpmetricgrpc.New(ctx, opts...)
}
Expand Down
Loading

0 comments on commit bca43ed

Please sign in to comment.