Skip to content

Commit 8439fda

Browse files
authored
[SVLS-7945] feat: Support TLS certificate for logs/proxy flusher (#979)
## Problem A customer reported that their Lambda is behind a proxy, and the Rust-based extension encounters an error when sending logs and metrics to Datadog via the proxy. A previous PR #961 fixed this for traces and stats, but not for other things because the customer and I didn't see any error with them at that time. ## This PR Applies the env var `DD_TLS_CERT_FILE` to logs flusher and proxy flusher as well. Example: `DD_TLS_CERT_FILE=/opt/ca-cert.pem`, so the when the extension flushes logs or proxied data to Datadog, the HTTP client created can load and use this cert, and connect the proxy properly. ## Testing 1. Create a Lambda in a VPC with a proxy EC2 instance. 2. Connect to the proxy instance. With the help of ChatGPT, set up a custom-build nginx with `ngx_http_proxy_connect_module` 3. Save the CA certificate from the proxy server to local machine 4. In the CDK stack, add a layer to the Lambda, which includes the CA certificate `ca-cert.pem` 5. Set env vars: - `DD_TLS_CERT_FILE=/opt/ca-cert.pem` - `DD_PROXY_HTTPS=http://10.0.0.30:3128`, where `10.0.0.30` is the private IP of the proxy EC2 instance - `DD_LOG_LEVEL=debug` 6. Invoke the Lambda ## Result **Before:** Log flushing failed: > DD_EXTENSION | ERROR | LOGS | Failed to send request after 97 ms and 3 attempts: reqwest::Error { kind: Request, url: "https://http-intake.logs.datadoghq.com/api/v2/logs", source: hyper_util::client::legacy::Error(Connect, ConnectFailed(Custom { kind: Other, error: Custom { kind: InvalidData, error: InvalidCertificate(UnknownIssuer) } })) } **After:** No such error ## Next steps Do the same thing for dogstatsd metric flusher. Metric flusher is in a separate repo https://github.com/DataDog/serverless-components, so let's create separate PRs for that change. ## Notes Customer report issue: #919
1 parent 73e6a6e commit 8439fda

1 file changed

Lines changed: 42 additions & 2 deletions

File tree

bottlecap/src/http.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use bytes::Bytes;
88
use core::time::Duration;
99
use datadog_fips::reqwest_adapter::create_reqwest_client_builder;
1010
use std::sync::Arc;
11-
use std::{collections::HashMap, error::Error};
12-
use tracing::error;
11+
use std::{collections::HashMap, error::Error, fs::File, io::BufReader};
12+
use tracing::{debug, error};
1313

1414
#[must_use]
1515
pub fn get_client(config: &Arc<config::Config>) -> reqwest::Client {
@@ -47,6 +47,28 @@ fn build_client(config: &Arc<config::Config>) -> Result<reqwest::Client, Box<dyn
4747
.http2_keep_alive_timeout(Duration::from_secs(1000));
4848
}
4949

50+
// Load custom TLS certificate if configured
51+
if let Some(cert_path) = &config.tls_cert_file {
52+
match load_custom_cert(cert_path) {
53+
Ok(certs) => {
54+
let cert_count = certs.len();
55+
for cert in certs {
56+
client = client.add_root_certificate(cert);
57+
}
58+
debug!(
59+
"HTTP | Added {} root certificate(s) from {}",
60+
cert_count, cert_path
61+
);
62+
}
63+
Err(e) => {
64+
error!(
65+
"Failed to load TLS certificate from {}: {}, continuing without custom cert",
66+
cert_path, e
67+
);
68+
}
69+
}
70+
}
71+
5072
// This covers DD_PROXY_HTTPS and HTTPS_PROXY
5173
if let Some(https_uri) = &config.proxy_https {
5274
let proxy = reqwest::Proxy::https(https_uri.clone())?;
@@ -56,6 +78,24 @@ fn build_client(config: &Arc<config::Config>) -> Result<reqwest::Client, Box<dyn
5678
}
5779
}
5880

81+
fn load_custom_cert(cert_path: &str) -> Result<Vec<reqwest::Certificate>, Box<dyn Error>> {
82+
let file = File::open(cert_path)?;
83+
let mut reader = BufReader::new(file);
84+
85+
// Parse PEM certificates
86+
let certs = rustls_pemfile::certs(&mut reader).collect::<Result<Vec<_>, _>>()?;
87+
88+
if certs.is_empty() {
89+
return Err("No certificates found in file".into());
90+
}
91+
92+
// Convert all certificates found in the file
93+
certs
94+
.into_iter()
95+
.map(|cert| reqwest::Certificate::from_der(&cert).map_err(Into::into))
96+
.collect()
97+
}
98+
5999
pub async fn handler_not_found() -> Response {
60100
(StatusCode::NOT_FOUND, "Not Found").into_response()
61101
}

0 commit comments

Comments
 (0)