Skip to content

Commit 50894e9

Browse files
committed
feat: configure TLS with environment variables.
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]>
1 parent 957659f commit 50894e9

File tree

7 files changed

+328
-34
lines changed

7 files changed

+328
-34
lines changed

opentelemetry-otlp/src/exporter/mod.rs

+61
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,19 @@ pub const OTEL_EXPORTER_OTLP_PROTOCOL: &str = "OTEL_EXPORTER_OTLP_PROTOCOL";
2828
/// Compression algorithm to use, defaults to none.
2929
pub const OTEL_EXPORTER_OTLP_COMPRESSION: &str = "OTEL_EXPORTER_OTLP_COMPRESSION";
3030

31+
/// Certificate file to validate the OTLP server connection
32+
#[cfg(feature = "tls")]
33+
pub const OTEL_EXPORTER_OTLP_CERTIFICATE: &str = "OTEL_EXPORTER_OTLP_CERTIFICATE";
34+
/// Path to the certificate file to use for client authentication (mTLS).
35+
#[cfg(feature = "tls")]
36+
pub const OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE: &str = "OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE";
37+
/// Path to the key file to use for client authentication (mTLS).
38+
#[cfg(feature = "tls")]
39+
pub const OTEL_EXPORTER_OTLP_CLIENT_KEY: &str = "OTEL_EXPORTER_OTLP_CLIENT_KEY";
40+
/// Use insecure connection. Disable TLS
41+
#[cfg(feature = "tls")]
42+
pub const OTEL_EXPORTER_OTLP_INSECURE: &str = "OTEL_EXPORTER_OTLP_INSECURE";
43+
3144
#[cfg(feature = "http-json")]
3245
/// Default protocol, using http-json.
3346
pub const OTEL_EXPORTER_OTLP_PROTOCOL_DEFAULT: &str = OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_JSON;
@@ -76,6 +89,18 @@ pub struct ExportConfig {
7689

7790
/// The timeout to the collector.
7891
pub timeout: Duration,
92+
93+
/// Disable TLS
94+
pub insecure: Option<bool>,
95+
96+
/// The certificate file to validate the OTLP server connection
97+
pub certificate: Option<String>,
98+
99+
/// The path to the certificate file to use for client authentication (mTLS).
100+
pub client_certificate: Option<String>,
101+
102+
/// The path to the key file to use for client authentication (mTLS).
103+
pub client_key: Option<String>,
79104
}
80105

81106
impl Default for ExportConfig {
@@ -88,6 +113,10 @@ impl Default for ExportConfig {
88113
// won't know if user provided a value
89114
protocol,
90115
timeout: Duration::from_secs(OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT),
116+
insecure: None,
117+
certificate: None,
118+
client_certificate: None,
119+
client_key: None,
91120
}
92121
}
93122
}
@@ -195,6 +224,17 @@ pub trait WithExportConfig {
195224
fn with_timeout(self, timeout: Duration) -> Self;
196225
/// Set export config. This will override all previous configuration.
197226
fn with_export_config(self, export_config: ExportConfig) -> Self;
227+
/// Set insecure connection. Disable TLS
228+
fn with_insecure(self) -> Self;
229+
/// Set the certificate file to validate the OTLP server connection
230+
/// This is only available when the `tls` feature is enabled.
231+
fn with_certificate<T: Into<String>>(self, certificate: T) -> Self;
232+
/// Set the path to the certificate file to use for client authentication (mTLS).
233+
/// This is only available when the `tls` feature is enabled.
234+
fn with_client_certificate<T: Into<String>>(self, client_certificate: T) -> Self;
235+
/// Set the path to the key file to use for client authentication (mTLS).
236+
/// This is only available when the `tls` feature is enabled.
237+
fn with_client_key<T: Into<String>>(self, client_key: T) -> Self;
198238
}
199239

