Skip to content

Commit

Permalink
config: add client certificate and client key functionality open-tele…
Browse files Browse the repository at this point in the history
  • Loading branch information
mattsains committed Jan 3, 2025
1 parent 00786cc commit ffb80c8
Show file tree
Hide file tree
Showing 9 changed files with 233 additions and 68 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Added SNS instrumentation in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws`. (#6388)
- Use a `sync.Pool` for metric options in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#6394)
- Added support for configuring `Certificate` field when configuring OTLP exporters in `go.opentelemetry.io/contrib/config`. (#6376)
- Added support for configuring `ClientCertificate` and `ClientKey` field when exporting OTLP over gRPC in `go.opentelemetry.io/contrib/config`. (#6378)

### Changed

Expand Down Expand Up @@ -56,7 +57,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)
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 @@ import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"os"

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

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

return &tls.Config{
RootCAs: cp,
}, nil
return tlsConfig, nil
}
57 changes: 57 additions & 0 deletions config/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...)
}
48 changes: 42 additions & 6 deletions config/v0.3.0/log_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ func TestLogProcessor(t *testing.T) {
Endpoint: ptr("localhost:4317"),
Compression: ptr("gzip"),
Timeout: ptr(1000),
Certificate: ptr(filepath.Join("..", "testdata", "ca.crt")),
Certificate: ptr(filepath.Join("testdata", "ca.crt")),
},
},
},
Expand All @@ -250,12 +250,30 @@ func TestLogProcessor(t *testing.T) {
Endpoint: ptr("localhost:4317"),
Compression: ptr("gzip"),
Timeout: ptr(1000),
Certificate: ptr(filepath.Join("..", "testdata", "bad_cert.crt")),
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")),
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 @@ -359,7 +377,7 @@ func TestLogProcessor(t *testing.T) {
Endpoint: ptr("localhost:4317"),
Compression: ptr("gzip"),
Timeout: ptr(1000),
Certificate: ptr(filepath.Join("..", "testdata", "ca.crt")),
Certificate: ptr(filepath.Join("testdata", "ca.crt")),
},
},
},
Expand All @@ -376,12 +394,30 @@ func TestLogProcessor(t *testing.T) {
Endpoint: ptr("localhost:4317"),
Compression: ptr("gzip"),
Timeout: ptr(1000),
Certificate: ptr(filepath.Join("..", "testdata", "bad_cert.crt")),
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 create client tls credentials: %w", errors.New("failed to append certificate to the cert pool")),
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
48 changes: 42 additions & 6 deletions config/v0.3.0/metric_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ func TestReader(t *testing.T) {
Endpoint: ptr("https://localhost:4317"),
Compression: ptr("gzip"),
Timeout: ptr(1000),
Certificate: ptr(filepath.Join("..", "testdata", "ca.crt")),
Certificate: ptr(filepath.Join("testdata", "ca.crt")),
},
},
},
Expand All @@ -243,12 +243,30 @@ func TestReader(t *testing.T) {
Endpoint: ptr("https://localhost:4317"),
Compression: ptr("gzip"),
Timeout: ptr(1000),
Certificate: ptr(filepath.Join("..", "testdata", "bad_cert.crt")),
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")),
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 @@ -453,7 +471,7 @@ func TestReader(t *testing.T) {
Endpoint: ptr("https://localhost:4317"),
Compression: ptr("gzip"),
Timeout: ptr(1000),
Certificate: ptr(filepath.Join("..", "testdata", "ca.crt")),
Certificate: ptr(filepath.Join("testdata", "ca.crt")),
},
},
},
Expand All @@ -470,12 +488,30 @@ func TestReader(t *testing.T) {
Endpoint: ptr("https://localhost:4317"),
Compression: ptr("gzip"),
Timeout: ptr(1000),
Certificate: ptr(filepath.Join("..", "testdata", "bad_cert.crt")),
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 create client tls credentials: %w", errors.New("failed to append certificate to the cert pool")),
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
Loading

0 comments on commit ffb80c8

Please sign in to comment.