Skip to content

Commit

Permalink
feat: configure TLS with environment variables.
Browse files Browse the repository at this point in the history
Updates the opentelemetry-otlp crate to allow users to configure TLS
using environment variables. Removing the need to crating the TLS config
object and defining it with the `with_tls_config` method. In the same
way other OTLP libraries does (e.g. go lang).

Signed-off-by: José Guilherme Vanz <[email protected]>
  • Loading branch information
jvanz committed Dec 24, 2024
1 parent 957659f commit 50894e9
Show file tree
Hide file tree
Showing 7 changed files with 328 additions and 34 deletions.
61 changes: 61 additions & 0 deletions opentelemetry-otlp/src/exporter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ pub const OTEL_EXPORTER_OTLP_PROTOCOL: &str = "OTEL_EXPORTER_OTLP_PROTOCOL";
/// Compression algorithm to use, defaults to none.
pub const OTEL_EXPORTER_OTLP_COMPRESSION: &str = "OTEL_EXPORTER_OTLP_COMPRESSION";

/// Certificate file to validate the OTLP server connection
#[cfg(feature = "tls")]
pub const OTEL_EXPORTER_OTLP_CERTIFICATE: &str = "OTEL_EXPORTER_OTLP_CERTIFICATE";
/// Path to the certificate file to use for client authentication (mTLS).
#[cfg(feature = "tls")]
pub const OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE: &str = "OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE";
/// Path to the key file to use for client authentication (mTLS).
#[cfg(feature = "tls")]
pub const OTEL_EXPORTER_OTLP_CLIENT_KEY: &str = "OTEL_EXPORTER_OTLP_CLIENT_KEY";
/// Use insecure connection. Disable TLS
#[cfg(feature = "tls")]
pub const OTEL_EXPORTER_OTLP_INSECURE: &str = "OTEL_EXPORTER_OTLP_INSECURE";

#[cfg(feature = "http-json")]
/// Default protocol, using http-json.
pub const OTEL_EXPORTER_OTLP_PROTOCOL_DEFAULT: &str = OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_JSON;
Expand Down Expand Up @@ -76,6 +89,18 @@ pub struct ExportConfig {

/// The timeout to the collector.
pub timeout: Duration,

/// Disable TLS
pub insecure: Option<bool>,

/// The certificate file to validate the OTLP server connection
pub certificate: Option<String>,

/// The path to the certificate file to use for client authentication (mTLS).
pub client_certificate: Option<String>,

/// The path to the key file to use for client authentication (mTLS).
pub client_key: Option<String>,
}

impl Default for ExportConfig {
Expand All @@ -88,6 +113,10 @@ impl Default for ExportConfig {
// won't know if user provided a value
protocol,
timeout: Duration::from_secs(OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT),
insecure: None,
certificate: None,
client_certificate: None,
client_key: None,
}
}
}
Expand Down Expand Up @@ -195,6 +224,17 @@ pub trait WithExportConfig {
fn with_timeout(self, timeout: Duration) -> Self;
/// Set export config. This will override all previous configuration.
fn with_export_config(self, export_config: ExportConfig) -> Self;
/// Set insecure connection. Disable TLS
fn with_insecure(self) -> Self;
/// Set the certificate file to validate the OTLP server connection
/// This is only available when the `tls` feature is enabled.
fn with_certificate<T: Into<String>>(self, certificate: T) -> Self;
/// Set the path to the certificate file to use for client authentication (mTLS).
/// This is only available when the `tls` feature is enabled.
fn with_client_certificate<T: Into<String>>(self, client_certificate: T) -> Self;
/// Set the path to the key file to use for client authentication (mTLS).
/// This is only available when the `tls` feature is enabled.
fn with_client_key<T: Into<String>>(self, client_key: T) -> Self;
}