200240
impl<B: HasExportConfig> WithExportConfig for B {
@@ -217,6 +257,27 @@ impl<B: HasExportConfig> WithExportConfig for B {
217257
self.export_config().endpoint = exporter_config.endpoint;
218258
self.export_config().protocol = exporter_config.protocol;
219259
self.export_config().timeout = exporter_config.timeout;
260+
self.export_config().insecure = Some(true);
261+
self
262+
}
263+
264+
fn with_insecure(mut self) -> Self {
265+
self.export_config().insecure = Some(true);
266+
self
267+
}
268+
269+
fn with_certificate<T: Into<String>>(mut self, certificate: T) -> Self {
270+
self.export_config().certificate = Some(certificate.into());
271+
self
272+
}
273+
274+
fn with_client_certificate<T: Into<String>>(mut self, client_certificate: T) -> Self {
275+
self.export_config().client_certificate = Some(client_certificate.into());
276+
self
277+
}
278+
279+
fn with_client_key<T: Into<String>>(mut self, client_key: T) -> Self {
280+
self.export_config().client_key = Some(client_key.into());
220281
self
221282
}
222283
}

opentelemetry-otlp/src/exporter/tonic/mod.rs

+198-34
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::env;
22
use std::fmt::{Debug, Formatter};
3+
#[cfg(feature = "tls")]
4+
use std::fs;
35
use std::str::FromStr;
46
use std::time::Duration;
57

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

1416
use super::{default_headers, parse_header_string, OTEL_EXPORTER_OTLP_GRPC_ENDPOINT_DEFAULT};
1517
use crate::exporter::Compression;
@@ -18,6 +20,12 @@ use crate::{
1820
OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TIMEOUT,
1921
};
2022

23+
#[cfg(feature = "tls")]
24+
use crate::{
25+
OTEL_EXPORTER_OTLP_CERTIFICATE, OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE,
26+
OTEL_EXPORTER_OTLP_CLIENT_KEY, OTEL_EXPORTER_OTLP_INSECURE,
27+
};
28+
2129
#[cfg(feature = "logs")]
2230
mod logs;
2331

@@ -144,12 +152,17 @@ impl Default for TonicExporterBuilder {
144152
}
145153

