Skip to content

Commit

Permalink
TLS configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
olegbespalov committed Jun 19, 2024
1 parent 2ff1f89 commit 4cdf100
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 37 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ It's worth to mention that the extension is using the [OpenTelemetry Go SDK](htt
* `K6_OTEL_EXPORT_INTERVAL` - configures the intervening time between metrics exports. Default is `10s`.
* `K6_OTEL_EXPORTER_TYPE` - metric exporter type. Default is `grpc`.

#### TLS configuration

* `K6_OTEL_TLS_INSECURE_SKIP_VERIFY` - disables server certificate verification.
* `K6_OTEL_TLS_CERTIFICATE` - path to the certificate file for TLS credentials.
* `K6_OTEL_TLS_CLIENT_CERTIFICATE` - path to the client certificate file (must be PEM encoded data).
* `K6_OTEL_TLS_CLIENT_KEY` - path to the client key file (must be PEM encoded data).

If TLS configuration is provided, the exporter will use it.

#### GRPC exporter

* `K6_OTEL_GRPC_EXPORTER_INSECURE` - disables client transport security for the gRPC exporter.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
go.opentelemetry.io/otel/metric v1.26.0
go.opentelemetry.io/otel/sdk v1.26.0
go.opentelemetry.io/otel/sdk/metric v1.26.0
google.golang.org/grpc v1.63.2
gopkg.in/guregu/null.v3 v3.5.0
)

