From 89317f84f7d70c0f0896164a88274de5ba55f57c Mon Sep 17 00:00:00 2001 From: Alex Boten <223565+codeboten@users.noreply.github.com> Date: Wed, 27 Nov 2024 10:23:53 -0800 Subject: [PATCH 1/9] config: add support for certificate configuration Fixes https://github.com/open-telemetry/opentelemetry-go-contrib/issues/6351 Signed-off-by: Alex Boten <223565+codeboten@users.noreply.github.com> --- config/log.go | 8 ++++++++ config/log_test.go | 36 ++++++++++++++++++++++++++++++++++++ config/metric.go | 8 ++++++++ config/metric_test.go | 35 +++++++++++++++++++++++++++++++++++ config/testdata/bad_cert.crt | 1 + config/testdata/ca.crt | 20 ++++++++++++++++++++ config/trace.go | 9 +++++++++ config/trace_test.go | 36 ++++++++++++++++++++++++++++++++++++ 8 files changed, 153 insertions(+) create mode 100644 config/testdata/bad_cert.crt create mode 100644 config/testdata/ca.crt diff --git a/config/log.go b/config/log.go index 81a769238cd..abc60e83645 100644 --- a/config/log.go +++ b/config/log.go @@ -17,6 +17,7 @@ import ( "go.opentelemetry.io/otel/log/noop" sdklog "go.opentelemetry.io/otel/sdk/log" "go.opentelemetry.io/otel/sdk/resource" + "google.golang.org/grpc/credentials" ) func loggerProvider(cfg configOptions, res *resource.Resource) (log.LoggerProvider, shutdownFunc, error) { @@ -178,6 +179,13 @@ func otlpGRPCLogExporter(ctx context.Context, otlpConfig *OTLP) (sdklog.Exporter if u.Scheme == "http" { opts = append(opts, otlploggrpc.WithInsecure()) } + 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)) + } } if otlpConfig.Compression != nil { switch *otlpConfig.Compression { diff --git a/config/log_test.go b/config/log_test.go index 3e1efe11b3a..984f06b50b8 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,40 @@ 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 client tls credentials: %w", errors.New("credentials: failed to append certificates")), + }, { name: "batch/otlp-grpc-exporter-no-scheme", processor: LogRecordProcessor{ diff --git a/config/metric.go b/config/metric.go index de5629a7edb..91edf15eb3c 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" @@ -205,6 +206,13 @@ func otlpGRPCMetricExporter(ctx context.Context, otlpConfig *OTLPMetric) (sdkmet if u.Scheme == "http" { opts = append(opts, otlpmetricgrpc.WithInsecure()) } + 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)) + } } if otlpConfig.Compression != nil { diff --git a/config/metric_test.go b/config/metric_test.go index 07defc008bb..fac9b6c4a60 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,40 @@ 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: fmt.Errorf("could not create client tls credentials: %w", errors.New("credentials: failed to append certificates")), + }, { name: "periodic/otlp-grpc-exporter-no-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..38e77f0dc5e 100644 --- a/config/trace.go +++ b/config/trace.go @@ -17,6 +17,7 @@ import ( sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" + "google.golang.org/grpc/credentials" ) func tracerProvider(cfg configOptions, res *resource.Resource) (trace.TracerProvider, shutdownFunc, error) { @@ -108,6 +109,14 @@ func otlpGRPCSpanExporter(ctx context.Context, otlpConfig *OTLP) (sdktrace.SpanE if u.Scheme == "http" { opts = append(opts, otlptracegrpc.WithInsecure()) } + + 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, otlptracegrpc.WithTLSCredentials(creds)) + } } if otlpConfig.Compression != nil { diff --git a/config/trace_test.go b/config/trace_test.go index fb6e9b14cdc..80c3a91ac41 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,40 @@ 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.NewBatchProcessor(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: fmt.Errorf("could not create client tls credentials: %w", errors.New("credentials: failed to append certificates")), + }, { name: "batch/otlp-grpc-exporter-no-scheme", processor: SpanProcessor{ From cb372f3d1ffe82ce2117d76608b40c6983b0a4f7 Mon Sep 17 00:00:00 2001 From: Alex Boten <223565+codeboten@users.noreply.github.com> Date: Wed, 27 Nov 2024 10:25:38 -0800 Subject: [PATCH 2/9] changelog Signed-off-by: Alex Boten <223565+codeboten@users.noreply.github.com> --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e61f0af8f6b..ffe9e004984 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 certificates when exporting OTLP over gRPC in `go.opentelemetry.io/contrib/config`. (#6376) ### 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) From 1c4b25a41325e3b01ec9441da3fc4112d63b9d5f Mon Sep 17 00:00:00 2001 From: Alex Boten <223565+codeboten@users.noreply.github.com> Date: Wed, 27 Nov 2024 10:27:13 -0800 Subject: [PATCH 3/9] fix typo Signed-off-by: Alex Boten <223565+codeboten@users.noreply.github.com> --- config/trace_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/trace_test.go b/config/trace_test.go index 80c3a91ac41..c57ea90250c 100644 --- a/config/trace_test.go +++ b/config/trace_test.go @@ -278,7 +278,7 @@ func TestSpanProcessor(t *testing.T) { }, }, }, - wantProcessor: sdktrace.NewBatchProcessor(otlpGRPCExporter), + wantProcessor: sdktrace.NewBatchSpanProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-bad-ca-certificate", From 740a57b4bbbbd2309218c3993a0ba0ec06716241 Mon Sep 17 00:00:00 2001 From: Alex Boten <223565+codeboten@users.noreply.github.com> Date: Wed, 27 Nov 2024 10:30:57 -0800 Subject: [PATCH 4/9] tidy Signed-off-by: Alex Boten <223565+codeboten@users.noreply.github.com> --- config/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ) From 95ad7c565a31d7b481120700514c967e3d3aa0cb Mon Sep 17 00:00:00 2001 From: Alex Boten <223565+codeboten@users.noreply.github.com> Date: Wed, 27 Nov 2024 10:33:45 -0800 Subject: [PATCH 5/9] more tidying Signed-off-by: Alex Boten <223565+codeboten@users.noreply.github.com> --- config/log.go | 3 ++- config/trace.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/config/log.go b/config/log.go index abc60e83645..df460ea53d6 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" @@ -17,7 +19,6 @@ import ( "go.opentelemetry.io/otel/log/noop" sdklog "go.opentelemetry.io/otel/sdk/log" "go.opentelemetry.io/otel/sdk/resource" - "google.golang.org/grpc/credentials" ) func loggerProvider(cfg configOptions, res *resource.Resource) (log.LoggerProvider, shutdownFunc, error) { diff --git a/config/trace.go b/config/trace.go index 38e77f0dc5e..21ea912f42b 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" @@ -17,7 +19,6 @@ import ( sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" - "google.golang.org/grpc/credentials" ) func tracerProvider(cfg configOptions, res *resource.Resource) (trace.TracerProvider, shutdownFunc, error) { From e388ed9815b87a9b98fdb23f5e997c452bf5988e Mon Sep 17 00:00:00 2001 From: Alex Boten <223565+codeboten@users.noreply.github.com> Date: Wed, 27 Nov 2024 14:20:43 -0800 Subject: [PATCH 6/9] add support for http export as well Signed-off-by: Alex Boten <223565+codeboten@users.noreply.github.com> --- config/config.go | 20 ++++++++++++++++++++ config/log.go | 23 ++++++++++++++++------- config/log_test.go | 34 ++++++++++++++++++++++++++++++++++ config/metric.go | 23 ++++++++++++++++------- config/metric_test.go | 34 ++++++++++++++++++++++++++++++++++ config/trace.go | 24 ++++++++++++++++-------- config/trace_test.go | 34 ++++++++++++++++++++++++++++++++++ 7 files changed, 170 insertions(+), 22 deletions(-) diff --git a/config/config.go b/config/config.go index 80d72c9364d..3fb3b9abc5b 100644 --- a/config/config.go +++ b/config/config.go @@ -5,7 +5,10 @@ package config // import "go.opentelemetry.io/contrib/config" import ( "context" + "crypto/tls" + "crypto/x509" "errors" + "os" "gopkg.in/yaml.v3" @@ -155,3 +158,20 @@ 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 + } + cp := x509.NewCertPool() + if ok := cp.AppendCertsFromPEM(b); !ok { + return nil, errors.New("failed to append certificate to the cert pool") + } + + return &tls.Config{ + RootCAs: cp, + }, nil +} diff --git a/config/log.go b/config/log.go index df460ea53d6..ba729f8d792 100644 --- a/config/log.go +++ b/config/log.go @@ -156,6 +156,14 @@ 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)) + } + return otlploghttp.New(ctx, opts...) } @@ -180,13 +188,6 @@ func otlpGRPCLogExporter(ctx context.Context, otlpConfig *OTLP) (sdklog.Exporter if u.Scheme == "http" { opts = append(opts, otlploggrpc.WithInsecure()) } - 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)) - } } if otlpConfig.Compression != nil { switch *otlpConfig.Compression { @@ -205,5 +206,13 @@ 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)) + } + return otlploggrpc.New(ctx, opts...) } diff --git a/config/log_test.go b/config/log_test.go index 984f06b50b8..89092e13d03 100644 --- a/config/log_test.go +++ b/config/log_test.go @@ -349,6 +349,40 @@ 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 client tls credentials: %w", errors.New("failed to append certificate to the cert pool")), + }, { name: "batch/otlp-http-exporter-with-path", processor: LogRecordProcessor{ diff --git a/config/metric.go b/config/metric.go index 91edf15eb3c..5bd7bffe4f6 100644 --- a/config/metric.go +++ b/config/metric.go @@ -182,6 +182,14 @@ 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)) + } + return otlpmetrichttp.New(ctx, opts...) } @@ -206,13 +214,6 @@ func otlpGRPCMetricExporter(ctx context.Context, otlpConfig *OTLPMetric) (sdkmet if u.Scheme == "http" { opts = append(opts, otlpmetricgrpc.WithInsecure()) } - 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)) - } } if otlpConfig.Compression != nil { @@ -244,6 +245,14 @@ 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)) + } + return otlpmetricgrpc.New(ctx, opts...) } diff --git a/config/metric_test.go b/config/metric_test.go index fac9b6c4a60..00b663a58ea 100644 --- a/config/metric_test.go +++ b/config/metric_test.go @@ -443,6 +443,40 @@ 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: fmt.Errorf("could not create client tls credentials: %w", errors.New("failed to append certificate to the cert pool")), + }, { name: "periodic/otlp-http-exporter-with-path", reader: MetricReader{ diff --git a/config/trace.go b/config/trace.go index 21ea912f42b..158002051f5 100644 --- a/config/trace.go +++ b/config/trace.go @@ -110,14 +110,6 @@ func otlpGRPCSpanExporter(ctx context.Context, otlpConfig *OTLP) (sdktrace.SpanE if u.Scheme == "http" { opts = append(opts, otlptracegrpc.WithInsecure()) } - - 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, otlptracegrpc.WithTLSCredentials(creds)) - } } if otlpConfig.Compression != nil { @@ -137,6 +129,14 @@ func otlpGRPCSpanExporter(ctx context.Context, otlpConfig *OTLP) (sdktrace.SpanE opts = append(opts, otlptracegrpc.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, otlptracegrpc.WithTLSCredentials(creds)) + } + return otlptracegrpc.New(ctx, opts...) } @@ -174,6 +174,14 @@ func otlpHTTPSpanExporter(ctx context.Context, otlpConfig *OTLP) (sdktrace.SpanE opts = append(opts, otlptracehttp.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, otlptracehttp.WithTLSClientConfig(creds)) + } + return otlptracehttp.New(ctx, opts...) } diff --git a/config/trace_test.go b/config/trace_test.go index c57ea90250c..32f6b4c70b5 100644 --- a/config/trace_test.go +++ b/config/trace_test.go @@ -389,6 +389,40 @@ 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: fmt.Errorf("could not create client tls credentials: %w", errors.New("failed to append certificate to the cert pool")), + }, { name: "batch/otlp-http-exporter-with-path", processor: SpanProcessor{ From c10078236a374f8b18bfb615aa7160528a02132b Mon Sep 17 00:00:00 2001 From: Alex Boten <223565+codeboten@users.noreply.github.com> Date: Wed, 27 Nov 2024 14:30:20 -0800 Subject: [PATCH 7/9] add ignore to G402 as per the otel-go repo Signed-off-by: Alex Boten <223565+codeboten@users.noreply.github.com> --- .golangci.yml | 5 +++++ 1 file changed, 5 insertions(+) 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 From bca43ed846c1461c1233f33795d494262360652e Mon Sep 17 00:00:00 2001 From: Matthew Sainsbury Date: Wed, 27 Nov 2024 15:34:37 -0800 Subject: [PATCH 8/9] config: add client certificate and client key functionality --- config/config.go | 37 +++++++++++------ config/config_test.go | 57 +++++++++++++++++++++++++ config/log.go | 20 ++++----- config/log_test.go | 96 +++++++++++++++++++++++++++++-------------- config/metric.go | 20 ++++----- config/metric_test.go | 76 +++++++++++++++++++++++++--------- config/trace.go | 20 ++++----- config/trace_test.go | 96 +++++++++++++++++++++++++++++-------------- 8 files changed, 293 insertions(+), 129 deletions(-) diff --git a/config/config.go b/config/config.go index 3fb3b9abc5b..8a4201cf20e 100644 --- a/config/config.go +++ b/config/config.go @@ -8,6 +8,7 @@ import ( "crypto/tls" "crypto/x509" "errors" + "fmt" "os" "gopkg.in/yaml.v3" @@ -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 } 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/log.go b/config/log.go index ba729f8d792..bf38523e3cf 100644 --- a/config/log.go +++ b/config/log.go @@ -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...) } @@ -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...) } diff --git a/config/log_test.go b/config/log_test.go index 89092e13d03..982a82c2bdf 100644 --- a/config/log_test.go +++ b/config/log_test.go @@ -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", @@ -350,16 +368,22 @@ 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")}, + }, }, }, }, @@ -367,24 +391,29 @@ func TestLogProcessor(t *testing.T) { 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), @@ -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")}, @@ -407,21 +436,16 @@ 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")), }, }, }, @@ -429,27 +453,39 @@ func TestLogProcessor(t *testing.T) { 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", diff --git a/config/metric.go b/config/metric.go index 5bd7bffe4f6..f0a4ea10699 100644 --- a/config/metric.go +++ b/config/metric.go @@ -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...) } @@ -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...) } diff --git a/config/metric_test.go b/config/metric_test.go index 00b663a58ea..60685f77219 100644 --- a/config/metric_test.go +++ b/config/metric_test.go @@ -248,7 +248,25 @@ func TestReader(t *testing.T) { }, }, }, - wantErr: fmt.Errorf("could not create client tls credentials: %w", errors.New("credentials: failed to append certificates")), + 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", @@ -444,16 +462,18 @@ func TestReader(t *testing.T) { wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { - name: "periodic/otlp-http-good-ca-certificate", + name: "periodic/otlp-http-exporter-with-path", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), - Endpoint: ptr("https://localhost:4317"), + Endpoint: ptr("http://localhost:4318/path/123"), Compression: ptr("gzip"), Timeout: ptr(1000), - Certificate: ptr(filepath.Join("testdata", "ca.crt")), + Headers: []NameStringValuePair{ + {Name: "test", Value: ptr("test1")}, + }, }, }, }, @@ -461,30 +481,31 @@ func TestReader(t *testing.T) { wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { - name: "periodic/otlp-http-bad-ca-certificate", + name: "periodic/otlp-http-exporter-no-endpoint", 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")), + 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")), + wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { - name: "periodic/otlp-http-exporter-with-path", + name: "periodic/otlp-http-exporter-no-scheme", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), - Endpoint: ptr("http://localhost:4318/path/123"), + Endpoint: ptr("localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ @@ -497,17 +518,16 @@ func TestReader(t *testing.T) { wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { - name: "periodic/otlp-http-exporter-no-endpoint", + 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), - Headers: []NameStringValuePair{ - {Name: "test", Value: ptr("test1")}, - }, + Certificate: ptr(filepath.Join("testdata", "ca.crt")), }, }, }, @@ -515,23 +535,39 @@ func TestReader(t *testing.T) { wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { - name: "periodic/otlp-http-exporter-no-scheme", + name: "periodic/otlp-http-bad-ca-certificate", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), - Endpoint: ptr("localhost:4318"), + Endpoint: ptr("https://localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), - Headers: []NameStringValuePair{ - {Name: "test", Value: ptr("test1")}, - }, + Certificate: ptr(filepath.Join("testdata", "bad_cert.crt")), }, }, }, }, - wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), + 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", diff --git a/config/trace.go b/config/trace.go index 158002051f5..d57fbe86de4 100644 --- a/config/trace.go +++ b/config/trace.go @@ -129,13 +129,11 @@ func otlpGRPCSpanExporter(ctx context.Context, otlpConfig *OTLP) (sdktrace.SpanE opts = append(opts, otlptracegrpc.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, otlptracegrpc.WithTLSCredentials(creds)) + 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...) } @@ -174,13 +172,11 @@ func otlpHTTPSpanExporter(ctx context.Context, otlpConfig *OTLP) (sdktrace.SpanE opts = append(opts, otlptracehttp.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, otlptracehttp.WithTLSClientConfig(creds)) + 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 32f6b4c70b5..e63ef0fc105 100644 --- a/config/trace_test.go +++ b/config/trace_test.go @@ -295,7 +295,25 @@ func TestSpanProcessor(t *testing.T) { }, }, }, - wantErr: fmt.Errorf("could not create client tls credentials: %w", errors.New("credentials: failed to append certificates")), + 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", @@ -390,16 +408,22 @@ func TestSpanProcessor(t *testing.T) { wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { - name: "batch/otlp-http-good-ca-certificate", + name: "batch/otlp-http-exporter-with-path", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), Exporter: SpanExporter{ 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")}, + }, }, }, }, @@ -407,24 +431,29 @@ func TestSpanProcessor(t *testing.T) { wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { - name: "batch/otlp-http-bad-ca-certificate", + name: "batch/otlp-http-exporter-no-endpoint", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), 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")), + 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: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { - name: "batch/otlp-http-exporter-with-path", + name: "batch/otlp-http-exporter-no-scheme", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), @@ -434,8 +463,8 @@ func TestSpanProcessor(t *testing.T) { Exporter: SpanExporter{ 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")}, @@ -447,21 +476,16 @@ func TestSpanProcessor(t *testing.T) { wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { - name: "batch/otlp-http-exporter-no-endpoint", + name: "batch/otlp-http-good-ca-certificate", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ - MaxExportBatchSize: ptr(0), - ExportTimeout: ptr(0), - MaxQueueSize: ptr(0), - ScheduleDelay: ptr(0), Exporter: SpanExporter{ 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")), }, }, }, @@ -469,27 +493,39 @@ func TestSpanProcessor(t *testing.T) { wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { - name: "batch/otlp-http-exporter-no-scheme", + name: "batch/otlp-http-bad-ca-certificate", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ - MaxExportBatchSize: ptr(0), - ExportTimeout: ptr(0), - MaxQueueSize: ptr(0), - ScheduleDelay: ptr(0), Exporter: SpanExporter{ 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: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), + 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", From 9e399647985a9f6dbd61f915d92e2d4d1bcf4ba3 Mon Sep 17 00:00:00 2001 From: Matthew Sainsbury Date: Wed, 27 Nov 2024 14:08:08 -0800 Subject: [PATCH 9/9] add PR link to changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ffe9e004984..4d1e939718f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +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 certificates when exporting OTLP over gRPC in `go.opentelemetry.io/contrib/config`. (#6376) +- Added support for configuring `Certificate`, `ClientCertificate`, and `ClientKey` field when exporting OTLP over gRPC in `go.opentelemetry.io/contrib/config`. (#6376) (#6378) ### Changed