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

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Added

- Generate server metrics with semantic conventions v1.26 in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` when `OTEL_SEMCONV_STABILITY_OPT_IN` is set to `http/dup`. (#6411)
- Added support for configuring `ClientCertificate` and `ClientKey` field when exporting OTLP over gRPC in `go.opentelemetry.io/contrib/config`. (#6378)

mattsains marked this conversation as resolved.
Show resolved Hide resolved
### Fixed

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
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/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 @@ -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 @@ -381,7 +399,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 @@ -475,7 +493,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
40 changes: 38 additions & 2 deletions config/v0.3.0/trace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -421,7 +439,25 @@ func TestSpanProcessor(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: "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-exporter-with-path",
Expand Down
Loading