Feature request: support client certificates (mTLS) for registry-mirror back-to-source
Summary
dfdaemon can't present a client certificate when it fetches from an upstream
registry, so it can't talk to any registry that requires mutual TLS (mTLS).
The back-to-source fetch fails at the TLS handshake, even though a direct
containerd pull to the same registry works.
Today, using Dragonfly as a pull-through cache in front of an mTLS-enabled
registry requires standing up extra components (e.g. an external mTLS-terminating
proxy in front of dragonfly) to work around this. There's no config option for a
client cert/key: RegistryMirror only takes a CA cert, and the TLS client is
always built with .with_no_client_auth(). Native support for mTLS from Dragonfly would remove the need
for that extra setup.
Environment
dragonflyoss/client: v1.3.12 (also present on main)
dragonflyoss/helm-charts: dragonfly chart 1.6.27
- Deployment:
dfdaemon DaemonSet acting as a registry pull-through cache; containerd routes pulls through it via a hosts.toml mirror entry.
Use case
We run Dragonfly as a P2P registry pull-through cache in front of an OCI registry
that requires mTLS (clients must present an x509 client certificate). The
topology is the standard one:
containerd → Dragonfly mirror (hosts.toml) → dfdaemon → upstream registry.
- On a cache miss,
dfdaemon does a "back-to-source" fetch to the registry.
Direct containerd pulls work because hosts.toml has an option for a client key
pair:
[host."https://registry.example.com"]
capabilities = ["pull", "resolve"]
client = [["/path/to/client.pem", "/path/to/client-key.pem"]]
But routed through Dragonfly, dfdaemon presents no client certificate, so the
registry kills the handshake. Registries that don't require client auth work fine. This gap is specific to mTLS-enabled upstreams.
Current behavior / root cause
In dragonfly-client-backend/src/http.rs, both client builders hard-code
.with_no_client_auth(): link to code
// http.rs (default client, ~L163-166)
let client_config_builder = rustls::ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(NoVerifier::new())
.with_no_client_auth(); // <-- no client cert presented
link to code
// http.rs (cert-configured client, fn client ~L221-234)
fn client(&self, client_cert: Option<Vec<CertificateDer<'static>>>, ...) -> Result<...> {
match client_cert.as_ref() {
Some(client_cert) => {
let mut root_cert_store = rustls::RootCertStore::empty();
root_cert_store.add_parsable_certificates(client_cert.to_owned());
let client_config_builder = rustls::ClientConfig::builder()
.with_root_certificates(root_cert_store) // <-- trust bundle to verify the server (in case running custom CA/self-signed cert)
.with_no_client_auth(); // <-- still no client cert presented
One thing worth highlighting: even though this path takes a client_cert, it's used as
a server trust bundle, which is passed to
.with_root_certificates(...) to verify the registry/server, rather than presented as a client
identity. So despite the name, there's currently no code path that calls
with_client_auth_cert(),
which is what would be needed to present a client certificate.
The config struct reflects the same gap. RegistryMirror in
dragonfly-client-config/src/dfdaemon.rs (~L1271-1294) only has the option to pass the trust bundle:
pub struct RegistryMirror {
pub addr: String,
/// Cert is the client cert path with PEM format for the registry.
/// If registry use self-signed cert, the client should set the
/// cert for the registry mirror.
pub cert: Option<PathBuf>, // <-- CA / server trust root, not client identity
pub enable_task_id_based_blob_digest: bool,
}
If we are adding mTLS, we will need to update the Helm chart as well:
charts/dragonfly/values.yaml (~L1120-1135):
registryMirror:
addr: https://index.docker.io
enableTaskIDBasedBlobDigest: true
# cert: "" # CA cert only
Feature request: support client certificates (mTLS) for registry-mirror back-to-source
Summary
dfdaemoncan't present a client certificate when it fetches from an upstreamregistry, so it can't talk to any registry that requires mutual TLS (mTLS).
The back-to-source fetch fails at the TLS handshake, even though a direct
containerdpull to the same registry works.Today, using Dragonfly as a pull-through cache in front of an mTLS-enabled
registry requires standing up extra components (e.g. an external mTLS-terminating
proxy in front of dragonfly) to work around this. There's no config option for a
client cert/key:
RegistryMirroronly takes a CAcert, and the TLS client isalways built with
.with_no_client_auth(). Native support for mTLS from Dragonfly would remove the needfor that extra setup.
Environment
dragonflyoss/client:v1.3.12(also present onmain)dragonflyoss/helm-charts:dragonflychart1.6.27dfdaemonDaemonSet acting as a registry pull-through cache;containerdroutes pulls through it via ahosts.tomlmirror entry.Use case
We run Dragonfly as a P2P registry pull-through cache in front of an OCI registry
that requires mTLS (clients must present an x509 client certificate). The
topology is the standard one:
containerd→ Dragonfly mirror (hosts.toml) →dfdaemon→ upstream registry.dfdaemondoes a "back-to-source" fetch to the registry.Direct
containerdpulls work becausehosts.tomlhas an option for a client keypair:
But routed through Dragonfly,
dfdaemonpresents no client certificate, so theregistry kills the handshake. Registries that don't require client auth work fine. This gap is specific to mTLS-enabled upstreams.
Current behavior / root cause
In
dragonfly-client-backend/src/http.rs, both client builders hard-code.with_no_client_auth(): link to codelink to code
One thing worth highlighting: even though this path takes a
client_cert, it's used asa server trust bundle, which is passed to
.with_root_certificates(...)to verify the registry/server, rather than presented as a clientidentity. So despite the name, there's currently no code path that calls
with_client_auth_cert(),which is what would be needed to present a client certificate.
The config struct reflects the same gap.
RegistryMirrorindragonfly-client-config/src/dfdaemon.rs(~L1271-1294) only has the option to pass the trust bundle:If we are adding mTLS, we will need to update the Helm chart as well:
charts/dragonfly/values.yaml(~L1120-1135):