Skip to content

feat(health): add TLS and mTLS support for the OTLP sink#3478

Merged
jayzhudev merged 1 commit into
NVIDIA:mainfrom
jayzhudev:health/otlp-sink-tls-support
Jul 14, 2026
Merged

feat(health): add TLS and mTLS support for the OTLP sink#3478
jayzhudev merged 1 commit into
NVIDIA:mainfrom
jayzhudev:health/otlp-sink-tls-support

Conversation

@jayzhudev

Copy link
Copy Markdown
Contributor

Why

The OTLP sink may need to send logs and metrics directly to secured targets without relying on TLS termination elsewhere.

What's new

Each target can now configure:

  • A CA bundle for server certificate verification
  • An optional client certificate and key for mTLS
  • An optional TLS server name override
  • A configurable certificate reload interval

HTTPS targets without a TLS table use the host or container's installed CA certificates for server verification. Plaintext HTTP targets remain supported.

Behavior

Certificate material is validated during startup and periodically reloaded. Replacement channels are adopted only after a successful connection. If rotated material is invalid or the replacement connection fails, the existing channel remains active.

Related issues

Closes #3470

Type of Change

  • Add - New feature or capability
  • Change - Changes in existing functionality
  • Fix - Bug fixes
  • Remove - Removed features or deprecated functionality
  • Internal - Internal changes (refactoring, tests, docs, etc.)

Breaking Changes

  • This PR contains breaking changes

The tls table is optional.

Testing

  • Unit tests added/updated
  • Integration tests added/updated
  • Manual testing performed
  • No testing required (docs, internal refactor, etc.)

Validated:

  • Plaintext HTTP
  • HTTPS with platform trust roots
  • TLS with an explicit CA bundle
  • mTLS with a client certificate and key
  • Certificate hot reload
  • Invalid rotated certificate material
  • Failed replacement connections retaining the existing client

Additional Notes

Example mTLS configuration:

[[sinks.otlp.targets]]
endpoint = "https://telemetry.example.com:4317"

[sinks.otlp.targets.tls]
ca_cert_path = "/var/run/secrets/central-otlp/ca.crt"
client_cert_path = "/var/run/secrets/central-otlp/tls.crt"
client_key_path = "/var/run/secrets/central-otlp/tls.key"
tls_server_name = "telemetry.example.com"
reload_interval = "5m"

For server-authenticated TLS without client authentication, omit client_cert_path and client_key_path.

No Helm changes are included because the current chart does not template OTLP sink settings or certificate mounts.

Signed-off-by: Jay Zhu <jayzhu@nvidia.com>
@jayzhudev jayzhudev self-assigned this Jul 14, 2026
@jayzhudev jayzhudev requested a review from a team as a code owner July 14, 2026 07:01
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Summary by CodeRabbit

  • New Features

    • Added configurable TLS and mutual TLS support for OTLP targets.
    • Added optional CA certificates, client certificates, private keys, server names, and certificate reload intervals.
    • Added automatic TLS material refresh without interrupting telemetry collection.
    • Added platform-trusted TLS for HTTPS endpoints without an explicit profile.
  • Bug Fixes

    • Improved connection retry behavior and timeout handling.
    • Added validation for incomplete or invalid TLS configurations.

Walkthrough

Changes

OTLP targets now support optional TLS/mTLS profiles with validation, CA and client identity loading, platform roots for implicit HTTPS, TLS server-name overrides, startup preflight, and periodic client replacement in logs and metrics drains.

OTLP TLS and mTLS

