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 configuration #6378

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
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
5 changes: 5 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ issues:
- gosec
- perfsprint
- usestdlibvars
# Ignoring gosec G402: TLS MinVersion too low
dmathieu marked this conversation as resolved.
Show resolved Hide resolved
# 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
Expand Down
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Added support exporting logs via OTLP over gRPC in `go.opentelemetry.io/contrib/config`. (#6340)
- The `go.opentelemetry.io/contrib/bridges/otellogr` module.
This module provides an OpenTelemetry logging bridge for `github.com/go-logr/logr`. (#6386)
- Added support for configuring `Certificate`, `ClientCertificate`, and `ClientKey` field when exporting OTLP over gRPC in `go.opentelemetry.io/contrib/config`. (#6376) (#6378)

### Changed

Expand Down Expand Up @@ -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)
Expand Down
31 changes: 31 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@

import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"os"

"gopkg.in/yaml.v3"

Expand Down Expand Up @@ -155,3 +159,30 @@
}
return output
}

// createTLSConfig creates a tls.Config from certificate files.
func createTLSConfig(caCertFile *string, clientCertFile *string, clientKeyFile *string) (*tls.Config, error) {
tlsConfig := &tls.Config{}
if caCertFile != nil {
caText, err := os.ReadFile(*caCertFile)
if err != nil {
return nil, err
}
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(caText) {
return nil, errors.New("could not create certificate authority chain from certificate")
}
tlsConfig.RootCAs = certPool
}
if clientCertFile != nil {
if clientKeyFile == nil {
return nil, errors.New("client certificate was provided but no client key was provided")
}

Check warning on line 180 in config/config.go

View check run for this annotation

Codecov / codecov/patch

config/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/config.go

View check run for this annotation

Codecov / codecov/patch

config/config.go#L185

Added line #L185 was not covered by tests
}
return tlsConfig, nil
}
57 changes: 57 additions & 0 deletions config/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 @@ -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
}
2 changes: 1 addition & 1 deletion config/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand All @@ -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
)
14 changes: 14 additions & 0 deletions config/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -154,6 +156,12 @@ func otlpHTTPLogExporter(ctx context.Context, otlpConfig *OTLP) (sdklog.Exporter
opts = append(opts, otlploghttp.WithHeaders(toStringMap(otlpConfig.Headers)))
}

tlsConfig, err := createTLSConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey)
if err != nil {
return nil, err
}
opts = append(opts, otlploghttp.WithTLSClientConfig(tlsConfig))

return otlploghttp.New(ctx, opts...)
}

Expand Down Expand Up @@ -196,5 +204,11 @@ func otlpGRPCLogExporter(ctx context.Context, otlpConfig *OTLP) (sdklog.Exporter
opts = append(opts, otlploggrpc.WithHeaders(toStringMap(otlpConfig.Headers)))
}

tlsConfig, err := createTLSConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey)
if err != nil {
return nil, err
}
opts = append(opts, otlploggrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig)))