impl<B: HasExportConfig> WithExportConfig for B {
Expand All @@ -217,6 +257,27 @@ impl<B: HasExportConfig> WithExportConfig for B {
self.export_config().endpoint = exporter_config.endpoint;
self.export_config().protocol = exporter_config.protocol;
self.export_config().timeout = exporter_config.timeout;
self.export_config().insecure = Some(true);
self
}

fn with_insecure(mut self) -> Self {
self.export_config().insecure = Some(true);
self
}

fn with_certificate<T: Into<String>>(mut self, certificate: T) -> Self {
self.export_config().certificate = Some(certificate.into());
self
}

fn with_client_certificate<T: Into<String>>(mut self, client_certificate: T) -> Self {
self.export_config().client_certificate = Some(client_certificate.into());
self
}

fn with_client_key<T: Into<String>>(mut self, client_key: T) -> Self {
self.export_config().client_key = Some(client_key.into());
self
}
}
Expand Down
232 changes: 198 additions & 34 deletions opentelemetry-otlp/src/exporter/tonic/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::env;
use std::fmt::{Debug, Formatter};
#[cfg(feature = "tls")]
use std::fs;
use std::str::FromStr;
use std::time::Duration;

Expand All @@ -9,7 +11,7 @@ use tonic::metadata::{KeyAndValueRef, MetadataMap};
use tonic::service::Interceptor;
use tonic::transport::Channel;
#[cfg(feature = "tls")]
use tonic::transport::ClientTlsConfig;
use tonic::transport::{Certificate, ClientTlsConfig, Identity};

use super::{default_headers, parse_header_string, OTEL_EXPORTER_OTLP_GRPC_ENDPOINT_DEFAULT};
use crate::exporter::Compression;
Expand All @@ -18,6 +20,12 @@ use crate::{
OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TIMEOUT,
};

#[cfg(feature = "tls")]
use crate::{
OTEL_EXPORTER_OTLP_CERTIFICATE, OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE,
OTEL_EXPORTER_OTLP_CLIENT_KEY, OTEL_EXPORTER_OTLP_INSECURE,
};

#[cfg(feature = "logs")]
mod logs;

Expand Down Expand Up @@ -144,12 +152,17 @@ impl Default for TonicExporterBuilder {
}

