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.
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/.gitignore +2 -1
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/Cargo.lock +1 -1
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/Cargo.toml +1 -1
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/PKG-INFO +1 -1
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/credentials/signer.rs +119 -34
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/lib.rs +10 -3
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/test_server.py +1 -1
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/.cargo/config.toml +0 -0
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/.github/workflows/ci.yml +0 -0
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/LICENSE +0 -0
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/README.md +0 -0
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/img/logo.svg +0 -0
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/img/request_lifecycle.svg +0 -0
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/img/request_stages.svg +0 -0
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/pyproject.toml +0 -0
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/requirements.txt +0 -0
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/credentials/hmac_keystore.rs +0 -0
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/credentials/mod.rs +0 -0
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/credentials/models.rs +0 -0
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/credentials/secrets_proxy.rs +0 -0
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/object_storage_proxy.pyi +0 -0
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/parsers/cos_map.rs +0 -0
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/parsers/credentials.rs +0 -0
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/parsers/keystore.rs +0 -0
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/parsers/mod.rs +0 -0
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/parsers/path.rs +0 -0
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/utils/mod.rs +0 -0
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/src/utils/validator.rs +0 -0
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/test_integration.sh +0 -0
- {object_storage_proxy-0.3.5 → object_storage_proxy-0.3.7}/uv.lock +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
99
|
-
} else {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
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
|
-
|
|
352
|
-
|
|
353
|
-
} else {
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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(&
|
|
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 =
|
|
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
|
-
"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|