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

add new env config options for OTLP exporter #3363

Merged
merged 13 commits into from Nov 8, 2022
10 changes: 10 additions & 0 deletions CHANGELOG.md
Expand Up @@ -13,6 +13,16 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Prometheus exporter will register with a prometheus registerer on creation, there are options to control this. (#3239)
- Added the `WithAggregationSelector` option to the `go.opentelemetry.io/otel/exporters/prometheus` package to change the `AggregationSelector` used. (#3341)
- Prometheus exporter will convert metrics `Resource` into a `target_info` metric. (#3285)
- OTLP exporters now recognize: (#3363)
- `OTEL_EXPORTER_OTLP_INSECURE`
- `OTEL_EXPORTER_OTLP_TRACES_INSECURE`
- `OTEL_EXPORTER_OTLP_METRICS_INSECURE`
- `OTEL_EXPORTER_OTLP_CLIENT_KEY`
- `OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY`
- `OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY`
- `OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE`
- `OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE`
- `OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE`

### Changed

Expand Down
46 changes: 38 additions & 8 deletions exporters/otlp/internal/envconfig/envconfig.go
Expand Up @@ -59,6 +59,16 @@ func WithString(n string, fn func(string)) func(e *EnvOptionsReader) {
}
}

// WithBool retrieves the specified config and passes it to ConfigFn as a boolean.
seankhliao marked this conversation as resolved.
Show resolved Hide resolved
func WithBool(n string, fn func(bool)) func(e *EnvOptionsReader) {
seankhliao marked this conversation as resolved.
Show resolved Hide resolved
return func(e *EnvOptionsReader) {
if v, ok := e.GetEnvValue(n); ok {
b := strings.ToLower(v) == "true"
fn(b)
}
}
}

// WithDuration retrieves the specified config and passes it to ConfigFn as a duration.
func WithDuration(n string, fn func(time.Duration)) func(e *EnvOptionsReader) {
return func(e *EnvOptionsReader) {
Expand Down Expand Up @@ -90,19 +100,42 @@ func WithURL(n string, fn func(*url.URL)) func(e *EnvOptionsReader) {
}
}

// WithTLSConfig retrieves the specified config and passes it to ConfigFn as a crypto/tls.Config.
func WithTLSConfig(n string, fn func(*tls.Config)) func(e *EnvOptionsReader) {
// WithCertPool retrieves the specified config and passes it to ConfigFn as a crypto/x509.CertPool
// constructed from reading the certificate at the given path.
seankhliao marked this conversation as resolved.
Show resolved Hide resolved
func WithCertPool(n string, fn func(*x509.CertPool)) func(e *EnvOptionsReader) {
seankhliao marked this conversation as resolved.
Show resolved Hide resolved
return func(e *EnvOptionsReader) {
if v, ok := e.GetEnvValue(n); ok {
if b, err := e.ReadFile(v); err == nil {
if c, err := createTLSConfig(b); err == nil {
if c, err := createCertPool(b); err == nil {
fn(c)
}
}
}
}
}

// WithCertPool retrieves the specified configs and passes it to ConfigFn as a crypto/tls.Certificate
// constructed from reading the key pair at the given paths. Both files must exist.
seankhliao marked this conversation as resolved.
Show resolved Hide resolved
func WithClientCert(nc, nk string, fn func(tls.Certificate)) func(e *EnvOptionsReader) {
seankhliao marked this conversation as resolved.
Show resolved Hide resolved
seankhliao marked this conversation as resolved.
Show resolved Hide resolved
return func(e *EnvOptionsReader) {
vc, okc := e.GetEnvValue(nc)
vk, okk := e.GetEnvValue(nk)
if !okc || !okk {
return
}
cert, errc := e.ReadFile(vc)
key, errk := e.ReadFile(vk)
if errc != nil && errk != nil {
MrAlias marked this conversation as resolved.
Show resolved Hide resolved
return
}
crt, err := tls.X509KeyPair(cert, key)
if err != nil {
seankhliao marked this conversation as resolved.
Show resolved Hide resolved
return
}
fn(crt)
}
}

func keyWithNamespace(ns, key string) string {
if ns == "" {
return key
Expand Down Expand Up @@ -136,13 +169,10 @@ func stringToHeader(value string) map[string]string {
return headers
}

func createTLSConfig(certBytes []byte) (*tls.Config, error) {
func createCertPool(certBytes []byte) (*x509.CertPool, 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
return cp, nil
}
142 changes: 128 additions & 14 deletions exporters/otlp/internal/envconfig/envconfig_test.go
Expand Up @@ -16,29 +16,40 @@ package envconfig // import "go.opentelemetry.io/otel/exporters/otlp/internal/en

import (
"crypto/tls"
"crypto/x509"
"errors"
"net/url"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

const WeakKey = `
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIEbrSPmnlSOXvVzxCyv+VR3a0HDeUTvOcqrdssZ2k4gFoAoGCCqGSM49
AwEHoUQDQgAEDMTfv75J315C3K9faptS9iythKOMEeV/Eep73nWX531YAkmmwBSB
2dXRD/brsgLnfG57WEpxZuY7dPRbxu33BA==
-----END EC PRIVATE KEY-----
`

const WeakCertificate = `
-----BEGIN CERTIFICATE-----
MIIBhzCCASygAwIBAgIRANHpHgAWeTnLZpTSxCKs0ggwCgYIKoZIzj0EAwIwEjEQ
MA4GA1UEChMHb3RlbC1nbzAeFw0yMTA0MDExMzU5MDNaFw0yMTA0MDExNDU5MDNa
MBIxEDAOBgNVBAoTB290ZWwtZ28wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS9
nWSkmPCxShxnp43F+PrOtbGV7sNfkbQ/kxzi9Ego0ZJdiXxkmv/C05QFddCW7Y0Z
sJCLHGogQsYnWJBXUZOVo2MwYTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYI
KwYBBQUHAwEwDAYDVR0TAQH/BAIwADAsBgNVHREEJTAjgglsb2NhbGhvc3SHEAAA
AAAAAAAAAAAAAAAAAAGHBH8AAAEwCgYIKoZIzj0EAwIDSQAwRgIhANwZVVKvfvQ/
1HXsTvgH+xTQswOwSSKYJ1cVHQhqK7ZbAiEAus8NxpTRnp5DiTMuyVmhVNPB+bVH
Lhnm4N/QDk5rek0=
MIIBjjCCATWgAwIBAgIUKQSMC66MUw+kPp954ZYOcyKAQDswCgYIKoZIzj0EAwIw
EjEQMA4GA1UECgwHb3RlbC1nbzAeFw0yMjEwMTkwMDA5MTlaFw0yMzEwMTkwMDA5
MTlaMBIxEDAOBgNVBAoMB290ZWwtZ28wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
AAQMxN+/vknfXkLcr19qm1L2LK2Eo4wR5X8R6nvedZfnfVgCSabAFIHZ1dEP9uuy
Aud8bntYSnFm5jt09FvG7fcEo2kwZzAdBgNVHQ4EFgQUicGuhnTTkYLZwofXMNLK
SHFeCWgwHwYDVR0jBBgwFoAUicGuhnTTkYLZwofXMNLKSHFeCWgwDwYDVR0TAQH/
BAUwAwEB/zAUBgNVHREEDTALgglsb2NhbGhvc3QwCgYIKoZIzj0EAwIDRwAwRAIg
Lfma8FnnxeSOi6223AsFfYwsNZ2RderNsQrS0PjEHb0CIBkrWacqARUAu7uT4cGu
jVcIxYQqhId5L8p/mAv2PWZS
-----END CERTIFICATE-----
`

type testOption struct {
TestString string
TestBool bool
TestDuration time.Duration
TestHeaders map[string]string
TestURL *url.URL
Expand Down Expand Up @@ -134,6 +145,56 @@ func TestEnvConfig(t *testing.T) {
},
expectedOptions: []testOption{},
},
{
name: "with a bool config",
reader: EnvOptionsReader{
GetEnv: func(n string) string {
if n == "HELLO" {
return "true"
} else if n == "WORLD" {
return "false"
}
return ""
},
},
configs: []ConfigFn{
WithBool("HELLO", func(b bool) {
options = append(options, testOption{TestBool: b})
}),
WithBool("WORLD", func(b bool) {
options = append(options, testOption{TestBool: b})
}),
},
expectedOptions: []testOption{
{
TestBool: true,
},
{
TestBool: false,
},
},
},
{
name: "with an invalid bool config",
reader: EnvOptionsReader{
GetEnv: func(n string) string {
if n == "HELLO" {
return "world"
}
return ""
},
},
configs: []ConfigFn{
WithBool("HELLO", func(b bool) {
options = append(options, testOption{TestBool: b})
}),
},
expectedOptions: []testOption{
{
TestBool: false,
},
},
},
{
name: "with a duration config",
reader: EnvOptionsReader{
Expand Down Expand Up @@ -265,7 +326,7 @@ func TestEnvConfig(t *testing.T) {
}

func TestWithTLSConfig(t *testing.T) {
tlsCert, err := createTLSConfig([]byte(WeakCertificate))
pool, err := createCertPool([]byte(WeakCertificate))
assert.NoError(t, err)

reader := EnvOptionsReader{
Expand All @@ -285,12 +346,65 @@ func TestWithTLSConfig(t *testing.T) {

var option testOption
reader.Apply(
WithTLSConfig("CERTIFICATE", func(v *tls.Config) {
option = testOption{TestTLS: v}
}))
WithCertPool("CERTIFICATE", func(cp *x509.CertPool) {
option = testOption{TestTLS: &tls.Config{RootCAs: cp}}
}),
)

// nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool.
assert.Equal(t, tlsCert.RootCAs.Subjects(), option.TestTLS.RootCAs.Subjects())
assert.Equal(t, pool.Subjects(), option.TestTLS.RootCAs.Subjects())
}

func TestWithClientCert(t *testing.T) {
cert, err := tls.X509KeyPair([]byte(WeakCertificate), []byte(WeakKey))
assert.NoError(t, err)

reader := EnvOptionsReader{
GetEnv: func(n string) string {
switch n {
case "CLIENT_CERTIFICATE":
return "/path/tls.crt"
case "CLIENT_KEY":
return "/path/tls.key"
}
return ""
},
ReadFile: func(n string) ([]byte, error) {
switch n {
case "/path/tls.crt":
return []byte(WeakCertificate), nil
case "/path/tls.key":
return []byte(WeakKey), nil
}
return []byte{}, nil
},
}

var option testOption
reader.Apply(
WithClientCert("CLIENT_CERTIFICATE", "CLIENT_KEY", func(c tls.Certificate) {
option = testOption{TestTLS: &tls.Config{Certificates: []tls.Certificate{c}}}
}),
)
assert.Equal(t, cert, option.TestTLS.Certificates[0])

reader.ReadFile = func(s string) ([]byte, error) { return nil, errors.New("oops") }
option.TestTLS = nil
reader.Apply(
WithClientCert("CLIENT_CERTIFICATE", "CLIENT_KEY", func(c tls.Certificate) {
option = testOption{TestTLS: &tls.Config{Certificates: []tls.Certificate{c}}}
}),
)
assert.Nil(t, option.TestTLS)

reader.GetEnv = func(s string) string { return "" }
option.TestTLS = nil
reader.Apply(
WithClientCert("CLIENT_CERTIFICATE", "CLIENT_KEY", func(c tls.Certificate) {
option = testOption{TestTLS: &tls.Config{Certificates: []tls.Certificate{c}}}
}),
)
assert.Nil(t, option.TestTLS)
}

func TestStringToHeader(t *testing.T) {
Expand Down
27 changes: 25 additions & 2 deletions exporters/otlp/otlpmetric/internal/oconf/envconfig.go
Expand Up @@ -16,6 +16,7 @@ package oconf // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/inte

import (
"crypto/tls"
"crypto/x509"
"net/url"
"os"
"path"
Expand Down Expand Up @@ -53,6 +54,7 @@ func ApplyHTTPEnvConfigs(cfg Config) Config {
func getOptionsFromEnv() []GenericOption {
opts := []GenericOption{}

tlsConf := &tls.Config{}
DefaultEnvOptionsReader.Apply(
envconfig.WithURL("ENDPOINT", func(u *url.URL) {
opts = append(opts, withEndpointScheme(u))
Expand Down Expand Up @@ -81,8 +83,13 @@ func getOptionsFromEnv() []GenericOption {
return cfg
}, withEndpointForGRPC(u)))
}),
envconfig.WithTLSConfig("CERTIFICATE", func(c *tls.Config) { opts = append(opts, WithTLSClientConfig(c)) }),
envconfig.WithTLSConfig("METRICS_CERTIFICATE", func(c *tls.Config) { opts = append(opts, WithTLSClientConfig(c)) }),
envconfig.WithCertPool("CERTIFICATE", func(p *x509.CertPool) { tlsConf.RootCAs = p }),
envconfig.WithCertPool("METRICS_CERTIFICATE", func(p *x509.CertPool) { tlsConf.RootCAs = p }),
envconfig.WithClientCert("CLIENT_CERTIFICATE", "CLIENT_KEY", func(c tls.Certificate) { tlsConf.Certificates = []tls.Certificate{c} }),
envconfig.WithClientCert("METRICS_CLIENT_CERTIFICATE", "METRICS_CLIENT_KEY", func(c tls.Certificate) { tlsConf.Certificates = []tls.Certificate{c} }),
envconfig.WithBool("INSECURE", func(b bool) { opts = append(opts, withInsecure(b)) }),
envconfig.WithBool("METRICS_INSECURE", func(b bool) { opts = append(opts, withInsecure(b)) }),
withTLSConfig(tlsConf, func(c *tls.Config) { opts = append(opts, WithTLSClientConfig(c)) }),
envconfig.WithHeaders("HEADERS", func(h map[string]string) { opts = append(opts, WithHeaders(h)) }),
envconfig.WithHeaders("METRICS_HEADERS", func(h map[string]string) { opts = append(opts, WithHeaders(h)) }),
WithEnvCompression("COMPRESSION", func(c Compression) { opts = append(opts, WithCompression(c)) }),
Expand Down Expand Up @@ -125,3 +132,19 @@ func withEndpointScheme(u *url.URL) GenericOption {
return WithSecure()
}
}

// revive:disable-next-line:flag-parameter
func withInsecure(b bool) GenericOption {
if b {
return WithInsecure()
}
return WithSecure()
}

func withTLSConfig(c *tls.Config, fn func(*tls.Config)) func(e *envconfig.EnvOptionsReader) {
return func(e *envconfig.EnvOptionsReader) {
if c.RootCAs != nil || len(c.Certificates) > 0 {
fn(c)
}
}
}
27 changes: 25 additions & 2 deletions exporters/otlp/otlptrace/internal/otlpconfig/envconfig.go
Expand Up @@ -16,6 +16,7 @@ package otlpconfig // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/

import (
"crypto/tls"
"crypto/x509"
"net/url"
"os"
"path"
Expand Down Expand Up @@ -53,6 +54,7 @@ func ApplyHTTPEnvConfigs(cfg Config) Config {
func getOptionsFromEnv() []GenericOption {
opts := []GenericOption{}

tlsConf := &tls.Config{}
DefaultEnvOptionsReader.Apply(
envconfig.WithURL("ENDPOINT", func(u *url.URL) {
opts = append(opts, withEndpointScheme(u))
Expand Down Expand Up @@ -81,8 +83,13 @@ func getOptionsFromEnv() []GenericOption {
return cfg
}, withEndpointForGRPC(u)))
}),
envconfig.WithTLSConfig("CERTIFICATE", func(c *tls.Config) { opts = append(opts, WithTLSClientConfig(c)) }),
envconfig.WithTLSConfig("TRACES_CERTIFICATE", func(c *tls.Config) { opts = append(opts, WithTLSClientConfig(c)) }),
envconfig.WithCertPool("CERTIFICATE", func(p *x509.CertPool) { tlsConf.RootCAs = p }),
envconfig.WithCertPool("TRACES_CERTIFICATE", func(p *x509.CertPool) { tlsConf.RootCAs = p }),
envconfig.WithClientCert("CLIENT_CERTIFICATE", "CLIENT_KEY", func(c tls.Certificate) { tlsConf.Certificates = []tls.Certificate{c} }),
envconfig.WithClientCert("TRACES_CLIENT_CERTIFICATE", "TRACES_CLIENT_KEY", func(c tls.Certificate) { tlsConf.Certificates = []tls.Certificate{c} }),
withTLSConfig(tlsConf, func(c *tls.Config) { opts = append(opts, WithTLSClientConfig(c)) }),
envconfig.WithBool("INSECURE", func(b bool) { opts = append(opts, withInsecure(b)) }),
envconfig.WithBool("TRACES_INSECURE", func(b bool) { opts = append(opts, withInsecure(b)) }),
envconfig.WithHeaders("HEADERS", func(h map[string]string) { opts = append(opts, WithHeaders(h)) }),
envconfig.WithHeaders("TRACES_HEADERS", func(h map[string]string) { opts = append(opts, WithHeaders(h)) }),
WithEnvCompression("COMPRESSION", func(c Compression) { opts = append(opts, WithCompression(c)) }),
Expand Down Expand Up @@ -125,3 +132,19 @@ func WithEnvCompression(n string, fn func(Compression)) func(e *envconfig.EnvOpt
}
}
}

// revive:disable-next-line:flag-parameter
func withInsecure(b bool) GenericOption {
if b {
return WithInsecure()
}
return WithSecure()
}

func withTLSConfig(c *tls.Config, fn func(*tls.Config)) func(e *envconfig.EnvOptionsReader) {
return func(e *envconfig.EnvOptionsReader) {
if c.RootCAs != nil || len(c.Certificates) > 0 {
fn(c)
}
}
}