diff --git a/.golangci.yml b/.golangci.yml index a86971538da..13e41d8d2a9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -70,6 +70,11 @@ issues: - gosec - perfsprint - usestdlibvars + # Ignoring gosec G402: TLS MinVersion too low + # as the https://pkg.go.dev/crypto/tls#Config handles MinVersion default well. + - text: "G402: TLS MinVersion too low." + linters: + - gosec include: # revive exported should have comment or be unexported. - EXC0012 diff --git a/CHANGELOG.md b/CHANGELOG.md index e61f0af8f6b..4d1e939718f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Added support exporting logs via OTLP over gRPC in `go.opentelemetry.io/contrib/config`. (#6340) - The `go.opentelemetry.io/contrib/bridges/otellogr` module. This module provides an OpenTelemetry logging bridge for `github.com/go-logr/logr`. (#6386) +- Added support for configuring `Certificate`, `ClientCertificate`, and `ClientKey` field when exporting OTLP over gRPC in `go.opentelemetry.io/contrib/config`. (#6376) (#6378) ### Changed @@ -41,7 +42,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Use `baggagecopy.NewLogProcessor` when configuring a Log Provider. - `NewLogProcessor` accepts a `Filter` function type that selects which baggage members are added to the log record. -### Changed +### Changed - Transform raw (`slog.KindAny`) attribute values to matching `log.Value` types. 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) diff --git a/config/config.go b/config/config.go index 80d72c9364d..8a4201cf20e 100644 --- a/config/config.go +++ b/config/config.go @@ -5,7 +5,11 @@ package config // import "go.opentelemetry.io/contrib/config" import ( "context" + "crypto/tls" + "crypto/x509" "errors" + "fmt" + "os" "gopkg.in/yaml.v3" @@ -155,3 +159,30 @@ func toStringMap(pairs []NameStringValuePair) map[string]string { } return output } + +// 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 + } + 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 tlsConfig, nil +} diff --git a/config/config_test.go b/config/config_test.go index cdc3ddcd45b..fe3f654fe09 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -5,6 +5,7 @@ package config import ( "context" + "crypto/tls" "encoding/json" "errors" "os" @@ -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 } diff --git a/config/go.mod b/config/go.mod index 55b21465983..1fc1302ed10 100644 --- a/config/go.mod +++ b/config/go.mod @@ -22,6 +22,7 @@ require ( go.opentelemetry.io/otel/sdk/log v0.8.0 go.opentelemetry.io/otel/sdk/metric v1.32.0 go.opentelemetry.io/otel/trace v1.32.0 + google.golang.org/grpc v1.68.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -47,6 +48,5 @@ require ( golang.org/x/text v0.20.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 // indirect - google.golang.org/grpc v1.68.0 // indirect google.golang.org/protobuf v1.35.2 // indirect ) diff --git a/config/log.go b/config/log.go index 81a769238cd..bf38523e3cf 100644 --- a/config/log.go +++ b/config/log.go @@ -10,6 +10,8 @@ import ( "net/url" "time" + "google.golang.org/grpc/credentials" + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" @@ -154,6 +156,12 @@ func otlpHTTPLogExporter(ctx context.Context, otlpConfig *OTLP) (sdklog.Exporter opts = append(opts, otlploghttp.WithHeaders(toStringMap(otlpConfig.Headers))) } + 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...) } @@ -196,5 +204,11 @@ func otlpGRPCLogExporter(ctx context.Context, otlpConfig *OTLP) (sdklog.Exporter opts = append(opts, otlploggrpc.WithHeaders(toStringMap(otlpConfig.Headers))) } + 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...) } diff --git a/config/log_test.go b/config/log_test.go index 3e1efe11b3a..982a82c2bdf 100644 --- a/config/log_test.go +++ b/config/log_test.go @@ -6,7 +6,9 @@ package config // import "go.opentelemetry.io/contrib/config" import ( "context" "errors" + "fmt" "net/url" + "path/filepath" "reflect" "testing" @@ -221,6 +223,58 @@ func TestLogProcessor(t *testing.T) { }, wantProcessor: sdklog.NewBatchProcessor(otlpGRPCExporter), }, + { + name: "batch/otlp-grpc-good-ca-certificate", + processor: LogRecordProcessor{ + Batch: &BatchLogRecordProcessor{ + Exporter: LogRecordExporter{ + OTLP: &OTLP{ + Protocol: ptr("grpc"), + Endpoint: ptr("localhost:4317"), + Compression: ptr("gzip"), + Timeout: ptr(1000), + Certificate: ptr(filepath.Join("testdata", "ca.crt")), + }, + }, + }, + }, + wantProcessor: sdklog.NewBatchProcessor(otlpGRPCExporter), + }, + { + name: "batch/otlp-grpc-bad-ca-certificate", + processor: LogRecordProcessor{ + Batch: &BatchLogRecordProcessor{ + Exporter: LogRecordExporter{ + OTLP: &OTLP{ + Protocol: ptr("grpc"), + Endpoint: ptr("localhost:4317"), + Compression: ptr("gzip"), + Timeout: ptr(1000), + Certificate: ptr(filepath.Join("testdata", "bad_cert.crt")), + }, + }, + }, + }, + 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", processor: LogRecordProcessor{ @@ -381,6 +435,58 @@ func TestLogProcessor(t *testing.T) { }, wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), }, + { + name: "batch/otlp-http-good-ca-certificate", + processor: LogRecordProcessor{ + Batch: &BatchLogRecordProcessor{ + Exporter: LogRecordExporter{ + OTLP: &OTLP{ + Protocol: ptr("http/protobuf"), + Endpoint: ptr("localhost:4317"), + Compression: ptr("gzip"), + Timeout: ptr(1000), + Certificate: ptr(filepath.Join("testdata", "ca.crt")), + }, + }, + }, + }, + wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), + }, + { + name: "batch/otlp-http-bad-ca-certificate", + processor: LogRecordProcessor{ + Batch: &BatchLogRecordProcessor{ + 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")), + }, + }, + }, + }, + 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", processor: LogRecordProcessor{ diff --git a/config/metric.go b/config/metric.go index de5629a7edb..f0a4ea10699 100644 --- a/config/metric.go +++ b/config/metric.go @@ -17,6 +17,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" + "google.golang.org/grpc/credentials" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" @@ -181,6 +182,12 @@ func otlpHTTPMetricExporter(ctx context.Context, otlpConfig *OTLPMetric) (sdkmet } } + 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...) } @@ -236,6 +243,12 @@ func otlpGRPCMetricExporter(ctx context.Context, otlpConfig *OTLPMetric) (sdkmet } } + 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...) } diff --git a/config/metric_test.go b/config/metric_test.go index 07defc008bb..60685f77219 100644 --- a/config/metric_test.go +++ b/config/metric_test.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "net/url" + "path/filepath" "reflect" "testing" "time" @@ -215,6 +216,58 @@ func TestReader(t *testing.T) { }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, + { + name: "periodic/otlp-grpc-good-ca-certificate", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: PushMetricExporter{ + OTLP: &OTLPMetric{ + Protocol: ptr("grpc"), + Endpoint: ptr("https://localhost:4317"), + Compression: ptr("gzip"), + Timeout: ptr(1000), + Certificate: ptr(filepath.Join("testdata", "ca.crt")), + }, + }, + }, + }, + wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), + }, + { + name: "periodic/otlp-grpc-bad-ca-certificate", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: PushMetricExporter{ + OTLP: &OTLPMetric{ + Protocol: ptr("grpc"), + Endpoint: ptr("https://localhost:4317"), + Compression: ptr("gzip"), + Timeout: ptr(1000), + Certificate: ptr(filepath.Join("testdata", "bad_cert.crt")), + }, + }, + }, + }, + wantErr: errors.New("could not create certificate authority chain from certificate"), + }, + { + name: "periodic/otlp-grpc-bad-client-certificate", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: PushMetricExporter{ + OTLP: &OTLPMetric{ + 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: "periodic/otlp-grpc-exporter-no-endpoint", reader: MetricReader{ @@ -464,6 +517,58 @@ func TestReader(t *testing.T) { }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, + { + name: "periodic/otlp-http-good-ca-certificate", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: PushMetricExporter{ + OTLP: &OTLPMetric{ + Protocol: ptr("http/protobuf"), + Endpoint: ptr("https://localhost:4317"), + Compression: ptr("gzip"), + Timeout: ptr(1000), + Certificate: ptr(filepath.Join("testdata", "ca.crt")), + }, + }, + }, + }, + wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), + }, + { + name: "periodic/otlp-http-bad-ca-certificate", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: PushMetricExporter{ + OTLP: &OTLPMetric{ + Protocol: ptr("http/protobuf"), + Endpoint: ptr("https://localhost:4317"), + Compression: ptr("gzip"), + Timeout: ptr(1000), + Certificate: ptr(filepath.Join("testdata", "bad_cert.crt")), + }, + }, + }, + }, + wantErr: errors.New("could not create certificate authority chain from certificate"), + }, + { + name: "periodic/otlp-http-bad-client-certificate", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: PushMetricExporter{ + OTLP: &OTLPMetric{ + 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: "periodic/otlp-http-invalid-endpoint", reader: MetricReader{ diff --git a/config/testdata/bad_cert.crt b/config/testdata/bad_cert.crt new file mode 100644 index 00000000000..dbbd2b8576f --- /dev/null +++ b/config/testdata/bad_cert.crt @@ -0,0 +1 @@ +This is intentionally not a PEM formatted cert file. diff --git a/config/testdata/ca.crt b/config/testdata/ca.crt new file mode 100644 index 00000000000..2272f84e64d --- /dev/null +++ b/config/testdata/ca.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDNjCCAh4CCQC0I5IQT7eziDANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJB +VTESMBAGA1UECAwJQXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEjAQBgNVBAoM +CU15T3JnTmFtZTEVMBMGA1UEAwwMTXlDb21tb25OYW1lMB4XDTIyMDgwMzA0MTky +MVoXDTMyMDczMTA0MTkyMVowXTELMAkGA1UEBhMCQVUxEjAQBgNVBAgMCUF1c3Ry +YWxpYTEPMA0GA1UEBwwGU3lkbmV5MRIwEAYDVQQKDAlNeU9yZ05hbWUxFTATBgNV +BAMMDE15Q29tbW9uTmFtZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AMhGP0dy3zvkdx9zI+/XVjPOWlER0OUp7Sgzidc3nLOk42+bH4ofIVNtOFVqlNKi +O1bImu238VdBhd6R5IZZ1ZdIMcCeDgSJYu2X9wA3m4PKz8IdXo5ly2OHghhmCvqG +WxgqDj5wPXiczQwuf1EcDMtRWbXJ6Z/XH1U68R/kRdNLkiZ2LwtjoQpis5XYckLL +CrdF+AL6GeDIe0Mh9QGs26Vux+2kvaOGNUWRPE6Wt4GkqyKqmzYfR9HbflJ4xHT2 +I+jE1lg+jMBeom7z8Z90RE4GGcHjO+Vens/88r5EAjTnFj1Kb5gL2deSHY1m/++R +Z/kRyg+zQJyw4fAzlAA4+VkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAM3gRdTKX +eGwGYVmmKqA2vTxeigQYLHml7OSopcWj2wJfxfp49HXPRuvgpQn9iubxO3Zmhd83 +2X1E+T0A8oy5CfxgpAhHb3lY0jm3TjKXm6m+dSODwL3uND8tX+SqR8sRTFxPvPuo +pmvhdTZoRI3EzIiHLTgCuSU25JNP/vrVoKk0JvCkDYTU/WcVfj0v95DTMoWR4JGz +mtBwrgD0EM2XRw5ZMc7sMPli1gqmCbCQUrDZ+rPB78WDCBILBd8Cz75qYTUp98BY +akJyBckdJHAdyEQYDKa9HpmpexOO7IhSXCTEN1DEBgpZgEi/lBDRG/b0OzenUUgt +LUABtWt3pNQ9HA== +-----END CERTIFICATE----- diff --git a/config/trace.go b/config/trace.go index 1b08b2c56ae..d57fbe86de4 100644 --- a/config/trace.go +++ b/config/trace.go @@ -10,6 +10,8 @@ import ( "net/url" "time" + "google.golang.org/grpc/credentials" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" @@ -127,6 +129,12 @@ func otlpGRPCSpanExporter(ctx context.Context, otlpConfig *OTLP) (sdktrace.SpanE opts = append(opts, otlptracegrpc.WithHeaders(toStringMap(otlpConfig.Headers))) } + tlsConfig, err := createTLSConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey) + if err != nil { + return nil, err + } + opts = append(opts, otlptracegrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig))) + return otlptracegrpc.New(ctx, opts...) } @@ -164,6 +172,12 @@ func otlpHTTPSpanExporter(ctx context.Context, otlpConfig *OTLP) (sdktrace.SpanE opts = append(opts, otlptracehttp.WithHeaders(toStringMap(otlpConfig.Headers))) } + tlsConfig, err := createTLSConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey) + if err != nil { + return nil, err + } + opts = append(opts, otlptracehttp.WithTLSClientConfig(tlsConfig)) + return otlptracehttp.New(ctx, opts...) } diff --git a/config/trace_test.go b/config/trace_test.go index fb6e9b14cdc..e63ef0fc105 100644 --- a/config/trace_test.go +++ b/config/trace_test.go @@ -6,7 +6,9 @@ package config import ( "context" "errors" + "fmt" "net/url" + "path/filepath" "reflect" "testing" @@ -261,6 +263,58 @@ func TestSpanProcessor(t *testing.T) { }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpGRPCExporter), }, + { + name: "batch/otlp-grpc-good-ca-certificate", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + Exporter: SpanExporter{ + OTLP: &OTLP{ + Protocol: ptr("grpc"), + Endpoint: ptr("localhost:4317"), + Compression: ptr("gzip"), + Timeout: ptr(1000), + Certificate: ptr(filepath.Join("testdata", "ca.crt")), + }, + }, + }, + }, + wantProcessor: sdktrace.NewBatchSpanProcessor(otlpGRPCExporter), + }, + { + name: "batch/otlp-grpc-bad-ca-certificate", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + Exporter: SpanExporter{ + OTLP: &OTLP{ + Protocol: ptr("grpc"), + Endpoint: ptr("localhost:4317"), + Compression: ptr("gzip"), + Timeout: ptr(1000), + Certificate: ptr(filepath.Join("testdata", "bad_cert.crt")), + }, + }, + }, + }, + wantErr: errors.New("could not create certificate authority chain from certificate"), + }, + { + name: "batch/otlp-grpc-bad-client-certificate", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + Exporter: SpanExporter{ + 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", processor: SpanProcessor{ @@ -421,6 +475,58 @@ func TestSpanProcessor(t *testing.T) { }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, + { + name: "batch/otlp-http-good-ca-certificate", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + Exporter: SpanExporter{ + OTLP: &OTLP{ + Protocol: ptr("http/protobuf"), + Endpoint: ptr("localhost:4317"), + Compression: ptr("gzip"), + Timeout: ptr(1000), + Certificate: ptr(filepath.Join("testdata", "ca.crt")), + }, + }, + }, + }, + wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), + }, + { + name: "batch/otlp-http-bad-ca-certificate", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + Exporter: SpanExporter{ + OTLP: &OTLP{ + Protocol: ptr("http/protobuf"), + Endpoint: ptr("localhost:4317"), + Compression: ptr("gzip"), + Timeout: ptr(1000), + Certificate: ptr(filepath.Join("testdata", "bad_cert.crt")), + }, + }, + }, + }, + wantErr: errors.New("could not create certificate authority chain from certificate"), + }, + { + name: "batch/otlp-http-bad-client-certificate", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + Exporter: SpanExporter{ + 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-endpoint", processor: SpanProcessor{