object-storage-proxy 0.3.0__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/Cargo.lock +1 -1
  2. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/Cargo.toml +1 -1
  3. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/PKG-INFO +1 -1
  4. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/src/credentials/signer.rs +18 -152
  5. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/src/lib.rs +28 -30
  6. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/src/parsers/credentials.rs +9 -9
  7. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/.cargo/config.toml +0 -0
  8. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/.github/workflows/ci.yml +0 -0
  9. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/.gitignore +0 -0
  10. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/LICENSE +0 -0
  11. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/README.md +0 -0
  12. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/img/logo.svg +0 -0
  13. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/img/request_lifecycle.svg +0 -0
  14. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/img/request_stages.svg +0 -0
  15. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/pyproject.toml +0 -0
  16. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/requirements.txt +0 -0
  17. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/src/credentials/hmac_keystore.rs +0 -0
  18. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/src/credentials/mod.rs +0 -0
  19. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/src/credentials/models.rs +0 -0
  20. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/src/credentials/secrets_proxy.rs +0 -0
  21. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/src/object_storage_proxy.pyi +0 -0
  22. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/src/parsers/cos_map.rs +0 -0
  23. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/src/parsers/keystore.rs +0 -0
  24. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/src/parsers/mod.rs +0 -0
  25. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/src/parsers/path.rs +0 -0
  26. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/src/utils/mod.rs +0 -0
  27. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/src/utils/validator.rs +0 -0
  28. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/test_integration.sh +0 -0
  29. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/test_server.py +0 -0
  30. {object_storage_proxy-0.3.0 → object_storage_proxy-0.3.1}/uv.lock +0 -0
@@ -1701,7 +1701,7 @@ dependencies = [
1701
1701
 
1702
1702
  [[package]]
1703
1703
  name = "object-storage-proxy"
1704
- version = "0.3.0"
1704
+ version = "0.3.1"
1705
1705
  dependencies = [
1706
1706
  "async-trait",
1707
1707
  "chrono",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "object-storage-proxy"
3
- version = "0.3.0"
3
+ version = "0.3.1"
4
4
  edition = "2024"
5
5
 
6
6
  [dependencies]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: object-storage-proxy
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
@@ -2,7 +2,7 @@ use chrono::{DateTime, NaiveDateTime, Utc};
2
2
  use http::header::HeaderMap;
3
3
  use pingora::{http::RequestHeader, proxy::Session};
4
4
  use sha256::digest;
5
- use tracing::{debug, error, info};
5
+ use tracing::{debug, error};
6
6
  use std::{collections::HashMap, fmt};
7
7
  use url::Url;
8
8
 
@@ -106,7 +106,7 @@ impl<'a> AwsSign<'a, HashMap<String, String>> {
106
106
  ]
107
107
  };
108
108
 
109
- dbg!(&url);
109
+ debug!("{:#?}", &url);
110
110
  let url: Url = url.parse().unwrap();
111
111
  let headers: HashMap<String, String> = headers
112
112
  .iter()
@@ -227,7 +227,7 @@ where
227
227
  signed_headers = signed_headers,
228
228
  signature = signature
229
229
  );
230
- info!("sign_string: {}", sign_string);
230
+ debug!("sign_string: {}", sign_string);
231
231
  sign_string
232
232
  }
233
233
  }
@@ -388,122 +388,6 @@ pub(crate) async fn sign_request(
388
388
  }
389
389
 
390
390
 
