1
0
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:
Gustavo Silva Paiva 2021-04-02 18:53:40 -03:00 committed by GitHub
parent 35cfbc7e87
commit 52a24774da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 261 additions and 27 deletions

View File

@ -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

View 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
}

View File

@ -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)

View File

@ -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":

View File

@ -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 {

View File

@ -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)
}