Skip to content

Commit 00e2aca

Browse files
author
Paul C
committed
wolfdisk v2.10.1: configurable S3 region for the gateway (klasSponsor)
The S3-compatible gateway hardcoded its region to us-east-1, returned in the GetBucketLocation LocationConstraint. Some S3 clients and services validate the bucket region and refuse to proceed unless it matches their own configured region, so the region must be settable. - New [s3] region config key, #[serde(default)] -> us-east-1 so existing configs upgrade cleanly (Golden Rule). Threaded through to S3State. - GetBucketLocation now mirrors the AWS spec: an EMPTY LocationConstraint for us-east-1 (the historical default's special case), the region name otherwise. SigV4 auth is unaffected (it verifies against the client's own signed region, not the server's). - 3 config tests cover default, legacy-without-region, and explicit.
1 parent 9c97735 commit 00e2aca

5 files changed

Lines changed: 63 additions & 4 deletions

File tree

wolfdisk/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wolfdisk/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "wolfdisk"
3-
version = "2.10.0"
3+
version = "2.10.1"
44
edition = "2021"
55
authors = ["Wolf Software Systems Ltd"]
66
description = "Distributed file system with replicated and shared storage modes"

wolfdisk/src/config.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,15 @@ pub struct S3Config {
216216
/// Optional secret key for authentication
217217
pub secret_key: Option<String>,
218218

219+
/// AWS region this gateway reports for the bucket (the value returned in
220+
/// the GetBucketLocation `LocationConstraint`). SigV4 signatures are
221+
/// verified against whatever region the client signed with, so this does
222+
/// not affect authentication — but some S3 clients/services validate the
223+
/// bucket's region and refuse to proceed unless it matches their own
224+
/// configured region. Defaults to `us-east-1` (the historical behaviour).
225+
#[serde(default = "default_s3_region")]
226+
pub region: String,
227+
219228
/// Optional bucket → folder mappings, relative to the WolfDisk root.
220229
/// A bucket listed here serves the given folder instead of a same-named
221230
/// top-level directory, e.g. in `[s3.buckets]`:
@@ -235,6 +244,7 @@ impl Default for S3Config {
235244
bind: default_s3_bind(),
236245
access_key: None,
237246
secret_key: None,
247+
region: default_s3_region(),
238248
buckets: std::collections::HashMap::new(),
239249
}
240250
}
@@ -257,6 +267,10 @@ fn default_s3_bind() -> String {
257267
"0.0.0.0:9878".to_string()
258268
}
259269

270+
fn default_s3_region() -> String {
271+
"us-east-1".to_string()
272+
}
273+
260274
impl Default for Config {
261275
fn default() -> Self {
262276
Self {
@@ -396,3 +410,29 @@ fn copy_dir_files(src: &Path, dst: &Path) -> std::io::Result<()> {
396410
}
397411
Ok(())
398412
}
413+
414+
#[cfg(test)]
415+
mod tests {
416+
use super::*;
417+
418+
#[test]
419+
fn s3_config_default_region_is_us_east_1() {
420+
assert_eq!(S3Config::default().region, "us-east-1");
421+
}
422+
423+
#[test]
424+
fn s3_config_without_region_defaults_for_existing_installs() {
425+
// Golden Rule: an existing [s3] config written before the region field
426+
// existed must still deserialize, falling back to us-east-1.
427+
let s3: S3Config = toml::from_str("enabled = true\nbind = \"0.0.0.0:9878\"\n")
428+
.expect("legacy s3 config without region should parse");
429+
assert_eq!(s3.region, "us-east-1");
430+
}
431+
432+
#[test]
433+
fn s3_config_honours_explicit_region() {
434+
let s3: S3Config = toml::from_str("enabled = true\nregion = \"eu-west-1\"\n")
435+
.expect("s3 config with region should parse");
436+
assert_eq!(s3.region, "eu-west-1");
437+
}
438+
}

wolfdisk/src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2332,6 +2332,7 @@ fn main() {
23322332
let s3_next_inode = next_inode.clone();
23332333
let s3_bind = config.s3.bind.clone();
23342334
let s3_credentials = config.s3.credentials();
2335+
let s3_region = config.s3.region.clone();
23352336
let s3_meta_path = wolfdisk::s3::meta::meta_path(&config.index_dir());
23362337
let s3_buckets = config.s3.buckets.clone();
23372338

@@ -2350,6 +2351,7 @@ fn main() {
23502351
s3_inode_table,
23512352
s3_next_inode,
23522353
s3_credentials,
2354+
s3_region,
23532355
s3_meta_path,
23542356
s3_buckets,
23552357
);

wolfdisk/src/s3/server.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,17 +83,25 @@ impl S3Server {
8383
inode_table: Arc<RwLock<InodeTable>>,
8484
next_inode: Arc<RwLock<u64>>,
8585
credentials: Option<S3Credentials>,
86+
region: String,
8687
meta_path: PathBuf,
8788
bucket_mappings: HashMap<String, String>,
8889
) -> Self {
8990
let meta = S3MetaStore::load(&meta_path);
91+
// An empty region in the config falls back to the historical default so
92+
// GetBucketLocation never returns a blank LocationConstraint.
93+
let region = if region.trim().is_empty() {
94+
"us-east-1".to_string()
95+
} else {
96+
region
97+
};
9098
let state = S3State {
9199
file_index,
92100
chunk_store,
93101
inode_table,
94102
next_inode,
95103
credentials,
96-
region: "us-east-1".to_string(),
104+
region,
97105
multipart: Arc::new(RwLock::new(HashMap::new())),
98106
meta: Arc::new(RwLock::new(meta)),
99107
meta_path,
@@ -336,10 +344,19 @@ async fn list_buckets(state: S3State) -> Response {
336344

337345
/// GET /bucket?location → GetBucketLocation
338346
async fn get_bucket_location(state: S3State) -> Response {
347+
// AWS reports us-east-1 buckets with an EMPTY LocationConstraint (the
348+
// historical default region's special case); every other region returns
349+
// its own name. Strict AWS SDKs validate the bucket region against this,
350+
// so mirror the spec exactly rather than emitting the literal "us-east-1".
351+
let body = if state.region == "us-east-1" {
352+
String::new()
353+
} else {
354+
xml_escape(&state.region)
355+
};
339356
let xml = format!(
340357
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
341358
<LocationConstraint xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">{}</LocationConstraint>",
342-
state.region
359+
body
343360
);
344361
(
345362
StatusCode::OK,

0 commit comments

Comments
 (0)