Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

config: add support for extra TLS configuration #6378

Merged
merged 14 commits into from
Jan 21, 2025
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased]

### Added

- Add support for configuring `ClientCertificate` and `ClientKey` field for OTLP exporters in `go.opentelemetry.io/contrib/config`. (#6378)

### Fixed

- Use `context.Background()` as default context instead of nil in `go.opentelemetry.io/contrib/bridges/otellogr`. (#6527)
Expand Down
37 changes: 24 additions & 13 deletions config/v0.3.0/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"os"

"gopkg.in/yaml.v3"
Expand Down Expand Up @@ -159,19 +160,29 @@
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")
}

Check warning on line 180 in config/v0.3.0/config.go

View check run for this annotation

Codecov / codecov/patch

config/v0.3.0/config.go#L179-L180

Added lines #L179 - L180 were not covered by tests
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}

Check warning on line 185 in config/v0.3.0/config.go

View check run for this annotation

Codecov / codecov/patch

config/v0.3.0/config.go#L185

Added line #L185 was not covered by tests
}

return &tls.Config{
RootCAs: cp,
}, nil
return tlsConfig, nil
}
57 changes: 57 additions & 0 deletions config/v0.3.0/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 @@ -511,6 +512,62 @@ func TestSerializeJSON(t *testing.T) {
}
}

func TestCreateTLSConfig(t *testing.T) {
tests := []struct {
name string
caCertFile *string
clientCertFile *string
clientKeyFile *string
wantErrContains string
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"),
wantErrContains: "open nowhere.crt:",
},
{
name: "nonexistent-clientcert-file",
clientCertFile: ptr("nowhere.crt"),
clientKeyFile: ptr("nowhere.crt"),
wantErrContains: "could not use client certificate: open nowhere.crt:",
},
{
name: "bad-cacert-file",
caCertFile: ptr(filepath.Join("..", "testdata", "bad_cert.crt")),
wantErrContains: "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.wantErrContains != "" {
require.Contains(t, err.Error(), tt.wantErrContains)
} 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/v0.3.0/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...)
}
40 changes: 38 additions & 2 deletions config/v0.3.0/log_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,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 @@ -404,7 +422,25 @@ func TestLogProcessor(t *testing.T) {
},
},
},
wantErr: fmt.Errorf("could not create client tls credentials: %w", errors.New("failed to append certificate to the cert pool")),
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-exporter-with-path",
Expand Down
20 changes: 8 additions & 12 deletions config/v0.3.0/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
40 changes: 38 additions & 2 deletions config/v0.3.0/metric_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -494,7 +512,25 @@ func TestReader(t *testing.T) {
},
},
},
wantErr: fmt.Errorf("could not create client tls credentials: %w", errors.New("failed to append certificate to the cert pool")),
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-exporter-with-path",
Expand Down
20 changes: 8 additions & 12 deletions config/v0.3.0/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -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...)
}
Expand Down Expand Up @@ -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...)
}
Expand Down
Loading
Loading