Skip to content

Commit 929e93d

Browse files
pmaxhoganclaude
andauthored
feat(net): support a custom corporate root CA for all outbound connections (#134)
## What / why Corporate and TLS-inspection networks terminate TLS with a private root CA that is not in the public trust store, so every outbound HTTPS connection Driven makes fails behind them. This adds an optional **`custom_root_ca_path`** global setting (a PEM file that may hold multiple certs) whose certificates are added to the system trust store for **all** outbound HTTP clients. Empty/unset = system trust only (unchanged behaviour). Implements item 14 ("Corporate CA pinning") of the V2 backlog, #34 (which DESIGN s5.8.7 had deferred). ## Trust semantics (the security boundary) - **Additive, and that is the whole security boundary.** The CA is *added* on top of the OS/enterprise native roots via `add_root_certificate`; it never replaces them. Source-verified in both reqwest versions in the tree: 0.12 populates one `RootCertStore` with native + custom roots (`async_impl/client.rs:687-714`); 0.13 (the updater plugin) uses `rustls_platform_verifier::Verifier::new_with_extra_roots(extra, platform)` (`async_impl/client.rs:756-769`). Because it is purely additive, *failing to apply the CA can only make a connection stricter, never weaker* - so the security boundary is entirely "additive + no verification bypass", nothing else. - **No verification bypass anywhere.** No `danger_accept_invalid_certs`, no `tls_built_in_root_certs(false)` / `tls_certs_only`, no hostname-verification disable. Grep-verified; the one helper crate (`driven-tls`) documents this as a locked invariant at the call site. - **A configured-but-bad PEM fails the client build** (missing / unreadable / unparseable / zero certs). This is a UX/correctness choice, not the security boundary (additive trust cannot fail *open*): we would rather surface a broken corporate-CA config than silently make requests the proxy will reject anyway. Save-time validation (below) stops a broken path being persisted in the first place. Note the best-effort paths (telemetry ping, Google userinfo, and the updater when AppState is unavailable) deliberately *proceed without* on a load error / missing state rather than hard-fail - they cannot weaken trust, so this is a functionality edge case, not a fail-open. ## The one shared helper + the 10 outbound sites New leaf crate **`driven-tls`** (depends only on `reqwest` + `thiserror`) hosts `apply_custom_ca(builder, &CustomCaConfig)` plus `load_certificates` / `validate_ca_file`. It is a leaf because `driven-drive` is itself a leaf that `driven-core` depends on, so no pre-existing crate could host a helper reachable by all HTTP-using crates without a cycle. The 9 reqwest build sites from the seams audit, each threaded via constructor/param following that crate's existing config style: | # | Site | How it gets the CA | |---|------|--------------------| | 1 | captive-portal probe (`driven-net`) | `ReqwestBackend::new(ca)` stores it; rebuilt on pool teardown | | 2 | per-service probe (`driven-net`) | same | | 3 | OAuth refresh client (`driven-drive` token_store) | `from_stored_refresh_token(.., &ca)` | | 4 | OAuth PKCE consent exchange (`driven-drive` oauth) | `run_pkce_loopback_flow(.., &ca)` | | 5 | Drive metadata client (`driven-drive`) | `with_default_clients(tokens, &ca)` / `new(.., &ca)` | | 6 | Drive stream client (`driven-drive`) | same | | 7 | telemetry sink (`src-tauri`) | `HttpTelemetrySink::new(ca)`, resolved once at boot | | 8 | Google userinfo fetch (`src-tauri` accounts) | loaded per add-account command | | 9 | GitHub-releases update check (`src-tauri` settings) | loaded per check/list command | Plus a **10th** path the seams list did not cover and the advisor flagged as the one that matters most in a TLS-inspecting environment: **tauri-plugin-updater's own download client** (the signed manifest + binary fetch). The plugin is on reqwest **0.13** (workspace + `driven-tls` are 0.12), so it is wired through the plugin's `configure_client` hook using an aliased 0.13 `reqwest_updater` dep (already in the tree via the plugin) - the fallible PEM parse happens first in `run_check` (fail-closed), then the pre-parsed certs are added inside the infallible closure. A hidden-constructor grep (`Client::new()` / `reqwest::get` / `Client::default()`) found no other outbound clients. ## Restart vs hot-reload Applied **when each client is built**. Long-lived clients (Drive metadata/stream, OAuth refresh, network probes, telemetry sink) are built once at account assembly / boot, so a CA change takes effect when the account is next assembled - in practice an **app restart** (the live `reconfigure_all` path only re-applies pacer/gates, it does not rebuild these clients). The per-operation clients (OAuth consent, userinfo, releases check, updater) rebuild each call and pick up the current setting on next use. The UI caption states "applies to new connections after an app restart". ## Settings UI + validation - `GlobalSettings.custom_root_ca_path: Option<PathBuf>` (storage `#[serde(default)]` for back-compat), DTO + patch (double-option) + TS mirrors. - New `validate_custom_ca(path)` IPC command parses the PEM and returns the cert count or a parse error, for inline save-time feedback. `update_settings` also validates on save, so a broken path cannot be persisted; a blank path clears the setting. - Rules-tab input with live cert-count / error feedback. Reviewer note: `validate_custom_ca` (and the build-time re-read) read a plain, user-typed **persisted** path rather than going through the app's dialog-token confinement model. This is intentional and low-risk: a CA path is an inherently user-supplied setting, and the command returns only a cert count / parse status, never file contents. ## Tests - `driven-tls`: unit tests for `apply_custom_ca` / `validate_ca_file` - valid single cert, multi-cert bundle, garbage (-> `NoCertificates`), missing (-> `Read`), corrupt-body PEM, and the `None` no-op. (Additivity itself rests on the reqwest source read above - it needs a live handshake to test, so it is asserted by source + the locked invariant comment, not a unit test.) - `driven-drive`: fail-closed wiring test (bad CA -> refresh-client build fails). - `src-tauri`: `normalize_ca_path`, `storage::Global` serde back-compat (pre-field blob -> `None`), and the `validate_custom_ca` command (cert count / blank / garbage). - UI: three Settings.vue vitest cases (valid -> validate+save+count, invalid -> no save + error, clear -> null patch). - Gates: `cargo fmt --check`, `clippy --workspace --all-targets -D warnings`, per-crate `cargo test` (driven-tls/net/drive/app), `cargo check --workspace`; `pnpm lint` (0 errors), `vue-tsc`, `prettier --check`, `vitest` (265 pass). Part of #34 (does not close the tracking epic). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent fa9d622 commit 929e93d

29 files changed

Lines changed: 1261 additions & 106 deletions

Cargo.lock

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ members = [
88
"crates/driven-vss",
99
"crates/driven-vss-helper",
1010
"crates/driven-net",
11+
"crates/driven-tls",
1112
"crates/driven-cli",
1213
"crates/driven-test-fixtures",
1314
"crates/driven-chaos",

crates/driven-cli/src/main.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ use driven_drive::google::oauth::{run_pkce_loopback_flow, OAuthProgress};
3333
use driven_drive::google::token_store::{KeyringTokenStore, RefreshingTokenSource};
3434
use driven_drive::google::{md5_hex, parse_installed_client_config, GoogleDriveStore, UploadBytes};
3535
use driven_drive::remote_store::{RemoteStore, UploadBody};
36+
use driven_drive::CustomCaConfig;
3637

3738
/// The public installed-app client id (SPEC s4; M4 brief). Used when neither
3839
/// `--client-id`, the env var, nor `client_secret.json` supplies one.
@@ -265,6 +266,7 @@ async fn run_auth(args: AuthArgs) -> anyhow::Result<()> {
265266
&creds.client_secret,
266267
open_system_browser,
267268
tx,
269+
&cli_custom_ca(),
268270
)
269271
.await?;
270272

@@ -399,19 +401,51 @@ fn build_store(account: &str, creds: &ClientCreds) -> anyhow::Result<GoogleDrive
399401
"no refresh token stored for account '{account}'; run `driven-cli auth --account {account}` first"
400402
)
401403
})?;
404+
let ca = cli_custom_ca();
402405
let token_source = RefreshingTokenSource::from_stored_refresh_token(
403406
refresh_token,
404407
creds.client_id.clone(),
405408
creds.client_secret.clone(),
409+
&ca,
406410
)?
407411
.with_store(store);
408-
GoogleDriveStore::with_default_clients(token_source)
412+
GoogleDriveStore::with_default_clients(token_source, &ca)
413+
}
414+
415+
/// Issue #34: the dev/e2e CLI reads its custom root CA (if any) from the
416+
/// `DRIVEN_CUSTOM_CA_PATH` env var so it can run behind the same corporate
417+
/// TLS-inspecting proxy the desktop app supports via its settings. Unset =
418+
/// system trust only (unchanged behaviour).
419+
fn cli_custom_ca() -> CustomCaConfig {
420+
match std::env::var_os("DRIVEN_CUSTOM_CA_PATH") {
421+
Some(v) if !v.is_empty() => CustomCaConfig::from_path(Some(PathBuf::from(v))),
422+
_ => CustomCaConfig::none(),
423+
}
409424
}
410425

