modelpipe 0.1.0

Reach an OpenAI-compatible model server from anywhere over p2p — no VPN, no account, no cloud in the path
Documentation
//! What the serve side requires in `Authorization: Bearer …`.
//!
//! Owns the policy a listener is configured with, the cell that holds the
//! credential it currently enforces, and the comparison itself. It does not
//! know what an HTTP request looks like: it is handed the bytes of an
//! `Authorization` header, or nothing, and answers whether they admit.
//!
//! The cell is always present, even when serving open. `set_token` takes
//! `&self` and turns authentication *on* at runtime, so a listener that had
//! decided at startup not to install a check could not honour that later —
//! the difference between open and closed is whether the cell holds a
//! credential, never whether the check runs.

use std::fmt;
use std::sync::{Arc, RwLock};

use subtle::ConstantTimeEq;

use crate::ServeError;
use crate::base32;
use crate::token_policy::TokenPolicy;

/// Bytes of entropy in a generated token: 256 bits, which is not a number
/// anyone needs to reason about again.
const MINTED_ENTROPY_BYTES: usize = 32;

/// The scheme, with its trailing space, as it appears in the header.
const BEARER_PREFIX: &str = "Bearer ";

/// The credential a listener currently enforces, and the comparison
/// against it.
pub(crate) struct Credential {
    /// `None` means serving open. Wrapped in an `Arc` so a rotation swaps a
    /// pointer rather than mutating a buffer some request may be part way
    /// through comparing against.
    enforced: RwLock<Option<Arc<Enforced>>>,
}

/// The token a listener enforces.
///
/// One field, since the scheme stopped being part of what is compared: the
/// `Authorization` value is split at its single space and only the
/// credential after it is matched, so the pre-built `"Bearer <token>"`
/// string this used to carry beside the token had no reader left.
struct Enforced {
    /// What [`ServeHandle::token`](crate::ServeHandle::token) reports.
    token: String,
}

impl Credential {
    /// Build the cell a policy asks for, returning the token to show the
    /// operator — `None` when serving open.
    pub(crate) fn new(policy: &TokenPolicy) -> Result<(Self, Option<String>), ServeError> {
        let token = match policy {
            TokenPolicy::Generate => Some(mint()),
            // Refused rather than enforced. `"Bearer "` with a trailing
            // space is a header no conforming client can present, because
            // HTTP parsers trim trailing whitespace from values — so the
            // listener would come up and refuse everything, silently, for
            // the life of the process.
            TokenPolicy::Supplied(t) if !presentable(t) => return Err(ServeError::InvalidToken),
            TokenPolicy::Supplied(t) => Some(t.clone()),
            TokenPolicy::InsecureNoAuth => None,
            // `TokenPolicy` is `#[non_exhaustive]` within its own crate only
            // for downstream matches; here the match is total and a new
            // variant must be a compile error rather than silently serving
            // open, which is the one wrong default this type could have.
        };
        let cell = Self {
            enforced: RwLock::new(token.clone().map(Enforced::new)),
        };
        Ok((cell, token))
    }

    /// Whether an `Authorization` header value admits.
    ///
    /// `None` is a request with no such header, which is distinct from one
    /// carrying an empty value only in that neither is ever accepted while
    /// a credential is enforced.
    ///
    /// The comparison is constant-time in the **token**, via `subtle`. Two
    /// things deliberately are not, and both are public parameters of the
    /// system rather than secrets: the length, so an unequal-length value is
    /// rejected without comparing (the alternative is a padded buffer for no
    /// gain), and the scheme, which is a fixed seven-byte string every
    /// client sends in the clear.
    ///
    /// The scheme is matched case-insensitively because RFC 9110 §11.1 says
    /// it is: `auth-scheme` is a token, and token comparison is
    /// case-insensitive. This edge required `Bearer` exactly, so a client
    /// sending the equally-valid `bearer` was told its key was invalid —
    /// the least actionable 401 available, since the key really was correct
    /// and nothing in the message pointed at the capitalisation.
    ///
    /// Split at the first space rather than trimmed, so the single
    /// `SP` RFC 9110 §11.1 puts between scheme and credential stays exactly
    /// one: the whitespace refusals this type has always made are still
    /// made, and a token that begins with a space is still a different
    /// token.
    pub(crate) fn admits(&self, offered: Option<&[u8]>) -> bool {
        // The Arc is cloned and the lock released before comparing, so a
        // rotation is never blocked behind an in-flight request.
        let enforced = self.snapshot();
        let Some(enforced) = enforced else {
            return true; // serving open
        };
        let Some(offered) = offered else {
            return false;
        };
        // `BEARER_PREFIX` carries the space, so this splits scheme from
        // credential in one step and a value shorter than the scheme cannot
        // index past its end.
        let scheme = BEARER_PREFIX.len();
        if offered.len() <= scheme
            || !offered[..scheme].eq_ignore_ascii_case(BEARER_PREFIX.as_bytes())
        {
            return false;
        }
        let expected = enforced.token.as_bytes();
        let presented = &offered[scheme..];
        expected.len() == presented.len() && bool::from(expected.ct_eq(presented))
    }

