Skip to content

Commit 011cece

Browse files
committed
added aws headers
The minio package sends metadata as x-amz-meta-* headers. So: x-pinning-service → x-amz-meta-x-pinning-service x-pinning-token → x-amz-meta-x-pinning-token
1 parent 2a4be93 commit 011cece

File tree

2 files changed

+83
-16
lines changed

2 files changed

+83
-16
lines changed

crates/fula-cli/src/pinning.rs

Lines changed: 83 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,18 @@ use tracing::{info, warn};
1414

1515
/// Header name for pinning service endpoint
1616
pub const HEADER_PINNING_SERVICE: &str = "x-pinning-service";
17+
/// S3-compatible metadata header for pinning service endpoint
18+
pub const HEADER_AMZ_PINNING_SERVICE: &str = "x-amz-meta-x-pinning-service";
1719

1820
/// Header name for pinning service token
1921
pub const HEADER_PINNING_TOKEN: &str = "x-pinning-token";
22+
/// S3-compatible metadata header for pinning service token
23+
pub const HEADER_AMZ_PINNING_TOKEN: &str = "x-amz-meta-x-pinning-token";
24+
25+
/// Header name for pinning name
26+
pub const HEADER_PINNING_NAME: &str = "x-pinning-name";
27+
/// S3-compatible metadata header for pinning name
28+
pub const HEADER_AMZ_PINNING_NAME: &str = "x-amz-meta-x-pinning-name";
2029