impl TonicExporterBuilder {
#[allow(clippy::too_many_arguments)]
fn build_channel(
self,
signal_endpoint_var: &str,
signal_timeout_var: &str,
signal_compression_var: &str,
signal_headers_var: &str,
#[cfg(feature = "tls")] signal_insecure_var: &str,
#[cfg(feature = "tls")] signal_certificate_var: &str,
#[cfg(feature = "tls")] signal_client_cert_var: &str,
#[cfg(feature = "tls")] signal_client_key_var: &str,
) -> Result<(Channel, BoxInterceptor, Option<CompressionEncoding>), crate::Error> {
let compression = self.resolve_compression(signal_compression_var)?;

Expand Down Expand Up @@ -203,19 +216,107 @@ impl TonicExporterBuilder {
};

#[cfg(feature = "tls")]
let channel = match self.tonic_config.tls_config {
Some(tls_config) => endpoint
.tls_config(tls_config)
.map_err(crate::Error::from)?,
None => endpoint,
{
let insecure = config.insecure.unwrap_or_else(|| {
env::var(signal_insecure_var)
.or_else(|_| env::var(OTEL_EXPORTER_OTLP_INSECURE))
.map_or(false, |x| {
if x == "1" {
true
} else if x == "0" || x != "true" || x != "false" {
false
} else {
bool::from_str(&x).unwrap_or(false)
}
})
});

let channel = match self.tonic_config.tls_config {
Some(tls_config) => endpoint
.tls_config(tls_config)
.map_err(crate::Error::from)?,
None => {
if !insecure {
let tls_config = Self::resolve_tls_config(
signal_certificate_var,
signal_client_cert_var,
signal_client_key_var,
self.tonic_config.tls_config,
config.certificate,
config.client_certificate,
config.client_key,
)?;
endpoint
.tls_config(tls_config)
.map_err(crate::Error::from)?
} else {
endpoint
}
}
}
.timeout(timeout)
.connect_lazy();
println!("{:?}", channel);
Ok((channel, interceptor, compression))
}
.timeout(timeout)
.connect_lazy();

#[cfg(not(feature = "tls"))]
let channel = endpoint.timeout(timeout).connect_lazy();
{
let channel = endpoint.timeout(timeout).connect_lazy();
Ok((channel, interceptor, compression))
}
}

Ok((channel, interceptor, compression))
#[cfg(feature = "tls")]
fn resolve_tls_config(
signal_certificate_var: &str,
signal_client_cert_var: &str,
signal_client_key_var: &str,
tls_config: Option<ClientTlsConfig>,
provided_certificate: Option<String>,
provided_client_cert: Option<String>,
provided_client_key: Option<String>,
) -> Result<ClientTlsConfig, crate::Error> {
// User provided tls config. Use it.
if let Some(tls_config) = tls_config {
return Ok(tls_config);
}

// No user provided tls config. Try to build one from env vars.
let mut client_tls_config = ClientTlsConfig::new();

let ca_file = provided_certificate.or_else(|| {
env::var(signal_certificate_var)
.or_else(|_| env::var(OTEL_EXPORTER_OTLP_CERTIFICATE))
.ok()
});
let client_cert_file = provided_client_cert.or_else(|| {
env::var(signal_client_cert_var)
.or_else(|_| env::var(OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE))
.ok()
});
let client_key_file = provided_client_key.or_else(|| {
env::var(signal_client_key_var)
.or_else(|_| env::var(OTEL_EXPORTER_OTLP_CLIENT_KEY))
.ok()
});
if let Some(ca_path) = ca_file {
let ca_cert =
std::fs::read(ca_path).map_err(|x| crate::Error::TLSConfigError(x.to_string()))?;
client_tls_config = client_tls_config.ca_certificate(Certificate::from_pem(ca_cert));
}

if let (Some(cert_path), Some(key_path)) = (client_cert_file, client_key_file) {
let cert =
fs::read(cert_path).map_err(|x| crate::Error::TLSConfigError(x.to_string()))?;
let key =
fs::read(key_path).map_err(|x| crate::Error::TLSConfigError(x.to_string()))?;

let identity = Identity::from_pem(cert, key);
client_tls_config = client_tls_config.identity(identity);
}
println!("{:?}", client_tls_config);
Ok(client_tls_config)
}

fn resolve_endpoint(default_endpoint_var: &str, provided_endpoint: Option<String>) -> String {
Expand Down Expand Up @@ -257,16 +358,37 @@ impl TonicExporterBuilder {
) -> Result<crate::logs::LogExporter, opentelemetry_sdk::logs::LogError> {
use crate::exporter::tonic::logs::TonicLogsClient;

let (channel, interceptor, compression) = self.build_channel(
crate::logs::OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
crate::logs::OTEL_EXPORTER_OTLP_LOGS_TIMEOUT,
crate::logs::OTEL_EXPORTER_OTLP_LOGS_COMPRESSION,
crate::logs::OTEL_EXPORTER_OTLP_LOGS_HEADERS,
)?;
#[cfg(not(feature = "tls"))]
{
let (channel, interceptor, compression) = self.build_channel(
crate::logs::OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
crate::logs::OTEL_EXPORTER_OTLP_LOGS_TIMEOUT,
crate::logs::OTEL_EXPORTER_OTLP_LOGS_COMPRESSION,
crate::logs::OTEL_EXPORTER_OTLP_LOGS_HEADERS,
)?;

let client = TonicLogsClient::new(channel, interceptor, compression);

let client = TonicLogsClient::new(channel, interceptor, compression);
Ok(crate::logs::LogExporter::new(client))
}

Ok(crate::logs::LogExporter::new(client))
#[cfg(feature = "tls")]
{
let (channel, interceptor, compression) = self.build_channel(
crate::logs::OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
crate::logs::OTEL_EXPORTER_OTLP_LOGS_TIMEOUT,
crate::logs::OTEL_EXPORTER_OTLP_LOGS_COMPRESSION,
crate::logs::OTEL_EXPORTER_OTLP_LOGS_HEADERS,
crate::logs::OTEL_EXPORTER_OTLP_LOGS_INSECURE,
crate::logs::OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE,
crate::logs::OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE,
crate::logs::OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY,
)?;

let client = TonicLogsClient::new(channel, interceptor, compression);

Ok(crate::logs::LogExporter::new(client))
}
}

/// Build a new tonic metrics exporter
Expand All @@ -278,16 +400,37 @@ impl TonicExporterBuilder {
use crate::MetricExporter;
use metrics::TonicMetricsClient;

let (channel, interceptor, compression) = self.build_channel(
crate::metric::OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
crate::metric::OTEL_EXPORTER_OTLP_METRICS_TIMEOUT,
crate::metric::OTEL_EXPORTER_OTLP_METRICS_COMPRESSION,
crate::metric::OTEL_EXPORTER_OTLP_METRICS_HEADERS,
)?;
#[cfg(not(feature = "tls"))]
{
let (channel, interceptor, compression) = self.build_channel(
crate::metric::OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
crate::metric::OTEL_EXPORTER_OTLP_METRICS_TIMEOUT,
crate::metric::OTEL_EXPORTER_OTLP_METRICS_COMPRESSION,
crate::metric::OTEL_EXPORTER_OTLP_METRICS_HEADERS,
)?;

let client = TonicMetricsClient::new(channel, interceptor, compression);
let client = TonicMetricsClient::new(channel, interceptor, compression);

Ok(MetricExporter::new(client, temporality))
}

Ok(MetricExporter::new(client, temporality))
#[cfg(feature = "tls")]
{
let (channel, interceptor, compression) = self.build_channel(
crate::metric::OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
crate::metric::OTEL_EXPORTER_OTLP_METRICS_TIMEOUT,
crate::metric::OTEL_EXPORTER_OTLP_METRICS_COMPRESSION,
crate::metric::OTEL_EXPORTER_OTLP_METRICS_HEADERS,
crate::metric::OTEL_EXPORTER_OTLP_METRICS_INSECURE,
crate::metric::OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE,
crate::metric::OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE,
crate::metric::OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY,
)?;

let client = TonicMetricsClient::new(channel, interceptor, compression);

Ok(MetricExporter::new(client, temporality))
}
}

/// Build a new tonic span exporter
Expand All @@ -297,16 +440,37 @@ impl TonicExporterBuilder {
) -> Result<crate::SpanExporter, opentelemetry::trace::TraceError> {
use crate::exporter::tonic::trace::TonicTracesClient;

let (channel, interceptor, compression) = self.build_channel(
crate::span::OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
crate::span::OTEL_EXPORTER_OTLP_TRACES_TIMEOUT,
crate::span::OTEL_EXPORTER_OTLP_TRACES_COMPRESSION,
crate::span::OTEL_EXPORTER_OTLP_TRACES_HEADERS,
)?;
#[cfg(not(feature = "tls"))]
{
let (channel, interceptor, compression) = self.build_channel(
crate::span::OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
crate::span::OTEL_EXPORTER_OTLP_TRACES_TIMEOUT,
crate::span::OTEL_EXPORTER_OTLP_TRACES_COMPRESSION,
crate::span::OTEL_EXPORTER_OTLP_TRACES_HEADERS,
)?;

let client = TonicTracesClient::new(channel, interceptor, compression);

let client = TonicTracesClient::new(channel, interceptor, compression);
Ok(crate::SpanExporter::new(client))
}

Ok(crate::SpanExporter::new(client))
#[cfg(feature = "tls")]
{
let (channel, interceptor, compression) = self.build_channel(
crate::span::OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
crate::span::OTEL_EXPORTER_OTLP_TRACES_TIMEOUT,
crate::span::OTEL_EXPORTER_OTLP_TRACES_COMPRESSION,
crate::span::OTEL_EXPORTER_OTLP_TRACES_HEADERS,
crate::span::OTEL_EXPORTER_OTLP_TRACES_INSECURE,
crate::span::OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE,
crate::span::OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE,
crate::span::OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY,
)?;

let client = TonicTracesClient::new(channel, interceptor, compression);

Ok(crate::SpanExporter::new(client))
}
}
}

Expand Down
Loading

0 comments on commit 50894e9

Please sign in to comment.