    /// What the listener currently enforces, or `None` when serving open.
    pub(crate) fn token(&self) -> Option<String> {
        self.snapshot().map(|e| e.token.clone())
    }

    /// Install `token`, replacing whatever is enforced. Turns
    /// authentication on if it was off.
    ///
    /// Returns whether it did. A token nothing can present is refused here
    /// for the reason [`Credential::new`] refuses it, and refusing means
    /// keeping the credential already in force — installing it would take a
    /// working listener down to one that answers nothing.
    pub(crate) fn set(&self, token: String) -> bool {
        if !presentable(&token) {
            return false;
        }
        *self.write() = Some(Enforced::new(token));
        true
    }

    /// Install a freshly minted token and return it.
    pub(crate) fn rotate(&self) -> String {
        let token = mint();
        // Always presentable: 256 bits of base32 is never empty. Asserted
        // rather than ignored, so that a change to `mint` that broke it
        // fails here instead of producing a listener nobody can reach.
        assert!(self.set(token.clone()), "a minted token is always usable");
        token
    }

    fn snapshot(&self) -> Option<Arc<Enforced>> {
        self.read().clone()
    }

    // A poisoned lock cannot happen here: nothing panics while holding it —
    // the only operations are a clone and a store. Recovering the guard
    // rather than propagating is the honest response to an impossible case,
    // and turns a hypothetical panic in one request into no effect on the
    // rest.
    fn read(&self) -> std::sync::RwLockReadGuard<'_, Option<Arc<Enforced>>> {
        self.enforced
            .read()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
    }

    fn write(&self) -> std::sync::RwLockWriteGuard<'_, Option<Arc<Enforced>>> {
        self.enforced
            .write()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
    }
}

impl fmt::Debug for Credential {
    /// Reports only whether a credential is enforced, never which one — the
    /// same rule `Debug for TokenPolicy` follows one screen up.
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let state = if self.read().is_some() {
            "enforced"
        } else {
            "open"
        };
        f.debug_tuple("Credential").field(&state).finish()
    }
}

impl Enforced {
    fn new(token: String) -> Arc<Self> {
        Arc::new(Self { token })
    }
}

/// Whether a token is one a client could actually send.
///
/// The check is deliberately only "not empty after trimming". Anything more
/// — a byte-set rule, a minimum length — is a policy this crate has no
/// standing to impose on an embedder's existing API key. What it does have
/// standing to refuse is a value that makes the listener unusable.
fn presentable(token: &str) -> bool {
    !token.trim().is_empty()
}

/// A fresh token from the operating system's CSPRNG.
///
/// Base32 of 256 random bits, reusing the ticket's alphabet rather than
/// inventing a second one: it has no characters a person can confuse when
/// reading a token off a screen, it survives a shell without quoting, and it
/// is already a header-safe subset of ASCII.
fn mint() -> String {
    let mut bytes = [0u8; MINTED_ENTROPY_BYTES];
    // A CSPRNG that cannot produce bytes is not a condition to paper over
    // with a weaker source: serving with a guessable credential would be
    // worse than not serving.
    getrandom::fill(&mut bytes).expect("the OS CSPRNG must be available");
    base32::encode(&bytes)
}

#[cfg(test)]
#[path = "credential_tests.rs"]
mod credential_tests;