Layer / File(s) Summary
Per-target TLS configuration and validation
crates/health/src/config.rs, crates/health/example/config.example.toml
Adds per-target TLS settings, HTTPS and identity validation, reload defaults, independent target parsing tests, and TLS/mTLS configuration examples.
TLS material and client configuration
crates/health/src/tls.rs, crates/health/Cargo.toml
Refactors shared TLS material handling for optional client identity, OTLP preflight, server-name overrides, and tonic TLS/mTLS clients.
OTLP endpoint and replacement connections
crates/health/src/otlp/mod.rs
Builds TLS-aware tonic endpoints and bounded replacement connections, with tests for HTTPS handshakes and timeout handling.
Startup preflight and drain refresh
crates/health/src/lib.rs, crates/health/src/otlp/*, crates/health/src/sink/otlp.rs
Preflights OTLP TLS material at startup, retries endpoint resolution, and periodically replaces logs and metrics clients after TLS reload intervals.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant run_service
  participant otlp_preflight
  participant OtlpDrainTask
  participant connect_replacement_target
  participant tonicChannel
  run_service->>otlp_preflight: validate each configured TLS profile
  OtlpDrainTask->>connect_replacement_target: refresh target client on reload tick
  connect_replacement_target->>tonicChannel: connect with timeout
  tonicChannel-->>connect_replacement_target: replacement channel or error
  connect_replacement_target-->>OtlpDrainTask: client refresh result
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding TLS and mTLS support for the OTLP sink.
Description check ✅ Passed The description is directly aligned with the implemented TLS, mTLS, reload, and validation changes.
Linked Issues check ✅ Passed The PR satisfies #3470 by covering platform trust roots, CA bundles, mTLS identity, validation, target isolation, and SNI override.
Out of Scope Changes check ✅ Passed The changed files remain focused on OTLP TLS/mTLS support, validation, reloads, tests, and examples.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
crates/health/src/config.rs (1)

440-529: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate tls_server_name validation logic with MtlsProfileConfig::validate.

OtlpTlsConfig::validate's trim/empty/whitespace/DnsName::try_from block (lines 508-521) is byte-for-byte identical to MtlsProfileConfig::validate's equivalent block (crates/health/src/config.rs:767-799, shown in graph context). Extracting a shared helper (e.g. fn validate_tls_server_name(path: &str, name: &str) -> Result<(), String>) would remove this duplication and keep the two error-message families from drifting apart over time.

♻️ Sketch of the shared helper
+fn validate_tls_server_name(path: &str, tls_server_name: &str) -> Result<(), String> {
+    if tls_server_name.trim().is_empty() {
+        return Err(format!("{path}.tls_server_name must not be empty"));
+    }
+
+    if tls_server_name.trim() != tls_server_name {
+        return Err(format!(
+            "{path}.tls_server_name must not contain leading or trailing whitespace"
+        ));
+    }
+
+    DnsName::try_from(tls_server_name)
+        .map(|_| ())
+        .map_err(|_| format!("{path}.tls_server_name must be a valid DNS name"))
+}

Then both OtlpTlsConfig::validate and MtlsProfileConfig::validate call validate_tls_server_name(&path, tls_server_name)? instead of repeating the block.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/health/src/config.rs` around lines 440 - 529, Extract the duplicated
TLS server-name checks into a shared validate_tls_server_name helper that
accepts the validation path and name, preserving the existing empty, whitespace,
and DnsName error messages. Update both OtlpTlsConfig::validate and
MtlsProfileConfig::validate to call the helper and remove their repeated
validation blocks.
crates/health/src/otlp/drain.rs (2)

54-77: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the duplicated TLS-reload interval construction into a shared helper.

The 14-line block computing tls_reload_period (falling back to OtlpTlsConfig::DEFAULT_RELOAD_INTERVAL) and building tls_reload_interval with MissedTickBehavior::Delay is copied verbatim between the logs and metrics drains. otlp/mod.rs already hosts shared helpers (target_endpoint, connect_replacement_target) for this cohort, making it a natural home for this logic too.

  • crates/health/src/otlp/drain.rs#L54-L77: replace this block with a call to a new pub(crate) fn tls_reload_interval(target: &OtlpTargetConfig) -> tokio::time::Interval in otlp/mod.rs.
  • crates/health/src/otlp/metrics_drain.rs#L63-L86: replace the identical block here with the same shared helper call.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/health/src/otlp/drain.rs` around lines 54 - 77, Extract the duplicated
TLS reload interval construction into a new pub(crate) helper named
tls_reload_interval in otlp/mod.rs, preserving the default reload period,
delayed initial tick, and MissedTickBehavior::Delay. In
crates/health/src/otlp/drain.rs lines 54-77 and
crates/health/src/otlp/metrics_drain.rs lines 63-86, remove the local
construction and call this helper with the OtlpTargetConfig.

94-113: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Emit a carbide_instrument::Event for TLS reload outcomes in both drains.

Both drains only log reload success/failure via tracing::debug!/tracing::warn!; there is no counter to alert on repeated reload failures, so a rotting or unrotated certificate can go unnoticed until the channel eventually fails outright. Both signal drains already emit a structured event (OtlpExportFailed) for export failures — reload outcomes deserve the same treatment for a count/rate.

  • crates/health/src/otlp/drain.rs#L94-L113: define and emit a carbide_instrument::Event (e.g. OtlpTlsReloadFailed/OtlpTlsReloaded) with signal = OtlpSignal::Logs alongside the existing tracing::warn!/tracing::debug! calls.
  • crates/health/src/otlp/metrics_drain.rs#L103-L121: emit the same event with signal = OtlpSignal::Metrics alongside its tracing::warn!/tracing::debug! calls.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/health/src/otlp/drain.rs` around lines 94 - 113, Define TLS reload
outcome events alongside the existing OtlpExportFailed event, then emit success
and failure events in the reload branches of crates/health/src/otlp/drain.rs
lines 94-113 with signal = OtlpSignal::Logs, preserving the existing tracing
logs. Apply the same event definitions and emissions in
crates/health/src/otlp/metrics_drain.rs lines 103-121 with signal =
OtlpSignal::Metrics, covering both successful and failed reloads.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/health/src/config.rs`:
- Around line 440-529: Extract the duplicated TLS server-name checks into a
shared validate_tls_server_name helper that accepts the validation path and
name, preserving the existing empty, whitespace, and DnsName error messages.
Update both OtlpTlsConfig::validate and MtlsProfileConfig::validate to call the
helper and remove their repeated validation blocks.

In `@crates/health/src/otlp/drain.rs`:
- Around line 54-77: Extract the duplicated TLS reload interval construction
into a new pub(crate) helper named tls_reload_interval in otlp/mod.rs,
preserving the default reload period, delayed initial tick, and
MissedTickBehavior::Delay. In crates/health/src/otlp/drain.rs lines 54-77 and
crates/health/src/otlp/metrics_drain.rs lines 63-86, remove the local
construction and call this helper with the OtlpTargetConfig.
- Around line 94-113: Define TLS reload outcome events alongside the existing
OtlpExportFailed event, then emit success and failure events in the reload
branches of crates/health/src/otlp/drain.rs lines 94-113 with signal =
OtlpSignal::Logs, preserving the existing tracing logs. Apply the same event
definitions and emissions in crates/health/src/otlp/metrics_drain.rs lines
103-121 with signal = OtlpSignal::Metrics, covering both successful and failed
reloads.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 7ceeaa63-cce0-4d38-82b3-1d22776df657

📥 Commits

Reviewing files that changed from the base of the PR and between 1eb3a7f and 361107e.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • crates/health/Cargo.toml
  • crates/health/example/config.example.toml
  • crates/health/src/config.rs
  • crates/health/src/lib.rs
  • crates/health/src/otlp/drain.rs
  • crates/health/src/otlp/metrics_drain.rs
  • crates/health/src/otlp/mod.rs
  • crates/health/src/sink/otlp.rs
  • crates/health/src/tls.rs

@github-actions

Copy link
Copy Markdown

🔍 Container Scan Summary

Service Total Critical High Medium Low Other
boot-artifacts-aarch64 3 0 0 3 0 0
boot-artifacts-x86_64 3 0 0 3 0 0
forge-admin-cli-x86_64 255 13 30 79 7 126
machine-validation-runner 800 40 234 292 36 198
machine_validation 800 40 234 292 36 198
machine_validation-aarch64 800 40 234 292 36 198
nvmetal-carbide 807 40 234 288 36 209
TOTAL 3468 173 966 1249 151 929

Per-CVE detail lives in the per-service grype-* artifacts (JSON + SARIF). Severity counts only — no CVE IDs published here.

@jayzhudev jayzhudev merged commit 1e103e9 into NVIDIA:main Jul 14, 2026
59 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add TLS and mTLS support to hw-health OTLP sink

2 participants