Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/knut

crates/graphar-falkordb/src/tls.rs

//! **Mutual TLS to FalkorDB** — the client identity and the ONE function that turns a URL
//! plus an optional identity into a [`redis::Client`].
//!
//! It lives HERE, in the loader crate, and not in `graphar-flight` where it was born:
//! `graphar-flight` depends on this crate, so the read side (`FalkorExecutor`) and the write
//! side ([`FalkorDbLoader`](crate::FalkorDbLoader)) can only share one TLS path if it is
//! down here. `graphar_flight::FalkorTls` is a re-export of this type, so every existing
//! caller keeps its path.
//!
//! A server that runs `tls-auth-clients yes` (+ `tls-auth-clients-user CN`) accepts nothing
//! else: a plain `redis://` connect is refused at the handshake, and the certificate's CN is
//! the ACL user — no AUTH is sent.

use std::path::Path;

/// A **mutual-TLS client identity** for a `rediss://` FalkorDB connection — the client leaf
/// certificate + its private key (both PEM), and an optional CA (PEM) to verify the server
/// with. `None` in place of this ⇒ the plain `redis://` path, unchanged.
///
/// `Clone` because a load's parameters are cloned onto the worker that runs it. `Debug`
/// prints byte counts only — never the key, so a `{:?}` of the parameters in a log cannot
/// leak it.
#[derive(Clone)]
pub struct FalkorTls {
    /// The client leaf certificate, PEM (a chain after it is fine).
    pub client_cert_pem: Vec<u8>,
    /// The matching client private key, PEM.
    pub client_key_pem: Vec<u8>,
    /// An optional CA (PEM) used to verify the server; `None` ⇒ the system root store.
    pub ca_pem: Option<Vec<u8>>,
}

impl std::fmt::Debug for FalkorTls {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("FalkorTls")
            .field("client_cert_pem", &format_args!("{} bytes", self.client_cert_pem.len()))
            .field("client_key_pem", &format_args!("<redacted, {} bytes>", self.client_key_pem.len()))
            .field("ca_pem", &self.ca_pem.as_ref().map(|c| format!("{} bytes", c.len())))
            .finish()
    }
}

impl FalkorTls {
    /// Read the three PEM files a CLI is handed (`--tls-cert`, `--tls-key`, `--tls-ca`). The
    /// error names the file that could not be read.
    pub fn from_files(cert: &Path, key: &Path, ca: Option<&Path>) -> std::io::Result<Self> {
        let read = |p: &Path| {
            std::fs::read(p).map_err(|e| std::io::Error::new(e.kind(), format!("{}: {e}", p.display())))
        };
        Ok(Self {
            client_cert_pem: read(cert)?,
            client_key_pem: read(key)?,
            ca_pem: ca.map(read).transpose()?,
        })
    }
}

/// **The one place knut builds a FalkorDB client.** `None` ⇒ `redis::Client::open(url)`,
/// byte for byte the historical path; `Some` ⇒ the rustls client with the identity presented
/// and `ca_pem` (or the system roots) verifying the server. The URL must be `rediss://` for
/// the identity to be used at all.
pub fn client(url: &str, tls: Option<FalkorTls>) -> redis::RedisResult<redis::Client> {
    match tls {
        None => redis::Client::open(url),
        Some(id) => {
            // rustls 0.23 chooses a crypto provider by itself only when exactly ONE is
            // compiled in. A program that links both ring and aws-lc-rs - korp does - panics
            // on the first handshake ("no process-level CryptoProvider"), which is how korp's
            // fill into an mTLS FalkorDB died before sending a byte. Install ring once,
            // process-wide; a program that already chose a provider keeps its choice.
            let _ = rustls::crypto::ring::default_provider().install_default();
            redis::Client::build_with_tls(
                url,
                redis::TlsCertificates {
                    client_tls: Some(redis::ClientTlsConfig {
                        client_cert: id.client_cert_pem,
                        client_key: id.client_key_pem,
                    }),
                    root_cert: id.ca_pem,
                },
            )
        }
    }
}