object-storage-proxy 0.3.5__tar.gz → 0.3.7__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.5 → object_storage_proxy-0.3.7}/.gitignore +2 -1
  2. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/Cargo.lock +1 -1
  3. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/Cargo.toml +1 -1
  4. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/PKG-INFO +1 -1
  5. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/credentials/signer.rs +119 -34
  6. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/lib.rs +10 -3
  7. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/test_server.py +1 -1
  8. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/.cargo/config.toml +0 -0
  9. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/.github/workflows/ci.yml +0 -0
  10. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/LICENSE +0 -0
  11. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/README.md +0 -0
  12. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/img/logo.svg +0 -0
  13. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/img/request_lifecycle.svg +0 -0
  14. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/img/request_stages.svg +0 -0
  15. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/pyproject.toml +0 -0
  16. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/requirements.txt +0 -0
  17. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/credentials/hmac_keystore.rs +0 -0
  18. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/credentials/mod.rs +0 -0
  19. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/credentials/models.rs +0 -0
  20. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/credentials/secrets_proxy.rs +0 -0
  21. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/object_storage_proxy.pyi +0 -0
  22. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/parsers/cos_map.rs +0 -0
  23. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/parsers/credentials.rs +0 -0
  24. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/parsers/keystore.rs +0 -0
  25. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/parsers/mod.rs +0 -0
  26. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/parsers/path.rs +0 -0
  27. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/utils/mod.rs +0 -0
  28. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/utils/validator.rs +0 -0
  29. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/test_integration.sh +0 -0
  30. {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/uv.lock +0 -0
@@ -5,4 +5,5 @@
5
5
  .venv
6
6
  .DS_Store
7
7
  *.pem
8
- */with_*.log
8
+ */with_*.log
9
+ troubleshooting/
@@ -1701,7 +1701,7 @@ dependencies = [
1701
1701
 
1702
1702
  [[package]]
1703
1703
  name = "object-storage-proxy"
1704
- version = "0.3.5"
1704
+ version = "0.3.7"
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.5"
3
+ version = "0.3.7"
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.5
3
+ Version: 0.3.7
4
4
  Classifier: License :: Other/Proprietary License
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: Programming Language :: Python :: Implementation :: CPython
@@ -3,7 +3,7 @@ use http::header::HeaderMap;
3
3
  use pingora::{http::RequestHeader, proxy::Session};
4
4
  use sha256::digest;
5
5
  use tracing::{debug, error};
6
- use std::{collections::HashMap, fmt};
6
+ use std::{collections::{HashMap, HashSet}, fmt};
7
7
  use url::Url;
8
8
 
9
9
  use crate::parsers::{cos_map::CosMapItem, credentials::{parse_credential_scope, parse_token_from_header}};
@@ -90,40 +90,88 @@ impl<'a> AwsSign<'a, HashMap<String, String>> {
90
90
  secret_key: &'a str,
91
91
  service: &'a str,
92
92
  body: &'a B,
93
- signed_headers: Option<&'a Vec<String>>,
93
+ _signed_headers: Option<&'a Vec<String>>,
94
94
  ) -> Self {
95
95
 
96
96
 
97
- let allowed: Vec<&str> = if let Some(sh) = signed_headers {
98
- sh.iter().map(String::as_str).collect()
99
- } else {
100
- vec![
101
- "host",
102
- "x-amz-date",
103
- "range",
104
- "x-amz-content-sha256",
105
- "x-amz-security-token",
106
- "trailer",
107
- "x-amz-trailer",
108
- "content-encoding",
109
- ]
110
- };
111
-
112
- debug!("{:#?}", &url);
113
- let url: Url = url.parse().unwrap();
97
+ // let allowed: Vec<&str> = if let Some(sh) = signed_headers {
98
+ // sh.iter().map(String::as_str).collect()
99
+ // } else {
100
+ // vec![
101
+ // "host",
102
+ // "x-amz-date",
103
+ // "range",
104
+ // "x-amz-content-sha256",
105
+ // "x-amz-security-token",
106
+ // ]
107
+ // };
108
+
109
+ let signed_allow: Option<HashSet<&str>> =
110
+ _signed_headers.map(|v| v.iter().map(String::as_str).collect());
111
+
114
112
  let headers: HashMap<String, String> = headers
115
113
  .iter()
116
114
  .filter_map(|(key, value)| {
117
115
  let name = key.as_str().to_lowercase();
118
- if !allowed.contains(&name.as_str()) {
116
+
117
+ // ─── decide whether to keep `name` ──────────────────────────
118
+ let keep = if let Some(ref set) = signed_allow {
119
+ // verifier path → keep exactly what the client signed
120
+ set.contains(name.as_str())
121
+ } else {
122
+ // re-signing path → keep the full streaming whitelist
123
+ name == "host"
124
+ || name.starts_with("x-amz-")
125
+ || matches!(
126
+ name.as_str(),
127
+ "content-length"
128
+ | "content-encoding"
129
+ | "transfer-encoding"
130
+ | "range"
131
+ | "expect"
132
+ | "x-amz-decoded-content-length"
133
+ )
134
+ };
135
+ if !keep {
119
136
  return None;
120
137
  }
121
- value
122
- .to_str()
123
- .ok()
124
- .map(|v| (name, v.trim().to_owned()))
138
+ value.to_str().ok().map(|v| (name, v.trim().to_owned()))
125
139
  })
126
140
  .collect();
141
+
142
+
143
+ // let headers: HashMap<String, String> = headers
144
+ // .iter()
145
+ // .filter_map(|(key, value)| {
146
+ // let name = key.as_str().to_lowercase();
147
+ // let keep = name == "host"
148
+ // || name.starts_with("x-amz-")
149
+ // || name == "content-length"
150
+ // || name == "content-encoding"
151
+ // || name == "transfer-encoding"
152
+ // || name == "range";
153
+ // if !keep {
154
+ // return None;
155
+ // }
156
+ // value.to_str().ok().map(|v| (name, v.trim().to_owned()))
157
+ // })
158
+ // .collect();
159
+
160
+ debug!("{:#?}", &url);
161
+ let url: Url = url.parse().unwrap();
162
+ // let headers: HashMap<String, String> = headers
163
+ // .iter()
164
+ // .filter_map(|(key, value)| {
165
+ // let name = key.as_str().to_lowercase();
166
+ // if !allowed.contains(&name.as_str()) {
167
+ // return None;
168
+ // }
169
+ // value
170
+ // .to_str()
171
+ // .ok()
172
+ // .map(|v| (name, v.trim().to_owned()))
173
+ // })
174
+ // .collect();
127
175
  Self {
128
176
  method,
129
177
  url,
@@ -347,17 +395,34 @@ pub(crate) async fn sign_request(
347
395
  .parse::<http::header::HeaderValue>()
348
396
  .unwrap(),
349
397
  )?;
350
- let payload_hash = if method == "GET" || method == "HEAD" || method == "DELETE" {
351
- // spec uses empty‑body hash for reads
352
- &sha256::digest(b"")
353
- } else {
354
- // for streaming uploads we sign UNSIGNED‑PAYLOAD
355
- "UNSIGNED-PAYLOAD"
398
+ // let payload_hash = if method == "GET" || method == "HEAD" || method == "DELETE" {
399
+ // // spec uses empty‑body hash for reads
400
+ // &sha256::digest(b"")
401
+ // } else {
402
+ // // for streaming uploads we sign UNSIGNED‑PAYLOAD
403
+ // "UNSIGNED-PAYLOAD"
404
+ // };
405
+ let payload_hdr = request
406
+ .headers
407
+ .get("x-amz-content-sha256")
408
+ .and_then(|v| v.to_str().ok());
409
+
410
+ let payload_hash = match payload_hdr {
411
+ // client already supplied one → keep it verbatim
412
+ Some(h) => h,
413
+
414
+ // empty-body requests (GET/HEAD/DELETE) → spec hash of “”
415
+ None if matches!(method.as_str(), "GET" | "HEAD" | "DELETE") =>
416
+ &sha256::digest(b""),
417
+
418
+ // default for uploads over TLS
419
+ _ => "UNSIGNED-PAYLOAD",
356
420
  };
357
421
 
358
- request.insert_header("x-amz-content-sha256", payload_hash)?;
422
+ let payload_hash_value = payload_hash.to_string();
423
+ request.insert_header("x-amz-content-sha256", payload_hash_value.clone())?;
359
424
 
360
- let body_bytes: &[u8] = match payload_hash {
425
+ let body_bytes: &[u8] = match payload_hash_value.clone().as_str() {
361
426
  // empty body → empty slice
362
427
  "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" => &[], // sha256 hash of empty string
363
428
  "UNSIGNED-PAYLOAD" => b"UNSIGNED-PAYLOAD",
@@ -380,12 +445,24 @@ pub(crate) async fn sign_request(
380
445
  );
381
446
  debug!("{:#?}", &auth_header);
382
447
 
383
- let signature = auth_header.sign();
448
+ let mut signer = auth_header;
449
+
450
+ // if payload_hash_value.starts_with("STREAMING-") {
451
+ // // don’t hash the literal bytes – embed the magic string itself
452
+ // signer.set_payload_override(payload_hash_value.to_string());
453
+ // }
454
+ // if payload_hash_value != "UNSIGNED-PAYLOAD" {
455
+ // signer.set_payload_override(payload_hash_value.to_string());
456
+ // }
457
+
458
+ signer.set_payload_override(payload_hash_value.clone());
459
+
460
+ let signature = signer.sign();
384
461
  debug!("{:#?}", signature);
385
462
 
386
463
  request.insert_header(
387
464
  "Authorization",
388
- http::header::HeaderValue::from_str(&auth_header.sign())?,
465
+ http::header::HeaderValue::from_str(&signature)?,
389
466
  )?;
390
467
 
391
468
  Ok(())
@@ -421,9 +498,17 @@ async fn signature_is_valid_core(
421
498
  body_bytes,
422
499
  Some(&signed_headers),
423
500
  );
501
+
502
+ // if payload_hash.starts_with("STREAMING-") {
503
+ // // don’t hash the literal bytes – embed the magic string itself
504
+ // signer.set_payload_override(payload_hash.to_string());
505
+ // }
506
+
424
507
  if let Some(ov) = payload_override {
508
+ dbg!("payload_override: {}", &ov);
425
509
  signer.set_payload_override(ov);
426
510
  }
511
+
427
512
  let signature = signer.sign();
428
513
  let computed = signature.split("Signature=").nth(1).unwrap_or_default();
429
514
  debug!("Provided signature: {}", provided_signature);
@@ -272,7 +272,7 @@ impl ProxyHttp for MyProxy {
272
272
 
273
273
  // todo: make ths configurable
274
274
 
275
- peer.options.max_h2_streams = 32;
275
+ peer.options.max_h2_streams = 128;
276
276
  peer.options.h2_ping_interval = Some(Duration::from_secs(30));
277
277
 
278
278
 
@@ -634,10 +634,17 @@ impl ProxyHttp for MyProxy {
634
634
  "x-amz-date",
635
635
  "x-amz-content-sha256",
636
636
  "x-amz-security-token",
637
+ "transfer-encoding",
637
638
  "content-encoding",
638
- "range",
639
- "trailer",
639
+ "x-amz-decoded-content-length",
640
640
  "x-amz-trailer",
641
+ "x-amz-sdk-checksum-algorithm",
642
+ "range",
643
+ "expect",
644
+ // "content-encoding",
645
+ // "range",
646
+ // "trailer",
647
+ // "x-amz-trailer",
641
648
  ];
642
649
 
643
650
  let to_check: Vec<String> = upstream_request
@@ -142,7 +142,7 @@ def main() -> None:
142
142
  bucket_creds_fetcher=do_hmac_creds,
143
143
  validator=do_validation,
144
144
  http_port=6190,
145
- # https_port=8443,
145
+ https_port=8443,
146
146
  threads=1,
147
147
  # verify=False,
148
148
  hmac_keystore=hmac_keys,