Codex Handbook
app-server/src/attestation.rs 220 lines
use std::sync::Arc;use std::sync::Weak;use axum::http::HeaderValue;use codex_app_server_protocol::AttestationGenerateParams;use codex_app_server_protocol::AttestationGenerateResponse;use codex_app_server_protocol::ServerRequestPayload;use codex_core::AttestationContext;use codex_core::AttestationProvider;use codex_core::GenerateAttestationFuture;use serde::Serialize;use tokio::time::Duration;use tokio::time::timeout;use tracing::warn;use crate::outgoing_message::OutgoingMessageSender;use crate::thread_state::ThreadStateManager;const ATTESTATION_GENERATE_TIMEOUT: Duration = Duration::from_millis(100);pub(crate) fn app_server_attestation_provider(    outgoing: Arc<OutgoingMessageSender>,    thread_state_manager: ThreadStateManager,) -> Arc<dyn AttestationProvider> {    Arc::new(AppServerAttestationProvider {        outgoing: Arc::downgrade(&outgoing),        thread_state_manager,    })}struct AppServerAttestationProvider {    outgoing: Weak<OutgoingMessageSender>,    thread_state_manager: ThreadStateManager,}impl std::fmt::Debug for AppServerAttestationProvider {    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {        formatter            .debug_struct("AppServerAttestationProvider")            .finish()    }}impl AttestationProvider for AppServerAttestationProvider {    fn header_for_request(&self, context: AttestationContext) -> GenerateAttestationFuture<'_> {        let Some(outgoing) = self.outgoing.upgrade() else {            return Box::pin(async { None });        };        let thread_state_manager = self.thread_state_manager.clone();        Box::pin(async move {            request_attestation_header_value_with_timeout(                outgoing,                thread_state_manager,                context.thread_id,                ATTESTATION_GENERATE_TIMEOUT,            )            .await            .and_then(|value| HeaderValue::from_bytes(value.as_bytes()).ok())        })    }}async fn request_attestation_header_value_with_timeout(    outgoing: Arc<OutgoingMessageSender>,    thread_state_manager: ThreadStateManager,    thread_id: codex_protocol::ThreadId,    timeout_duration: Duration,) -> Option<String> {    let connection_id = thread_state_manager        .first_attestation_capable_connection_for_thread(thread_id)        .await?;    let connection_ids = [connection_id];    let (request_id, rx) = outgoing        .send_request_to_connections(            Some(&connection_ids),            ServerRequestPayload::AttestationGenerate(AttestationGenerateParams {}),            /*thread_id*/ None,        )        .await;    let result = match timeout(timeout_duration, rx).await {        Ok(Ok(Ok(result))) => result,        Ok(Ok(Err(err))) => {            warn!(                code = err.code,                message = %err.message,                "attestation generation request failed"            );            return app_server_attestation_header_value(                AppServerAttestationStatus::RequestFailed,                /*token*/ None,            );        }        Ok(Err(err)) => {            warn!("attestation generation request canceled: {err}");            return app_server_attestation_header_value(                AppServerAttestationStatus::RequestCanceled,                /*token*/ None,            );        }        Err(_) => {            let _canceled = outgoing.cancel_request(&request_id).await;            warn!(                timeout_seconds = timeout_duration.as_secs(),                "attestation generation request timed out"            );            return app_server_attestation_header_value(                AppServerAttestationStatus::Timeout,                /*token*/ None,            );        }    };    match serde_json::from_value::<AttestationGenerateResponse>(result) {        Ok(response) => app_server_attestation_header_value(            AppServerAttestationStatus::Ok,            Some(&response.token),        ),        Err(err) => {            warn!("failed to deserialize attestation generation response: {err}");            app_server_attestation_header_value(                AppServerAttestationStatus::MalformedResponse,                /*token*/ None,            )        }    }}#[derive(Clone, Copy)]enum AppServerAttestationStatus {    Ok,    Timeout,    RequestFailed,    RequestCanceled,    MalformedResponse,}impl AppServerAttestationStatus {    const fn code(self) -> u8 {        match self {            Self::Ok => 0,            Self::Timeout => 1,            Self::RequestFailed => 2,            Self::RequestCanceled => 3,            Self::MalformedResponse => 4,        }    }}#[derive(Serialize)]struct AppServerAttestationEnvelope<'a> {    v: u8,    s: u8,    #[serde(skip_serializing_if = "Option::is_none")]    t: Option<&'a str>,}fn app_server_attestation_header_value(    status: AppServerAttestationStatus,    token: Option<&str>,) -> Option<String> {    serde_json::to_string(&AppServerAttestationEnvelope {        v: 1,        s: status.code(),        t: token,    })    .map_err(|err| warn!("failed to serialize app-server attestation envelope: {err}"))    .ok()}#[cfg(test)]mod tests {    use super::AppServerAttestationStatus;    use super::app_server_attestation_header_value;    use pretty_assertions::assert_eq;    #[test]    fn app_server_attestation_header_value_wraps_opaque_client_payloads() {        assert_eq!(            app_server_attestation_header_value(                AppServerAttestationStatus::Ok,                Some("v1.opaque-client-payload"),            ),            Some(r#"{"v":1,"s":0,"t":"v1.opaque-client-payload"}"#.to_string())        );    }    #[test]    fn app_server_attestation_header_value_reports_app_server_failures() {        assert_eq!(            app_server_attestation_header_value(                AppServerAttestationStatus::Timeout,                /*token*/ None,            ),            Some(r#"{"v":1,"s":1}"#.to_string())        );        assert_eq!(            app_server_attestation_header_value(                AppServerAttestationStatus::RequestFailed,                /*token*/ None,            ),            Some(r#"{"v":1,"s":2}"#.to_string())        );        assert_eq!(            app_server_attestation_header_value(                AppServerAttestationStatus::RequestCanceled,                /*token*/ None,            ),            Some(r#"{"v":1,"s":3}"#.to_string())        );        assert_eq!(            app_server_attestation_header_value(                AppServerAttestationStatus::MalformedResponse,                /*token*/ None            ),            Some(r#"{"v":1,"s":4}"#.to_string())        );    }}