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.
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/.gitignore +2 -1
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/Cargo.lock +1 -1
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/Cargo.toml +1 -1
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/PKG-INFO +1 -1
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/credentials/signer.rs +117 -33
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/lib.rs +11 -3
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/test_server.py +1 -1
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/.cargo/config.toml +0 -0
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/.github/workflows/ci.yml +0 -0
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/LICENSE +0 -0
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/README.md +0 -0
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/img/logo.svg +0 -0
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/img/request_lifecycle.svg +0 -0
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/img/request_stages.svg +0 -0
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/pyproject.toml +0 -0
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/requirements.txt +0 -0
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/credentials/hmac_keystore.rs +0 -0
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/credentials/mod.rs +0 -0
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/credentials/models.rs +0 -0
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/credentials/secrets_proxy.rs +0 -0
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/object_storage_proxy.pyi +0 -0
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/parsers/cos_map.rs +0 -0
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/parsers/credentials.rs +0 -0
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/parsers/keystore.rs +0 -0
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/parsers/mod.rs +0 -0
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/parsers/path.rs +0 -0
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/utils/mod.rs +0 -0
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/src/utils/validator.rs +0 -0
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/test_integration.sh +0 -0
- {object_storage_proxy-0.3.4 → object_storage_proxy-0.3.6}/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,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
|
-
|
|
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
|
-
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
|
-
|
|
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
|
-
|
|
351
|
-
|
|
352
|
-
} else {
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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(&
|
|
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 =
|
|
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
|
-
"
|
|
638
|
-
"
|
|
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
|
|
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
|