411426
#[cfg(test)]
412427
mod tests {
413428
use super::*;
414429

430+
#[test]
431+
fn cli_custom_ca_reads_the_env_var() {
432+
// Issue #34: the dev CLI resolves its custom root CA from
433+
// DRIVEN_CUSTOM_CA_PATH (unset / blank = system trust only). No other
434+
// test touches this env var, so the set/remove here does not race.
435+
std::env::remove_var("DRIVEN_CUSTOM_CA_PATH");
436+
assert!(!cli_custom_ca().is_enabled(), "unset = system trust only");
437+
438+
std::env::set_var("DRIVEN_CUSTOM_CA_PATH", "");
439+
assert!(!cli_custom_ca().is_enabled(), "blank = system trust only");
440+
441+
std::env::set_var("DRIVEN_CUSTOM_CA_PATH", "/etc/corp/ca.pem");
442+
let ca = cli_custom_ca();
443+
assert!(ca.is_enabled());
444+
assert_eq!(ca.path(), Some(Path::new("/etc/corp/ca.pem")));
445+
446+
std::env::remove_var("DRIVEN_CUSTOM_CA_PATH");
447+
}
448+
415449
#[test]
416450
fn resolve_creds_prefers_explicit_args() {
417451
let creds = resolve_creds(

crates/driven-drive/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ uuid.workspace = true
2929
oauth2.workspace = true
3030
keyring.workspace = true
3131
reqwest.workspace = true
32+
# Issue #34 corporate CA pinning: threads a user-configured custom root CA into
33+
# the Drive metadata/stream clients, the OAuth refresh client, and the PKCE
34+
# consent-exchange client (additive; see driven-tls).
35+
driven-tls = { path = "../driven-tls" }
3236
url.workspace = true
3337
hex.workspace = true
3438
# Direct deps (not yet in workspace.dependencies). parking_lot for an

crates/driven-drive/src/google/mod.rs

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ use std::time::Duration;
4949

5050
use async_trait::async_trait;
5151
use bytes::Bytes;
52+
use driven_tls::CustomCaConfig;
5253
use futures::Stream;
5354
use serde::Deserialize;
5455
use tokio::io::{AsyncRead, ReadBuf};
@@ -532,12 +533,14 @@ impl GoogleDriveStore {
532533
/// flows over (the caller builds it with the DESIGN s5.8.4 metadata
533534
/// timeouts). A second, no-overall-timeout streaming client is derived
534535
/// for the resumable chunk PUT + download paths.
535-
pub fn new(http: reqwest::Client, tokens: RefreshingTokenSource) -> Self {
536+
pub fn new(http: reqwest::Client, tokens: RefreshingTokenSource, ca: &CustomCaConfig) -> Self {
536537
let _ = TARGET;
537-
// The streaming client mirrors `http`'s TLS/proxy config but drops
538-
// the overall request cap; if the dedicated build fails we fall back
539-
// to the provided client (correctness over a missing idle timeout).
540-
let http_stream = build_stream_client().unwrap_or_else(|e| {
538+
// The streaming client mirrors `http`'s TLS/proxy config (including the
539+
// issue #34 custom root CA via `ca`) but drops the overall request cap;
540+
// if the dedicated build fails we fall back to the provided client
541+
// (correctness over a missing idle timeout - `http` already carries the
542+
// same additive CA trust).
543+
let http_stream = build_stream_client(ca).unwrap_or_else(|e| {
541544
warn!(
542545
target: TARGET,
543546
error = %e,
@@ -557,9 +560,12 @@ impl GoogleDriveStore {
557560
/// clients (DESIGN s5.8.4 timeouts) from a [`RefreshingTokenSource`].
558561
/// Convenience for the CLI / e2e paths that do not already hold a tuned
559562
/// client.
560-
pub fn with_default_clients(tokens: RefreshingTokenSource) -> anyhow::Result<Self> {
561-
let http = build_meta_client()?;
562-
let http_stream = build_stream_client()?;
563+
pub fn with_default_clients(
564+
tokens: RefreshingTokenSource,
565+
ca: &CustomCaConfig,
566+
) -> anyhow::Result<Self> {
567+
let http = build_meta_client(ca)?;
568+
let http_stream = build_stream_client(ca)?;
563569
Ok(Self {
564570
http,
565571
http_stream,
@@ -1621,26 +1627,31 @@ impl GoogleDriveStore {
16211627
// Free helpers.
16221628
// ---------------------------------------------------------------------------
16231629

1624-
/// Builds the metadata Drive client with the DESIGN s5.8.4 timeouts.
1625-
pub(crate) fn build_meta_client() -> anyhow::Result<reqwest::Client> {
1626-
reqwest::Client::builder()
1630+
/// Builds the metadata Drive client with the DESIGN s5.8.4 timeouts. `ca` adds
1631+
/// the user's custom root CA (issue #34) additively; fail-closed if it cannot be
1632+
/// loaded.
1633+
pub(crate) fn build_meta_client(ca: &CustomCaConfig) -> anyhow::Result<reqwest::Client> {
1634+
let builder = reqwest::Client::builder()
16271635
.connect_timeout(CONNECT_TIMEOUT)
16281636
.timeout(META_TOTAL_TIMEOUT)
16291637
.read_timeout(STREAM_IDLE_TIMEOUT)
16301638
.pool_max_idle_per_host(4)
1631-
.pool_idle_timeout(Duration::from_secs(90))
1639+
.pool_idle_timeout(Duration::from_secs(90));
1640+
driven_tls::apply_custom_ca(builder, ca)?
16321641
.build()
16331642
.map_err(|e| anyhow::anyhow!("drive: failed to build metadata client: {e}"))
16341643
}
16351644

16361645
/// Builds the streaming Drive client (resumable chunk PUT + download): no
16371646
/// overall request cap, only the per-byte idle timeout (DESIGN s5.8.4 `*`).
1638-
pub(crate) fn build_stream_client() -> anyhow::Result<reqwest::Client> {
1639-
reqwest::Client::builder()
1647+
/// `ca` adds the user's custom root CA (issue #34) additively; fail-closed.
1648+
pub(crate) fn build_stream_client(ca: &CustomCaConfig) -> anyhow::Result<reqwest::Client> {
1649+
let builder = reqwest::Client::builder()
16401650
.connect_timeout(CONNECT_TIMEOUT)
16411651
.read_timeout(STREAM_IDLE_TIMEOUT)
16421652
.pool_max_idle_per_host(4)
1643-
.pool_idle_timeout(Duration::from_secs(90))
1653+
.pool_idle_timeout(Duration::from_secs(90));
1654+
driven_tls::apply_custom_ca(builder, ca)?
16441655
.build()
16451656
.map_err(|e| anyhow::anyhow!("drive: failed to build streaming client: {e}"))
16461657
}
@@ -1861,6 +1872,47 @@ pub(crate) fn clone_kind(kind: &ResumableKind) -> ResumableKind {
18611872
mod tests {
18621873
use super::*;
18631874

1875+
#[test]
1876+
fn google_store_new_builds_a_stream_client_with_the_ca() {
1877+
// Issue #34: `GoogleDriveStore::new` derives its streaming client via
1878+
// `build_stream_client(ca)` (here `none`, offline). Covers the ctor +
1879+
// stream-client build path without a network/keychain dependency.
1880+
let tokens = crate::google::oauth::Tokens {
1881+
access_token: String::new(),
1882+
refresh_token: "rt".to_string(),
1883+
expires_at: 0,
1884+
};
1885+
let http = build_meta_client(&CustomCaConfig::none()).expect("meta client");
1886+
let source = RefreshingTokenSource::new(tokens, http, "cid", "secret");
1887+
let store = GoogleDriveStore::new(
1888+
build_meta_client(&CustomCaConfig::none()).expect("meta client"),
1889+
source,
1890+
&CustomCaConfig::none(),
1891+
);
1892+
// The streaming client is a distinct, usable handle (no panic on build).
1893+
let _ = store.http_stream();
1894+
}
1895+
1896+
#[test]
1897+
fn drive_clients_apply_custom_ca_fail_closed() {
1898+
// Issue #34: the Drive metadata + stream clients add the custom CA
1899+
// additively and fail closed on a bad one; `None` builds normally.
1900+
let none = CustomCaConfig::none();
1901+
assert!(build_meta_client(&none).is_ok(), "no-CA meta client builds");
1902+
assert!(
1903+
build_stream_client(&none).is_ok(),
1904+
"no-CA stream client builds"
1905+
);
1906+
let bad = CustomCaConfig::from_path(Some(std::path::PathBuf::from(
1907+
"/driven/no/such/drive-ca.pem",
1908+
)));
1909+
assert!(build_meta_client(&bad).is_err(), "bad CA fails meta build");
1910+
assert!(
1911+
build_stream_client(&bad).is_err(),
1912+
"bad CA fails stream build"
1913+
);
1914+
}
1915+
18641916
#[test]
18651917
fn rfc3339_parses_to_unix_ms() {
18661918
// 2024-01-01T00:00:00Z == 1704067200000 ms.

crates/driven-drive/src/google/oauth.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
2525
use std::time::Duration;
2626

27+
use driven_tls::CustomCaConfig;
2728
use oauth2::basic::BasicClient;
2829
use oauth2::{
2930
AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, PkceCodeChallenge, RedirectUrl,
@@ -116,6 +117,7 @@ pub async fn run_pkce_loopback_flow(
116117
client_secret: &str,
117118
open_browser: impl FnOnce(&str) -> anyhow::Result<()>,
118119
progress_tx: Sender<OAuthProgress>,
120+
ca: &CustomCaConfig,
119121
) -> anyhow::Result<Tokens> {
120122
let (listener_v4, listener_v6, port) = bind_dual_loopback().await?;
121123
// The redirect URI we register with Google MUST be one literal string and
@@ -130,11 +132,13 @@ pub async fn run_pkce_loopback_flow(
130132
// SPEC s4 / oauth2 v5 upgrade notes) with bounded connect/total timeouts
131133
// (DESIGN s5.8.4; codex V-A1) so a hung token endpoint cannot stall the
132134
// code exchange indefinitely.
133-
let http = reqwest::Client::builder()
135+
let http_builder = reqwest::Client::builder()
134136
.redirect(reqwest::redirect::Policy::none())
135137
.connect_timeout(EXCHANGE_CONNECT_TIMEOUT)
136-
.timeout(EXCHANGE_TOTAL_TIMEOUT)
137-
.build()?;
138+
.timeout(EXCHANGE_TOTAL_TIMEOUT);
139+
// Issue #34: add the user's custom root CA additively (fail-closed) so the
140+
// token exchange works behind a corporate TLS-inspecting proxy.
141+
let http = driven_tls::apply_custom_ca(http_builder, ca)?.build()?;
138142

139143
let client = BasicClient::new(ClientId::new(client_id.to_string()))
140144
.set_client_secret(ClientSecret::new(client_secret.to_string()))

crates/driven-drive/src/google/token_store.rs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
use std::sync::Arc;
3535
use std::time::Duration;
3636

37+
use driven_tls::CustomCaConfig;
3738
use keyring::Entry;
3839
use serde::Deserialize;
3940
use tokio::sync::Mutex;
@@ -324,8 +325,9 @@ impl RefreshingTokenSource {
324325
refresh_token: impl Into<String>,
325326
client_id: impl Into<String>,
326327
client_secret: impl Into<String>,
328+
ca: &CustomCaConfig,
327329
) -> anyhow::Result<Self> {
328-
let http = build_refresh_client()?;
330+
let http = build_refresh_client(ca)?;
329331
let tokens = Tokens {
330332
access_token: String::new(),
331333
refresh_token: refresh_token.into(),
@@ -473,11 +475,15 @@ fn now_unix() -> i64 {
473475
/// time keeps a hung token endpoint from wedging every Drive request (the
474476
/// refresh holds the token mutex across the await); disabling redirects keeps
475477
/// the credential-bearing client from being steered to an attacker endpoint.
476-
fn build_refresh_client() -> anyhow::Result<reqwest::Client> {
477-
reqwest::Client::builder()
478+
fn build_refresh_client(ca: &CustomCaConfig) -> anyhow::Result<reqwest::Client> {
479+
let builder = reqwest::Client::builder()
478480
.connect_timeout(REFRESH_CONNECT_TIMEOUT)
479481
.timeout(REFRESH_TOTAL_TIMEOUT)
480-
.redirect(reqwest::redirect::Policy::none())
482+
.redirect(reqwest::redirect::Policy::none());
483+
// Issue #34: add the user's custom root CA additively; fail-closed if a
484+
// configured CA cannot be loaded (a corporate proxy would break the refresh
485+
// otherwise, and silently ignoring the CA is never correct).
486+
driven_tls::apply_custom_ca(builder, ca)?
481487
.build()
482488
.map_err(|e| anyhow::anyhow!("drive: failed to build OAuth refresh client: {e}"))
483489
}
@@ -591,18 +597,31 @@ mod tests {
591597
// V-A1: the refresh client must build with timeouts + redirect::none.
592598
// Building it is offline (no network); a failure would be a TLS-init
593599
// bug, so this is a real assertion, not a skip.
594-
let client = build_refresh_client();
600+
let client = build_refresh_client(&CustomCaConfig::none());
595601
assert!(
596602
client.is_ok(),
597603
"refresh client must build offline: {:?}",
598604
client.err()
599605
);
600606
}
601607

608+
#[test]
609+
fn refresh_client_fails_closed_with_a_bad_ca() {
610+
// Issue #34: a configured-but-unloadable custom CA must FAIL the client
611+
// build (fail-closed), not silently fall back to system-trust-only. This
612+
// is the representative wiring assertion for the driven-tls threading.
613+
let missing = std::path::PathBuf::from("/driven/no/such/ca-bundle.pem");
614+
let ca = CustomCaConfig::from_path(Some(missing));
615+
assert!(
616+
build_refresh_client(&ca).is_err(),
617+
"a missing custom CA file must fail the refresh-client build"
618+
);
619+
}
620+
602621
#[test]
603622
fn with_store_wires_the_keychain_store() {
604623
// C-P2-4 / V-A3: with_store attaches a store; without it, none.
605-
let http = build_refresh_client().unwrap();
624+
let http = build_refresh_client(&CustomCaConfig::none()).unwrap();
606625
let tokens = Tokens {
607626
access_token: String::new(),
608627
refresh_token: "rt".to_string(),

crates/driven-drive/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,9 @@
1414
pub mod fake;
1515
pub mod google;
1616
pub mod remote_store;
17+
18+
// Issue #34: re-export the custom-root-CA config type so callers that already
19+
// depend on `driven-drive` (the CLI, the google_e2e integration test) can name
20+
// it without a separate `driven-tls` dependency. `apply_custom_ca` /
21+
// `validate_ca_file` live in `driven_tls` for the crates that build clients.
22+
pub use driven_tls::CustomCaConfig;

crates/driven-drive/tests/google_e2e.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,16 @@ fn e2e_creds(test_name: &str) -> Option<E2eCreds> {
8585
/// child folder id the scenario should operate under (ROADMAP M4: "each test
8686
/// uses a UUID-named child folder under the dest folder and cleans up").
8787
async fn setup_store(creds: &E2eCreds) -> (GoogleDriveStore, String) {
88+
let ca = driven_drive::CustomCaConfig::none();
8889
let token_source = RefreshingTokenSource::from_stored_refresh_token(
8990
creds.refresh_token.clone(),
9091
creds.client_id.clone(),
9192
creds.client_secret.clone(),
93+
&ca,
9294
)
9395
.expect("build refreshing token source");
9496
let store =
95-
GoogleDriveStore::with_default_clients(token_source).expect("build GoogleDriveStore");
97+
GoogleDriveStore::with_default_clients(token_source, &ca).expect("build GoogleDriveStore");
9698

9799
// Each test operates inside a fresh UUID-named child folder so concurrent
98100
// runs never collide and cleanup is a single trash of that subtree

0 commit comments

Comments
 (0)