2130
/// Extracted pinning credentials from request headers
2231
#[derive(Debug, Clone)]
@@ -32,13 +41,14 @@ pub struct PinningCredentials {
3241
impl PinningCredentials {
3342
/// Extract pinning credentials from request headers
3443
///
44+
/// Checks both direct headers (x-pinning-*) and S3-compatible metadata headers
45+
/// (x-amz-meta-x-pinning-*) for compatibility with S3 client libraries like MinIO.
46+
///
3547
/// Returns None if headers are not present (pinning not requested)
3648
/// Security audit fix #3: Validates endpoint to prevent SSRF
3749
pub fn from_headers(headers: &HeaderMap) -> Option<Self> {
38-
let endpoint = headers
39-
.get(HEADER_PINNING_SERVICE)
40-
.and_then(|v| v.to_str().ok())
41-
.map(|s| s.to_string())?;
50+
// Check direct header first, then fall back to x-amz-meta-* prefixed version
51+
let endpoint = Self::get_header_value(headers, HEADER_PINNING_SERVICE, HEADER_AMZ_PINNING_SERVICE)?;
4252

4353
// Security audit fix #3: Validate endpoint
4454
if !Self::is_valid_pinning_endpoint(&endpoint) {
@@ -48,16 +58,10 @@ impl PinningCredentials {
4858
return None;
4959
}
5060

51-
let token = headers
52-
.get(HEADER_PINNING_TOKEN)
53-
.and_then(|v| v.to_str().ok())
54-
.map(|s| s.to_string())?;
61+
let token = Self::get_header_value(headers, HEADER_PINNING_TOKEN, HEADER_AMZ_PINNING_TOKEN)?;
5562

56-
// Optional: pin name from x-pinning-name header
57-
let name = headers
58-
.get("x-pinning-name")
59-
.and_then(|v| v.to_str().ok())
60-
.map(|s| s.to_string());
63+
// Optional: pin name from x-pinning-name header (or x-amz-meta-x-pinning-name)
64+
let name = Self::get_header_value(headers, HEADER_PINNING_NAME, HEADER_AMZ_PINNING_NAME);
6165

6266
Some(Self {
6367
endpoint,
@@ -66,6 +70,15 @@ impl PinningCredentials {
6670
})
6771
}
6872

73+
/// Get a header value, checking the direct header first, then the x-amz-meta-* prefixed version
74+
fn get_header_value(headers: &HeaderMap, direct: &str, amz_meta: &str) -> Option<String> {
75+
headers
76+
.get(direct)
77+
.or_else(|| headers.get(amz_meta))
78+
.and_then(|v| v.to_str().ok())
79+
.map(|s| s.to_string())
80+
}
81+
6982
/// Security audit fix #3: Validate pinning endpoint to prevent SSRF
7083
/// - Must use https scheme
7184
/// - Must not be a private/localhost address
@@ -124,12 +137,15 @@ impl PinningCredentials {
124137
pub async fn pin_for_user(headers: &HeaderMap, cid: &Cid, object_key: Option<&str>) {
125138
// Security audit fix #2: Don't log header VALUES (contains tokens)
126139
// Only log presence of headers, never their values
127-
let has_service = headers.get(HEADER_PINNING_SERVICE).is_some();
128-
let has_token = headers.get(HEADER_PINNING_TOKEN).is_some();
140+
// Check both direct and x-amz-meta-* prefixed headers
141+
let has_service = headers.get(HEADER_PINNING_SERVICE).is_some()
142+
|| headers.get(HEADER_AMZ_PINNING_SERVICE).is_some();
143+
let has_token = headers.get(HEADER_PINNING_TOKEN).is_some()
144+
|| headers.get(HEADER_AMZ_PINNING_TOKEN).is_some();
129145
tracing::debug!(
130146
has_pinning_service = has_service,
131147
has_pinning_token = has_token,
132-
"Checking for pinning credentials"
148+
"Checking for pinning credentials (direct or x-amz-meta-* headers)"
133149
);
134150

135151
if let Some(creds) = PinningCredentials::from_headers(headers) {
@@ -263,4 +279,55 @@ mod tests {
263279
// Missing token
264280
assert!(PinningCredentials::from_headers(&headers).is_none());
265281
}
282+
283+
#[test]
284+
fn test_s3_compatible_amz_meta_headers() {
285+
// Test that x-amz-meta-x-pinning-* headers work (S3 client compatibility)
286+
let mut headers = HeaderMap::new();
287+
headers.insert(
288+
HEADER_AMZ_PINNING_SERVICE,
289+
HeaderValue::from_static("https://api.pinata.cloud/psa"),
290+
);
291+
headers.insert(
292+
HEADER_AMZ_PINNING_TOKEN,
293+
HeaderValue::from_static("test-token-456"),
294+
);
295+
headers.insert(
296+
HEADER_AMZ_PINNING_NAME,
297+
HeaderValue::from_static("my-pin-name"),
298+
);
299+
300+
let creds = PinningCredentials::from_headers(&headers).unwrap();
301+
assert_eq!(creds.endpoint, "https://api.pinata.cloud/psa");
302+
assert_eq!(creds.token, "test-token-456");
303+
assert_eq!(creds.name, Some("my-pin-name".to_string()));
304+
}
305+
306+
#[test]
307+
fn test_direct_headers_take_precedence() {
308+
// Direct headers should take precedence over x-amz-meta-* headers
309+
let mut headers = HeaderMap::new();
310+
// Direct headers
311+
headers.insert(
312+
HEADER_PINNING_SERVICE,
313+
HeaderValue::from_static("https://direct.example.com/psa"),
314+
);
315+
headers.insert(
316+
HEADER_PINNING_TOKEN,
317+
HeaderValue::from_static("direct-token"),
318+
);
319+
// Also add x-amz-meta-* headers (should be ignored)
320+
headers.insert(
321+
HEADER_AMZ_PINNING_SERVICE,
322+
HeaderValue::from_static("https://amz.example.com/psa"),
323+
);
324+
headers.insert(
325+
HEADER_AMZ_PINNING_TOKEN,
326+
HeaderValue::from_static("amz-token"),
327+
);
328+
329+
let creds = PinningCredentials::from_headers(&headers).unwrap();
330+
assert_eq!(creds.endpoint, "https://direct.example.com/psa");
331+
assert_eq!(creds.token, "direct-token");
332+
}
266333
}

crates/fula-crypto/src/chunking.rs

Whitespace-only changes.

0 commit comments

Comments
 (0)