[#13] support tls over grpc for otlp_grpc exporter type
All checks were successful
DCO action / DCO (pull_request) Successful in 49s
Tests and linters / Tests (pull_request) Successful in 51s
Tests and linters / Staticcheck (pull_request) Successful in 1m5s
Tests and linters / Tests with -race (pull_request) Successful in 1m20s
Tests and linters / Lint (pull_request) Successful in 1m44s

Signed-off-by: Aleksey Savaitan <a.savaitan@yadro.com>
This commit is contained in:
Aleksey Savaitan 2024-09-04 14:42:22 +03:00
parent 6dd265d949
commit cb173523f4
9 changed files with 228 additions and 2 deletions

25
testdata/testdata.go vendored Normal file
View file

@ -0,0 +1,25 @@
package testdata
import (
"path/filepath"
"runtime"
)
// basepath is the root directory of this package.
var basepath string
func init() {
_, currentFile, _, _ := runtime.Caller(0)
basepath = filepath.Dir(currentFile)
}
// Path returns the absolute path the given relative file or directory path,
// relative to the frostfs-observability/testdata directory in the user's GOPATH.
// If rel is already absolute, it is returned unmodified.
func Path(rel string) string {
if filepath.IsAbs(rel) {
return rel
}
return filepath.Join(basepath, rel)
}

View file

1
testdata/tracing/invalid_root_ca.pem vendored Normal file
View file

@ -0,0 +1 @@
invalid content

View file

@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD
VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh
bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw
MTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0g
UjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wWTAT
BgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkWymOx
uYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNV
HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/
+wpu+74zyTyjhNUwCgYIKoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147
bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm
-----END CERTIFICATE-----

View file

@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD
VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG
A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw
WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz
IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi
QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR
HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D
9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8
p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD
-----END CERTIFICATE-----

View file

@ -1,6 +1,9 @@
package tracing package tracing
import "fmt" import (
"crypto/x509"
"fmt"
)
// Exporter is type of tracing target. // Exporter is type of tracing target.
type Exporter string type Exporter string
@ -18,6 +21,8 @@ type Config struct {
Exporter Exporter Exporter Exporter
// Endpoint is collector endpoint for OTLP exporters. // Endpoint is collector endpoint for OTLP exporters.
Endpoint string Endpoint string
// ServerCaCertPool is cert pool of to remote server CA certificate. Use for TLS setup.
ServerCaCertPool *x509.CertPool
// Service is service name that will be used in tracing. // Service is service name that will be used in tracing.
// Mandatory. // Mandatory.
@ -64,6 +69,10 @@ func (c *Config) hasChange(other *Config) bool {
return !c.serviceInfoEqual(other) return !c.serviceInfoEqual(other)
} }
if other.Exporter == OTLPgRPCExporter && !c.ServerCaCertPool.Equal(other.ServerCaCertPool) {
return true
}
return c.Exporter != other.Exporter || return c.Exporter != other.Exporter ||
c.Endpoint != other.Endpoint || c.Endpoint != other.Endpoint ||
!c.serviceInfoEqual(other) !c.serviceInfoEqual(other)

View file

@ -1,7 +1,12 @@
package tracing package tracing
import ( import (
"crypto/x509"
"os"
"testing" "testing"
"git.frostfs.info/TrueCloudLab/frostfs-observability/testdata"
"github.com/stretchr/testify/require"
) )
func TestConfig_validate(t *testing.T) { func TestConfig_validate(t *testing.T) {
@ -232,6 +237,28 @@ func TestConfig_hasChange(t *testing.T) {
Version: "v1.0.0", Version: "v1.0.0",
}, },
}, },
{
name: "use tls root ca certificate for grpc",
want: true,
config: Config{
Enabled: true,
Exporter: OTLPgRPCExporter,
Endpoint: "localhost:4717",
Service: "test",
InstanceID: "s01",
Version: "v1.0.0",
ServerCaCertPool: readCertPoolByPath(t, "tracing/valid_google_globalsign_r4_rsa_root_ca.pem"),
},
other: Config{
Enabled: true,
Exporter: OTLPgRPCExporter,
Endpoint: "localhost:4717",
Service: "test",
InstanceID: "s01",
Version: "v1.0.0",
ServerCaCertPool: readCertPoolByPath(t, "tracing/valid_google_gts_r4_ecdsa_root_ca.pem"),
},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -241,3 +268,12 @@ func TestConfig_hasChange(t *testing.T) {
}) })
} }
} }
func readCertPoolByPath(t *testing.T, path string) *x509.CertPool {
ca, err := os.ReadFile(testdata.Path(path))
require.NoError(t, err)
roots := x509.NewCertPool()
ok := roots.AppendCertsFromPEM(ca)
require.True(t, ok)
return roots
}

View file

