object-storage-proxy 0.3.8__tar.gz → 0.3.10__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.8 → object_storage_proxy-0.3.10}/Cargo.lock +10 -5
  2. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/Cargo.toml +6 -1
  3. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/PKG-INFO +1 -1
  4. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/src/credentials/signer.rs +68 -0
  5. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/src/lib.rs +53 -8
  6. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/.cargo/config.toml +0 -0
  7. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/.github/workflows/ci.yml +0 -0
  8. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/.gitignore +0 -0
  9. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/LICENSE +0 -0
  10. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/README.md +0 -0
  11. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/img/logo.svg +0 -0
  12. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/img/request_lifecycle.svg +0 -0
  13. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/img/request_stages.svg +0 -0
  14. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/pyproject.toml +0 -0
  15. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/requirements.txt +0 -0
  16. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/src/credentials/hmac_keystore.rs +0 -0
  17. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/src/credentials/mod.rs +0 -0
  18. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/src/credentials/models.rs +0 -0
  19. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/src/credentials/secrets_proxy.rs +0 -0
  20. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/src/object_storage_proxy.pyi +0 -0
  21. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/src/parsers/cos_map.rs +0 -0
  22. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/src/parsers/credentials.rs +0 -0
  23. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/src/parsers/keystore.rs +0 -0
  24. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/src/parsers/mod.rs +0 -0
  25. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/src/parsers/path.rs +0 -0
  26. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/src/utils/mod.rs +0 -0
  27. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/src/utils/validator.rs +0 -0
  28. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/test_integration.sh +0 -0
  29. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/test_server.py +0 -0
  30. {object_storage_proxy-0.3.8 → object_storage_proxy-0.3.10}/uv.lock +0 -0
@@ -382,9 +382,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
382
382
 
383
383
  [[package]]
384
384
  name = "bytes"
385
- version = "1.10.0"
385
+ version = "1.10.1"
386
386
  source = "registry+https://github.com/rust-lang/crates.io-index"
387
- checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
387
+ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
388
388
 
389
389
  [[package]]
390
390
  name = "cc"