146154
impl TonicExporterBuilder {
155+
#[allow(clippy::too_many_arguments)]
147156
fn build_channel(
148157
self,
149158
signal_endpoint_var: &str,
150159
signal_timeout_var: &str,
151160
signal_compression_var: &str,
152161
signal_headers_var: &str,
162+
#[cfg(feature = "tls")] signal_insecure_var: &str,
163+
#[cfg(feature = "tls")] signal_certificate_var: &str,
164+
#[cfg(feature = "tls")] signal_client_cert_var: &str,
165+
#[cfg(feature = "tls")] signal_client_key_var: &str,
153166
) -> Result<(Channel, BoxInterceptor, Option<CompressionEncoding>), crate::Error> {
154167
let compression = self.resolve_compression(signal_compression_var)?;
155168

@@ -203,19 +216,107 @@ impl TonicExporterBuilder {
203216
};
204217

205218
#[cfg(feature = "tls")]
206-
let channel = match self.tonic_config.tls_config {
207-
Some(tls_config) => endpoint
208-
.tls_config(tls_config)
209-
.map_err(crate::Error::from)?,
210-
None => endpoint,
219+
{
220+
let insecure = config.insecure.unwrap_or_else(|| {
221+
env::var(signal_insecure_var)
222+
.or_else(|_| env::var(OTEL_EXPORTER_OTLP_INSECURE))
223+
.map_or(false, |x| {
224+
if x == "1" {
225+
true
226+
} else if x == "0" || x != "true" || x != "false" {
227+
false
228+
} else {
229+
bool::from_str(&x).unwrap_or(false)
230+
}
231+
})
232+
});
233+
234+
let channel = match self.tonic_config.tls_config {
235+
Some(tls_config) => endpoint
236+
.tls_config(tls_config)
237+
.map_err(crate::Error::from)?,
238+
None => {
239+
if !insecure {
240+
let tls_config = Self::resolve_tls_config(
241+
signal_certificate_var,
242+
signal_client_cert_var,
243+
signal_client_key_var,
244+
self.tonic_config.tls_config,
245+
config.certificate,
246+
config.client_certificate,
247+
config.client_key,
248+
)?;
249+
endpoint
250+
.tls_config(tls_config)
251+
.map_err(crate::Error::from)?
252+
} else {
253+
endpoint
254+
}
255+
}
256+
}
257+
.timeout(timeout)
258+
.connect_lazy();
259+
println!("{:?}", channel);
260+
Ok((channel, interceptor, compression))
211261
}
212-
.timeout(timeout)
213-
.connect_lazy();
214262

215263
#[cfg(not(feature = "tls"))]
216-
let channel = endpoint.timeout(timeout).connect_lazy();
264+
{
265+
let channel = endpoint.timeout(timeout).connect_lazy();
266+
Ok((channel, interceptor, compression))
267+
}
268+
}
217269

218-
Ok((channel, interceptor, compression))
270+
#[cfg(feature = "tls")]
271+
fn resolve_tls_config(
272+
signal_certificate_var: &str,
273+
signal_client_cert_var: &str,
274+
signal_client_key_var: &str,
275+
tls_config: Option<ClientTlsConfig>,
276+
provided_certificate: Option<String>,
277+
provided_client_cert: Option<String>,
278+
provided_client_key: Option<String>,
279+
) -> Result<ClientTlsConfig, crate::Error> {
280+
// User provided tls config. Use it.
281+
if let Some(tls_config) = tls_config {
282+
return Ok(tls_config);
283+
}
284+
285+
// No user provided tls config. Try to build one from env vars.
286+
let mut client_tls_config = ClientTlsConfig::new();
287+
288+
let ca_file = provided_certificate.or_else(|| {
289+
env::var(signal_certificate_var)
290+
.or_else(|_| env::var(OTEL_EXPORTER_OTLP_CERTIFICATE))
291+
.ok()
292+
});
293+
let client_cert_file = provided_client_cert.or_else(|| {
294+
env::var(signal_client_cert_var)
295+
.or_else(|_| env::var(OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE))
296+
.ok()
297+
});
298+
let client_key_file = provided_client_key.or_else(|| {
299+
env::var(signal_client_key_var)
300+
.or_else(|_| env::var(OTEL_EXPORTER_OTLP_CLIENT_KEY))
301+
.ok()
302+
});
303+
if let Some(ca_path) = ca_file {
304+
let ca_cert =
305+
std::fs::read(ca_path).map_err(|x| crate::Error::TLSConfigError(x.to_string()))?;
306+
client_tls_config = client_tls_config.ca_certificate(Certificate::from_pem(ca_cert));
307+
}
308+
309+
if let (Some(cert_path), Some(key_path)) = (client_cert_file, client_key_file) {
310+
let cert =
311+
fs::read(cert_path).map_err(|x| crate::Error::TLSConfigError(x.to_string()))?;
312+
let key =
313+
fs::read(key_path).map_err(|x| crate::Error::TLSConfigError(x.to_string()))?;
314+
315+
let identity = Identity::from_pem(cert, key);
316+
client_tls_config = client_tls_config.identity(identity);
317+
}
318+
println!("{:?}", client_tls_config);
319+
Ok(client_tls_config)
219320
}
220321

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

260-
let (channel, interceptor, compression) = self.build_channel(
261-
crate::logs::OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
262-
crate::logs::OTEL_EXPORTER_OTLP_LOGS_TIMEOUT,
263-
crate::logs::OTEL_EXPORTER_OTLP_LOGS_COMPRESSION,
264-
crate::logs::OTEL_EXPORTER_OTLP_LOGS_HEADERS,
265-
)?;
361+
#[cfg(not(feature = "tls"))]
362+
{
363+
let (channel, interceptor, compression) = self.build_channel(
364+
crate::logs::OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
365+
crate::logs::OTEL_EXPORTER_OTLP_LOGS_TIMEOUT,
366+
crate::logs::OTEL_EXPORTER_OTLP_LOGS_COMPRESSION,
367+
crate::logs::OTEL_EXPORTER_OTLP_LOGS_HEADERS,
368+
)?;
369+
370+
let client = TonicLogsClient::new(channel, interceptor, compression);
266371

267-
let client = TonicLogsClient::new(channel, interceptor, compression);
372+
Ok(crate::logs::LogExporter::new(client))
373+
}
268374

269-
Ok(crate::logs::LogExporter::new(client))
375+
#[cfg(feature = "tls")]
376+
{
377+
let (channel, interceptor, compression) = self.build_channel(
378+
crate::logs::OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
379+
crate::logs::OTEL_EXPORTER_OTLP_LOGS_TIMEOUT,
380+
crate::logs::OTEL_EXPORTER_OTLP_LOGS_COMPRESSION,
381+
crate::logs::OTEL_EXPORTER_OTLP_LOGS_HEADERS,
382+
crate::logs::OTEL_EXPORTER_OTLP_LOGS_INSECURE,
383+
crate::logs::OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE,
384+
crate::logs::OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE,
385+
crate::logs::OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY,
386+
)?;
387+
388+
let client = TonicLogsClient::new(channel, interceptor, compression);
389+
390+
Ok(crate::logs::LogExporter::new(client))
391+
}
270392
}
271393

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

281-
let (channel, interceptor, compression) = self.build_channel(
282-
crate::metric::OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
283-
crate::metric::OTEL_EXPORTER_OTLP_METRICS_TIMEOUT,
284-
crate::metric::OTEL_EXPORTER_OTLP_METRICS_COMPRESSION,
285-
crate::metric::OTEL_EXPORTER_OTLP_METRICS_HEADERS,
286-
)?;
403+
#[cfg(not(feature = "tls"))]
404+
{
405+
let (channel, interceptor, compression) = self.build_channel(
406+
crate::metric::OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
407+
crate::metric::OTEL_EXPORTER_OTLP_METRICS_TIMEOUT,
408+
crate::metric::OTEL_EXPORTER_OTLP_METRICS_COMPRESSION,
409+
crate::metric::OTEL_EXPORTER_OTLP_METRICS_HEADERS,
410+
)?;
287411

288-
let client = TonicMetricsClient::new(channel, interceptor, compression);
412+
let client = TonicMetricsClient::new(channel, interceptor, compression);
413+
414+
Ok(MetricExporter::new(client, temporality))
415+
}
289416

290-
Ok(MetricExporter::new(client, temporality))
417+
#[cfg(feature = "tls")]
418+
{
419+
let (channel, interceptor, compression) = self.build_channel(
420+
crate::metric::OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
421+
crate::metric::OTEL_EXPORTER_OTLP_METRICS_TIMEOUT,
422+
crate::metric::OTEL_EXPORTER_OTLP_METRICS_COMPRESSION,
423+
crate::metric::OTEL_EXPORTER_OTLP_METRICS_HEADERS,
424+
crate::metric::OTEL_EXPORTER_OTLP_METRICS_INSECURE,
425+
crate::metric::OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE,
426+
crate::metric::OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE,
427+
crate::metric::OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY,
428+
)?;
429+
430+
let client = TonicMetricsClient::new(channel, interceptor, compression);
431+
432+
Ok(MetricExporter::new(client, temporality))
433+
}
291434
}
292435

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

