Skip to content

Commit 257b59a

Browse files
gumptjohnhurt
authored andcommitted
remove CacheKey::default impl
Anyone using caching must must now implement cache_key_callback themselves. This forces an explicit decision about what belongs in the cache key for anyone using thet trait for caching rather than providing an unsafe default that does not support web standards.
1 parent b083273 commit 257b59a

4 files changed

Lines changed: 48 additions & 23 deletions

File tree

‎.bleep‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
118e3d5e0e12ebf28edd7310d88eaee183f95861
1+
41876b9acd525b8c3a8718ace49d3407b4ec6fb9

‎pingora-cache/src/key.rs‎

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414

1515
//! Cache key
1616
17-
use super::*;
18-
1917
use blake2::{Blake2b, Digest};
2018
use http::Extensions;
2119
use serde::{Deserialize, Serialize};
@@ -214,18 +212,6 @@ impl CacheKey {
214212
hasher
215213
}
216214

217-
/// Create a default [CacheKey] from a request, which just takes its URI as the primary key.
218-
pub fn default(req_header: &ReqHeader) -> Self {
219-
CacheKey {
220-
namespace: Vec::new(),
221-
primary: format!("{}", req_header.uri).into_bytes(),
222-
primary_bin_override: None,
223-
variance: None,
224-
user_tag: "".into(),
225-
extensions: Extensions::new(),
226-
}
227-
}
228-
229215
/// Create a new [CacheKey] from the given namespace, primary, and user_tag input.
230216
///
231217
/// Both `namespace` and `primary` will be used for the primary hash

‎pingora-proxy/src/proxy_trait.rs‎

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,14 +137,24 @@ pub trait ProxyHttp {
137137
Ok(())
138138
}
139139

140-
/// This callback generates the cache key
140+
/// This callback generates the cache key.
141141
///
142-
/// This callback is called only when cache is enabled for this request
142+
/// This callback is called only when cache is enabled for this request.
143143
///
144-
/// By default this callback returns a default cache key generated from the request.
145-
fn cache_key_callback(&self, session: &Session, _ctx: &mut Self::CTX) -> Result<CacheKey> {
146-
let req_header = session.req_header();
147-
Ok(CacheKey::default(req_header))
144+
/// There is no sensible default cache key for all proxy applications. The
145+
/// correct key depends on which request properties affect upstream responses
146+
/// (e.g. `Vary` headers, custom request filters that modify the origin host).
147+
/// Getting this wrong leads to cache poisoning.
148+
///
149+
/// See `pingora-proxy/tests/utils/server_utils.rs` for a minimal (not
150+
/// production-ready) reference implementation.
151+
///
152+
/// # Panics
153+
///
154+
/// The default implementation panics. You **must** override this method when
155+
/// caching is enabled.
156+
fn cache_key_callback(&self, _session: &Session, _ctx: &mut Self::CTX) -> Result<CacheKey> {
157+
unimplemented!("cache_key_callback must be implemented when caching is enabled")
148158
}
149159

150160
/// This callback is invoked when a cacheable response is ready to be admitted to cache.

‎pingora-proxy/tests/utils/server_utils.rs‎

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ use pingora_cache::key::HashBinary;
2626
use pingora_cache::lock::CacheKeyLockImpl;
2727
use pingora_cache::{
2828
eviction::simple_lru::Manager, filters::resp_cacheable, lock::CacheLock, predictor::Predictor,
29-
set_compression_dict_path, CacheMeta, CacheMetaDefaults, CachePhase, MemCache, NoCacheReason,
30-
RespCacheable,
29+
set_compression_dict_path, CacheKey, CacheMeta, CacheMetaDefaults, CachePhase, MemCache,
30+
NoCacheReason, RespCacheable,
3131
};
3232
use pingora_cache::{
3333
CacheOptionOverrides, ForcedFreshness, HitHandler, PurgeType, VarianceBuilder,
@@ -489,6 +489,35 @@ impl ProxyHttp for ExampleProxyCache {
489489
Ok(())
490490
}
491491

492+
/// Reference `cache_key_callback` implementation for integration tests.
493+
///
494+
/// Builds the primary key as `{host}{path_and_query}` from the request.
495+
/// This is **not production ready**: it does not account for `Vary`, custom
496+
/// request filters, or scheme differences. See the rustdoc on
497+
/// [`ProxyHttp::cache_key_callback`] for details.
498+
fn cache_key_callback(&self, session: &Session, _ctx: &mut Self::CTX) -> Result<CacheKey> {
499+
let req_header = session.req_header();
500+
501+
let host = req_header
502+
.headers
503+
.get(http::header::HOST)
504+
.and_then(|v| v.to_str().ok())
505+
.or_else(|| req_header.uri.authority().map(|a| a.as_str()))
506+
.unwrap_or("");
507+
508+
let path_and_query = req_header
509+
.uri
510+
.path_and_query()
511+
.map(|pq| pq.as_str())
512+
.unwrap_or("/");
513+
514+
Ok(CacheKey::new(
515+
String::new(),
516+
format!("{host}{path_and_query}"),
517+
String::new(),
518+
))
519+
}
520+
492521
async fn cache_hit_filter(
493522
&self,
494523
session: &mut Session,

0 commit comments

Comments
 (0)