@@ -1701,19 +1701,23 @@ dependencies = [
1701
1701
 
1702
1702
  [[package]]
1703
1703
  name = "object-storage-proxy"
1704
- version = "0.3.8"
1704
+ version = "0.3.10"
1705
1705
  dependencies = [
1706
+ "async-stream",
1706
1707
  "async-trait",
1708
+ "bytes",
1707
1709
  "chrono",
1708
1710
  "clap 4.5.37",
1709
1711
  "dotenv",
1710
1712
  "env_logger",
1711
1713
  "futures",
1714
+ "futures-core",
1712
1715
  "hex",
1713
1716
  "http",
1714
1717
  "log",
1715
1718
  "nom 8.0.0",
1716
1719
  "percent-encoding",
1720
+ "pin-project-lite",
1717
1721
  "pingora",
1718
1722
  "prometheus 0.14.0",
1719
1723
  "pyo3",
@@ -1725,6 +1729,7 @@ dependencies = [
1725
1729
  "serde_json",
1726
1730
  "sha256",
1727
1731
  "tokio",
1732
+ "tokio-util",
1728
1733
  "tracing",
1729
1734
  "tracing-subscriber",
1730
1735
  "url",
@@ -3256,9 +3261,9 @@ dependencies = [
3256
3261
 
3257
3262
  [[package]]
3258
3263
  name = "tokio-util"
3259
- version = "0.7.13"
3264
+ version = "0.7.15"
3260
3265
  source = "registry+https://github.com/rust-lang/crates.io-index"
3261
- checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078"
3266
+ checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
3262
3267
  dependencies = [
3263
3268
  "bytes",
3264
3269
  "futures-core",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "object-storage-proxy"
3
- version = "0.3.8"
3
+ version = "0.3.10"
4
4
  edition = "2024"
5
5
 
6
6
  [dependencies]
@@ -34,6 +34,11 @@ ring = "0.17.14"
34
34
  hex = "0.4.3"
35
35
  regex = "1.11.1"
36
36
  percent-encoding = "2.3.1"
37
+ bytes = "1.10.1"
38
+ async-stream = "0.3.6"
39
+ tokio-util = "0.7.15"
40
+ futures-core = "0.3.31"
41
+ pin-project-lite = "0.2.16"
37
42
 
38
43
  # [build-dependencies]
39
44
  # openssl-sys = { version = "0.9", features = ["vendored"] }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: object-storage-proxy
3
- Version: 0.3.8
3
+ Version: 0.3.10
4
4
  Classifier: License :: Other/Proprietary License
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: Programming Language :: Python :: Implementation :: CPython
@@ -6,6 +6,10 @@ use tracing::{debug, error};
6
6
  use std::{collections::{HashMap, HashSet}, fmt};
7
7
  use url::Url;
8
8
 
9
+ use ring::hmac;
10
+
11
+ use bytes::{Bytes, BytesMut};
12
+
9
13
  use crate::parsers::{cos_map::CosMapItem, credentials::{parse_credential_scope, parse_token_from_header}};
10
14
 
11
15
  const SHORT_DATE: &str = "%Y%m%d";
@@ -728,6 +732,70 @@ pub async fn signature_is_valid_for_presigned(
728
732
  ).await
729
733
  }
730
734
 
735
+
736
+
737
+ /// Build a stream whose items are *already* wrapped in
738
+ /// “AWS-chunk-signed” envelopes.
739
+ ///
740
+ /// * `body` – raw payload implementing `AsyncRead`
741
+ /// * `signing_key` – result of the usual `signing_key()` step
742
+ /// * `scope` – e.g. `"20250501/eu-west-3/s3/aws4_request"`
743
+ /// * `ts` – the `X-Amz-Date` you put in the header (`YYYYMMDDThhmmssZ`)
744
+ /// * `seed_sig` – the `Signature=` value you computed for the
745
+ /// *headers* (the one that goes into `Authorization:`)
746
+ ///
747
+ /// ```text
748
+ /// ┌──── header chunk ────┐┌── data ─┐┌─ CRLF ─┐
749
+ /// <hex-len>;chunk-signature=<sig>\r\n<bytes>\r\n
750
+ /// ```
751
+ ///
752
+ /// The very last frame is
753
+ /// ```text
754
+ /// 0;chunk-signature=<final-sig>\r\n\r\n
755
+ /// ```
756
+ pub async fn wrap_streaming_body(
757
+ session: &mut Session,
758
+ upstream_request: &mut RequestHeader,
759
+ region: &str,
760
+ access_key: &str,
761
+ secret_key: &str,
762
+ ) -> Result<(), Box<dyn std::error::Error>> {
763
+ // 1. pull the COMPLETE body from the client
764
+ let body: Bytes = session.read_request_body().await.expect("Failed to read request body").unwrap();
765
+
766
+ // 2. overwrite the x-amz-* headers so that we can sign UNSIGNED-PAYLOAD
767
+ upstream_request.insert_header("x-amz-content-sha256", "UNSIGNED-PAYLOAD")?;
768
+ upstream_request.remove_header("x-amz-decoded-content-length");
769
+ upstream_request.insert_header("content-length", body.len().to_string())?;
770
+
771
+ // 3. resign
772
+ let ts = chrono::Utc::now();
773
+ let url = upstream_request.uri.to_string();
774
+ upstream_request.insert_header("x-amz-date", ts.format("%Y%m%dT%H%M%SZ").to_string())?;
775
+ let signer = AwsSign::new(
776
+ upstream_request.method.as_str(),
777
+ &url,
778
+ &ts,
779
+ &upstream_request.headers,
780
+ region,
781
+ access_key,
782
+ secret_key,
783
+ "s3",
784
+ b"UNSIGNED-PAYLOAD",
785
+ None,
786
+ );
787
+ let auth = signer.sign();
788
+ upstream_request.insert_header("authorization", auth)?;
789
+
790
+ let end_of_stream: bool = session.is_body_done();
791
+
792
+ // 4. finally attach the body
793
+ session.write_response_body(Some(body), end_of_stream).await?;
794
+
795
+ Ok(())
796
+ }
797
+
798
+
731
799
  #[cfg(test)]
732
800
  mod tests {
733
801
  use super::*;
@@ -1,13 +1,12 @@
1
1
  #![warn(clippy::all)]
2
2
  use async_trait::async_trait;
3
- use credentials::signer::{signature_is_valid_for_presigned, signature_is_valid_for_request};
3
+ use credentials::signer::{signature_is_valid_for_presigned, signature_is_valid_for_request, wrap_streaming_body};
4
4
  use dotenv::dotenv;
5
5
  use http::Uri;
6
6
  use http::uri::Authority;
7
7
  use parsers::cos_map::{CosMapItem, parse_cos_map};
8
8
  use parsers::keystore::parse_hmac_list;
9
9
  use pingora::http::ResponseHeader;
10
- use pingora::protocols::ALPN;
11
10
  use pingora::Result;
12
11
  use pingora::proxy::{ProxyHttp, Session};
13
12
  use pingora::server::Server;
@@ -15,6 +14,7 @@ use pingora::upstreams::peer::HttpPeer;
15
14
  use pyo3::prelude::*;
16
15
  use pyo3::types::{PyModule, PyModuleMethods};
17
16
  use pyo3::{Bound, PyResult, Python, pyclass, pyfunction, pymodule, wrap_pyfunction};
17
+ use ring::hmac;
18
18
  use std::sync::{
19
19
  Arc,
20
20
  atomic::{AtomicBool, AtomicUsize, Ordering},
@@ -29,6 +29,9 @@ use tracing::{debug, error, info};
29
29
  use tracing_subscriber::EnvFilter;
30
30
  use tracing_subscriber::fmt::time::ChronoLocal;
31
31
 
32
+ use tokio_util::io::StreamReader;
33
+ use futures::TryStreamExt;
34
+
32
35
  pub mod parsers;
33
36
  use parsers::credentials::{parse_presigned_params, parse_token_from_header};
34
37
  use parsers::path::parse_path;
@@ -550,7 +553,7 @@ impl ProxyHttp for MyProxy {
550
553
 
551
554
  async fn upstream_request_filter(
552
555
  &self,
553
- _session: &mut Session,
556
+ session: &mut Session,
554
557
  upstream_request: &mut pingora::http::RequestHeader,
555
558
  ctx: &mut Self::CTX,
556
559
  ) -> Result<()> {
@@ -670,12 +673,54 @@ impl ProxyHttp for MyProxy {
670
673
 
671
674
  if maybe_hmac {
672
675
  debug!("HMAC: Signing request for bucket: {}", hdr_bucket);
673
- sign_request(upstream_request, bucket_config.as_ref().unwrap())
674
- .await
675
- .map_err(|e| {
676
- error!("Failed to sign request for {}: {e}", hdr_bucket);
677
- pingora::Error::new_str("Failed to sign request")
676
+
677
+ let streaming = {
678
+ upstream_request
679
+ .headers
680
+ .get("x-amz-content-sha256")
681
+ .map(|v| v.as_bytes().starts_with(b"STREAMING-AWS4"))
682
+ .unwrap_or(false)
683
+ };
684
+
685
+ if streaming {
686
+ // let auth_header = session
687
+ // .req_header()
688
+ // .headers
689
+ // .get("authorization")
690
+ // .and_then(|h| h.to_str().ok())
691
+ // .map(ToString::to_string)
692
+ // .unwrap_or_default();
693
+
694
+ let access_key= bucket_config.as_ref().unwrap().access_key.as_ref().unwrap_or(&String::new()).to_string();
695
+ let secret_key = bucket_config.as_ref().unwrap()
696
+ .secret_key
697
+ .as_ref()
698
+ .unwrap_or(&String::new())
699
+ .to_string();
700
+
701
+ let region = bucket_config.as_ref().unwrap().region.as_ref().unwrap_or(&String::new()).to_string();
702
+
703
+ wrap_streaming_body(
704
+ session,
705
+ upstream_request,
706
+ &region,
707
+ &access_key,
708
+ &secret_key,
709
+ )
710
+ .await.map_err(|e| {
711
+ error!("Failed to wrap streaming body: {e}");
712
+ pingora::Error::new_str("Failed to wrap streaming body")
678
713
  })?;
714
+ dbg!("streaming signature!!");
715
+ } else {
716
+ sign_request(upstream_request, bucket_config.as_ref().unwrap())
717
+ .await
718
+ .map_err(|e| {
719
+ error!("Failed to sign request for {}: {e}", hdr_bucket);
720
+ pingora::Error::new_str("Failed to sign request")
721
+ })?;
722
+ }
723
+
679
724
  debug!("Request signed for bucket: {}", hdr_bucket);
680
725
  debug!("{:#?}", &upstream_request.headers);
681
726
  } else {