Skip to content
Open
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
76 changes: 62 additions & 14 deletions crates/pccs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ use std::{
time::{SystemTime, UNIX_EPOCH},
};

use dcap_qvl::{QuoteCollateralV3, collateral::CollateralClient, tcb_info::TcbInfo};
use dcap_qvl::{
QuoteCollateralV3,
collateral::CollateralClient,
configs::DefaultConfig,
http::{HttpClient as DcapHttpClient, HttpResponse},
tcb_info::TcbInfo,
};
use thiserror::Error;
use time::{OffsetDateTime, format_description::well_known::Rfc3339};
use tokio::{
Expand All @@ -26,15 +32,23 @@ pub const PCS_URL: &str = "https://api.trustedservices.intel.com";
const REFRESH_MARGIN_SECS: i64 = 300;
/// How long to wait before retrying when failing to fetch collateral
const REFRESH_RETRY_SECS: u64 = 60;
/// Timeout for PCCS HTTP requests.
const PCCS_HTTP_TIMEOUT_SECS: u64 = 180;
/// How many collateral fetches to perform concurrently during initial
/// pre-warm
const STARTUP_PREWARM_CONCURRENCY: usize = 8;

type SharedCollateralClient = CollateralClient<DefaultConfig, SharedReqwestHttp>;

/// PCCS collateral cache with proactive background refresh
#[derive(Clone)]
pub struct Pccs {
/// The URL of the service used to fetch collateral (PCS / PCCS)
url: String,
/// HTTP client used for FMSPC fetches
http_client: reqwest::Client,
/// HTTP client for collateral fetches
collateral_client: SharedCollateralClient,
/// The internal cache
cache: Arc<RwLock<HashMap<PccsInput, CacheEntry>>>,
/// Dedupes one-shot background refreshes for cache misses
Expand Down Expand Up @@ -80,9 +94,14 @@ impl Pccs {
.trim_end_matches("/sgx/certification/v4")
.trim_end_matches("/tdx/certification/v4")
.to_string();
let http_client = reqwest::Client::new();
let collateral_client =
CollateralClient::new(SharedReqwestHttp { client: http_client.clone() }, url.clone());

Self {
url,
http_client,
collateral_client,
cache: RwLock::new(HashMap::new()).into(),
pending_refreshes: RwLock::new(HashSet::new()).into(),
prewarm_stats: Arc::new(PrewarmStats::default()),
Expand Down Expand Up @@ -138,7 +157,7 @@ impl Pccs {
}
}

let collateral = fetch_collateral(&self.url, fmspc.clone(), ca).await?;
let collateral = fetch_collateral(&self.collateral_client, fmspc.clone(), ca).await?;
let next_update = extract_next_update(&collateral, now)?;

{
Expand Down Expand Up @@ -207,7 +226,7 @@ impl Pccs {
ca: &'static str,
) -> Result<QuoteCollateralV3, PccsError> {
let now = unix_now()?;
let collateral = fetch_collateral(&self.url, fmspc.clone(), ca).await?;
let collateral = fetch_collateral(&self.collateral_client, fmspc.clone(), ca).await?;
let next_update = extract_next_update(&collateral, now)?;
let cache_key = PccsInput::new(fmspc, ca);

Expand Down Expand Up @@ -235,9 +254,9 @@ impl Pccs {

let weak_cache = Arc::downgrade(&self.cache);
let key = cache_key.clone();
let url = self.url.clone();
let collateral_client = self.collateral_client.clone();
entry.refresh_task = Some(tokio::spawn(async move {
refresh_loop(weak_cache, url, key).await;
refresh_loop(weak_cache, collateral_client, key).await;
}));
}

Expand Down Expand Up @@ -378,8 +397,12 @@ impl Pccs {
/// Fetches available FMSPC entries from configured PCCS/PCS endpoint
async fn fetch_fmspcs(&self) -> Result<Vec<FmspcEntry>, PccsError> {
let url = format!("{}/sgx/certification/v4/fmspcs", self.url);
let client = reqwest::Client::builder().timeout(Duration::from_secs(15)).build()?;
let response = client.get(&url).send().await?;
let response = self
.http_client
.get(&url)
.timeout(Duration::from_secs(PCCS_HTTP_TIMEOUT_SECS))
.send()
.await?;
if !response.status().is_success() {
return Err(PccsError::FmspcFetch(response.status()));
}
Expand All @@ -389,6 +412,34 @@ impl Pccs {
}
}

/// An HTTP client shared used by [CollateralClient] and to fetch FMSPCs
/// during pre-warm
#[derive(Clone)]
struct SharedReqwestHttp {
client: reqwest::Client,
}

impl DcapHttpClient for SharedReqwestHttp {
async fn get(&self, url: &str) -> anyhow::Result<HttpResponse> {
let resp = self
.client
.get(url)
.timeout(Duration::from_secs(PCCS_HTTP_TIMEOUT_SECS))
.send()
.await?;
let status = resp.status().as_u16();
let headers = resp
.headers()
.iter()
.filter_map(|(name, value)| {
value.to_str().ok().map(|v| (name.as_str().to_string(), v.to_string()))
})
.collect::<std::collections::BTreeMap<_, _>>();
let body = resp.bytes().await?.to_vec();
Ok(HttpResponse { status, headers, body })
}
}

/// Final startup pre-warm status and counters.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PrewarmSummary {
Expand Down Expand Up @@ -420,14 +471,11 @@ impl PccsInput {

/// Fetches collateral from PCCS for a given FMSPC and CA
async fn fetch_collateral(
url: &str,
client: &SharedCollateralClient,
fmspc: String,
ca: &'static str,
) -> Result<QuoteCollateralV3, PccsError> {
CollateralClient::with_default_http(url)?
.fetch_for_fmspc_without_pck_chain(&fmspc, ca, false)
.await
.map_err(Into::into)
client.fetch_for_fmspc_without_pck_chain(&fmspc, ca, false).await.map_err(Into::into)
}

/// Extracts the earliest next update timestamp from collateral metadata
Expand Down Expand Up @@ -530,7 +578,7 @@ fn ca_as_static(ca: &str) -> Option<&'static str> {
/// Background loop that refreshes collateral for a single cache key
async fn refresh_loop(
weak_cache: Weak<RwLock<HashMap<PccsInput, CacheEntry>>>,
pccs_url: String,
collateral_client: SharedCollateralClient,
key: PccsInput,
) {
let Some(ca_static) = ca_as_static(&key.ca) else {
Expand Down Expand Up @@ -592,7 +640,7 @@ async fn refresh_loop(
continue;
}

match fetch_collateral(&pccs_url, key.fmspc.clone(), ca_static).await {
match fetch_collateral(&collateral_client, key.fmspc.clone(), ca_static).await {
Ok(collateral) => {
let validate_now = match unix_now() {
Ok(timestamp) => timestamp,
Expand Down
Loading