391
- /// Validate the signature of the request
392
- // pub async fn signature_is_valid(
393
- // auth_header: &str,
394
- // session: &Session,
395
- // secret_key: &str,
396
- // ) -> Result<bool, Box<dyn std::error::Error>> {
397
-
398
- // let signed_headers_str = auth_header
399
- // .split("SignedHeaders=")
400
- // .nth(1)
401
- // .and_then(|s| s.split(',').next())
402
- // .unwrap_or("");
403
- // let signed_headers: Vec<String> =
404
- // signed_headers_str.split(';').map(|s| s.to_lowercase()).collect();
405
-
406
-
407
- // // extract access key from Authorization header
408
- // let (_, local_access_key) = parse_token_from_header(auth_header)
409
- // .map_err(|_| pingora::Error::new_str("Failed to parse token"))?;
410
- // let local_access_key = local_access_key.to_string();
411
- // if local_access_key.is_empty() {
412
- // error!("Missing access key");
413
- // return Ok(false);
414
- // }
415
-
416
- // // extract provided signature
417
- // let provided_signature = auth_header
418
- // .split("Signature=")
419
- // .nth(1)
420
- // .ok_or_else(|| pingora::Error::new_str("Invalid Authorization header: no Signature"))?
421
- // .to_string();
422
-
423
- // // parse x-amz-date header
424
- // let dt_header = session
425
- // .req_header()
426
- // .headers
427
- // .get("x-amz-date")
428
- // .ok_or_else(|| pingora::Error::new_str("Missing x-amz-date header"))?
429
- // .to_str()?;
430
-
431
- // // use NaiveDateTime then assign Utc timezone (format: %Y%m%dT%H%M%SZ)
432
- // let naive = NaiveDateTime::parse_from_str(dt_header, LONG_DATETIME)
433
- // .map_err(|_| {
434
- // pingora::Error::new_str("invalid date")
435
- // })?;
436
- // let datetime = naive.and_utc();
437
-
438
-
439
- // info!("parsing the region and service");
440
-
441
- // let (_, (region, service)) = parse_credential_scope(auth_header)
442
- // .map_err(|_| pingora::Error::new_str("Invalid Credential scope"))?;
443
-
444
- // dbg!(&region);
445
- // dbg!(&service);
446
-
447
- // // determine payload hash header
448
- // let content_sha256 = session
449
- // .req_header()
450
- // .headers
451
- // .get("x-amz-content-sha256")
452
- // .and_then(|h| h.to_str().ok())
453
- // .ok_or_else(|| pingora::Error::new_str("Missing x-amz-content-sha256 header"))?;
454
-
455
-
456
- // let (body_bytes, payload_override) = if content_sha256 == "UNSIGNED-PAYLOAD" {
457
- // (b"UNSIGNED-PAYLOAD" as &[u8], None)
458
- // } else {
459
- // // we don't have the raw body here, but we do have its hash:
460
- // // tell AwsSign to use this string directly
461
- // (&[] as &[u8], Some(content_sha256.to_owned()))
462
- // };
463
-
464
- // let original_uri = session.req_header().uri.to_string();
465
- // let full_url = if original_uri.starts_with('/') {
466
- // let host = session
467
- // .req_header()
468
- // .headers
469
- // .get("host")
470
- // .ok_or_else(|| pingora::Error::new_str("Missing host header"))?
471
- // .to_str()?;
472
- // format!("https://{}{}", host, original_uri)
473
- // } else {
474
- // original_uri
475
- // };
476
-
477
- // // construct AwsSign and compute signature
478
- // let method = session.req_header().method.to_string();
479
- // let mut signer = AwsSign::new(
480
- // &method,
481
- // &full_url,
482
- // &datetime,
483
- // &session.req_header().headers,
484
- // region,
485
- // &local_access_key,
486
- // &secret_key,
487
- // service,
488
- // body_bytes,
489
- // Some(&signed_headers),
490
- // );
491
-
492
- // if let Some(ov) = payload_override {
493
- // signer.set_payload_override(ov);
494
- // }
495
-
496
- // let signature = signer.sign();
497
- // let computed_signature = signature
498
- // .split("Signature=")
499
- // .nth(1)
500
- // .unwrap_or_default();
501
-
502
- // info!("Provided signature: {}", provided_signature);
503
- // info!("Computed signature: {}", computed_signature);
504
- // Ok(computed_signature == provided_signature)
505
- // }
506
-
507
391
  /// Core signature validation: compares provided vs computed
