Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/cargo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,14 @@ jobs:
shell: bash
run: chmod +x ./scripts/install-protoc.sh && ./scripts/install-protoc.sh $HOME
- shell: bash
run: cargo clippy --workspace --all-features
run: |
if [[ "${{ inputs.runner }}" == "windows-2022" ]]; then
# we don't technially support the datadog-fips crate on windows
# right now anyway, so let's set this so that the windows build
# doesn't fail.
export AWS_LC_FIPS_SYS_NO_ASM=1
fi
cargo clippy --workspace --all-features

build:
name: Build
Expand Down
26 changes: 26 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions clippy.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
disallowed-methods = [
{ path = "reqwest::Client::builder", reason = "prefer the FIPS-compatible adapter", replacement = "datadog_fips::reqwest_adapter::create_reqwest_client_builder" },
]
17 changes: 17 additions & 0 deletions crates/datadog-fips/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "datadog-fips"
version = "0.1.0"
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true

[dependencies]
reqwest = { version = "0.12.4", features = ["json", "http2"], default-features = false }
rustls = { version = "0.23.18", default-features = false, features = ["fips"], optional = true }
rustls-native-certs = { version = "0.8.1", optional = true }
tracing = { version = "0.1.40", default-features = false }

[features]
default = [ "reqwest/rustls-tls" ]
fips = [ "reqwest/rustls-tls-no-provider", "rustls", "rustls-native-certs" ]
11 changes: 11 additions & 0 deletions crates/datadog-fips/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Datadog FIPS for Serverless

Crate which provides utils to build FIPS compliant components.

Please add the following to your `clippy.toml`:

```
disallowed-methods = [
{ path = "reqwest::Client::builder", reason = "prefer the FIPS-compatible adapter", replacement = "datadog_fips::reqwest_adapter::create_reqwest_client_builder" },
]
```
4 changes: 4 additions & 0 deletions crates/datadog-fips/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
// SPDX-License-Identifier: Apache-2.0

pub mod reqwest_adapter;
63 changes: 63 additions & 0 deletions crates/datadog-fips/src/reqwest_adapter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use reqwest::ClientBuilder;
use std::error::Error;
#[cfg(feature = "fips")]
use tracing::debug;

/// Creates a reqwest client builder with TLS configuration.
/// When the "fips" feature is enabled, it uses a FIPS-compliant TLS configuration.
/// Otherwise, it uses reqwest's default rustls TLS implementation.
#[cfg(not(feature = "fips"))]
pub fn create_reqwest_client_builder() -> Result<ClientBuilder, Box<dyn Error>> {
// Just return the default builder with rustls TLS. This is the one place we should be okay
// to call reqwest::Client::builder().
#[allow(clippy::disallowed_methods)]
Ok(reqwest::Client::builder().use_rustls_tls())
}

/// Creates a reqwest client builder with FIPS-compliant TLS configuration.
/// This version loads native root certificates and verifies FIPS compliance.
#[cfg(feature = "fips")]
pub fn create_reqwest_client_builder() -> Result<ClientBuilder, Box<dyn Error>> {
// Get the runtime crypto provider that should have been configured at the start of the
// application using something like rustls::crypto::default_fips_provider().install_default()
let provider =
rustls::crypto::CryptoProvider::get_default().ok_or("No crypto provider configured")?;

if !provider.fips() {
return Err("Crypto provider is not FIPS-compliant".into());
}

let mut root_cert_store = rustls::RootCertStore::empty();
let native_certs = rustls_native_certs::load_native_certs();
let mut valid_count = 0;
for cert in native_certs.certs {
match root_cert_store.add(cert) {
Ok(()) => valid_count += 1,
Err(err) => {
debug!("Failed to parse certificate: {:?}", err);
}
}
}
if valid_count == 0 {
return Err("No valid certificates found in native root store".into());
}

// FIPS typically requires TLS 1.2 or higher
let versions = rustls::ALL_VERSIONS.to_vec();
let config_builder = rustls::ClientConfig::builder_with_provider(provider.clone())
.with_protocol_versions(&versions)
.map_err(|_| "Failed to set protocol versions")?;

let config = config_builder
.with_root_certificates(root_cert_store)
.with_no_client_auth();

if !config.fips() {
return Err("The final TLS configuration is not FIPS-compliant".into());
}
debug!("Client builder is configured with FIPS.");

// This is the one place that it is okay to call reqwest::Client::builder().
#[allow(clippy::disallowed_methods)]
Ok(reqwest::Client::builder().use_preconfigured_tls(config))
}
3 changes: 2 additions & 1 deletion crates/dogstatsd/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ tokio-util = { version = "0.7.11", default-features = false }
tracing = { version = "0.1.40", default-features = false }
regex = { version = "1.10.6", default-features = false }
zstd = { version = "0.13.3", default-features = false }
datadog-fips = { path = "../datadog-fips", default-features = false }

[dev-dependencies]
mockito = { version = "1.5.0", default-features = false }
Expand All @@ -32,4 +33,4 @@ tracing-test = { version = "0.2.5", default-features = false }

[features]
default = [ "reqwest/rustls-tls" ]
fips = [ "reqwest/rustls-tls-no-provider" ]
fips = [ "reqwest/rustls-tls-no-provider", "datadog-fips/fips" ]
8 changes: 5 additions & 3 deletions crates/dogstatsd/src/datadog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//!Types to serialize data into the Datadog API

use crate::flusher::ShippingError;
use datadog_fips::reqwest_adapter::create_reqwest_client_builder;
use datadog_protos::metrics::SketchPayload;
use derive_more::{Display, Into};
use protobuf::Message;
Expand All @@ -12,6 +13,7 @@ use reqwest;
use reqwest::{Client, Response};
use serde::{Serialize, Serializer};
use serde_json;
use std::error::Error;
use std::io::Write;
use std::sync::OnceLock;
use std::time::Duration;
Expand Down Expand Up @@ -285,12 +287,12 @@ pub enum RetryStrategy {
LinearBackoff(u64, u64), // attempts, delay
}

fn build_client(https_proxy: Option<String>, timeout: Duration) -> Result<Client, reqwest::Error> {
let mut builder = Client::builder().timeout(timeout);
fn build_client(https_proxy: Option<String>, timeout: Duration) -> Result<Client, Box<dyn Error>> {
let mut builder = create_reqwest_client_builder()?.timeout(timeout);
if let Some(proxy) = https_proxy {
builder = builder.proxy(reqwest::Proxy::https(proxy)?);
}
builder.build()
Ok(builder.build()?)
}

#[derive(Debug, Serialize, Clone, Copy)]
Expand Down
Loading