return otlploggrpc.New(ctx, opts...)
}
106 changes: 106 additions & 0 deletions config/log_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ package config // import "go.opentelemetry.io/contrib/config"
import (
"context"
"errors"
"fmt"
"net/url"
"path/filepath"
"reflect"
"testing"

Expand Down Expand Up @@ -221,6 +223,58 @@ func TestLogProcessor(t *testing.T) {
},
wantProcessor: sdklog.NewBatchProcessor(otlpGRPCExporter),
},
{
name: "batch/otlp-grpc-good-ca-certificate",
processor: LogRecordProcessor{
Batch: &BatchLogRecordProcessor{
Exporter: LogRecordExporter{
OTLP: &OTLP{
Protocol: ptr("grpc"),
Endpoint: ptr("localhost:4317"),
Compression: ptr("gzip"),
Timeout: ptr(1000),
Certificate: ptr(filepath.Join("testdata", "ca.crt")),
},
},
},
},
wantProcessor: sdklog.NewBatchProcessor(otlpGRPCExporter),
},
{
name: "batch/otlp-grpc-bad-ca-certificate",
processor: LogRecordProcessor{
Batch: &BatchLogRecordProcessor{
Exporter: LogRecordExporter{
OTLP: &OTLP{
Protocol: ptr("grpc"),
Endpoint: ptr("localhost:4317"),
Compression: ptr("gzip"),
Timeout: ptr(1000),
Certificate: ptr(filepath.Join("testdata", "bad_cert.crt")),
},
},
},
},
wantErr: fmt.Errorf("could not create certificate authority chain from certificate"),
},
{
name: "batch/otlp-grpc-bad-client-certificate",
processor: LogRecordProcessor{
Batch: &BatchLogRecordProcessor{
Exporter: LogRecordExporter{
OTLP: &OTLP{
Protocol: ptr("grpc"),
Endpoint: ptr("localhost:4317"),
Compression: ptr("gzip"),
Timeout: ptr(1000),
ClientCertificate: ptr(filepath.Join("testdata", "bad_cert.crt")),
ClientKey: ptr(filepath.Join("testdata", "bad_cert.crt")),
},
},
},
},
wantErr: fmt.Errorf("could not use client certificate: %w", errors.New("tls: failed to find any PEM data in certificate input")),
},
{
name: "batch/otlp-grpc-exporter-no-scheme",
processor: LogRecordProcessor{
Expand Down Expand Up @@ -381,6 +435,58 @@ func TestLogProcessor(t *testing.T) {
},
wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter),
},
{
name: "batch/otlp-http-good-ca-certificate",
processor: LogRecordProcessor{
Batch: &BatchLogRecordProcessor{
Exporter: LogRecordExporter{
OTLP: &OTLP{
Protocol: ptr("http/protobuf"),
Endpoint: ptr("localhost:4317"),
Compression: ptr("gzip"),
Timeout: ptr(1000),
Certificate: ptr(filepath.Join("testdata", "ca.crt")),
},
},
},
},
wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter),
},
{
name: "batch/otlp-http-bad-ca-certificate",
processor: LogRecordProcessor{
Batch: &BatchLogRecordProcessor{
Exporter: LogRecordExporter{
OTLP: &OTLP{
Protocol: ptr("http/protobuf"),
Endpoint: ptr("localhost:4317"),
Compression: ptr("gzip"),
Timeout: ptr(1000),
Certificate: ptr(filepath.Join("testdata", "bad_cert.crt")),
},
},
},
},
wantErr: fmt.Errorf("could not create certificate authority chain from certificate"),
},
{
name: "batch/otlp-http-bad-client-certificate",
processor: LogRecordProcessor{
Batch: &BatchLogRecordProcessor{
Exporter: LogRecordExporter{
OTLP: &OTLP{
Protocol: ptr("http/protobuf"),
Endpoint: ptr("localhost:4317"),
Compression: ptr("gzip"),
Timeout: ptr(1000),
ClientCertificate: ptr(filepath.Join("testdata", "bad_cert.crt")),
ClientKey: ptr(filepath.Join("testdata", "bad_cert.crt")),
},
},
},
},
wantErr: fmt.Errorf("could not use client certificate: %w", errors.New("tls: failed to find any PEM data in certificate input")),
},
{
name: "batch/otlp-http-invalid-protocol",
processor: LogRecordProcessor{
Expand Down
13 changes: 13 additions & 0 deletions config/metric.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -181,6 +182,12 @@ func otlpHTTPMetricExporter(ctx context.Context, otlpConfig *OTLPMetric) (sdkmet
}
}

tlsConfig, err := createTLSConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey)
if err != nil {
return nil, err
}
opts = append(opts, otlpmetrichttp.WithTLSClientConfig(tlsConfig))

return otlpmetrichttp.New(ctx, opts...)
}

Expand Down Expand Up @@ -236,6 +243,12 @@ func otlpGRPCMetricExporter(ctx context.Context, otlpConfig *OTLPMetric) (sdkmet
}
}

tlsConfig, err := createTLSConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey)
if err != nil {
return nil, err
}
opts = append(opts, otlpmetricgrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig)))

return otlpmetricgrpc.New(ctx, opts...)
}

Expand Down
Loading
Loading