508
392
  async fn signature_is_valid_core(
509
393
  method: &str,
@@ -520,7 +404,7 @@ async fn signature_is_valid_core(
520
404
  body_bytes: &[u8],
521
405
  ) -> Result<bool, Box<dyn std::error::Error>> {
522
406
  // Build AwsSign for authorization header style
523
- dbg!(&headers);
407
+ debug!("{:#?}", &headers);
524
408
  let mut signer = AwsSign::new(
525
409
  method,
526
410
  full_url,
@@ -538,8 +422,8 @@ async fn signature_is_valid_core(
538
422
  }
539
423
  let signature = signer.sign();
540
424
  let computed = signature.split("Signature=").nth(1).unwrap_or_default();
541
- info!("Provided signature: {}", provided_signature);
542
- info!("Computed signature: {}", computed);
425
+ debug!("Provided signature: {}", provided_signature);
426
+ debug!("Computed signature: {}", computed);
543
427
  Ok(computed == provided_signature)
544
428
  }
545
429
 
@@ -653,7 +537,7 @@ pub async fn signature_is_valid_for_presigned(
653
537
 
654
538
 
655
539
  let mut url = Url::parse(&full_uri)?;
656
- info!("full_url: {}", url);
540
+ debug!("full_url: {}", url);
657
541
  let mut provided_signature = None;
658
542
  let mut qp: Vec<(String,String)> = vec![];
659
543
  for (k, v) in url.query_pairs() {
@@ -664,7 +548,6 @@ pub async fn signature_is_valid_for_presigned(
664
548
  }
665
549
  }
666
550
  let provided_signature = provided_signature.ok_or("Missing X-Amz-Signature")?;
667
- // ----------------------------------------------------------------------
668
551
 
669
552
  // rebuild query string without the signature
670
553
  qp.sort();
@@ -676,19 +559,15 @@ pub async fn signature_is_valid_for_presigned(
676
559
 
677
560
  // params map (also without the signature)
678
561
  let params: HashMap<_, _> = qp.into_iter().collect();
679
- info!("params: {:?}", params);
680
- info!("url: {:?}", url);
681
-
682
- // Required signature and credential
683
- // let provided_signature = params
684
- // .get("X-Amz-Signature")
685
- // .ok_or("Missing X-Amz-Signature")?;
686
- info!("provided signature: {}", provided_signature);
562
+ debug!("params: {:?}", params);
563
+ debug!("url: {:?}", url);
564
+
565
+ debug!("provided signature: {}", provided_signature);
687
566
  let credential = params
688
567
  .get("X-Amz-Credential")
689
568
  .ok_or("Missing X-Amz-Credential")?;
690
569
 
691
- info!("credential: {}", credential);
570
+ debug!("credential: {}", credential);
692
571
 
693
572
  // Parse credential: <access_key>/<date>/<region>/<service>/aws4_request
694
573
  let mut parts = credential.split('/');
@@ -697,9 +576,9 @@ pub async fn signature_is_valid_for_presigned(
697
576
  let region = parts.next().ok_or("Malformed Credential")?;
698
577
  let service = parts.next().ok_or("Malformed Credential")?;
699
578
 
700
- info!("access_key: {}", access_key);
701
- info!("region: {}", region);
702
- info!("service: {}", service);
579
+ debug!("access_key: {}", access_key);
580
+ debug!("region: {}", region);
581
+ debug!("service: {}", service);
703
582
 
704
583
  // Parse date from query
705
584
  let date_str = params
@@ -707,25 +586,12 @@ pub async fn signature_is_valid_for_presigned(
707
586
  .ok_or("Missing X-Amz-Date")?;
708
587
  let datetime = NaiveDateTime::parse_from_str(date_str, LONG_DATETIME)?.and_utc();
709
588
 
710
- info!("datetime: {}", datetime);
711
-
712
- // // Determine payload override
713
- // let payload_override = params
714
- // .get("X-Amz-Content-Sha256")
715
- // .filter(|v| *v != "UNSIGNED-PAYLOAD")
716
- // .map(|v| v.to_string());
717
- // info!("payload_override: {:?}", payload_override);
718
- // let body_bytes: &[u8] = if params.get("X-Amz-Content-Sha256")
719
- // == Some(&"UNSIGNED-PAYLOAD".to_string()) {
720
- // b"UNSIGNED-PAYLOAD"
721
- // } else {
722
- // &[]
723
- // };
589
+ debug!("datetime: {}", datetime);
724
590
 
725
591
  let body_bytes: &[u8] = b"UNSIGNED-PAYLOAD";
726
592
  let payload_override = None;
727
593
 
728
- info!("body_bytes: {:?}", body_bytes);
594
+ debug!("body_bytes: {:?}", body_bytes);
729
595
 
730
596
  // Collect signed headers list
731
597
  let signed_headers = params
@@ -755,7 +621,7 @@ pub async fn signature_is_valid_for_presigned(
755
621
 
756
622
 
757
623
 
758
- info!("signed_headers: {:?}", signed_headers);
624
+ debug!("signed_headers: {:?}", signed_headers);
759
625
  // Delegate to core validator
760
626
  signature_is_valid_core(
761
627
  session.req_header().method.as_str(),
@@ -315,11 +315,23 @@ impl ProxyHttp for MyProxy {
315
315
  map.get(bucket).and_then(|c| c.ttl).unwrap_or(0)
316
316
  };
317
317
  let mut access_key: String = String::new();
318
+
319
+ if auth_header.is_empty() {
320
+ if let Some(q) = session.req_header().uri.query() {
321
+ if q.contains("X-Amz-Credential") {
322
+ let (_, p) = parse_presigned_params(&format!("?{q}"))
323
+ .map_err(|_| pingora::Error::new_str("Failed to parse presigned params"))?;
324
+ access_key = p.access_key.clone();
325
+ }
326
+ }
327
+ } else {
328
+ access_key = parse_token_from_header(&auth_header)
329
+ .map_err(|_| pingora::Error::new_str("Failed to parse access_key"))?
330
+ .1
331
+ .to_string();
332
+ }
333
+
318
334
  let is_authorized = if let Some(py_cb) = &ctx.validator {
319
- // let access_key = parse_token_from_header(&auth_header)
320
- // .map_err(|_| pingora::Error::new_str("Failed to parse access_key"))?
321
- // .1
322
- // .to_string();
323
335
 
324
336
  let is_multipart = session
325
337
  .req_header()
@@ -328,7 +340,6 @@ impl ProxyHttp for MyProxy {
328
340
  .map_or(false, |q| q.contains("uploadId="));
329
341
 
330
342
 
331
-
332
343
  info!("CHECKING SIGNATURE");
333
344
  if let Some(skip) = self.skip_signature_validation {
334
345
  if skip || is_multipart {
@@ -341,18 +352,11 @@ impl ProxyHttp for MyProxy {
341
352
  let uri_q = session.req_header().uri.query().unwrap_or("");
342
353
 
343
354
  if auth_header.is_empty() && uri_q.contains("X-Amz-Signature") {
344
- let full_q = format!("?{uri_q}");
345
355
  ctx.is_presigned = Some(true);
346
- let (_, presigned_params) = parse_presigned_params(&full_q)
347
- .map_err(|_| pingora::Error::new_str("Failed to parse presigned params"))?;
348
- access_key = presigned_params.access_key.clone();
349
- // let access_key = parse_credential_scope(uri_q)
350
- // .map_err(|_| pingora::Error::new_str("Failed to parse access_key"))?
351
- // .1
352
- // .to_string();
356
+
353
357
  // ensure we have the secret_key in the keystore
354
358
  if !ctx.hmac_keystore.read().await.contains_key(&access_key) {
355
- info!("No key in keystore, trying to fetch via hmac_fetche for ->{}<-", access_key);
359
+ debug!("No key in keystore, trying to fetch via hmac_fetcher for ->{}<-", access_key);
356
360
  // fetch via hmac_fetcher exactly as you do below…
357
361
  if let Some(py_fetcher) = &ctx.hmac_fetcher {
358
362
  // call Python callback
@@ -361,10 +365,10 @@ impl ProxyHttp for MyProxy {
361
365
  cb.call1(py, (&access_key,))
362
366
  .and_then(|r| r.extract(py))
363
367
  });
364
- info!("Got secret: {:#?}", secret);
368
+ debug!("Got secret: {:#?}", secret);
365
369
  match secret {
366
370
  Ok(secret_key) => {
367
- info!("got key and inserting into keystore");
371
+ debug!("got key and inserting into keystore");
368
372
  ctx.hmac_keystore.write().await.insert(access_key.clone().to_string(), secret_key);
369
373
  }
370
374
  Err(_) => {
@@ -379,9 +383,11 @@ impl ProxyHttp for MyProxy {
379
383
  }
380
384
 
381
385
  }
382
- info!("now checking if the signature is valid for presigned...");
386
+ debug!("now checking if the signature is valid for presigned...");
383
387
  let sk = ctx.hmac_keystore.read().await.get(&access_key).unwrap().clone();
384
- info!("got secret {} from keystore", sk);
388
+ debug!("got secret {} from keystore", sk);
389
+ debug!("RAW_PATH = {}", &session.req_header().uri);
390
+ debug!("RAW_HOST_HDR = {:?}", &session.req_header().headers.get("host"));
385
391
  let ok = match signature_is_valid_for_presigned(&session, &sk).await {
386
392
  Ok(b) => b,
387
393
  Err(e) => {
@@ -396,11 +402,6 @@ impl ProxyHttp for MyProxy {
396
402
  }
397
403
  } else {
398
404
  info!("processing a regular request");
399
- // hmac request
400
- access_key = parse_token_from_header(&auth_header)
401
- .map_err(|_| pingora::Error::new_str("Failed to parse access_key"))?
402
- .1
403
- .to_string();
404
405
 
405
406
  let has_key = {
406
407
  let map = ctx.hmac_keystore.read().await;
@@ -463,7 +464,7 @@ impl ProxyHttp for MyProxy {
463
464
  }
464
465
  info!("Signature check passed, continuing now onto the bespoke validation");
465
466
  let cache_key = format!("{}:{}", &access_key, bucket);
466
- info!("Cache key: {}", cache_key);
467
+ debug!("Cache key: {}", cache_key);
467
468
 
468
469
  let bucket_clone = bucket.to_string();
469
470
  let callback_clone: PyObject = Python::with_gil(|py| py_cb.clone_ref(py));
@@ -494,11 +495,8 @@ impl ProxyHttp for MyProxy {
494
495
  let map = ctx.cos_mapping.read().await;
495
496
  map.get(&hdr_bucket).cloned()
496
497
  };
497
- // access_key = parse_token_from_header(&auth_header)
498
- // .map_err(|_| pingora::Error::new_str("Failed to parse access_key"))?
499
- // .1
500
- // .to_string();
501
- info!("Access key: {}", &access_key);
498
+
499
+ debug!("Access key: {}", &access_key);
502
500
 
503
501
  // we have to check for some available credentials here to be able to return unauthorized already if not
504
502
  match bucket_config.clone() {
@@ -728,7 +726,7 @@ pub fn run_server(py: Python, run_args: &ProxyServerConfig) {
728
726
  parse_hmac_list(py, &run_args.hmac_keystore).unwrap_or(HashMap::new())
729
727
  };
730
728
 
731
- info!("HMAC keys: {:#?}", &local_hmac_map);
729
+ debug!("HMAC keys: {:#?}", &local_hmac_map);
732
730
 
733
731
  let cosmap = Arc::new(RwLock::new(parse_cos_map(py, &run_args.cos_map).unwrap()));
734
732
  let hmac_keystore = Arc::new(RwLock::new(local_hmac_map));
@@ -37,15 +37,15 @@ pub fn parse_credential_scope(input: &str) -> IResult<&str, (&str, &str)> {
37
37
 
38
38
  #[derive(Debug, PartialEq)]
39
39
  pub struct PresignedParams {
40
- pub algorithm: String, // X-Amz-Algorithm
41
- pub access_key: String, // from X-Amz-Credential
42
- pub credential_date: String, // from X-Amz-Credential
43
- pub region: String, // from X-Amz-Credential
44
- pub service: String, // from X-Amz-Credential
45
- pub amz_date: String, // X-Amz-Date
46
- pub expires: String, // X-Amz-Expires
47
- pub signed_headers: String, // X-Amz-SignedHeaders
48
- pub signature: String, // X-Amz-Signature
40
+ pub algorithm: String,
41
+ pub access_key: String,
42
+ pub credential_date: String,
43
+ pub region: String,
44
+ pub service: String,
45
+ pub amz_date: String,
46
+ pub expires: String,
47
+ pub signed_headers: String,
48
+ pub signature: String,
49
49
  }
50
50
 
51
51
  /// key chars: letters, digits, dash, dot