300-
let (channel, interceptor, compression) = self.build_channel(
301-
crate::span::OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
302-
crate::span::OTEL_EXPORTER_OTLP_TRACES_TIMEOUT,
303-
crate::span::OTEL_EXPORTER_OTLP_TRACES_COMPRESSION,
304-
crate::span::OTEL_EXPORTER_OTLP_TRACES_HEADERS,
305-
)?;
443+
#[cfg(not(feature = "tls"))]
444+
{
445+
let (channel, interceptor, compression) = self.build_channel(
446+
crate::span::OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
447+
crate::span::OTEL_EXPORTER_OTLP_TRACES_TIMEOUT,
448+
crate::span::OTEL_EXPORTER_OTLP_TRACES_COMPRESSION,
449+
crate::span::OTEL_EXPORTER_OTLP_TRACES_HEADERS,
450+
)?;
451+
452+
let client = TonicTracesClient::new(channel, interceptor, compression);
306453

307-
let client = TonicTracesClient::new(channel, interceptor, compression);
454+
Ok(crate::SpanExporter::new(client))
455+
}
308456

309-
Ok(crate::SpanExporter::new(client))
457+
#[cfg(feature = "tls")]
458+
{
459+
let (channel, interceptor, compression) = self.build_channel(
460+
crate::span::OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
461+
crate::span::OTEL_EXPORTER_OTLP_TRACES_TIMEOUT,
462+
crate::span::OTEL_EXPORTER_OTLP_TRACES_COMPRESSION,
463+
crate::span::OTEL_EXPORTER_OTLP_TRACES_HEADERS,
464+
crate::span::OTEL_EXPORTER_OTLP_TRACES_INSECURE,
465+
crate::span::OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE,
466+
crate::span::OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE,
467+
crate::span::OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY,
468+
)?;
469+
470+
let client = TonicTracesClient::new(channel, interceptor, compression);
471+
472+
Ok(crate::SpanExporter::new(client))
473+
}
310474
}
311475
}
312476

0 commit comments

Comments
 (0)