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
12 changes: 4 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,15 @@ jobs:
- name: Install rustfmt + clippy components + wasm32-wasip1 target
run: mise exec -- rustup component add rustfmt clippy && mise exec -- rustup target add wasm32-wasip1

# Rust cache.
# - `cache-all-crates: true` — also caches build-deps (proc-macros).
# - `cache-workspace-crates: true` — caches our 7 workspace crates'
# target/ artifacts. cargo's incremental invalidation handles the
# "did this crate actually change" question; the cache just feeds
# it the previous build state instead of starting cold.
# Cache Cargo downloads, but build target/ from scratch. Restoring target/
# alongside the sandbox builds can exhaust the hosted runner's disk when
# a lockfile change leaves old and new artifacts side by side.
# - `shared-key: rust-ci` — every workflow ref reuses the same cache
# slot when the lockfile is identical.
- uses: Swatinem/rust-cache@v2
with:
shared-key: rust-ci
cache-all-crates: true
cache-workspace-crates: true
cache-targets: false

# Engine .wasm artifacts are deterministic in their inputs:
# (Go version, engine source, helm patch). Restore them by content
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ minor bump in the SDK.

## [Unreleased]

### Fixed

- Git HTTPS dependency fetches now preserve configured custom CA bundles while forcing certificate verification on. `GIT_SSL_CAINFO` and `http.sslCAInfo` work for initial clones and cached-repository refreshes, including `vendor add` and publish-time vendoring; `GIT_SSL_NO_VERIFY` and `http.sslVerify=false` cannot disable verification, and unrelated ambient Git HTTP options are not forwarded.

## [0.8.23] — 2026-06-23

### Added
Expand Down
89 changes: 75 additions & 14 deletions Cargo.lock

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

6 changes: 4 additions & 2 deletions crates/akua-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,7 @@ tar = { version = "0.4", optional = true }
# release builds use vendored OpenSSL instead of target system OpenSSL packages.
# Because this transport honours `GIT_SSL_NO_VERIFY` from the ambient
# environment, `git_fetcher::force_tls_verification` pins `ssl_verify = true` on
# every connection before the handshake so a poisoned env cannot disable cert
# validation on the first (pre-pin) clone.
# every connection before the handshake while retaining configured CA bundles.
gix = { version = "0.66", default-features = false, features = ["blocking-http-transport-curl-rustls", "max-performance-safe", "worktree-mutation"], optional = true }
curl = { version = "0.4", optional = true, features = ["static-ssl"] }
p256 = { version = "0.13", default-features = false, features = ["ecdsa", "std", "pem", "pkcs8"], optional = true }
Expand All @@ -138,6 +137,9 @@ rand = "0.8"
# endpoint on `http://127.0.0.1:<port>` so push/pull paths exercise the
# full transport without a real registry.
httpmock = "0.7"
# Authenticated HTTPS git fixture for the vendor regression test.
rcgen = "0.13"
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] }
# Pure-Rust OCI client. Dev-only — used to *parse* the manifest bytes
# our pusher PUTs and assert spec compliance via a real OCI parser
# (independent verification that we're not just round-tripping our own
Expand Down
56 changes: 41 additions & 15 deletions crates/akua-core/src/git_fetcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@
//! environment overrides. Left to the default, a poisoned environment
//! could disable certificate validation on the first clone — a TOFU
//! MITM window before any commit is pinned. [`force_tls_verification`]
//! pins `ssl_verify = true` on every connection *before* the handshake,
//! so neither half of a hostile env (`*_NO_VERIFY` nor a swapped CA
//! bundle) is ever consulted.
//! pins `ssl_verify = true` on every connection *before* the handshake
//! while preserving the configured CA bundle.
//!
//! ## Scope
//!
Expand Down Expand Up @@ -245,7 +244,7 @@ fn clone_bare(
// when the caller supplied an auth map.
let map = auth.map(|m| std::sync::Arc::new(m.clone()));
prep = prep.configure_connection(move |conn| {
force_tls_verification(conn);
force_tls_verification(conn)?;
if let Some(map) = &map {
// `Arc` over `clone()` because `configure_connection` and
// `set_credentials` are both `FnMut` — refcount bumps beat
Expand Down Expand Up @@ -293,7 +292,10 @@ fn refresh_bare(
url: url.to_string(),
detail: e.to_string(),
})?;
force_tls_verification(&mut conn);
force_tls_verification(&mut conn).map_err(|e| GitFetchError::Refresh {
url: url.to_string(),
detail: e.to_string(),
})?;
if let Some(map) = auth {
set_connection_credentials(&mut conn, std::sync::Arc::new(map.clone()));
}
Expand All @@ -314,7 +316,7 @@ fn refresh_bare(
}

/// Pin TLS certificate verification ON for this connection, defeating
/// any ambient `GIT_SSL_NO_VERIFY` / `http.sslNoVerify` in the
/// any ambient `GIT_SSL_NO_VERIFY` / `http.sslVerify = false` in the
/// environment.
///
/// gix's curl-rustls transport derives its `http::Options` from the
Expand All @@ -326,21 +328,45 @@ fn refresh_bare(
/// pinned. We pre-seed the connection's transport options with a
/// `ssl_verify: true` `http::Options` *before* the handshake. Because
/// `Connection::prepare_fetch` only derives options from config when
/// `transport_options` is still `None`, our explicit value wins and the
/// env-derived `ssl_verify = false` is never consulted.
///
/// `ssl_ca_info` is intentionally left `None` (curl's default trust
/// store) — we drop any env-supplied CA bundle along with the
/// env-supplied no-verify, so neither half of a poisoned env applies.
fn force_tls_verification<T>(conn: &mut gix::remote::Connection<'_, '_, T>)
/// `transport_options` is still `None`, we derive them here first and
/// copy only `ssl_ca_info` into fresh default options with verification
/// enabled. This keeps trusted CA configuration such as
/// `GIT_SSL_CAINFO` / `http.sslCAInfo` intact without carrying ambient
/// HTTP headers, proxy credentials, or other transport configuration.
#[allow(clippy::result_large_err)]
fn force_tls_verification<T>(
conn: &mut gix::remote::Connection<'_, '_, T>,
) -> Result<(), gix::config::transport::Error>
where
T: gix::protocol::transport::client::Transport,
{
let opts = gix::protocol::transport::client::http::Options {
use gix::bstr::ByteSlice;

let mut configured = {
let remote = conn.remote();
let url = remote
.url(gix::remote::Direction::Fetch)
.expect("connected remote has a fetch URL")
.to_bstring();
remote
.repo()
.transport_options(url.as_bstr(), remote.name().map(gix::remote::Name::as_bstr))?
};
let Some(options) = configured.as_mut() else {
return Ok(());
};
let ssl_ca_info = options
.downcast_mut::<gix::protocol::transport::client::http::Options>()
.expect("HTTP transport configuration has the expected options type")
.ssl_ca_info
.take();
let options = gix::protocol::transport::client::http::Options {
ssl_ca_info,
ssl_verify: true,
..Default::default()
};
conn.set_transport_options(Box::new(opts));
conn.set_transport_options(Box::new(options));
Ok(())
}

#[allow(clippy::result_large_err)]
Expand Down
Loading