object-storage-proxy 0.3.4__tar.gz → 0.3.6__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.4 → object_storage_proxy-0.3.6}/.gitignore +2 -1
  2. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/Cargo.lock +1 -1
  3. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/Cargo.toml +1 -1
  4. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/PKG-INFO +1 -1
  5. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/credentials/signer.rs +117 -33
  6. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/lib.rs +11 -3
  7. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/test_server.py +1 -1
  8. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/.cargo/config.toml +0 -0
  9. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/.github/workflows/ci.yml +0 -0
  10. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/LICENSE +0 -0
  11. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/README.md +0 -0
  12. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/img/logo.svg +0 -0
  13. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/img/request_lifecycle.svg +0 -0
  14. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/img/request_stages.svg +0 -0
  15. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/pyproject.toml +0 -0
  16. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/requirements.txt +0 -0
  17. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/credentials/hmac_keystore.rs +0 -0
  18. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/credentials/mod.rs +0 -0
  19. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/credentials/models.rs +0 -0
  20. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/credentials/secrets_proxy.rs +0 -0
  21. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/object_storage_proxy.pyi +0 -0
  22. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/parsers/cos_map.rs +0 -0
  23. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/parsers/credentials.rs +0 -0
  24. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/parsers/keystore.rs +0 -0
  25. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/parsers/mod.rs +0 -0
  26. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/parsers/path.rs +0 -0
  27. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/utils/mod.rs +0 -0
  28. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/utils/validator.rs +0 -0
  29. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/test_integration.sh +0 -0
  30. {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/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.4"
1704
+ version = "0.3.6"
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.4"
3
+ version = "0.3.6"
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.4
3
+ Version: 0.3.6
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,39 +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
- ]
109
- };
110
-
111
- debug!("{:#?}", &url);
112
- 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
+
113
112
  let headers: HashMap<String, String> = headers
114
113
  .iter()
115
114
  .filter_map(|(key, value)| {
116
115
  let name = key.as_str().to_lowercase();
117
- 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 {
118
136
  return None;
119
137
  }
120
- value
121
- .to_str()
122
- .ok()
123
- .map(|v| (name, v.trim().to_owned()))
138
+ value.to_str().ok().map(|v| (name, v.trim().to_owned()))
124
139
  })
125
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();
126
175
  Self {
127
176
  method,
128
177
  url,
@@ -346,17 +395,34 @@ pub(crate) async fn sign_request(
346
395
  .parse::<http::header::HeaderValue>()
347
396
  .unwrap(),
348
397
  )?;
349
- let payload_hash = if method == "GET" || method == "HEAD" || method == "DELETE" {
350
- // spec uses empty‑body hash for reads
351
- &sha256::digest(b"")
352
- } else {
353
- // for streaming uploads we sign UNSIGNED‑PAYLOAD
354
- "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",
355
420
  };
356
421
 
357
- 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())?;
358
424
 
359
- let body_bytes: &[u8] = match payload_hash {
425
+ let body_bytes: &[u8] = match payload_hash_value.clone().as_str() {
360
426
  // empty body → empty slice
361
427
  "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" => &[], // sha256 hash of empty string
362
428
  "UNSIGNED-PAYLOAD" => b"UNSIGNED-PAYLOAD",
@@ -379,12 +445,22 @@ pub(crate) async fn sign_request(
379
445
  );
380
446
  debug!("{:#?}", &auth_header);
381
447
 
382
- 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
+ let signature = signer.sign();
383
459
  debug!("{:#?}", signature);
384
460
 
385
461
  request.insert_header(
386
462
  "Authorization",
387
- http::header::HeaderValue::from_str(&auth_header.sign())?,
463
+ http::header::HeaderValue::from_str(&signature)?,
388
464
  )?;
389
465
 
390
466
  Ok(())
@@ -420,9 +496,17 @@ async fn signature_is_valid_core(
420
496
  body_bytes,
421
497
  Some(&signed_headers),
422
498
  );
499
+
500
+ // if payload_hash.starts_with("STREAMING-") {
501
+ // // don’t hash the literal bytes – embed the magic string itself
502
+ // signer.set_payload_override(payload_hash.to_string());
503
+ // }
504
+
423
505
  if let Some(ov) = payload_override {
506
+ dbg!("payload_override: {}", &ov);
424
507
  signer.set_payload_override(ov);
425
508
  }
509
+
426
510
  let signature = signer.sign();
427
511
  let computed = signature.split("Signature=").nth(1).unwrap_or_default();
428
512
  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,9 +634,17 @@ impl ProxyHttp for MyProxy {
634
634
  "x-amz-date",
635
635
  "x-amz-content-sha256",
636
636
  "x-amz-security-token",
637
- "range",
638
- "trailer",
637
+ "transfer-encoding",
638
+ "content-encoding",
639
+ "x-amz-decoded-content-length",
639
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",
640
648
  ];
641
649
 
642
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,