From 33b3a6569f1efbf7071b50cb982d77e1b1da4955 Mon Sep 17 00:00:00 2001 From: Michael Freeman Date: Sat, 11 Apr 2026 18:12:22 -0500 Subject: [PATCH 1/2] Add OTLP client certificate support --- pkg/sciontool/telemetry/config.go | 10 ++++ pkg/sciontool/telemetry/config_test.go | 24 ++++++++++ pkg/sciontool/telemetry/exporter.go | 24 ---------- pkg/sciontool/telemetry/exporter_test.go | 61 ++++++++++++++++++++++-- pkg/sciontool/telemetry/otlp_options.go | 39 ++++++++++++++- pkg/util/logging/otel.go | 6 +++ pkg/util/logging/otel_provider.go | 38 ++++++++++----- pkg/util/logging/otel_test.go | 9 ++++ 8 files changed, 172 insertions(+), 39 deletions(-) diff --git a/pkg/sciontool/telemetry/config.go b/pkg/sciontool/telemetry/config.go index ce1ee3f43..49f489982 100644 --- a/pkg/sciontool/telemetry/config.go +++ b/pkg/sciontool/telemetry/config.go @@ -28,6 +28,10 @@ const ( EnvInsecure = "SCION_OTEL_INSECURE" // EnvCAFile is the path to a PEM-encoded CA bundle for OTLP TLS. EnvCAFile = "SCION_OTEL_CA_FILE" + // EnvCertFile is the path to a PEM-encoded client certificate for OTLP mTLS. + EnvCertFile = "SCION_OTEL_CERT_FILE" + // EnvKeyFile is the path to a PEM-encoded client private key for OTLP mTLS. + EnvKeyFile = "SCION_OTEL_KEY_FILE" // EnvGRPCPort is the local gRPC receiver port. EnvGRPCPort = "SCION_OTEL_GRPC_PORT" // EnvHTTPPort is the local HTTP receiver port. @@ -81,6 +85,10 @@ type Config struct { Insecure bool // CAFile is the path to a PEM-encoded CA bundle for OTLP TLS. CAFile string + // CertFile is the path to a PEM-encoded client certificate for OTLP mTLS. + CertFile string + // KeyFile is the path to a PEM-encoded client private key for OTLP mTLS. + KeyFile string // GRPCPort is the local gRPC receiver port. GRPCPort int // HTTPPort is the local HTTP receiver port. @@ -122,6 +130,8 @@ func LoadConfig() *Config { Protocol: getEnvOrDefault(EnvProtocol, DefaultProtocol), Insecure: parseBoolEnv(EnvInsecure, false), CAFile: os.Getenv(EnvCAFile), + CertFile: os.Getenv(EnvCertFile), + KeyFile: os.Getenv(EnvKeyFile), GRPCPort: parseIntEnv(EnvGRPCPort, DefaultGRPCPort), HTTPPort: parseIntEnv(EnvHTTPPort, DefaultHTTPPort), ProjectID: os.Getenv(EnvProjectID), diff --git a/pkg/sciontool/telemetry/config_test.go b/pkg/sciontool/telemetry/config_test.go index 8e3c017a7..66d7c08dd 100644 --- a/pkg/sciontool/telemetry/config_test.go +++ b/pkg/sciontool/telemetry/config_test.go @@ -37,6 +37,12 @@ func TestLoadConfig_Defaults(t *testing.T) { if cfg.CAFile != "" { t.Errorf("Expected CAFile to be empty by default, got %q", cfg.CAFile) } + if cfg.CertFile != "" { + t.Errorf("Expected CertFile to be empty by default, got %q", cfg.CertFile) + } + if cfg.KeyFile != "" { + t.Errorf("Expected KeyFile to be empty by default, got %q", cfg.KeyFile) + } if cfg.MetricsDebug { t.Error("Expected MetricsDebug to be false by default") } @@ -57,6 +63,12 @@ func TestLoadConfig_EnvOverrides(t *testing.T) { if err := os.Setenv(EnvCAFile, "/etc/ssl/certs/custom-root.pem"); err != nil { t.Fatalf("failed to set %s: %v", EnvCAFile, err) } + if err := os.Setenv(EnvCertFile, "/etc/ssl/certs/client.pem"); err != nil { + t.Fatalf("failed to set %s: %v", EnvCertFile, err) + } + if err := os.Setenv(EnvKeyFile, "/etc/ssl/private/client-key.pem"); err != nil { + t.Fatalf("failed to set %s: %v", EnvKeyFile, err) + } os.Setenv(EnvGRPCPort, "14317") os.Setenv(EnvHTTPPort, "14318") os.Setenv(EnvProjectID, "my-project") @@ -85,6 +97,12 @@ func TestLoadConfig_EnvOverrides(t *testing.T) { if cfg.CAFile != "/etc/ssl/certs/custom-root.pem" { t.Errorf("Expected CAFile to be '/etc/ssl/certs/custom-root.pem', got %q", cfg.CAFile) } + if cfg.CertFile != "/etc/ssl/certs/client.pem" { + t.Errorf("Expected CertFile to be '/etc/ssl/certs/client.pem', got %q", cfg.CertFile) + } + if cfg.KeyFile != "/etc/ssl/private/client-key.pem" { + t.Errorf("Expected KeyFile to be '/etc/ssl/private/client-key.pem', got %q", cfg.KeyFile) + } if cfg.GRPCPort != 14317 { t.Errorf("Expected GRPCPort to be 14317, got %d", cfg.GRPCPort) } @@ -568,6 +586,12 @@ func clearTelemetryEnv() { if err := os.Unsetenv(EnvCAFile); err != nil { panic(err) } + if err := os.Unsetenv(EnvCertFile); err != nil { + panic(err) + } + if err := os.Unsetenv(EnvKeyFile); err != nil { + panic(err) + } os.Unsetenv(EnvGRPCPort) os.Unsetenv(EnvHTTPPort) os.Unsetenv(EnvFilterExclude) diff --git a/pkg/sciontool/telemetry/exporter.go b/pkg/sciontool/telemetry/exporter.go index 5dcfac674..b397adbe0 100644 --- a/pkg/sciontool/telemetry/exporter.go +++ b/pkg/sciontool/telemetry/exporter.go @@ -6,9 +6,6 @@ package telemetry import ( "context" - "crypto/tls" - "crypto/x509" - "errors" "fmt" "os" @@ -26,27 +23,6 @@ import ( "google.golang.org/grpc/credentials/oauth" ) -var errOTLPCACertsNotFound = errors.New("parsing OTLP CA file: no certificates found") - -func loadOTLPTLSConfig(caFile string) (*tls.Config, error) { - tlsConfig := &tls.Config{} - if caFile == "" { - return tlsConfig, nil - } - - pemBytes, err := os.ReadFile(caFile) - if err != nil { - return nil, fmt.Errorf("reading OTLP CA file: %w", err) - } - - roots := x509.NewCertPool() - if !roots.AppendCertsFromPEM(pemBytes) { - return nil, errOTLPCACertsNotFound - } - tlsConfig.RootCAs = roots - return tlsConfig, nil -} - // loadGCPDialOptions loads GCP credentials from a service account key file // and returns gRPC dial options for per-RPC authentication. Returns (nil, nil) // if credFile is empty. The credentials are scoped for Cloud Trace, Logging, diff --git a/pkg/sciontool/telemetry/exporter_test.go b/pkg/sciontool/telemetry/exporter_test.go index 299b6bc2b..6e07586a9 100644 --- a/pkg/sciontool/telemetry/exporter_test.go +++ b/pkg/sciontool/telemetry/exporter_test.go @@ -12,6 +12,7 @@ import ( "crypto/x509/pkix" "encoding/json" "encoding/pem" + "errors" "math/big" "os" "path/filepath" @@ -95,7 +96,7 @@ func TestLoadGCPDialOptions_ValidKey(t *testing.T) { } func TestLoadOTLPTLSConfig_EmptyPath(t *testing.T) { - tlsConfig, err := loadOTLPTLSConfig("") + tlsConfig, err := LoadOTLPTLSConfig("", "", "") if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -108,7 +109,7 @@ func TestLoadOTLPTLSConfig_EmptyPath(t *testing.T) { } func TestLoadOTLPTLSConfig_InvalidPath(t *testing.T) { - _, err := loadOTLPTLSConfig("/nonexistent/path/root.pem") + _, err := LoadOTLPTLSConfig("/nonexistent/path/root.pem", "", "") if err == nil { t.Fatal("expected error for missing CA file") } @@ -122,7 +123,7 @@ func TestLoadOTLPTLSConfig_ValidCAFile(t *testing.T) { t.Fatalf("failed to write CA file: %v", err) } - tlsConfig, err := loadOTLPTLSConfig(caPath) + tlsConfig, err := LoadOTLPTLSConfig(caPath, "", "") if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -142,6 +143,34 @@ func TestLoadOTLPTLSConfig_ValidCAFile(t *testing.T) { } } +func TestLoadOTLPTLSConfig_MissingClientKeyPair(t *testing.T) { + _, err := LoadOTLPTLSConfig("", "/tmp/client.pem", "") + if !errors.Is(err, errOTLPMissingClientKeyPair) { + t.Fatalf("expected errOTLPMissingClientKeyPair, got %v", err) + } +} + +func TestLoadOTLPTLSConfig_ClientKeyPair(t *testing.T) { + tmpDir := t.TempDir() + certPEM, keyPEM := generateTestClientKeyPairPEM(t) + certPath := filepath.Join(tmpDir, "client.pem") + keyPath := filepath.Join(tmpDir, "client-key.pem") + if err := os.WriteFile(certPath, certPEM, 0600); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(keyPath, keyPEM, 0600); err != nil { + t.Fatal(err) + } + + tlsConfig, err := LoadOTLPTLSConfig("", certPath, keyPath) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(tlsConfig.Certificates) != 1 { + t.Fatalf("expected one client certificate, got %d", len(tlsConfig.Certificates)) + } +} + func generateTestCertificatePEM(t *testing.T) []byte { t.Helper() @@ -172,3 +201,29 @@ func generateTestCertificatePEM(t *testing.T) []byte { Bytes: derBytes, }) } + +func generateTestClientKeyPairPEM(t *testing.T) ([]byte, []byte) { + t.Helper() + + privKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatalf("failed to generate RSA key: %v", err) + } + + tmpl := &x509.Certificate{ + SerialNumber: big.NewInt(2), + Subject: pkix.Name{CommonName: "scion-test-client"}, + NotBefore: time.Now().Add(-time.Hour), + NotAfter: time.Now().Add(time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + } + + derBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &privKey.PublicKey, privKey) + if err != nil { + t.Fatalf("failed to create test client certificate: %v", err) + } + + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privKey)}) + return certPEM, keyPEM +} diff --git a/pkg/sciontool/telemetry/otlp_options.go b/pkg/sciontool/telemetry/otlp_options.go index 97b84898d..f457e986a 100644 --- a/pkg/sciontool/telemetry/otlp_options.go +++ b/pkg/sciontool/telemetry/otlp_options.go @@ -7,7 +7,10 @@ package telemetry import ( "context" "crypto/tls" + "crypto/x509" + "errors" "fmt" + "os" "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" @@ -18,6 +21,40 @@ import ( "google.golang.org/grpc/credentials/insecure" ) +var errOTLPCACertsNotFound = errors.New("parsing OTLP CA file: no certificates found") +var errOTLPMissingClientKeyPair = errors.New("OTLP client certificate and key must be provided together") + +func LoadOTLPTLSConfig(caFile, certFile, keyFile string) (*tls.Config, error) { + tlsConfig := &tls.Config{} + + if caFile != "" { + pemBytes, err := os.ReadFile(caFile) + if err != nil { + return nil, fmt.Errorf("reading OTLP CA file: %w", err) + } + + roots := x509.NewCertPool() + if !roots.AppendCertsFromPEM(pemBytes) { + return nil, errOTLPCACertsNotFound + } + tlsConfig.RootCAs = roots + } + + if certFile == "" && keyFile == "" { + return tlsConfig, nil + } + if certFile == "" || keyFile == "" { + return nil, errOTLPMissingClientKeyPair + } + + clientCert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return nil, fmt.Errorf("loading OTLP client certificate: %w", err) + } + tlsConfig.Certificates = []tls.Certificate{clientCert} + return tlsConfig, nil +} + func loadSecureGCPDialOptions(ctx context.Context, config *Config) ([]grpc.DialOption, error) { if config.GCPCredentialsFile == "" || config.Insecure { return nil, nil @@ -32,7 +69,7 @@ func loadSecureGCPDialOptions(ctx context.Context, config *Config) ([]grpc.DialO } func loadSecureOTLPTLSConfig(config *Config) (*tls.Config, error) { - tlsConfig, err := loadOTLPTLSConfig(config.CAFile) + tlsConfig, err := LoadOTLPTLSConfig(config.CAFile, config.CertFile, config.KeyFile) if err != nil { return nil, fmt.Errorf("failed to load OTLP TLS config: %w", err) } diff --git a/pkg/util/logging/otel.go b/pkg/util/logging/otel.go index 8f4780286..e5cf846d8 100644 --- a/pkg/util/logging/otel.go +++ b/pkg/util/logging/otel.go @@ -78,6 +78,12 @@ type OTelConfig struct { Protocol string // Insecure skips TLS verification. Insecure bool + // CAFile is the path to a PEM-encoded CA bundle for OTLP TLS. + CAFile string + // CertFile is the path to a PEM-encoded client certificate for OTLP mTLS. + CertFile string + // KeyFile is the path to a PEM-encoded client private key for OTLP mTLS. + KeyFile string // ProjectID is the GCP project ID. ProjectID string } diff --git a/pkg/util/logging/otel_provider.go b/pkg/util/logging/otel_provider.go index e3c825f0e..5d76d15b2 100644 --- a/pkg/util/logging/otel_provider.go +++ b/pkg/util/logging/otel_provider.go @@ -9,17 +9,20 @@ import ( "fmt" "os" + "github.com/GoogleCloudPlatform/scion/pkg/sciontool/telemetry" "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" "go.opentelemetry.io/otel/log" sdklog "go.opentelemetry.io/otel/sdk/log" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/credentials" ) // Environment variable names for OTel logging configuration. const ( - EnvOTelEndpoint = "SCION_OTEL_ENDPOINT" - EnvOTelInsecure = "SCION_OTEL_INSECURE" + EnvOTelEndpoint = telemetry.EnvEndpoint + EnvOTelInsecure = telemetry.EnvInsecure + EnvOTelCAFile = telemetry.EnvCAFile + EnvOTelCertFile = telemetry.EnvCertFile + EnvOTelKeyFile = telemetry.EnvKeyFile EnvOTelLogEnable = "SCION_OTEL_LOG_ENABLED" ) @@ -30,17 +33,21 @@ func NewLoggerProvider(ctx context.Context, config OTelConfig) (log.LoggerProvid return nil, func() {}, nil } - // Build gRPC options - var opts []grpc.DialOption + opts := []otlploggrpc.Option{ + otlploggrpc.WithEndpoint(config.Endpoint), + } if config.Insecure { - opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) + opts = append(opts, otlploggrpc.WithInsecure()) + } else { + tlsConfig, err := telemetry.LoadOTLPTLSConfig(config.CAFile, config.CertFile, config.KeyFile) + if err != nil { + return nil, nil, fmt.Errorf("loading OTLP TLS config: %w", err) + } + opts = append(opts, otlploggrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig))) } // Create the exporter - exporter, err := otlploggrpc.New(ctx, - otlploggrpc.WithEndpoint(config.Endpoint), - otlploggrpc.WithDialOption(opts...), - ) + exporter, err := otlploggrpc.New(ctx, opts...) if err != nil { return nil, nil, fmt.Errorf("creating OTLP log exporter: %w", err) } @@ -80,6 +87,15 @@ func InitOTelLogging(ctx context.Context, config OTelConfig) (log.LoggerProvider if !config.Insecure { config.Insecure = os.Getenv(EnvOTelInsecure) == "true" } + if config.CAFile == "" { + config.CAFile = os.Getenv(EnvOTelCAFile) + } + if config.CertFile == "" { + config.CertFile = os.Getenv(EnvOTelCertFile) + } + if config.KeyFile == "" { + config.KeyFile = os.Getenv(EnvOTelKeyFile) + } return NewLoggerProvider(ctx, config) } diff --git a/pkg/util/logging/otel_test.go b/pkg/util/logging/otel_test.go index dd783638c..9b387a251 100644 --- a/pkg/util/logging/otel_test.go +++ b/pkg/util/logging/otel_test.go @@ -74,12 +74,18 @@ func TestOTelConfig(t *testing.T) { Endpoint: "localhost:4317", Protocol: "grpc", Insecure: true, + CAFile: "/etc/ssl/certs/custom-root.pem", + CertFile: "/etc/ssl/certs/client.pem", + KeyFile: "/etc/ssl/private/client-key.pem", ProjectID: "test-project", } if cfg.Endpoint != "localhost:4317" { t.Error("Endpoint not set correctly") } + if cfg.CAFile != "/etc/ssl/certs/custom-root.pem" || cfg.CertFile != "/etc/ssl/certs/client.pem" || cfg.KeyFile != "/etc/ssl/private/client-key.pem" { + t.Error("TLS file paths not set correctly") + } } func TestNewOTelHandler_NilProvider(t *testing.T) { @@ -154,6 +160,9 @@ func TestEnvVarConstants(t *testing.T) { if EnvOTelInsecure != "SCION_OTEL_INSECURE" { t.Errorf("EnvOTelInsecure = %s, want SCION_OTEL_INSECURE", EnvOTelInsecure) } + if EnvOTelCAFile != "SCION_OTEL_CA_FILE" || EnvOTelCertFile != "SCION_OTEL_CERT_FILE" || EnvOTelKeyFile != "SCION_OTEL_KEY_FILE" { + t.Error("OTel TLS env constants not set correctly") + } if EnvOTelLogEnable != "SCION_OTEL_LOG_ENABLED" { t.Errorf("EnvOTelLogEnable = %s, want SCION_OTEL_LOG_ENABLED", EnvOTelLogEnable) } From ef000f5cf49240204fa4c9cb9b163cf9164c696c Mon Sep 17 00:00:00 2001 From: Michael Freeman Date: Sat, 11 Apr 2026 23:51:10 -0500 Subject: [PATCH 2/2] Preserve OTLP client cert and key paths through config conversion and template merge --- pkg/api/types.go | 2 ++ pkg/config/settings_v1.go | 2 ++ pkg/config/telemetry_convert.go | 2 ++ pkg/config/telemetry_convert_test.go | 10 ++++++++++ pkg/config/templates.go | 6 ++++++ pkg/config/templates_test.go | 4 ++++ 6 files changed, 26 insertions(+) diff --git a/pkg/api/types.go b/pkg/api/types.go index 7d1559706..73931d173 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -258,6 +258,8 @@ type TelemetryTLS struct { Enabled *bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` InsecureSkipVerify *bool `json:"insecure_skip_verify,omitempty" yaml:"insecure_skip_verify,omitempty"` CAFile string `json:"ca_file,omitempty" yaml:"ca_file,omitempty"` + CertFile string `json:"cert_file,omitempty" yaml:"cert_file,omitempty"` + KeyFile string `json:"key_file,omitempty" yaml:"key_file,omitempty"` } // TelemetryBatch holds batch export settings. diff --git a/pkg/config/settings_v1.go b/pkg/config/settings_v1.go index 0fe2eaa27..3ff8d6ef4 100644 --- a/pkg/config/settings_v1.go +++ b/pkg/config/settings_v1.go @@ -466,6 +466,8 @@ type V1TelemetryTLSConfig struct { Enabled *bool `json:"enabled,omitempty" yaml:"enabled,omitempty" koanf:"enabled"` InsecureSkipVerify *bool `json:"insecure_skip_verify,omitempty" yaml:"insecure_skip_verify,omitempty" koanf:"insecure_skip_verify"` CAFile string `json:"ca_file,omitempty" yaml:"ca_file,omitempty" koanf:"ca_file"` + CertFile string `json:"cert_file,omitempty" yaml:"cert_file,omitempty" koanf:"cert_file"` + KeyFile string `json:"key_file,omitempty" yaml:"key_file,omitempty" koanf:"key_file"` } // V1TelemetryBatchConfig holds batch export settings. diff --git a/pkg/config/telemetry_convert.go b/pkg/config/telemetry_convert.go index cd3082202..b5247a51a 100644 --- a/pkg/config/telemetry_convert.go +++ b/pkg/config/telemetry_convert.go @@ -48,6 +48,8 @@ func ConvertV1TelemetryToAPI(v1 *V1TelemetryConfig) *api.TelemetryConfig { Enabled: v1.Cloud.TLS.Enabled, InsecureSkipVerify: v1.Cloud.TLS.InsecureSkipVerify, CAFile: v1.Cloud.TLS.CAFile, + CertFile: v1.Cloud.TLS.CertFile, + KeyFile: v1.Cloud.TLS.KeyFile, } } if v1.Cloud.Batch != nil { diff --git a/pkg/config/telemetry_convert_test.go b/pkg/config/telemetry_convert_test.go index df489a77a..91ba3375c 100644 --- a/pkg/config/telemetry_convert_test.go +++ b/pkg/config/telemetry_convert_test.go @@ -36,6 +36,8 @@ func TestConvertV1TelemetryToAPI_Full(t *testing.T) { insecure := false tlsEnabled := true caFile := "/etc/ssl/certs/custom-root.pem" + certFile := "/etc/ssl/certs/client.pem" + keyFile := "/etc/ssl/private/client-key.pem" hubEnabled := true localEnabled := true console := false @@ -53,6 +55,8 @@ func TestConvertV1TelemetryToAPI_Full(t *testing.T) { Enabled: &tlsEnabled, InsecureSkipVerify: &insecure, CAFile: caFile, + CertFile: certFile, + KeyFile: keyFile, }, Batch: &V1TelemetryBatchConfig{ MaxSize: 512, @@ -116,6 +120,12 @@ func TestConvertV1TelemetryToAPI_Full(t *testing.T) { if result.Cloud.TLS.CAFile != caFile { t.Errorf("Cloud.TLS.CAFile = %q, want %q", result.Cloud.TLS.CAFile, caFile) } + if result.Cloud.TLS.CertFile != certFile { + t.Errorf("Cloud.TLS.CertFile = %q, want %q", result.Cloud.TLS.CertFile, certFile) + } + if result.Cloud.TLS.KeyFile != keyFile { + t.Errorf("Cloud.TLS.KeyFile = %q, want %q", result.Cloud.TLS.KeyFile, keyFile) + } if result.Cloud.Batch == nil { t.Fatal("Cloud.Batch is nil") } diff --git a/pkg/config/templates.go b/pkg/config/templates.go index 575092f9c..09517eb33 100644 --- a/pkg/config/templates.go +++ b/pkg/config/templates.go @@ -820,6 +820,12 @@ func mergeTelemetryConfig(base, override *api.TelemetryConfig) *api.TelemetryCon if override.Cloud.TLS.CAFile != "" { result.Cloud.TLS.CAFile = override.Cloud.TLS.CAFile } + if override.Cloud.TLS.CertFile != "" { + result.Cloud.TLS.CertFile = override.Cloud.TLS.CertFile + } + if override.Cloud.TLS.KeyFile != "" { + result.Cloud.TLS.KeyFile = override.Cloud.TLS.KeyFile + } } if override.Cloud.Batch != nil { if result.Cloud.Batch == nil { diff --git a/pkg/config/templates_test.go b/pkg/config/templates_test.go index c9b09ef68..1ad426d41 100644 --- a/pkg/config/templates_test.go +++ b/pkg/config/templates_test.go @@ -1076,6 +1076,8 @@ func TestMergeScionConfigTelemetry(t *testing.T) { Enabled: boolP(true), InsecureSkipVerify: boolP(false), CAFile: "/etc/ssl/certs/base-root.pem", + CertFile: "/etc/ssl/certs/base-client.pem", + KeyFile: "/etc/ssl/private/base-client-key.pem", }, Batch: &api.TelemetryBatch{ MaxSize: 512, @@ -1111,6 +1113,8 @@ func TestMergeScionConfigTelemetry(t *testing.T) { TLS: &api.TelemetryTLS{ InsecureSkipVerify: boolP(true), CAFile: "/etc/ssl/certs/override-root.pem", + CertFile: "/etc/ssl/certs/override-client.pem", + KeyFile: "/etc/ssl/private/override-client-key.pem", }, Batch: &api.TelemetryBatch{ MaxSize: 256,