mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-01-01 22:09:57 +02:00
add support for configuring tls certs via env var to otlp/HTTP (#1769)
This commit is contained in:
parent
35cfbc7e87
commit
52a24774da
@ -24,6 +24,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
- `OTEL_EXPORTER_OTLP_TIMEOUT`
|
||||
- `OTEL_EXPORTER_OTLP_TRACES_TIMEOUT`
|
||||
- `OTEL_EXPORTER_OTLP_METRICS_TIMEOUT`
|
||||
- Added support for configuring OTLP/HTTP TLS Certificates via the Environment Variables. (#1769)
|
||||
- `OTEL_EXPORTER_OTLP_CERTIFICATE`
|
||||
- `OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE`
|
||||
- `OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE`
|
||||
|
||||
### Fixed
|
||||
|
||||
|
69
exporters/otlp/internal/otlpconfig/tls.go
Normal file
69
exporters/otlp/internal/otlpconfig/tls.go
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package otlpconfig
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
const (
|
||||
WeakCertificate = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBhzCCASygAwIBAgIRANHpHgAWeTnLZpTSxCKs0ggwCgYIKoZIzj0EAwIwEjEQ
|
||||
MA4GA1UEChMHb3RlbC1nbzAeFw0yMTA0MDExMzU5MDNaFw0yMTA0MDExNDU5MDNa
|
||||
MBIxEDAOBgNVBAoTB290ZWwtZ28wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS9
|
||||
nWSkmPCxShxnp43F+PrOtbGV7sNfkbQ/kxzi9Ego0ZJdiXxkmv/C05QFddCW7Y0Z
|
||||
sJCLHGogQsYnWJBXUZOVo2MwYTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYI
|
||||
KwYBBQUHAwEwDAYDVR0TAQH/BAIwADAsBgNVHREEJTAjgglsb2NhbGhvc3SHEAAA
|
||||
AAAAAAAAAAAAAAAAAAGHBH8AAAEwCgYIKoZIzj0EAwIDSQAwRgIhANwZVVKvfvQ/
|
||||
1HXsTvgH+xTQswOwSSKYJ1cVHQhqK7ZbAiEAus8NxpTRnp5DiTMuyVmhVNPB+bVH
|
||||
Lhnm4N/QDk5rek0=
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
WeakPrivateKey = `
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgN8HEXiXhvByrJ1zK
|
||||
SFT6Y2l2KqDWwWzKf+t4CyWrNKehRANCAAS9nWSkmPCxShxnp43F+PrOtbGV7sNf
|
||||
kbQ/kxzi9Ego0ZJdiXxkmv/C05QFddCW7Y0ZsJCLHGogQsYnWJBXUZOV
|
||||
-----END PRIVATE KEY-----
|
||||
`
|
||||
)
|
||||
|
||||
// ReadTLSConfigFromFile reads a PEM certificate file and creates
|
||||
// a tls.Config that will use this certifate to verify a server certificate.
|
||||
func ReadTLSConfigFromFile(path string) (*tls.Config, error) {
|
||||
b, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return CreateTLSConfig(b)
|
||||
}
|
||||
|
||||
// CreateTLSConfig creates a tls.Config from a raw certificate bytes
|
||||
// to verify a server certificate.
|
||||
func CreateTLSConfig(certBytes []byte) (*tls.Config, error) {
|
||||
cp := x509.NewCertPool()
|
||||
if ok := cp.AppendCertsFromPEM(certBytes); !ok {
|
||||
return nil, errors.New("failed to append certificate to the cert pool")
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
RootCAs: cp,
|
||||
}, nil
|
||||
}
|
@ -24,7 +24,6 @@ import (
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
@ -82,7 +81,7 @@ var _ otlp.ProtocolDriver = (*driver)(nil)
|
||||
// NewDriver creates a new HTTP driver.
|
||||
func NewDriver(opts ...Option) otlp.ProtocolDriver {
|
||||
cfg := newDefaultConfig()
|
||||
applyEnvConfigs(&cfg, os.Getenv)
|
||||
applyEnvConfigs(&cfg)
|
||||
|
||||
for _, opt := range opts {
|
||||
opt.Apply(&cfg)
|
||||
|
@ -15,72 +15,115 @@
|
||||
package otlphttp
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/internal/otlpconfig"
|
||||
)
|
||||
|
||||
func applyEnvConfigs(cfg *config, getEnv func(string) string) *config {
|
||||
opts := getOptionsFromEnv(getEnv)
|
||||
func applyEnvConfigs(cfg *config) {
|
||||
e := envOptionsReader{
|
||||
getEnv: os.Getenv,
|
||||
readFile: ioutil.ReadFile,
|
||||
}
|
||||
|
||||
opts := e.getOptionsFromEnv()
|
||||
for _, opt := range opts {
|
||||
opt.Apply(cfg)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func getOptionsFromEnv(env func(string) string) []Option {
|
||||
type envOptionsReader struct {
|
||||
getEnv func(string) string
|
||||
readFile func(filename string) ([]byte, error)
|
||||
}
|
||||
|
||||
func (e *envOptionsReader) applyEnvConfigs(cfg *config) {
|
||||
opts := e.getOptionsFromEnv()
|
||||
for _, opt := range opts {
|
||||
opt.Apply(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *envOptionsReader) getOptionsFromEnv() []Option {
|
||||
var opts []Option
|
||||
|
||||
// Endpoint
|
||||
if v, ok := getEnv(env, "ENDPOINT"); ok {
|
||||
if v, ok := e.getEnvValue("ENDPOINT"); ok {
|
||||
opts = append(opts, WithEndpoint(v))
|
||||
}
|
||||
if v, ok := getEnv(env, "TRACES_ENDPOINT"); ok {
|
||||
if v, ok := e.getEnvValue("TRACES_ENDPOINT"); ok {
|
||||
opts = append(opts, WithTracesEndpoint(v))
|
||||
}
|
||||
if v, ok := getEnv(env, "METRICS_ENDPOINT"); ok {
|
||||
if v, ok := e.getEnvValue("METRICS_ENDPOINT"); ok {
|
||||
opts = append(opts, WithMetricsEndpoint(v))
|
||||
}
|
||||
|
||||
// Certificate File
|
||||
// TODO: add certificate file env config support
|
||||
if path, ok := e.getEnvValue("CERTIFICATE"); ok {
|
||||
if tls, err := e.readTLSConfig(path); err == nil {
|
||||
opts = append(opts, WithTLSClientConfig(tls))
|
||||
} else {
|
||||
otel.Handle(fmt.Errorf("failed to configure otlp exporter certificate '%s': %w", path, err))
|
||||
}
|
||||
}
|
||||
if path, ok := e.getEnvValue("TRACES_CERTIFICATE"); ok {
|
||||
if tls, err := e.readTLSConfig(path); err == nil {
|
||||
opts = append(opts, WithTracesTLSClientConfig(tls))
|
||||
} else {
|
||||
otel.Handle(fmt.Errorf("failed to configure otlp traces exporter certificate '%s': %w", path, err))
|
||||
}
|
||||
}
|
||||
if path, ok := e.getEnvValue("METRICS_CERTIFICATE"); ok {
|
||||
if tls, err := e.readTLSConfig(path); err == nil {
|
||||
opts = append(opts, WithMetricsTLSClientConfig(tls))
|
||||
} else {
|
||||
otel.Handle(fmt.Errorf("failed to configure otlp metrics exporter certificate '%s': %w", path, err))
|
||||
}
|
||||
}
|
||||
|
||||
// Headers
|
||||
if h, ok := getEnv(env, "HEADERS"); ok {
|
||||
if h, ok := e.getEnvValue("HEADERS"); ok {
|
||||
opts = append(opts, WithHeaders(stringToHeader(h)))
|
||||
}
|
||||
if h, ok := getEnv(env, "TRACES_HEADERS"); ok {
|
||||
if h, ok := e.getEnvValue("TRACES_HEADERS"); ok {
|
||||
opts = append(opts, WithTracesHeaders(stringToHeader(h)))
|
||||
}
|
||||
if h, ok := getEnv(env, "METRICS_HEADERS"); ok {
|
||||
if h, ok := e.getEnvValue("METRICS_HEADERS"); ok {
|
||||
opts = append(opts, WithMetricsHeaders(stringToHeader(h)))
|
||||
}
|
||||
|
||||
// Compression
|
||||
if c, ok := getEnv(env, "COMPRESSION"); ok {
|
||||
if c, ok := e.getEnvValue("COMPRESSION"); ok {
|
||||
opts = append(opts, WithCompression(stringToCompression(c)))
|
||||
}
|
||||
if c, ok := getEnv(env, "TRACES_COMPRESSION"); ok {
|
||||
if c, ok := e.getEnvValue("TRACES_COMPRESSION"); ok {
|
||||
opts = append(opts, WithTracesCompression(stringToCompression(c)))
|
||||
}
|
||||
if c, ok := getEnv(env, "METRICS_COMPRESSION"); ok {
|
||||
if c, ok := e.getEnvValue("METRICS_COMPRESSION"); ok {
|
||||
opts = append(opts, WithMetricsCompression(stringToCompression(c)))
|
||||
}
|
||||
|
||||
// Timeout
|
||||
if t, ok := getEnv(env, "TIMEOUT"); ok {
|
||||
if t, ok := e.getEnvValue("TIMEOUT"); ok {
|
||||
if d, err := strconv.Atoi(t); err == nil {
|
||||
opts = append(opts, WithTimeout(time.Duration(d)*time.Millisecond))
|
||||
}
|
||||
}
|
||||
if t, ok := getEnv(env, "TRACES_TIMEOUT"); ok {
|
||||
if t, ok := e.getEnvValue("TRACES_TIMEOUT"); ok {
|
||||
if d, err := strconv.Atoi(t); err == nil {
|
||||
opts = append(opts, WithTracesTimeout(time.Duration(d)*time.Millisecond))
|
||||
}
|
||||
}
|
||||
if t, ok := getEnv(env, "METRICS_TIMEOUT"); ok {
|
||||
if t, ok := e.getEnvValue("METRICS_TIMEOUT"); ok {
|
||||
if d, err := strconv.Atoi(t); err == nil {
|
||||
opts = append(opts, WithMetricsTimeout(time.Duration(d)*time.Millisecond))
|
||||
}
|
||||
@ -89,13 +132,21 @@ func getOptionsFromEnv(env func(string) string) []Option {
|
||||
return opts
|
||||
}
|
||||
|
||||
// getEnv gets an OTLP environment variable value of the specified key using the env function.
|
||||
// getEnvValue gets an OTLP environment variable value of the specified key using the getEnv function.
|
||||
// This function already prepends the OTLP prefix to all key lookup.
|
||||
func getEnv(env func(string) string, key string) (string, bool) {
|
||||
v := strings.TrimSpace(env(fmt.Sprintf("OTEL_EXPORTER_OTLP_%s", key)))
|
||||
func (e *envOptionsReader) getEnvValue(key string) (string, bool) {
|
||||
v := strings.TrimSpace(e.getEnv(fmt.Sprintf("OTEL_EXPORTER_OTLP_%s", key)))
|
||||
return v, v != ""
|
||||
}
|
||||
|
||||
func (e *envOptionsReader) readTLSConfig(path string) (*tls.Config, error) {
|
||||
b, err := e.readFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return otlpconfig.CreateTLSConfig(b)
|
||||
}
|
||||
|
||||
func stringToCompression(value string) Compression {
|
||||
switch value {
|
||||
case "gzip":
|
||||
|
@ -226,6 +226,24 @@ func WithTLSClientConfig(tlsCfg *tls.Config) Option {
|
||||
})
|
||||
}
|
||||
|
||||
// WithTracesTLSClientConfig can be used to set up a custom TLS
|
||||
// configuration for the client used to send traces.
|
||||
// Use it if you want to use a custom certificate.
|
||||
func WithTracesTLSClientConfig(tlsCfg *tls.Config) Option {
|
||||
return newGenericOption(func(cfg *config) {
|
||||
cfg.traces.tlsCfg = tlsCfg
|
||||
})
|
||||
}
|
||||
|
||||
// WithMetricsTLSClientConfig can be used to set up a custom TLS
|
||||
// configuration for the client used to send metrics.
|
||||
// Use it if you want to use a custom certificate.
|
||||
func WithMetricsTLSClientConfig(tlsCfg *tls.Config) Option {
|
||||
return newGenericOption(func(cfg *config) {
|
||||
cfg.metrics.tlsCfg = tlsCfg
|
||||
})
|
||||
}
|
||||
|
||||
// WithInsecure tells the driver to connect to the collector using the
|
||||
// HTTP scheme, instead of HTTPS.
|
||||
func WithInsecure() Option {
|
||||
|
@ -15,9 +15,14 @@
|
||||
package otlphttp
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/internal/otlpconfig"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -27,12 +32,25 @@ func (e *env) getEnv(env string) string {
|
||||
return (*e)[env]
|
||||
}
|
||||
|
||||
type fileReader map[string][]byte
|
||||
|
||||
func (f *fileReader) readFile(filename string) ([]byte, error) {
|
||||
if b, ok := (*f)[filename]; ok {
|
||||
return b, nil
|
||||
}
|
||||
return nil, errors.New("File not found")
|
||||
}
|
||||
|
||||
func TestConfigs(t *testing.T) {
|
||||
tlsCert, err := otlpconfig.CreateTLSConfig([]byte(otlpconfig.WeakCertificate))
|
||||
assert.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
opts []Option
|
||||
env env
|
||||
asserts func(t *testing.T, c *config)
|
||||
name string
|
||||
opts []Option
|
||||
env env
|
||||
fileReader fileReader
|
||||
asserts func(t *testing.T, c *config)
|
||||
}{
|
||||
{
|
||||
name: "Test default configs",
|
||||
@ -107,6 +125,75 @@ func TestConfigs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
|
||||
// Certificate tests
|
||||
{
|
||||
name: "Test With Certificate",
|
||||
opts: []Option{
|
||||
WithTLSClientConfig(tlsCert),
|
||||
},
|
||||
asserts: func(t *testing.T, c *config) {
|
||||
assert.Equal(t, tlsCert.RootCAs.Subjects(), c.traces.tlsCfg.RootCAs.Subjects())
|
||||
assert.Equal(t, tlsCert.RootCAs.Subjects(), c.metrics.tlsCfg.RootCAs.Subjects())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test With Signal Specific Endpoint",
|
||||
opts: []Option{
|
||||
WithTLSClientConfig(&tls.Config{}),
|
||||
WithTracesTLSClientConfig(tlsCert),
|
||||
WithMetricsTLSClientConfig(&tls.Config{RootCAs: x509.NewCertPool()}),
|
||||
},
|
||||
asserts: func(t *testing.T, c *config) {
|
||||
assert.Equal(t, tlsCert.RootCAs.Subjects(), c.traces.tlsCfg.RootCAs.Subjects())
|
||||
assert.Equal(t, 0, len(c.metrics.tlsCfg.RootCAs.Subjects()))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Endpoint",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_CERTIFICATE": "cert_path",
|
||||
},
|
||||
fileReader: fileReader{
|
||||
"cert_path": []byte(otlpconfig.WeakCertificate),
|
||||
},
|
||||
asserts: func(t *testing.T, c *config) {
|
||||
assert.Equal(t, tlsCert.RootCAs.Subjects(), c.traces.tlsCfg.RootCAs.Subjects())
|
||||
assert.Equal(t, tlsCert.RootCAs.Subjects(), c.metrics.tlsCfg.RootCAs.Subjects())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Environment Signal Specific Endpoint",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_CERTIFICATE": "overrode_by_signal_specific",
|
||||
"OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE": "cert_path",
|
||||
"OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE": "invalid_cert",
|
||||
},
|
||||
fileReader: fileReader{
|
||||
"cert_path": []byte(otlpconfig.WeakCertificate),
|
||||
"invalid_cert": []byte("invalid certificate file."),
|
||||
},
|
||||
asserts: func(t *testing.T, c *config) {
|
||||
assert.Equal(t, tlsCert.RootCAs.Subjects(), c.traces.tlsCfg.RootCAs.Subjects())
|
||||
assert.Equal(t, (*tls.Config)(nil), c.metrics.tlsCfg)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test Mixed Environment and With Endpoint",
|
||||
opts: []Option{
|
||||
WithMetricsTLSClientConfig(&tls.Config{RootCAs: x509.NewCertPool()}),
|
||||
},
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_CERTIFICATE": "cert_path",
|
||||
},
|
||||
fileReader: fileReader{
|
||||
"cert_path": []byte(otlpconfig.WeakCertificate),
|
||||
},
|
||||
asserts: func(t *testing.T, c *config) {
|
||||
assert.Equal(t, tlsCert.RootCAs.Subjects(), c.traces.tlsCfg.RootCAs.Subjects())
|
||||
assert.Equal(t, 0, len(c.metrics.tlsCfg.RootCAs.Subjects()))
|
||||
},
|
||||
},
|
||||
|
||||
// Headers tests
|
||||
{
|
||||
name: "Test With Headers",
|
||||
@ -286,7 +373,13 @@ func TestConfigs(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cfg := newDefaultConfig()
|
||||
applyEnvConfigs(&cfg, tt.env.getEnv)
|
||||
|
||||
e := envOptionsReader{
|
||||
getEnv: tt.env.getEnv,
|
||||
readFile: tt.fileReader.readFile,
|
||||
}
|
||||
e.applyEnvConfigs(&cfg)
|
||||
|
||||
for _, opt := range tt.opts {
|
||||
opt.Apply(&cfg)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user