@ -2,6 +2,7 @@ package tracing
import ( import (
"context" "context"
"crypto/x509"
"fmt" "fmt"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -15,8 +16,11 @@ import (
semconv "go.opentelemetry.io/otel/semconv/v1.17.0" semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop" "go.opentelemetry.io/otel/trace/noop"
"google.golang.org/grpc/credentials"
) )
var ErrEmptyServerRootCaPool = fmt.Errorf("empty server root ca cert pool")
var ( var (
// tracingLock protects provider, done, config and tracer from concurrent update. // tracingLock protects provider, done, config and tracer from concurrent update.
// These fields change when the config is updated or the application is shutdown. // These fields change when the config is updated or the application is shutdown.
@ -135,7 +139,14 @@ func getExporter(ctx context.Context, cfg *Config) (sdktrace.SpanExporter, error
case NoOpExporter: case NoOpExporter:
return tracetest.NewNoopExporter(), nil return tracetest.NewNoopExporter(), nil
case OTLPgRPCExporter: case OTLPgRPCExporter:
return otlptracegrpc.New(ctx, otlptracegrpc.WithEndpoint(cfg.Endpoint), otlptracegrpc.WithInsecure()) securityOption := otlptracegrpc.WithInsecure()
if cfg.ServerCaCertPool != nil {
if cfg.ServerCaCertPool.Equal(x509.NewCertPool()) {
return nil, fmt.Errorf("failed to setup tracing: %w", ErrEmptyServerRootCaPool)
}
securityOption = otlptracegrpc.WithTLSCredentials(credentials.NewClientTLSFromCert(cfg.ServerCaCertPool, ""))
}
return otlptracegrpc.New(ctx, otlptracegrpc.WithEndpoint(cfg.Endpoint), securityOption)
} }
} }

119
tracing/setup_test.go Normal file
View file

@ -0,0 +1,119 @@
package tracing_test
import (
"context"
"crypto/x509"
"os"
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-observability/testdata"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"github.com/stretchr/testify/require"
)
func TestSetup(t *testing.T) {
tests := []struct {
name string
config tracing.Config
want bool
expErr error
}{
{
name: "setup stdout exporter",
config: tracing.Config{
Enabled: true,
Exporter: tracing.StdoutExporter,
Service: "service-name",
},
want: true,
expErr: nil,
},
{
name: "setup noop exporter",
config: tracing.Config{
Enabled: true,
Exporter: tracing.NoOpExporter,
Service: "service-name",
},
want: true,
expErr: nil,
},
{
name: "setup otlp_grpc insecure exporter",
config: tracing.Config{
Enabled: true,
Exporter: tracing.OTLPgRPCExporter,
Service: "service-name",
Endpoint: "test-endpoint.com:4317",
},
want: true,
expErr: nil,
},
{
name: "setup otlp_grpc secure exporter with valid rsa root ca certificate",
config: tracing.Config{
Enabled: true,
Exporter: tracing.OTLPgRPCExporter,
Service: "service-name",
Endpoint: "test-endpoint.com:4317",
ServerCaCertPool: readCertPoolByPath(t, "tracing/valid_google_globalsign_r4_rsa_root_ca.pem"),
},
want: true,
expErr: nil,
},
{
name: "setup otlp_grpc secure exporter with valid ecdsa root ca certificate",
config: tracing.Config{
Enabled: true,
Exporter: tracing.OTLPgRPCExporter,
Service: "service-name",
Endpoint: "test-endpoint.com:4317",
ServerCaCertPool: readCertPoolByPath(t, "tracing/valid_google_gts_r4_ecdsa_root_ca.pem"),
},
want: true,
expErr: nil,
},
{
name: "setup otlp_grpc secure exporter with invalid root ca certificate",
config: tracing.Config{
Enabled: true,
Exporter: tracing.OTLPgRPCExporter,
Service: "service-name",
Endpoint: "test-endpoint.com:4317",
ServerCaCertPool: readCertPoolByPath(t, "tracing/invalid_root_ca.pem"),
},
want: false,
expErr: tracing.ErrEmptyServerRootCaPool,
},
{
name: "setup otlp_grpc secure exporter with empty root ca certificate",
config: tracing.Config{
Enabled: true,
Exporter: tracing.OTLPgRPCExporter,
Service: "service-name",
Endpoint: "test-endpoint.com:4317",
ServerCaCertPool: readCertPoolByPath(t, "tracing/invalid_empty_root_ca.pem"),
},
want: false,
expErr: tracing.ErrEmptyServerRootCaPool,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tracing.Setup(context.Background(), tt.config)
require.ErrorIs(t, err, tt.expErr)
if got != tt.want {
t.Errorf("Setup config = %v, want %v", got, tt.want)
}
})
}
}
func readCertPoolByPath(t *testing.T, path string) *x509.CertPool {
ca, err := os.ReadFile(testdata.Path(path))
require.NoError(t, err)
roots := x509.NewCertPool()
_ = roots.AppendCertsFromPEM(ca)
return roots
}