Expand Down Expand Up @@ -41,7 +42,6 @@ require (
golang.org/x/time v0.5.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect
google.golang.org/grpc v1.63.2 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
26 changes: 26 additions & 0 deletions pkg/opentelemetry/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ type Config struct {
// ExportInterval configures the intervening time between metrics exports
ExportInterval types.NullDuration `json:"exportInterval" envconfig:"K6_OTEL_EXPORT_INTERVAL"`

// TLSInsecureSkipVerify disables verification of the server's certificate chain
TLSInsecureSkipVerify null.Bool `json:"tlsInsecureSkipVerify" envconfig:"K6_OTEL_TLS_INSECURE_SKIP_VERIFY"`
// TLSCertificate is the path to the certificate file (rootCAs) to use for the exporter's TLS connection
TLSCertificate null.String `json:"tlsCertificate" envconfig:"K6_OTEL_TLS_CERTIFICATE"`
// TLSClientCertificate is the path to the certificate file (must be PEM encoded data)
// to use for the exporter's TLS connection
TLSClientCertificate null.String `json:"tlsClientCertificate" envconfig:"K6_OTEL_TLS_CLIENT_CERTIFICATE"`
// TLSClientKey is the path to the private key file (must be PEM encoded data) to use for the exporter's TLS connection
TLSClientKey null.String `json:"tlsClientKey" envconfig:"K6_OTEL_TLS_CLIENT_KEY"`

// HTTPExporterInsecure disables client transport security for the Exporter's HTTP
// connection.
HTTPExporterInsecure null.Bool `json:"httpExporterInsecure" envconfig:"K6_OTEL_HTTP_EXPORTER_INSECURE"`
Expand Down Expand Up @@ -153,6 +163,22 @@ func (cfg Config) Apply(v Config) Config {
cfg.GRPCExporterInsecure = v.GRPCExporterInsecure
}

if v.TLSInsecureSkipVerify.Valid {
cfg.TLSInsecureSkipVerify = v.TLSInsecureSkipVerify
}

if v.TLSCertificate.Valid {
cfg.TLSCertificate = v.TLSCertificate
}

if v.TLSClientCertificate.Valid {
cfg.TLSClientCertificate = v.TLSClientCertificate
}

if v.TLSClientKey.Valid {
cfg.TLSClientKey = v.TLSClientKey
}

return cfg
}

Expand Down
78 changes: 47 additions & 31 deletions pkg/opentelemetry/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,28 +54,36 @@ func TestConfig(t *testing.T) {

"environment complete overwrite": {
env: map[string]string{
"K6_OTEL_SERVICE_NAME": "foo",
"K6_OTEL_SERVICE_VERSION": "v0.0.99",
"K6_OTEL_EXPORTER_TYPE": "http",
"K6_OTEL_EXPORT_INTERVAL": "4ms",
"K6_OTEL_HTTP_EXPORTER_INSECURE": "true",
"K6_OTEL_HTTP_EXPORTER_ENDPOINT": "localhost:5555",
"K6_OTEL_HTTP_EXPORTER_URL_PATH": "/foo/bar",
"K6_OTEL_GRPC_EXPORTER_INSECURE": "true",
"K6_OTEL_GRPC_EXPORTER_ENDPOINT": "else",
"K6_OTEL_FLUSH_INTERVAL": "13s",
"K6_OTEL_SERVICE_NAME": "foo",
"K6_OTEL_SERVICE_VERSION": "v0.0.99",
"K6_OTEL_EXPORTER_TYPE": "http",
"K6_OTEL_EXPORT_INTERVAL": "4ms",
"K6_OTEL_HTTP_EXPORTER_INSECURE": "true",
"K6_OTEL_HTTP_EXPORTER_ENDPOINT": "localhost:5555",
"K6_OTEL_HTTP_EXPORTER_URL_PATH": "/foo/bar",
"K6_OTEL_GRPC_EXPORTER_INSECURE": "true",
"K6_OTEL_GRPC_EXPORTER_ENDPOINT": "else",
"K6_OTEL_FLUSH_INTERVAL": "13s",
"K6_OTEL_TLS_INSECURE_SKIP_VERIFY": "true",
"K6_OTEL_TLS_CERTIFICATE": "cert_path",
"K6_OTEL_TLS_CLIENT_CERTIFICATE": "client_cert_path",
"K6_OTEL_TLS_CLIENT_KEY": "client_key_path",
},
expectedConfig: Config{
ServiceName: null.StringFrom("foo"),
ServiceVersion: null.StringFrom("v0.0.99"),
ExporterType: null.StringFrom(httpExporterType),
ExportInterval: types.NullDurationFrom(4 * time.Millisecond),
HTTPExporterInsecure: null.NewBool(true, true),
HTTPExporterEndpoint: null.StringFrom("localhost:5555"),
HTTPExporterURLPath: null.StringFrom("/foo/bar"),
GRPCExporterInsecure: null.NewBool(true, true),
GRPCExporterEndpoint: null.StringFrom("else"),
FlushInterval: types.NullDurationFrom(13 * time.Second),
ServiceName: null.StringFrom("foo"),
ServiceVersion: null.StringFrom("v0.0.99"),
ExporterType: null.StringFrom(httpExporterType),
ExportInterval: types.NullDurationFrom(4 * time.Millisecond),
HTTPExporterInsecure: null.NewBool(true, true),
HTTPExporterEndpoint: null.StringFrom("localhost:5555"),
HTTPExporterURLPath: null.StringFrom("/foo/bar"),
GRPCExporterInsecure: null.NewBool(true, true),
GRPCExporterEndpoint: null.StringFrom("else"),
FlushInterval: types.NullDurationFrom(13 * time.Second),
TLSInsecureSkipVerify: null.NewBool(true, true),
TLSCertificate: null.StringFrom("cert_path"),
TLSClientCertificate: null.StringFrom("client_cert_path"),
TLSClientKey: null.StringFrom("client_key_path"),
},
},

Expand All @@ -91,20 +99,28 @@ func TestConfig(t *testing.T) {
`"httpExporterURLPath":"/foo/bar",` +
`"grpcExporterInsecure":true,` +
`"grpcExporterEndpoint":"else",` +
`"flushInterval":"13s"` +
`"flushInterval":"13s",` +
`"tlsInsecureSkipVerify":true,` +
`"tlsCertificate":"cert_path",` +
`"tlsClientCertificate":"client_cert_path",` +
`"tlsClientKey":"client_key_path"` +
`}`,
),
expectedConfig: Config{
ServiceName: null.StringFrom("bar"),
ServiceVersion: null.StringFrom("v2.0.99"),
ExporterType: null.StringFrom(httpExporterType),
ExportInterval: types.NullDurationFrom(15 * time.Millisecond),
HTTPExporterInsecure: null.NewBool(true, true),
HTTPExporterEndpoint: null.StringFrom("localhost:5555"),
HTTPExporterURLPath: null.StringFrom("/foo/bar"),
GRPCExporterInsecure: null.NewBool(true, true),
GRPCExporterEndpoint: null.StringFrom("else"),
FlushInterval: types.NullDurationFrom(13 * time.Second),
ServiceName: null.StringFrom("bar"),
ServiceVersion: null.StringFrom("v2.0.99"),
ExporterType: null.StringFrom(httpExporterType),
ExportInterval: types.NullDurationFrom(15 * time.Millisecond),
HTTPExporterInsecure: null.NewBool(true, true),
HTTPExporterEndpoint: null.StringFrom("localhost:5555"),
HTTPExporterURLPath: null.StringFrom("/foo/bar"),
GRPCExporterInsecure: null.NewBool(true, true),
GRPCExporterEndpoint: null.StringFrom("else"),
FlushInterval: types.NullDurationFrom(13 * time.Second),
TLSInsecureSkipVerify: null.NewBool(true, true),
TLSCertificate: null.StringFrom("cert_path"),
TLSClientCertificate: null.StringFrom("client_cert_path"),
TLSClientKey: null.StringFrom("client_key_path"),
},
},

Expand Down
30 changes: 25 additions & 5 deletions pkg/opentelemetry/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,45 @@ package opentelemetry

import (
"context"
"crypto/tls"
"errors"

"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
"go.opentelemetry.io/otel/sdk/metric"
"google.golang.org/grpc/credentials"
)

func getExporter(cfg Config) (metric.Exporter, error) {
// at the point of writing this code
// ctx isn't used at any point in the exporter
// later on, it could be used for the connection timeout
ctx := context.Background()

tlsConfig, err := buildTLSConfig(
cfg.TLSInsecureSkipVerify,
cfg.TLSCertificate,
cfg.TLSClientCertificate,
cfg.TLSClientKey,
)
if err != nil {
return nil, err
}

exporterType := cfg.ExporterType.String

if exporterType == grpcExporterType {
return buildGRPCExporter(ctx, cfg)
return buildGRPCExporter(ctx, cfg, tlsConfig)
}

if exporterType == httpExporterType {
return buildHTTPExporter(ctx, cfg)
return buildHTTPExporter(ctx, cfg, tlsConfig)
}

return nil, errors.New("unsupported exporter type " + exporterType + " specified")
}

func buildHTTPExporter(ctx context.Context, cfg Config) (metric.Exporter, error) {
func buildHTTPExporter(ctx context.Context, cfg Config, tlsConfig *tls.Config) (metric.Exporter, error) {
opts := []otlpmetrichttp.Option{
otlpmetrichttp.WithEndpoint(cfg.HTTPExporterEndpoint.String),
otlpmetrichttp.WithURLPath(cfg.HTTPExporterURLPath.String),
Expand All @@ -37,18 +50,25 @@ func buildHTTPExporter(ctx context.Context, cfg Config) (metric.Exporter, error)
opts = append(opts, otlpmetrichttp.WithInsecure())
}

if tlsConfig != nil {
opts = append(opts, otlpmetrichttp.WithTLSClientConfig(tlsConfig))
}

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

func buildGRPCExporter(ctx context.Context, cfg Config) (metric.Exporter, error) {
func buildGRPCExporter(ctx context.Context, cfg Config, tlsConfig *tls.Config) (metric.Exporter, error) {
opt := []otlpmetricgrpc.Option{
otlpmetricgrpc.WithEndpoint(cfg.GRPCExporterEndpoint.String),
}

// TODO: give priority to the TLS
if cfg.GRPCExporterInsecure.Bool {
opt = append(opt, otlpmetricgrpc.WithInsecure())
}

if tlsConfig != nil {
opt = append(opt, otlpmetricgrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig)))
}

return otlpmetricgrpc.New(ctx, opt...)
}
57 changes: 57 additions & 0 deletions pkg/opentelemetry/tls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package opentelemetry

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

"gopkg.in/guregu/null.v3"
)

func buildTLSConfig(
insecureSkipVerify null.Bool,
certPath, clientCertPath, clientKeyPath null.String,
) (*tls.Config, error) {
set := false
tlsConfig := &tls.Config{} //nolint:gosec // it's up to the caller

if insecureSkipVerify.Valid {
tlsConfig.InsecureSkipVerify = insecureSkipVerify.Bool
set = true
}

// Load the root certificate
if certPath.Valid {
b, err := os.ReadFile(certPath.String) //nolint:forbidigo
if err != nil {
return nil, fmt.Errorf("failed to read root certificate from %q: %w", certPath.String, err)
}

cp := x509.NewCertPool()
if ok := cp.AppendCertsFromPEM(b); !ok {
return nil, errors.New("failed to append root certificate to the pool")
}

tlsConfig.RootCAs = cp
set = true
}

// Load the client certificate
if clientCertPath.Valid {
cert, err := tls.LoadX509KeyPair(clientCertPath.String, clientKeyPath.String)
if err != nil {
return nil, fmt.Errorf("failed to load client certificate: %w", err)
}

tlsConfig.Certificates = []tls.Certificate{cert}
set = true
}

if !set {
return nil, nil //nolint:nilnil // no TLS config set
}

return tlsConfig, nil
}

0 comments on commit 4cdf100

Please sign in to comment.