From 29c004d9352a9d42935726d2e0d8b5ccd771c44a Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 13 Sep 2025 14:48:14 +0800 Subject: [PATCH] feat: enhance console separation with enterprise-grade security, monitoring, and advanced tower-http integration (#513) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial plan * feat: implement console service separation from endpoint Co-authored-by: houseme <4829346+houseme@users.noreply.github.com> * feat: add console separation documentation and tests Co-authored-by: houseme <4829346+houseme@users.noreply.github.com> * feat: enhance console separation with configurable CORS and improved Docker support Co-authored-by: houseme <4829346+houseme@users.noreply.github.com> * feat: implement enhanced console separation with security hardening and monitoring Co-authored-by: houseme <4829346+houseme@users.noreply.github.com> * refactor: implement console TLS following endpoint logic and improve configuration Co-authored-by: houseme <4829346+houseme@users.noreply.github.com> * add tower-http feature "timeout|limit" * add dependencies crates `axum-server` * refactor: reconstruct console server with enhanced tower-http features and environment variables Co-authored-by: houseme <4829346+houseme@users.noreply.github.com> * upgrade dep * improve code for dns and console port `:9001` * improve code * fix * docs: comprehensive improvement of console separation documentation and Docker deployment standards Co-authored-by: houseme <4829346+houseme@users.noreply.github.com> * fmt * add logs * improve code for Config handler * remove logs * fix --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: houseme <4829346+houseme@users.noreply.github.com> Co-authored-by: houseme --- .gitignore | 4 +- Cargo.lock | 574 +++++---- Cargo.toml | 31 +- Dockerfile | 6 +- crates/ahm/Cargo.toml | 2 +- crates/config/src/constants/app.rs | 12 +- crates/config/src/constants/console.rs | 81 ++ crates/config/src/constants/mod.rs | 7 +- crates/config/src/lib.rs | 2 + crates/ecstore/src/endpoints.rs | 27 +- crates/ecstore/src/global.rs | 57 +- crates/lock/Cargo.toml | 10 +- crates/mcp/src/server.rs | 1 + crates/targets/src/target/mqtt.rs | 2 +- crates/utils/Cargo.toml | 2 +- crates/utils/src/dns_resolver.rs | 9 +- crates/utils/src/net.rs | 122 +- docker-compose.yml | 30 +- docs/console-separation.md | 1362 ++++++++++++++++++++++ examples/README.md | 270 +++++ examples/docker-comprehensive.yml | 224 ++++ examples/docker-quickstart.sh | 295 +++++ examples/enhanced-docker-deployment.sh | 321 +++++ examples/enhanced-security-deployment.sh | 207 ++++ rustfs/Cargo.toml | 4 + rustfs/src/admin/console.rs | 485 ++++---- rustfs/src/admin/handlers.rs | 22 + rustfs/src/admin/mod.rs | 6 +- rustfs/src/config/config_test.rs | 79 ++ rustfs/src/config/mod.rs | 25 + rustfs/src/main.rs | 71 +- rustfs/src/server/console.rs | 398 +++++++ rustfs/src/server/console_test.rs | 146 +++ rustfs/src/server/http.rs | 106 +- rustfs/src/server/mod.rs | 5 + rustfs/src/server/service_state.rs | 14 +- scripts/run.sh | 3 +- 37 files changed, 4327 insertions(+), 695 deletions(-) create mode 100644 crates/config/src/constants/console.rs create mode 100644 docs/console-separation.md create mode 100644 examples/README.md create mode 100644 examples/docker-comprehensive.yml create mode 100755 examples/docker-quickstart.sh create mode 100755 examples/enhanced-docker-deployment.sh create mode 100755 examples/enhanced-security-deployment.sh create mode 100644 rustfs/src/config/config_test.rs create mode 100644 rustfs/src/server/console.rs create mode 100644 rustfs/src/server/console_test.rs diff --git a/.gitignore b/.gitignore index cb7502bf..ed447fcc 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,6 @@ profile.json .docker/openobserve-otel/data *.zst .secrets -*.go \ No newline at end of file +*.go +*.pb +*.svg \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index e1b1eeef..7d618854 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,12 +105,6 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -601,9 +595,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.13.3" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" +checksum = "94b8ff6c09cd57b16da53641caa860168b88c172a5ee163b0288d3d6eea12786" dependencies = [ "aws-lc-sys", "zeroize", @@ -611,9 +605,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" +checksum = "0e44d16778acaf6a9ec9899b92cebd65580b83f685446bf2e1f5d3d732f99dcd" dependencies = [ "bindgen", "cc", @@ -899,9 +893,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3946acbe1ead1301ba6862e712c7903ca9bb230bdf1fbd1b5ac54158ef2ab1f" +checksum = "4fa63ad37685ceb7762fa4d73d06f1d5493feb88e3f27259b9ed277f4c01b185" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -1041,6 +1035,50 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-extra" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45bf463831f5131b7d3c756525b305d40f1185b688565648a92e1392ca35713d" +dependencies = [ + "axum", + "axum-core", + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "serde", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-server" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "495c05f60d6df0093e8fb6e74aa5846a0ad06abaf96d76166283720bf740f8ab" +dependencies = [ + "arc-swap", + "bytes", + "fs-err", + "http 1.3.1", + "http-body 1.0.1", + "hyper 1.7.0", + "hyper-util", + "pin-project-lite", + "rustls 0.23.31", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.2", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.75" @@ -1105,25 +1143,22 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.69.5" +version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "cexpr", "clang-sys", - "itertools 0.12.1", - "lazy_static", - "lazycell", + "itertools 0.13.0", "log", "prettyplease", "proc-macro2", "quote", "regex", - "rustc-hash 1.1.0", + "rustc-hash", "shlex", "syn 2.0.106", - "which", ] [[package]] @@ -1134,9 +1169,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.3" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "blake2" @@ -1171,9 +1206,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.11.0-rc.4" +version = "0.11.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a229bfd78e4827c91b9b95784f69492c1b77c1ab75a45a8a037b139215086f94" +checksum = "e9ef36a6fcdb072aa548f3da057640ec10859eb4e91ddf526ee648d50c76a949" dependencies = [ "hybrid-array", ] @@ -1348,10 +1383,11 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.34" +version = "1.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" +checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -1404,17 +1440,16 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", - "windows-link", + "windows-link 0.2.0", ] [[package]] @@ -1478,9 +1513,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.46" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57" +checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" dependencies = [ "clap_builder", "clap_derive", @@ -1488,9 +1523,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.46" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41" +checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" dependencies = [ "anstream", "anstyle", @@ -1500,9 +1535,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.45" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" +checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" dependencies = [ "heck", "proc-macro2", @@ -1533,9 +1568,9 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "comfy-table" -version = "7.2.0" +version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f8e18d0dca9578507f13f9803add0df13362b02c501c1c17734f0dbb52eaf0b" +checksum = "b03b7db8e0b4b2fdad6c551e634134e99ec000e5c8c3b6856c65e8bbaded7a3b" dependencies = [ "unicode-segmentation", "unicode-width", @@ -1587,15 +1622,21 @@ name = "const-str" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "451d0640545a0553814b4c646eb549343561618838e9b42495f466131fe3ad49" + +[[package]] +name = "const-str" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4d34b8f066904ed7cfa4a6f9ee96c3214aa998cb44b69ca20bd2054f47402ed" dependencies = [ "const-str-proc-macro", ] [[package]] name = "const-str-proc-macro" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95013972663dd72254b963e48857284080001ffee418731f065fcf5290a5530d" +checksum = "a08a8aee16926ee1c4ad18868b8c3dfe5106359053f91e035861ec2a17116988" dependencies = [ "proc-macro2", "quote", @@ -1851,9 +1892,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.0-rc.3" +version = "0.2.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a23fa214dea9efd4dacee5a5614646b30216ae0f05d4bb51bafb50e9da1c5be" +checksum = "6a8235645834fbc6832939736ce2f2d08192652269e11010a6240f61b908a1c6" dependencies = [ "hybrid-array", ] @@ -2495,9 +2536,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75d7cc94194b4dd0fa12845ef8c911101b7f37633cda14997a6e82099aa0b693" +checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" dependencies = [ "powerfmt", "serde", @@ -2569,9 +2610,9 @@ version = "0.11.0-pre.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c478574b20020306f98d61c8ca3322d762e1ff08117422ac6106438605ea516" dependencies = [ - "block-buffer 0.11.0-rc.4", + "block-buffer 0.11.0-rc.5", "const-oid 0.10.1", - "crypto-common 0.2.0-rc.3", + "crypto-common 0.2.0-rc.4", "subtle", ] @@ -2593,7 +2634,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -2639,7 +2680,7 @@ dependencies = [ "serde_json", "serial_test", "tokio", - "tonic 0.14.1", + "tonic 0.14.2", "url", ] @@ -2761,12 +2802,12 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -2832,6 +2873,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" + [[package]] name = "findshlibs" version = "0.10.2" @@ -2872,7 +2919,7 @@ version = "25.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1045398c1bfd89168b5fd3f1fc11f6e70b34f6f66300c87d44d3de849463abf1" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "rustc_version", ] @@ -2939,6 +2986,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs-err" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d7be93788013f265201256d58f04936a8079ad5dc898743aa20525f503b683" +dependencies = [ + "autocfg", + "tokio", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -3054,7 +3111,7 @@ dependencies = [ "libc", "log", "rustversion", - "windows 0.61.3", + "windows", ] [[package]] @@ -3090,7 +3147,7 @@ dependencies = [ "js-sys", "libc", "r-efi", - "wasi 0.14.3+wasi-0.2.4", + "wasi 0.14.5+wasi-0.2.4", "wasm-bindgen", ] @@ -3208,9 +3265,9 @@ dependencies = [ [[package]] name = "heapless" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +checksum = "b1edcd5a338e64688fbdcb7531a846cfd3476a54784dcb918a0844682bc7ada5" dependencies = [ "hash32", "stable_deref_trait", @@ -3398,15 +3455,15 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" [[package]] name = "hybrid-array" -version = "0.3.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d15931895091dea5c47afa5b3c9a01ba634b311919fd4d41388fa0e3d76af" +checksum = "c7116c472cf19838450b1d421b4e842569f52b519d640aee9ace1ebcf5b21051" dependencies = [ "typenum", ] @@ -3544,7 +3601,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.2", + "windows-core", ] [[package]] @@ -3671,9 +3728,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" +checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921" dependencies = [ "equivalent", "hashbrown 0.15.5", @@ -3709,7 +3766,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "inotify-sys", "libc", ] @@ -3744,7 +3801,7 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "cfg-if", "libc", ] @@ -3809,15 +3866,6 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.13.0" @@ -3854,9 +3902,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738" dependencies = [ "once_cell", "wasm-bindgen", @@ -3906,12 +3954,6 @@ dependencies = [ "spin 0.9.8", ] -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "lexical-core" version = "1.0.5" @@ -4004,7 +4046,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "libc", "redox_syscall 0.5.17", ] @@ -4047,9 +4089,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" @@ -4081,9 +4123,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" dependencies = [ "serde", "value-bag", @@ -4142,7 +4184,7 @@ version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" dependencies = [ - "twox-hash 2.1.1", + "twox-hash 2.1.2", ] [[package]] @@ -4344,7 +4386,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "cfg-if", "cfg_aliases", "libc", @@ -4357,7 +4399,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "cfg-if", "cfg_aliases", "libc", @@ -4388,7 +4430,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "fsevent-sys", "inotify", "kqueue", @@ -4605,7 +4647,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d5c6c0ef9702176a570f06ad94f3198bc29c524c8b498f1b9346e1b1bdcbb3a" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "libloading", "nvml-wrapper-sys", "static_assertions", @@ -4628,7 +4670,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", ] [[package]] @@ -5389,7 +5431,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "memchr", "unicase", ] @@ -5444,7 +5486,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.1", + "rustc-hash", "rustls 0.23.31", "socket2 0.6.0", "thiserror 2.0.16", @@ -5464,7 +5506,7 @@ dependencies = [ "lru-slab", "rand 0.9.2", "ring", - "rustc-hash 2.1.1", + "rustc-hash", "rustls 0.23.31", "rustls-pki-types", "slab", @@ -5653,7 +5695,7 @@ version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", ] [[package]] @@ -5819,9 +5861,9 @@ dependencies = [ [[package]] name = "rmcp" -version = "0.6.1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f521fbd040eba82684b17d787d423f43afb6e97974029b51f679157a589592a" +checksum = "41ab0892f4938752b34ae47cb53910b1b0921e55e77ddb6e44df666cab17939f" dependencies = [ "base64 0.22.1", "chrono", @@ -5840,9 +5882,9 @@ dependencies = [ [[package]] name = "rmcp-macros" -version = "0.6.1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c162bf8a2846f70464ded6dda6430b60d1e2fb4b0e371f0906e39f63916641b9" +checksum = "1827cd98dab34cade0513243c6fe0351f0f0b2c9d6825460bcf45b42804bdda0" dependencies = [ "darling 0.21.3", "proc-macro2", @@ -5895,20 +5937,23 @@ dependencies = [ [[package]] name = "rumqttc" -version = "0.24.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1568e15fab2d546f940ed3a21f48bbbd1c494c90c99c4481339364a497f94a9" +checksum = "128f632072dc89ced3359668399026d90eadc06c65c807c298d15ff3d1eacf63" dependencies = [ "bytes", + "fixedbitset 0.5.7", "flume", "futures-util", "log", - "rustls-native-certs 0.7.3", + "rustls-native-certs 0.8.1", "rustls-pemfile 2.2.0", "rustls-webpki 0.102.8", - "thiserror 1.0.69", + "thiserror 2.0.16", "tokio", - "tokio-rustls 0.25.0", + "tokio-rustls 0.26.2", + "tokio-stream", + "tokio-util", ] [[package]] @@ -5952,12 +5997,6 @@ version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc-hash" version = "2.1.1" @@ -5981,10 +6020,12 @@ dependencies = [ "atoi", "atomic_enum", "axum", + "axum-extra", + "axum-server", "bytes", "chrono", "clap", - "const-str", + "const-str 0.7.0", "datafusion", "futures", "http 1.3.1", @@ -6035,7 +6076,7 @@ dependencies = [ "tokio-stream", "tokio-tar", "tokio-util", - "tonic 0.14.1", + "tonic 0.14.2", "tower", "tower-http", "tracing", @@ -6067,7 +6108,7 @@ dependencies = [ "serde", "serde_json", "serial_test", - "sysinfo 0.30.13", + "sysinfo", "tempfile", "thiserror 2.0.16", "time", @@ -6137,7 +6178,7 @@ dependencies = [ "s3s", "serde", "tokio", - "tonic 0.14.1", + "tonic 0.14.2", "uuid", ] @@ -6145,7 +6186,7 @@ dependencies = [ name = "rustfs-config" version = "0.0.5" dependencies = [ - "const-str", + "const-str 0.7.0", ] [[package]] @@ -6235,7 +6276,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-util", - "tonic 0.14.1", + "tonic 0.14.2", "tower", "tracing", "url", @@ -6307,7 +6348,7 @@ dependencies = [ "smartstring", "thiserror 2.0.16", "tokio", - "tonic 0.14.1", + "tonic 0.14.2", "tracing", "url", "uuid", @@ -6391,7 +6432,7 @@ dependencies = [ "serde", "serde_json", "smallvec", - "sysinfo 0.37.0", + "sysinfo", "thiserror 2.0.16", "tokio", "tracing", @@ -6427,7 +6468,7 @@ dependencies = [ "flatbuffers 25.2.10", "prost 0.14.1", "rustfs-common", - "tonic 0.14.1", + "tonic 0.14.2", "tonic-prost", "tonic-prost-build", ] @@ -6591,7 +6632,7 @@ dependencies = [ "sha2 0.10.9", "siphasher", "snap", - "sysinfo 0.37.0", + "sysinfo", "tempfile", "thiserror 2.0.16", "tokio", @@ -6626,7 +6667,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "errno", "libc", "linux-raw-sys 0.4.15", @@ -6635,15 +6676,15 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "errno", "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.60.2", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.0", ] [[package]] @@ -6658,20 +6699,6 @@ dependencies = [ "sct", ] -[[package]] -name = "rustls" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" -dependencies = [ - "log", - "ring", - "rustls-pki-types", - "rustls-webpki 0.102.8", - "subtle", - "zeroize", -] - [[package]] name = "rustls" version = "0.23.31" @@ -6683,7 +6710,7 @@ dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.4", + "rustls-webpki 0.103.5", "subtle", "zeroize", ] @@ -6700,19 +6727,6 @@ dependencies = [ "security-framework 2.11.1", ] -[[package]] -name = "rustls-native-certs" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" -dependencies = [ - "openssl-probe", - "rustls-pemfile 2.2.0", - "rustls-pki-types", - "schannel", - "security-framework 2.11.1", -] - [[package]] name = "rustls-native-certs" version = "0.8.1" @@ -6722,7 +6736,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.3.0", + "security-framework 3.4.0", ] [[package]] @@ -6776,9 +6790,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.4" +version = "0.103.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +checksum = "b5a37813727b78798e53c2bec3f5e8fe12a6d6f8389bf9ca7802add4c9905ad8" dependencies = [ "aws-lc-rs", "ring", @@ -6811,7 +6825,7 @@ dependencies = [ "bytes", "bytestring", "chrono", - "const-str", + "const-str 0.6.4", "crc32c", "crc32fast", "crc64fast-nvme", @@ -6869,11 +6883,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.0", ] [[package]] @@ -6950,7 +6964,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -6959,11 +6973,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c" +checksum = "60b369d18893388b345804dc0007963c99b7d665ae71d275812d828c6f089640" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -6972,9 +6986,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ "core-foundation-sys", "libc", @@ -7305,9 +7319,9 @@ dependencies = [ [[package]] name = "snafu" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4800ae0e2ebdfaea32ffb9745642acdc378740dcbd74d3fb3cd87572a34810c6" +checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" dependencies = [ "backtrace", "snafu-derive", @@ -7315,9 +7329,9 @@ dependencies = [ [[package]] name = "snafu-derive" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186f5ba9999528053fb497fdf0dd330efcc69cfe4ad03776c9d704bc54fee10f" +checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" dependencies = [ "heck", "proc-macro2", @@ -7634,7 +7648,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "byteorder", "enum-as-inner", "libc", @@ -7642,21 +7656,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "sysinfo" -version = "0.30.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3" -dependencies = [ - "cfg-if", - "core-foundation-sys", - "libc", - "ntapi", - "once_cell", - "rayon", - "windows 0.52.0", -] - [[package]] name = "sysinfo" version = "0.37.0" @@ -7668,7 +7667,7 @@ dependencies = [ "ntapi", "objc2-core-foundation", "objc2-io-kit", - "windows 0.61.3", + "windows", ] [[package]] @@ -7677,7 +7676,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.9.4", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -7709,15 +7708,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.21.0" +version = "3.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" +checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", - "rustix 1.0.8", - "windows-sys 0.60.2", + "rustix 1.1.2", + "windows-sys 0.61.0", ] [[package]] @@ -7835,9 +7834,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.42" +version = "0.3.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca967379f9d8eb8058d86ed467d81d03e81acd45757e4ca341c24affbe8e8e3" +checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031" dependencies = [ "deranged", "libc", @@ -7851,15 +7850,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9108bb380861b07264b950ded55a44a14a4adc68b9f5efd85aafc3aa4d40a68" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7182799245a7264ce590b349d90338f1c1affad93d2639aed5f8f69c090b334c" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", @@ -7950,17 +7949,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" -dependencies = [ - "rustls 0.22.4", - "rustls-pki-types", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.2" @@ -8094,9 +8082,9 @@ dependencies = [ [[package]] name = "tonic" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ac5a8627ada0968acec063a4746bf79588aa03ccb66db2f75d7dce26722a40" +checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203" dependencies = [ "async-trait", "axum", @@ -8124,9 +8112,9 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49e323d8bba3be30833707e36d046deabf10a35ae8ad3cae576943ea8933e25d" +checksum = "4c40aaccc9f9eccf2cd82ebc111adc13030d23e887244bc9cfa5d1d636049de3" dependencies = [ "prettyplease", "proc-macro2", @@ -8136,20 +8124,20 @@ dependencies = [ [[package]] name = "tonic-prost" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9c511b9a96d40cb12b7d5d00464446acf3b9105fd3ce25437cfe41c92b1c87d" +checksum = "66bd50ad6ce1252d87ef024b3d64fe4c3cf54a86fb9ef4c631fdd0ded7aeaa67" dependencies = [ "bytes", "prost 0.14.1", - "tonic 0.14.1", + "tonic 0.14.2", ] [[package]] name = "tonic-prost-build" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ef298fcd01b15e135440c4b8c974460ceca4e6a5af7f1c933b08e4d2875efa1" +checksum = "b4a16cba4043dc3ff43fcb3f96b4c5c154c64cbd18ca8dce2ab2c6a451d058a2" dependencies = [ "prettyplease", "proc-macro2", @@ -8187,7 +8175,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ "async-compression", - "bitflags 2.9.3", + "bitflags 2.9.4", "bytes", "futures-core", "futures-util", @@ -8347,9 +8335,9 @@ dependencies = [ [[package]] name = "twox-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b907da542cbced5261bd3256de1b3a1bf340a3d37f93425a07362a1d687de56" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" [[package]] name = "typeid" @@ -8406,9 +8394,9 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-segmentation" @@ -8476,9 +8464,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.18.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ "getrandom 0.3.3", "js-sys", @@ -8490,9 +8478,9 @@ dependencies = [ [[package]] name = "uuid-macro-internal" -version = "1.18.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22b7ad00068276db5fea436dba78daa7891b8d60db76e4f51cbdefbdecdab97e" +checksum = "d9384a660318abfbd7f8932c34d67e4d1ec511095f95972ddc01e19d7ba8413f" dependencies = [ "proc-macro2", "quote", @@ -8586,30 +8574,40 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.3+wasi-0.2.4" +version = "0.14.5+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95" +checksum = "a4494f6290a82f5fe584817a676a34b9d6763e8d9d18204009fb31dceca98fd4" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.0+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03fa2761397e5bd52002cd7e73110c71af2109aca4e521a9f40473fe685b0a24" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.100" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb" dependencies = [ "bumpalo", "log", @@ -8621,9 +8619,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "0ca85039a9b469b38336411d6d6ced91f3fc87109a2a27b0c197663f5144dffe" dependencies = [ "cfg-if", "js-sys", @@ -8634,9 +8632,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -8644,9 +8642,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa" dependencies = [ "proc-macro2", "quote", @@ -8657,9 +8655,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1" dependencies = [ "unicode-ident", ] @@ -8679,9 +8677,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "77e4b637749ff0d92b8fad63aa1f7cff3cbe125fd49c175cd6345e7272638b12" dependencies = [ "js-sys", "wasm-bindgen", @@ -8751,11 +8749,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -8764,16 +8762,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" -dependencies = [ - "windows-core 0.52.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows" version = "0.61.3" @@ -8781,9 +8769,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ "windows-collections", - "windows-core 0.61.2", + "windows-core", "windows-future", - "windows-link", + "windows-link 0.1.3", "windows-numerics", ] @@ -8793,16 +8781,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core 0.61.2", -] - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", + "windows-core", ] [[package]] @@ -8813,7 +8792,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", - "windows-link", + "windows-link 0.1.3", "windows-result", "windows-strings", ] @@ -8824,8 +8803,8 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core 0.61.2", - "windows-link", + "windows-core", + "windows-link 0.1.3", "windows-threading", ] @@ -8857,14 +8836,20 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + [[package]] name = "windows-numerics" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core 0.61.2", - "windows-link", + "windows-core", + "windows-link 0.1.3", ] [[package]] @@ -8873,7 +8858,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ - "windows-link", + "windows-link 0.1.3", "windows-result", "windows-strings", ] @@ -8884,7 +8869,7 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -8893,7 +8878,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -8932,6 +8917,15 @@ dependencies = [ "windows-targets 0.53.3", ] +[[package]] +name = "windows-sys" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +dependencies = [ + "windows-link 0.2.0", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -8969,7 +8963,7 @@ version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ - "windows-link", + "windows-link 0.1.3", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -8986,7 +8980,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -9148,9 +9142,9 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.45.0" +version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" +checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" [[package]] name = "wrapcenum-derive" @@ -9177,7 +9171,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" dependencies = [ "libc", - "rustix 1.0.8", + "rustix 1.1.2", ] [[package]] @@ -9239,18 +9233,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", @@ -9393,9 +9387,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.15+zstd.1.5.7" +version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index cbda4991..412062d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,6 +100,8 @@ atomic_enum = "0.3.0" aws-config = { version = "1.8.6" } aws-sdk-s3 = "1.101.0" axum = "0.8.4" +axum-extra = "0.10.1" +axum-server = "0.7.2" base64-simd = "0.8.0" base64 = "0.22.1" brotli = "8.0.2" @@ -109,11 +111,12 @@ byteorder = "1.5.0" cfg-if = "1.0.3" crc-fast = "1.5.0" chacha20poly1305 = { version = "0.10.1" } -chrono = { version = "0.4.41", features = ["serde"] } -clap = { version = "4.5.46", features = ["derive", "env"] } -const-str = { version = "0.6.4", features = ["std", "proc"] } +chrono = { version = "0.4.42", features = ["serde"] } +clap = { version = "4.5.47", features = ["derive", "env"] } +const-str = { version = "0.7.0", features = ["std", "proc"] } crc32fast = "1.5.0" criterion = { version = "0.7", features = ["html_reports"] } +crossbeam-queue = "0.3.12" dashmap = "6.1.0" datafusion = "46.0.1" derive_builder = "0.20.2" @@ -126,6 +129,7 @@ futures = "0.3.31" futures-core = "0.3.31" futures-util = "0.3.31" glob = "0.3.3" +heapless = "0.9.1" hex = "0.4.3" hex-simd = "0.8.0" highway = { version = "1.3.0" } @@ -141,7 +145,7 @@ hyper-util = { version = "0.1.16", features = [ hyper-rustls = "0.27.7" http = "1.3.1" http-body = "1.0.1" -humantime = "2.2.0" +humantime = "2.3.0" ipnetwork = { version = "0.21.1", features = ["serde"] } jsonwebtoken = "9.3.1" lazy_static = "1.5.0" @@ -196,11 +200,11 @@ reqwest = { version = "0.12.23", default-features = false, features = [ "json", "blocking", ] } -rmcp = { version = "0.6.1" } +rmcp = { version = "0.6.4" } rmp = "0.8.14" rmp-serde = "1.3.0" rsa = "0.9.8" -rumqttc = { version = "0.24" } +rumqttc = { version = "0.25.0" } rust-embed = { version = "8.7.2" } rustfs-rsc = "2025.506.1" rustls = { version = "0.23.31" } @@ -217,17 +221,18 @@ sha2 = "0.10.9" shadow-rs = { version = "1.3.0", default-features = false } siphasher = "1.0.1" smallvec = { version = "1.15.1", features = ["serde"] } -snafu = "0.8.8" +smartstring = "1.0.1" +snafu = "0.8.9" snap = "1.1.1" socket2 = "0.6.0" strum = { version = "0.27.2", features = ["derive"] } sysinfo = "0.37.0" sysctl = "0.6.0" -tempfile = "3.21.0" +tempfile = "3.22.0" temp-env = "0.3.6" test-case = "3.3.1" thiserror = "2.0.16" -time = { version = "0.3.42", features = [ +time = { version = "0.3.43", features = [ "std", "parsing", "formatting", @@ -240,9 +245,9 @@ tokio-stream = { version = "0.1.17" } tokio-tar = "0.3.1" tokio-test = "0.4.4" tokio-util = { version = "0.7.16", features = ["io", "compat"] } -tonic = { version = "0.14.1", features = ["gzip"] } -tonic-prost = { version = "0.14.1" } -tonic-prost-build = { version = "0.14.1" } +tonic = { version = "0.14.2", features = ["gzip"] } +tonic-prost = { version = "0.14.2" } +tonic-prost-build = { version = "0.14.2" } tower = { version = "0.5.2", features = ["timeout"] } tower-http = { version = "0.6.6", features = ["cors"] } tracing = "0.1.41" @@ -253,7 +258,7 @@ tracing-subscriber = { version = "0.3.20", features = ["env-filter", "time"] } transform-stream = "0.3.1" url = "2.5.7" urlencoding = "2.1.3" -uuid = { version = "1.18.0", features = [ +uuid = { version = "1.18.1", features = [ "v4", "fast-rng", "macro-diagnostics", diff --git a/Dockerfile b/Dockerfile index c8f1f14d..e57acc7b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -69,15 +69,19 @@ RUN chmod +x /usr/bin/rustfs /entrypoint.sh && \ chmod 0750 /data /logs ENV RUSTFS_ADDRESS=":9000" \ + RUSTFS_CONSOLE_ADDRESS=":9001" \ RUSTFS_ACCESS_KEY="rustfsadmin" \ RUSTFS_SECRET_KEY="rustfsadmin" \ RUSTFS_CONSOLE_ENABLE="true" \ + RUSTFS_EXTERNAL_ADDRESS="" \ + RUSTFS_CORS_ALLOWED_ORIGINS="*" \ + RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="*" \ RUSTFS_VOLUMES="/data" \ RUST_LOG="warn" \ RUSTFS_OBS_LOG_DIRECTORY="/logs" \ RUSTFS_SINKS_FILE_PATH="/logs" -EXPOSE 9000 +EXPOSE 9000 9001 VOLUME ["/data", "/logs"] ENTRYPOINT ["/entrypoint.sh"] diff --git a/crates/ahm/Cargo.toml b/crates/ahm/Cargo.toml index aea00f35..59bb66ba 100644 --- a/crates/ahm/Cargo.toml +++ b/crates/ahm/Cargo.toml @@ -45,4 +45,4 @@ tracing-subscriber = { workspace = true } walkdir = "2.5.0" tempfile = { workspace = true } criterion = { workspace = true, features = ["html_reports"] } -sysinfo = "0.30.8" +sysinfo = { workspace = true } diff --git a/crates/config/src/constants/app.rs b/crates/config/src/constants/app.rs index 3c32276e..00917d42 100644 --- a/crates/config/src/constants/app.rs +++ b/crates/config/src/constants/app.rs @@ -124,7 +124,7 @@ pub const DEFAULT_LOG_FILENAME: &str = "rustfs"; /// This is the default log filename for OBS. /// It is used to store the logs of the application. /// Default value: rustfs.log -pub const DEFAULT_OBS_LOG_FILENAME: &str = concat!(DEFAULT_LOG_FILENAME, "."); +pub const DEFAULT_OBS_LOG_FILENAME: &str = concat!(DEFAULT_LOG_FILENAME, ""); /// Default sink file log file for rustfs /// This is the default sink file log file for rustfs. @@ -160,6 +160,16 @@ pub const DEFAULT_LOG_ROTATION_TIME: &str = "day"; /// Environment variable: RUSTFS_OBS_LOG_KEEP_FILES pub const DEFAULT_LOG_KEEP_FILES: u16 = 30; +/// This is the external address for rustfs to access endpoint (used in Docker deployments). +/// This should match the mapped host port when using Docker port mapping. +/// Example: ":9020" when mapping host port 9020 to container port 9000. +/// Default value: DEFAULT_ADDRESS +/// Environment variable: RUSTFS_EXTERNAL_ADDRESS +/// Command line argument: --external-address +/// Example: RUSTFS_EXTERNAL_ADDRESS=":9020" +/// Example: --external-address ":9020" +pub const ENV_EXTERNAL_ADDRESS: &str = "RUSTFS_EXTERNAL_ADDRESS"; + #[cfg(test)] mod tests { use super::*; diff --git a/crates/config/src/constants/console.rs b/crates/config/src/constants/console.rs new file mode 100644 index 00000000..4d2b0a84 --- /dev/null +++ b/crates/config/src/constants/console.rs @@ -0,0 +1,81 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// CORS allowed origins for the endpoint service +/// Comma-separated list of origins or "*" for all origins +pub const ENV_CORS_ALLOWED_ORIGINS: &str = "RUSTFS_CORS_ALLOWED_ORIGINS"; + +/// Default CORS allowed origins for the endpoint service +/// Comes from the console service default +/// See DEFAULT_CONSOLE_CORS_ALLOWED_ORIGINS +pub const DEFAULT_CORS_ALLOWED_ORIGINS: &str = DEFAULT_CONSOLE_CORS_ALLOWED_ORIGINS; + +/// CORS allowed origins for the console service +/// Comma-separated list of origins or "*" for all origins +pub const ENV_CONSOLE_CORS_ALLOWED_ORIGINS: &str = "RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS"; + +/// Default CORS allowed origins for the console service +pub const DEFAULT_CONSOLE_CORS_ALLOWED_ORIGINS: &str = "*"; + +/// Enable or disable the console service +pub const ENV_CONSOLE_ENABLE: &str = "RUSTFS_CONSOLE_ENABLE"; + +/// Address for the console service to bind to +pub const ENV_CONSOLE_ADDRESS: &str = "RUSTFS_CONSOLE_ADDRESS"; + +/// RUSTFS_CONSOLE_RATE_LIMIT_ENABLE +/// Enable or disable rate limiting for the console service +pub const ENV_CONSOLE_RATE_LIMIT_ENABLE: &str = "RUSTFS_CONSOLE_RATE_LIMIT_ENABLE"; + +/// Default console rate limit enable +/// This is the default value for enabling rate limiting on the console server. +/// Rate limiting helps protect against abuse and DoS attacks on the management interface. +/// Default value: false +/// Environment variable: RUSTFS_CONSOLE_RATE_LIMIT_ENABLE +/// Command line argument: --console-rate-limit-enable +/// Example: RUSTFS_CONSOLE_RATE_LIMIT_ENABLE=true +/// Example: --console-rate-limit-enable true +pub const DEFAULT_CONSOLE_RATE_LIMIT_ENABLE: bool = false; + +/// Set the rate limit requests per minute for the console service +/// Limits the number of requests per minute per client IP when rate limiting is enabled +/// Default: 100 requests per minute +pub const ENV_CONSOLE_RATE_LIMIT_RPM: &str = "RUSTFS_CONSOLE_RATE_LIMIT_RPM"; + +/// Default console rate limit requests per minute +/// This is the default rate limit for console requests when rate limiting is enabled. +/// Limits the number of requests per minute per client IP to prevent abuse. +/// Default value: 100 requests per minute +/// Environment variable: RUSTFS_CONSOLE_RATE_LIMIT_RPM +/// Command line argument: --console-rate-limit-rpm +/// Example: RUSTFS_CONSOLE_RATE_LIMIT_RPM=100 +/// Example: --console-rate-limit-rpm 100 +pub const DEFAULT_CONSOLE_RATE_LIMIT_RPM: u32 = 100; + +/// Set the console authentication timeout in seconds +/// Specifies how long a console authentication session remains valid +/// Default: 3600 seconds (1 hour) +/// Minimum: 300 seconds (5 minutes) +/// Maximum: 86400 seconds (24 hours) +pub const ENV_CONSOLE_AUTH_TIMEOUT: &str = "RUSTFS_CONSOLE_AUTH_TIMEOUT"; + +/// Default console authentication timeout in seconds +/// This is the default timeout for console authentication sessions. +/// After this timeout, users need to re-authenticate to access the console. +/// Default value: 3600 seconds (1 hour) +/// Environment variable: RUSTFS_CONSOLE_AUTH_TIMEOUT +/// Command line argument: --console-auth-timeout +/// Example: RUSTFS_CONSOLE_AUTH_TIMEOUT=3600 +/// Example: --console-auth-timeout 3600 +pub const DEFAULT_CONSOLE_AUTH_TIMEOUT: u64 = 3600; diff --git a/crates/config/src/constants/mod.rs b/crates/config/src/constants/mod.rs index 3bc007b2..046bb192 100644 --- a/crates/config/src/constants/mod.rs +++ b/crates/config/src/constants/mod.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub mod app; -pub mod env; -pub mod tls; +pub(crate) mod app; +pub(crate) mod console; +pub(crate) mod env; +pub(crate) mod tls; diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 98bd42c1..29e840df 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -17,6 +17,8 @@ pub mod constants; #[cfg(feature = "constants")] pub use constants::app::*; #[cfg(feature = "constants")] +pub use constants::console::*; +#[cfg(feature = "constants")] pub use constants::env::*; #[cfg(feature = "constants")] pub use constants::tls::*; diff --git a/crates/ecstore/src/endpoints.rs b/crates/ecstore/src/endpoints.rs index d9e209ed..f6895ac3 100644 --- a/crates/ecstore/src/endpoints.rs +++ b/crates/ecstore/src/endpoints.rs @@ -169,7 +169,7 @@ impl AsMut> for PoolEndpointList { impl PoolEndpointList { /// creates a list of endpoints per pool, resolves their relevant /// hostnames and discovers those are local or remote. - fn create_pool_endpoints(server_addr: &str, disks_layout: &DisksLayout) -> Result { + async fn create_pool_endpoints(server_addr: &str, disks_layout: &DisksLayout) -> Result { if disks_layout.is_empty_layout() { return Err(Error::other("invalid number of endpoints")); } @@ -244,7 +244,7 @@ impl PoolEndpointList { let host_ip_set = if let Some(set) = host_ip_cache.get(&host) { set } else { - let ips = match get_host_ip(host.clone()) { + let ips = match get_host_ip(host.clone()).await { Ok(ips) => ips, Err(e) => { error!("host {} not found, error:{}", host, e); @@ -466,19 +466,22 @@ impl EndpointServerPools { } None } - pub fn from_volumes(server_addr: &str, endpoints: Vec) -> Result<(EndpointServerPools, SetupType)> { + pub async fn from_volumes(server_addr: &str, endpoints: Vec) -> Result<(EndpointServerPools, SetupType)> { let layouts = DisksLayout::from_volumes(endpoints.as_slice())?; - Self::create_server_endpoints(server_addr, &layouts) + Self::create_server_endpoints(server_addr, &layouts).await } /// validates and creates new endpoints from input args, supports /// both ellipses and without ellipses transparently. - pub fn create_server_endpoints(server_addr: &str, disks_layout: &DisksLayout) -> Result<(EndpointServerPools, SetupType)> { + pub async fn create_server_endpoints( + server_addr: &str, + disks_layout: &DisksLayout, + ) -> Result<(EndpointServerPools, SetupType)> { if disks_layout.pools.is_empty() { return Err(Error::other("Invalid arguments specified")); } - let pool_eps = PoolEndpointList::create_pool_endpoints(server_addr, disks_layout)?; + let pool_eps = PoolEndpointList::create_pool_endpoints(server_addr, disks_layout).await?; let mut ret: EndpointServerPools = Vec::with_capacity(pool_eps.as_ref().len()).into(); for (i, eps) in pool_eps.inner.into_iter().enumerate() { @@ -753,8 +756,8 @@ mod test { } } - #[test] - fn test_create_pool_endpoints() { + #[tokio::test] + async fn test_create_pool_endpoints() { #[derive(Default)] struct TestCase<'a> { num: usize, @@ -1276,7 +1279,7 @@ mod test { match ( test_case.expected_err, - PoolEndpointList::create_pool_endpoints(test_case.server_addr, &disks_layout), + PoolEndpointList::create_pool_endpoints(test_case.server_addr, &disks_layout).await, ) { (None, Err(err)) => panic!("Test {}: error: expected = , got = {}", test_case.num, err), (Some(err), Ok(_)) => panic!("Test {}: error: expected = {}, got = ", test_case.num, err), @@ -1343,8 +1346,8 @@ mod test { (urls, local_flags) } - #[test] - fn test_create_server_endpoints() { + #[tokio::test] + async fn test_create_server_endpoints() { let test_cases = [ // Invalid input. ("", vec![], false), @@ -1379,7 +1382,7 @@ mod test { } }; - let ret = EndpointServerPools::create_server_endpoints(test_case.0, &disks_layout); + let ret = EndpointServerPools::create_server_endpoints(test_case.0, &disks_layout).await; if let Err(err) = ret { if test_case.2 { diff --git a/crates/ecstore/src/global.rs b/crates/ecstore/src/global.rs index b7168045..f793f453 100644 --- a/crates/ecstore/src/global.rs +++ b/crates/ecstore/src/global.rs @@ -37,26 +37,27 @@ pub const DISK_FILL_FRACTION: f64 = 0.99; pub const DISK_RESERVE_FRACTION: f64 = 0.15; lazy_static! { -static ref GLOBAL_RUSTFS_PORT: OnceLock = OnceLock::new(); -pub static ref GLOBAL_OBJECT_API: OnceLock> = OnceLock::new(); -pub static ref GLOBAL_LOCAL_DISK: Arc>>> = Arc::new(RwLock::new(Vec::new())); -pub static ref GLOBAL_IsErasure: RwLock = RwLock::new(false); -pub static ref GLOBAL_IsDistErasure: RwLock = RwLock::new(false); -pub static ref GLOBAL_IsErasureSD: RwLock = RwLock::new(false); -pub static ref GLOBAL_LOCAL_DISK_MAP: Arc>>> = Arc::new(RwLock::new(HashMap::new())); -pub static ref GLOBAL_LOCAL_DISK_SET_DRIVES: Arc> = Arc::new(RwLock::new(Vec::new())); -pub static ref GLOBAL_Endpoints: OnceLock = OnceLock::new(); -pub static ref GLOBAL_RootDiskThreshold: RwLock = RwLock::new(0); -pub static ref GLOBAL_TierConfigMgr: Arc> = TierConfigMgr::new(); -pub static ref GLOBAL_LifecycleSys: Arc = LifecycleSys::new(); -pub static ref GLOBAL_EventNotifier: Arc> = EventNotifier::new(); -//pub static ref GLOBAL_RemoteTargetTransport -static ref globalDeploymentIDPtr: OnceLock = OnceLock::new(); -pub static ref GLOBAL_BOOT_TIME: OnceCell = OnceCell::new(); -pub static ref GLOBAL_LocalNodeName: String = "127.0.0.1:9000".to_string(); -pub static ref GLOBAL_LocalNodeNameHex: String = rustfs_utils::crypto::hex(GLOBAL_LocalNodeName.as_bytes()); -pub static ref GLOBAL_NodeNamesHex: HashMap = HashMap::new(); -pub static ref GLOBAL_REGION: OnceLock = OnceLock::new(); + static ref GLOBAL_RUSTFS_PORT: OnceLock = OnceLock::new(); + static ref GLOBAL_RUSTFS_EXTERNAL_PORT: OnceLock = OnceLock::new(); + pub static ref GLOBAL_OBJECT_API: OnceLock> = OnceLock::new(); + pub static ref GLOBAL_LOCAL_DISK: Arc>>> = Arc::new(RwLock::new(Vec::new())); + pub static ref GLOBAL_IsErasure: RwLock = RwLock::new(false); + pub static ref GLOBAL_IsDistErasure: RwLock = RwLock::new(false); + pub static ref GLOBAL_IsErasureSD: RwLock = RwLock::new(false); + pub static ref GLOBAL_LOCAL_DISK_MAP: Arc>>> = Arc::new(RwLock::new(HashMap::new())); + pub static ref GLOBAL_LOCAL_DISK_SET_DRIVES: Arc> = Arc::new(RwLock::new(Vec::new())); + pub static ref GLOBAL_Endpoints: OnceLock = OnceLock::new(); + pub static ref GLOBAL_RootDiskThreshold: RwLock = RwLock::new(0); + pub static ref GLOBAL_TierConfigMgr: Arc> = TierConfigMgr::new(); + pub static ref GLOBAL_LifecycleSys: Arc = LifecycleSys::new(); + pub static ref GLOBAL_EventNotifier: Arc> = EventNotifier::new(); + //pub static ref GLOBAL_RemoteTargetTransport + static ref globalDeploymentIDPtr: OnceLock = OnceLock::new(); + pub static ref GLOBAL_BOOT_TIME: OnceCell = OnceCell::new(); + pub static ref GLOBAL_LocalNodeName: String = "127.0.0.1:9000".to_string(); + pub static ref GLOBAL_LocalNodeNameHex: String = rustfs_utils::crypto::hex(GLOBAL_LocalNodeName.as_bytes()); + pub static ref GLOBAL_NodeNamesHex: HashMap = HashMap::new(); + pub static ref GLOBAL_REGION: OnceLock = OnceLock::new(); } // Global cancellation token for background services (data scanner and auto heal) @@ -108,6 +109,22 @@ pub fn set_global_rustfs_port(value: u16) { GLOBAL_RUSTFS_PORT.set(value).expect("set_global_rustfs_port fail"); } +/// Get the global rustfs external port +pub fn global_rustfs_external_port() -> u16 { + if let Some(p) = GLOBAL_RUSTFS_EXTERNAL_PORT.get() { + *p + } else { + rustfs_config::DEFAULT_PORT + } +} + +/// Set the global rustfs external port +pub fn set_global_rustfs_external_port(value: u16) { + GLOBAL_RUSTFS_EXTERNAL_PORT + .set(value) + .expect("set_global_rustfs_external_port fail"); +} + /// Get the global rustfs port pub fn set_global_deployment_id(id: Uuid) { globalDeploymentIDPtr.set(id).unwrap(); diff --git a/crates/lock/Cargo.toml b/crates/lock/Cargo.toml index 3d65b815..2878fceb 100644 --- a/crates/lock/Cargo.toml +++ b/crates/lock/Cargo.toml @@ -42,8 +42,8 @@ url.workspace = true uuid.workspace = true thiserror.workspace = true once_cell.workspace = true -parking_lot = "0.12" -smallvec = "1.11" -smartstring = "1.0" -crossbeam-queue = "0.3" -heapless = "0.8" +parking_lot.workspace = true +smallvec.workspace = true +smartstring.workspace = true +crossbeam-queue = { workspace = true } +heapless = { workspace = true } diff --git a/crates/mcp/src/server.rs b/crates/mcp/src/server.rs index ae71dc56..1b43c7fa 100644 --- a/crates/mcp/src/server.rs +++ b/crates/mcp/src/server.rs @@ -616,6 +616,7 @@ impl ServerHandler for RustfsMcpServer { server_info: Implementation { name: "rustfs-mcp-server".into(), version: env!("CARGO_PKG_VERSION").into(), + ..Default::default() }, } } diff --git a/crates/targets/src/target/mqtt.rs b/crates/targets/src/target/mqtt.rs index 9dbf6411..d3dad907 100644 --- a/crates/targets/src/target/mqtt.rs +++ b/crates/targets/src/target/mqtt.rs @@ -438,7 +438,7 @@ fn is_fatal_mqtt_error(err: &ConnectionError) -> bool { rumqttc::StateError::InvalidState // The internal state machine is in invalid state | rumqttc::StateError::WrongPacket // Agreement Violation: Unexpected Data Packet Received | rumqttc::StateError::Unsolicited(_) // Agreement Violation: Unsolicited ACK Received - | rumqttc::StateError::OutgoingPacketTooLarge { .. } // Try to send too large packets + | rumqttc::StateError::CollisionTimeout // Agreement Violation (if this stage occurs) | rumqttc::StateError::EmptySubscription // Agreement violation (if this stage occurs) => true, diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index 9e99c258..dcad489e 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -81,7 +81,7 @@ workspace = true default = ["ip"] # features that are enabled by default ip = ["dep:local-ip-address"] # ip characteristics and their dependencies tls = ["dep:rustls", "dep:rustls-pemfile", "dep:rustls-pki-types"] # tls characteristics and their dependencies -net = ["ip", "dep:url", "dep:netif", "dep:futures", "dep:transform-stream", "dep:bytes", "dep:s3s", "dep:hyper", "dep:hyper-util", "dep:hickory-resolver", "dep:hickory-proto", "dep:moka", "dep:thiserror"] # network features with DNS resolver +net = ["ip", "dep:url", "dep:netif", "dep:futures", "dep:transform-stream", "dep:bytes", "dep:s3s", "dep:hyper", "dep:hyper-util", "dep:hickory-resolver", "dep:hickory-proto", "dep:moka", "dep:thiserror", "dep:tokio"] # network features with DNS resolver io = ["dep:tokio"] path = [] notify = ["dep:hyper", "dep:s3s"] # file system notification features diff --git a/crates/utils/src/dns_resolver.rs b/crates/utils/src/dns_resolver.rs index e952d999..3b3a841e 100644 --- a/crates/utils/src/dns_resolver.rs +++ b/crates/utils/src/dns_resolver.rs @@ -127,6 +127,7 @@ impl LayeredDnsResolver { /// Validate domain format according to RFC standards #[instrument(skip_all, fields(domain = %domain))] fn validate_domain_format(domain: &str) -> Result<(), DnsError> { + info!("Validating domain format start"); // Check FQDN length if domain.len() > MAX_FQDN_LENGTH { return Err(DnsError::InvalidFormat { @@ -157,7 +158,7 @@ impl LayeredDnsResolver { }); } } - + info!("DNS resolver validated successfully"); Ok(()) } @@ -209,7 +210,6 @@ impl LayeredDnsResolver { let ips: Vec = lookup.iter().collect(); if !ips.is_empty() { info!("System DNS resolution successful for domain: {} -> {} IPs", domain, ips.len()); - debug!("System DNS resolved IPs: {:?}", ips); Ok(ips) } else { warn!("System DNS returned empty result for domain: {}", domain); @@ -242,7 +242,6 @@ impl LayeredDnsResolver { let ips: Vec = lookup.iter().collect(); if !ips.is_empty() { info!("Public DNS resolution successful for domain: {} -> {} IPs", domain, ips.len()); - debug!("Public DNS resolved IPs: {:?}", ips); Ok(ips) } else { warn!("Public DNS returned empty result for domain: {}", domain); @@ -270,6 +269,7 @@ impl LayeredDnsResolver { /// 3. Public DNS (hickory-resolver with TLS-enabled Cloudflare DNS fallback) #[instrument(skip_all, fields(domain = %domain))] pub async fn resolve(&self, domain: &str) -> Result, DnsError> { + info!("Starting DNS resolution process for domain: {} start", domain); // Validate domain format first Self::validate_domain_format(domain)?; @@ -305,7 +305,7 @@ impl LayeredDnsResolver { } Err(public_err) => { error!( - "All DNS resolution attempts failed for domain: {}. System DNS: failed, Public DNS: {}", + "All DNS resolution attempts failed for domain:` {}`. System DNS: failed, Public DNS: {}", domain, public_err ); Err(DnsError::AllAttemptsFailed { @@ -345,6 +345,7 @@ pub fn get_global_dns_resolver() -> Option<&'static LayeredDnsResolver> { /// Resolve domain using the global DNS resolver with comprehensive tracing #[instrument(skip_all, fields(domain = %domain))] pub async fn resolve_domain(domain: &str) -> Result, DnsError> { + info!("resolving domain for: {}", domain); match get_global_dns_resolver() { Some(resolver) => resolver.resolve(domain).await, None => Err(DnsError::InitializationFailed { diff --git a/crates/utils/src/net.rs b/crates/utils/src/net.rs index dd81578e..8244e21c 100644 --- a/crates/utils/src/net.rs +++ b/crates/utils/src/net.rs @@ -15,6 +15,7 @@ use bytes::Bytes; use futures::pin_mut; use futures::{Stream, StreamExt}; +use std::io::Error; use std::net::Ipv6Addr; use std::sync::{LazyLock, Mutex}; use std::{ @@ -23,6 +24,7 @@ use std::{ net::{IpAddr, SocketAddr, TcpListener, ToSocketAddrs}, time::{Duration, Instant}, }; +use tracing::{error, info}; use transform_stream::AsyncTryStream; use url::{Host, Url}; @@ -61,7 +63,7 @@ pub fn is_socket_addr(addr: &str) -> bool { pub fn check_local_server_addr(server_addr: &str) -> std::io::Result { let addr: Vec = match server_addr.to_socket_addrs() { Ok(addr) => addr.collect(), - Err(err) => return Err(std::io::Error::other(err)), + Err(err) => return Err(Error::other(err)), }; // 0.0.0.0 is a wildcard address and refers to local network @@ -82,7 +84,7 @@ pub fn check_local_server_addr(server_addr: &str) -> std::io::Result } } - Err(std::io::Error::other("host in server address should be this server")) + Err(Error::other("host in server address should be this server")) } /// checks if the given parameter correspond to one of @@ -93,7 +95,7 @@ pub fn is_local_host(host: Host<&str>, port: u16, local_port: u16) -> std::io::R Host::Domain(domain) => { let ips = match (domain, 0).to_socket_addrs().map(|v| v.map(|v| v.ip()).collect::>()) { Ok(ips) => ips, - Err(err) => return Err(std::io::Error::other(err)), + Err(err) => return Err(Error::other(err)), }; ips.iter().any(|ip| local_set.contains(ip)) @@ -113,49 +115,20 @@ pub fn is_local_host(host: Host<&str>, port: u16, local_port: u16) -> std::io::R /// /// This is the async version of `get_host_ip()` that provides enhanced DNS resolution /// with Kubernetes support when the "net" feature is enabled. -pub async fn get_host_ip_async(host: Host<&str>) -> std::io::Result> { +pub async fn get_host_ip(host: Host<&str>) -> std::io::Result> { match host { Host::Domain(domain) => { - #[cfg(feature = "net")] - { - use crate::dns_resolver::resolve_domain; - match resolve_domain(domain).await { - Ok(ips) => Ok(ips.into_iter().collect()), - Err(e) => Err(std::io::Error::other(format!("DNS resolution failed: {}", e))), + match crate::dns_resolver::resolve_domain(domain).await { + Ok(ips) => { + info!("Resolved domain {domain} using custom DNS resolver: {ips:?}"); + return Ok(ips.into_iter().collect()); + } + Err(err) => { + error!( + "Failed to resolve domain {domain} using custom DNS resolver, falling back to system resolver,err: {err}" + ); } } - #[cfg(not(feature = "net"))] - { - // Fallback to standard resolution when DNS resolver is not available - match (domain, 0) - .to_socket_addrs() - .map(|v| v.map(|v| v.ip()).collect::>()) - { - Ok(ips) => Ok(ips), - Err(err) => Err(std::io::Error::other(err)), - } - } - } - Host::Ipv4(ip) => { - let mut set = HashSet::with_capacity(1); - set.insert(IpAddr::V4(ip)); - Ok(set) - } - Host::Ipv6(ip) => { - let mut set = HashSet::with_capacity(1); - set.insert(IpAddr::V6(ip)); - Ok(set) - } - } -} - -/// returns IP address of given host using standard resolution. -/// -/// **Note**: This function uses standard library DNS resolution with caching. -/// For enhanced DNS resolution with Kubernetes support, use `get_host_ip_async()`. -pub fn get_host_ip(host: Host<&str>) -> std::io::Result> { - match host { - Host::Domain(domain) => { // Check cache first if let Ok(mut cache) = DNS_CACHE.lock() { if let Some(entry) = cache.get(domain) { @@ -167,7 +140,9 @@ pub fn get_host_ip(host: Host<&str>) -> std::io::Result> { } } - // Perform DNS resolution + info!("Cache miss for domain {domain}, querying system resolver."); + + // Fallback to standard resolution when DNS resolver is not available match (domain, 0) .to_socket_addrs() .map(|v| v.map(|v| v.ip()).collect::>()) @@ -181,21 +156,17 @@ pub fn get_host_ip(host: Host<&str>) -> std::io::Result> { cache.retain(|_, v| !v.is_expired(DNS_CACHE_TTL)); } } + info!("System query for domain {domain}: {:?}", ips); Ok(ips) } - Err(err) => Err(std::io::Error::other(err)), + Err(err) => { + error!("Failed to resolve domain {domain} using system resolver, err: {err}"); + Err(Error::other(err)) + } } } - Host::Ipv4(ip) => { - let mut set = HashSet::with_capacity(1); - set.insert(IpAddr::V4(ip)); - Ok(set) - } - Host::Ipv6(ip) => { - let mut set = HashSet::with_capacity(1); - set.insert(IpAddr::V6(ip)); - Ok(set) - } + Host::Ipv4(ip) => Ok([IpAddr::V4(ip)].into_iter().collect()), + Host::Ipv6(ip) => Ok([IpAddr::V6(ip)].into_iter().collect()), } } @@ -207,7 +178,7 @@ pub fn get_available_port() -> u16 { pub fn must_get_local_ips() -> std::io::Result> { match netif::up() { Ok(up) => Ok(up.map(|x| x.address().to_owned()).collect()), - Err(err) => Err(std::io::Error::other(format!("Unable to get IP addresses of this host: {err}"))), + Err(err) => Err(Error::other(format!("Unable to get IP addresses of this host: {err}"))), } } @@ -215,7 +186,7 @@ pub fn get_default_location(_u: Url, _region_override: &str) -> String { todo!(); } -pub fn get_endpoint_url(endpoint: &str, secure: bool) -> Result { +pub fn get_endpoint_url(endpoint: &str, secure: bool) -> Result { let mut scheme = "https"; if !secure { scheme = "http"; @@ -223,7 +194,7 @@ pub fn get_endpoint_url(endpoint: &str, secure: bool) -> Result for XHost { - type Error = std::io::Error; + type Error = Error; fn try_from(value: String) -> Result { if let Some(addr) = value.to_socket_addrs()?.next() { @@ -268,7 +239,7 @@ impl TryFrom for XHost { is_port_set: addr.port() > 0, }) } else { - Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "value invalid")) + Err(Error::new(std::io::ErrorKind::InvalidData, "value invalid")) } } } @@ -278,7 +249,7 @@ pub fn parse_and_resolve_address(addr_str: &str) -> std::io::Result let port_str = port; let port: u16 = port_str .parse() - .map_err(|e| std::io::Error::other(format!("Invalid port format: {addr_str}, err:{e:?}")))?; + .map_err(|e| Error::other(format!("Invalid port format: {addr_str}, err:{e:?}")))?; let final_port = if port == 0 { get_available_port() // assume get_available_port is available here } else { @@ -318,9 +289,9 @@ where #[cfg(test)] mod test { - use std::net::{Ipv4Addr, Ipv6Addr}; - use super::*; + use crate::init_global_dns_resolver; + use std::net::{Ipv4Addr, Ipv6Addr}; #[test] fn test_is_socket_addr() { @@ -424,23 +395,29 @@ mod test { assert!(is_local_host(invalid_host, 0, 0).is_err()); } - #[test] - fn test_get_host_ip() { + #[tokio::test] + async fn test_get_host_ip() { + match init_global_dns_resolver().await { + Ok(_) => {} + Err(e) => { + error!("Failed to initialize global DNS resolver: {e}"); + } + } // Test IPv4 address let ipv4_host = Host::Ipv4(Ipv4Addr::new(192, 168, 1, 1)); - let ipv4_result = get_host_ip(ipv4_host).unwrap(); + let ipv4_result = get_host_ip(ipv4_host).await.unwrap(); assert_eq!(ipv4_result.len(), 1); assert!(ipv4_result.contains(&IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)))); // Test IPv6 address let ipv6_host = Host::Ipv6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)); - let ipv6_result = get_host_ip(ipv6_host).unwrap(); + let ipv6_result = get_host_ip(ipv6_host).await.unwrap(); assert_eq!(ipv6_result.len(), 1); assert!(ipv6_result.contains(&IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)))); // Test localhost domain let localhost_host = Host::Domain("localhost"); - let localhost_result = get_host_ip(localhost_host).unwrap(); + let localhost_result = get_host_ip(localhost_host).await.unwrap(); assert!(!localhost_result.is_empty()); // Should contain at least loopback address assert!( @@ -450,7 +427,16 @@ mod test { // Test invalid domain let invalid_host = Host::Domain("invalid.nonexistent.domain.example"); - assert!(get_host_ip(invalid_host).is_err()); + match get_host_ip(invalid_host.clone()).await { + Ok(ips) => { + // Depending on DNS resolver behavior, it might return empty set or error + assert!(ips.is_empty(), "Expected empty IP set for invalid domain, got: {:?}", ips); + } + Err(_) => { + error!("Expected error for invalid domain"); + } // Expected error + } + assert!(get_host_ip(invalid_host).await.is_err()); } #[test] diff --git a/docker-compose.yml b/docker-compose.yml index af16483f..e4a32378 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,10 +28,15 @@ services: TARGETPLATFORM: linux/amd64 ports: - "9000:9000" # S3 API port + - "9001:9001" # Console port environment: - RUSTFS_VOLUMES=/data/rustfs0,/data/rustfs1,/data/rustfs2,/data/rustfs3 - RUSTFS_ADDRESS=0.0.0.0:9000 + - RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001 - RUSTFS_CONSOLE_ENABLE=true + - RUSTFS_EXTERNAL_ADDRESS=:9000 # Same as internal since no port mapping + - RUSTFS_CORS_ALLOWED_ORIGINS=* + - RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS=* - RUSTFS_ACCESS_KEY=rustfsadmin - RUSTFS_SECRET_KEY=rustfsadmin - RUSTFS_LOG_LEVEL=info @@ -49,11 +54,8 @@ services: test: [ "CMD", - "wget", - "--no-verbose", - "--tries=1", - "--spider", - "http://localhost:9000/health", + "sh", "-c", + "curl -f http://localhost:9000/health && curl -f http://localhost:9001/health" ] interval: 30s timeout: 10s @@ -71,11 +73,16 @@ services: dockerfile: Dockerfile.source # Pure development environment ports: - - "9010:9000" + - "9010:9000" # S3 API port + - "9011:9001" # Console port environment: - RUSTFS_VOLUMES=/data/rustfs0,/data/rustfs1 - RUSTFS_ADDRESS=0.0.0.0:9000 + - RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001 - RUSTFS_CONSOLE_ENABLE=true + - RUSTFS_EXTERNAL_ADDRESS=:9010 # External port mapping 9010 -> 9000 + - RUSTFS_CORS_ALLOWED_ORIGINS=* + - RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS=* - RUSTFS_ACCESS_KEY=devadmin - RUSTFS_SECRET_KEY=devadmin - RUSTFS_LOG_LEVEL=debug @@ -85,6 +92,17 @@ services: networks: - rustfs-network restart: unless-stopped + healthcheck: + test: + [ + "CMD", + "sh", "-c", + "curl -f http://localhost:9000/health && curl -f http://localhost:9001/health" + ] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s profiles: - dev diff --git a/docs/console-separation.md b/docs/console-separation.md new file mode 100644 index 00000000..8b6b3861 --- /dev/null +++ b/docs/console-separation.md @@ -0,0 +1,1362 @@ +# RustFS Console & Endpoint Service Separation Guide + +This document provides comprehensive guidance on RustFS's console and endpoint service separation architecture, enabling independent deployment of the web management interface and S3 API service with enterprise-grade security, monitoring, and Docker deployment standards. + +## Table of Contents + +- [Overview](#overview) +- [Architecture](#architecture) +- [Quick Start](#quick-start) +- [Configuration Reference](#configuration-reference) +- [Docker Deployment](#docker-deployment) +- [Kubernetes Deployment](#kubernetes-deployment) +- [Security Hardening](#security-hardening) +- [Health Monitoring](#health-monitoring) +- [Troubleshooting](#troubleshooting) +- [Migration Guide](#migration-guide) +- [Best Practices](#best-practices) + +## Overview + +RustFS implements complete separation between the console web interface and the S3 API endpoint service, enabling: + +- **Independent Port Management**: Console (`:9001`) and API (`:9000`) run on separate ports +- **Enhanced Security**: Different CORS policies, TLS configurations, and access controls +- **Flexible Deployment**: Console can be disabled or restricted to internal networks +- **Docker-Native**: Optimized for containerized deployments with proper port mapping +- **Enterprise Ready**: Rate limiting, authentication timeouts, and comprehensive monitoring + +## Architecture + +### Service Components + +- **S3 API Endpoint** (Port 9000) + - Handles all S3-compatible API requests + - Independent CORS configuration via `RUSTFS_CORS_ALLOWED_ORIGINS` + - Health check endpoint: `GET /health` + - Production-ready with comprehensive error handling + +- **Console Interface** (Port 9001) + - Web-based management dashboard at `/rustfs/console/` + - Independent CORS configuration via `RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS` + - TLS support using shared certificate infrastructure + - Rate limiting and authentication timeout controls + - Health check endpoint: `GET /health` + +### Communication Flow + +``` +Browser → Console (9001) → API Endpoint (9000) → Storage Backend + ↓ + External Address Configuration + (RUSTFS_EXTERNAL_ADDRESS) +``` + +The console communicates with the API endpoint using the `RUSTFS_EXTERNAL_ADDRESS` parameter, which is critical for Docker deployments with port mapping. + +## Quick Start + +### Local Development + +```bash +# Start with default configuration +rustfs /data/volume + +# Access points: +# API: http://localhost:9000 +# Console: http://localhost:9001/rustfs/console/ +``` + +### Docker Quick Start + +```bash +# Basic Docker deployment +docker run -d \ + --name rustfs \ + -p 9020:9000 -p 9021:9001 \ + -e RUSTFS_EXTERNAL_ADDRESS=":9020" \ + rustfs/rustfs:latest + +# Access points: +# API: http://localhost:9020 +# Console: http://localhost:9021/rustfs/console/ +``` + +### Production Quick Start + +Use our enhanced deployment script for production-ready setup: + +```bash +# Use the enhanced security deployment script +./examples/enhanced-security-deployment.sh + +# Or customize the enhanced Docker deployment +./examples/enhanced-docker-deployment.sh prod +``` + +## Configuration Reference + +### Core Service Configuration + +| Parameter | Environment Variable | Default | Description | +|-----------|---------------------|---------|-------------| +| `address` | `RUSTFS_ADDRESS` | `:9000` | S3 API endpoint bind address | +| `console_address` | `RUSTFS_CONSOLE_ADDRESS` | `:9001` | Console service bind address | +| `console_enable` | `RUSTFS_CONSOLE_ENABLE` | `true` | Enable/disable console service | +| `external_address` | `RUSTFS_EXTERNAL_ADDRESS` | `:9000` | External endpoint address for console→API communication | + +### CORS Configuration + +| Parameter | Environment Variable | Default | Description | +|-----------|---------------------|---------|-------------| +| `cors_allowed_origins` | `RUSTFS_CORS_ALLOWED_ORIGINS` | `*` | Comma-separated allowed origins for endpoint CORS | +| `console_cors_allowed_origins` | `RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS` | `*` | Comma-separated allowed origins for console CORS | + +### Security Configuration + +| Parameter | Environment Variable | Default | Description | +|-----------|---------------------|---------|-------------| +| `tls_path` | `RUSTFS_TLS_PATH` | - | TLS certificate directory path (shared by both services) | +| `console_rate_limit_enable` | `RUSTFS_CONSOLE_RATE_LIMIT_ENABLE` | `false` | Enable rate limiting for console access | +| `console_rate_limit_rpm` | `RUSTFS_CONSOLE_RATE_LIMIT_RPM` | `100` | Console rate limit (requests per minute) | +| `console_auth_timeout` | `RUSTFS_CONSOLE_AUTH_TIMEOUT` | `3600` | Console authentication timeout (seconds) | + +### Authentication Configuration + +| Parameter | Environment Variable | Default | Description | +|-----------|---------------------|---------|-------------| +| `access_key` | `RUSTFS_ACCESS_KEY` | `rustfsadmin` | Administrative access key | +| `secret_key` | `RUSTFS_SECRET_KEY` | `rustfsadmin` | Administrative secret key | + +### Important Notes + +- **External Address**: Critical for Docker deployments. Must match the host-mapped API port. +- **TLS Configuration**: Console uses shared TLS certificates from `RUSTFS_TLS_PATH` (no separate cert config needed). +- **Environment Priority**: Console security settings are read directly from environment variables. + +## Docker Deployment + +### Prerequisites + +Ensure Docker is installed and the RustFS image is available: + +```bash +# Pull the latest RustFS image +docker pull rustfs/rustfs:latest + +# Or build from source +docker build -t rustfs/rustfs:latest . +``` + +### Basic Docker Deployment + +Simple deployment with port mapping: + +```bash +docker run -d \ + --name rustfs-basic \ + -p 9020:9000 \ # API: host 9020 → container 9000 + -p 9021:9001 \ # Console: host 9021 → container 9001 + -e RUSTFS_EXTERNAL_ADDRESS=":9020" \ # Critical: must match host API port + -e RUSTFS_CORS_ALLOWED_ORIGINS="http://localhost:9021" \ + -v rustfs-data:/data \ + rustfs/rustfs:latest + +# Access: +# API: http://localhost:9020 +# Console: http://localhost:9021/rustfs/console/ +``` + +### Docker Compose Deployment + +Use the provided `docker-compose.yml` for complete setup: + +```bash +# Start the complete stack +docker-compose up -d + +# Start with specific profiles +docker-compose --profile dev up -d # Development environment +docker-compose --profile observability up -d # With monitoring stack +``` + +The compose configuration provides: + +- **Production Service** (`rustfs`): Ports 9000:9000 and 9001:9001 +- **Development Service** (`rustfs-dev`): Ports 9010:9000 and 9011:9001 +- **Observability Stack**: Grafana, Prometheus, Jaeger, and OpenTelemetry +- **Reverse Proxy**: Nginx configuration for production deployments + +### Enhanced Docker Deployment Scripts + +#### Production Deployment with Security + +```bash +# Use the enhanced security deployment script +./examples/enhanced-security-deployment.sh + +# This will: +# - Generate TLS certificates +# - Create secure credentials +# - Deploy with rate limiting +# - Configure restricted CORS +# - Enable health monitoring +``` + +#### Multiple Environment Deployment + +```bash +# Deploy different environments simultaneously +./examples/enhanced-docker-deployment.sh all + +# Individual deployments: +./examples/enhanced-docker-deployment.sh basic # Basic setup +./examples/enhanced-docker-deployment.sh dev # Development environment +./examples/enhanced-docker-deployment.sh prod # Production-like setup +``` + +### Custom Docker Deployment Examples + +#### Development Environment + +```bash +docker run -d \ + --name rustfs-dev \ + -p 9000:9000 -p 9001:9001 \ + -e RUSTFS_EXTERNAL_ADDRESS=":9000" \ + -e RUSTFS_CORS_ALLOWED_ORIGINS="*" \ + -e RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="*" \ + -e RUSTFS_ACCESS_KEY="dev-admin" \ + -e RUSTFS_SECRET_KEY="dev-secret" \ + -e RUST_LOG="debug" \ + -v rustfs-dev-data:/data \ + rustfs/rustfs:latest +``` + +#### Production with TLS and Security + +```bash +docker run -d \ + --name rustfs-production \ + -p 9443:9001 -p 9000:9000 \ + -v /path/to/certs:/certs:ro \ + -v /path/to/data:/data \ + -e RUSTFS_TLS_PATH="/certs" \ + -e RUSTFS_CONSOLE_RATE_LIMIT_ENABLE="true" \ + -e RUSTFS_CONSOLE_RATE_LIMIT_RPM="60" \ + -e RUSTFS_CONSOLE_AUTH_TIMEOUT="1800" \ + -e RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="https://admin.yourdomain.com" \ + -e RUSTFS_CORS_ALLOWED_ORIGINS="https://api.yourdomain.com" \ + -e RUSTFS_ACCESS_KEY="$(openssl rand -hex 16)" \ + -e RUSTFS_SECRET_KEY="$(openssl rand -hex 32)" \ + rustfs/rustfs:latest +``` + +#### Console-Disabled API-Only Deployment + +```bash +docker run -d \ + --name rustfs-api-only \ + -p 9000:9000 \ + -e RUSTFS_CONSOLE_ENABLE="false" \ + -e RUSTFS_CORS_ALLOWED_ORIGINS="https://your-app.com" \ + -v rustfs-api-data:/data \ + rustfs/rustfs:latest + +# Only API available: http://localhost:9000 +``` + +### Docker Health Checks + +The Dockerfile includes health checks for both services: + +```dockerfile +HEALTHCHECK --interval=30s --timeout=10s --retries=3 \ + CMD curl -f http://localhost:9000/health && curl -f http://localhost:9001/health || exit 1 +``` + +Check container health: + +```bash +# View health status +docker ps --format "table {{.Names}}\t{{.Status}}" + +# View detailed health check logs +docker inspect rustfs --format='{{json .State.Health}}' | jq +``` + +## Kubernetes Deployment + +### Basic Kubernetes Deployment + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: rustfs + labels: + app: rustfs +spec: + replicas: 1 + selector: + matchLabels: + app: rustfs + template: + metadata: + labels: + app: rustfs + spec: + containers: + - name: rustfs + image: rustfs/rustfs:latest + ports: + - containerPort: 9000 + name: api + - containerPort: 9001 + name: console + env: + - name: RUSTFS_ADDRESS + value: "0.0.0.0:9000" + - name: RUSTFS_CONSOLE_ADDRESS + value: "0.0.0.0:9001" + - name: RUSTFS_EXTERNAL_ADDRESS + value: ":9000" + - name: RUSTFS_CORS_ALLOWED_ORIGINS + value: "*" + - name: RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS + value: "*" + livenessProbe: + httpGet: + path: /health + port: 9000 + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /health + port: 9001 + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - name: data + mountPath: /data + volumes: + - name: data + persistentVolumeClaim: + claimName: rustfs-data + +--- +apiVersion: v1 +kind: Service +metadata: + name: rustfs-service +spec: + selector: + app: rustfs + ports: + - name: api + port: 9000 + targetPort: 9000 + - name: console + port: 9001 + targetPort: 9001 + type: LoadBalancer + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: rustfs-data +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi +``` + +### Production Kubernetes with TLS + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: rustfs-production +spec: + replicas: 3 + selector: + matchLabels: + app: rustfs-production + template: + metadata: + labels: + app: rustfs-production + spec: + containers: + - name: rustfs + image: rustfs/rustfs:latest + env: + - name: RUSTFS_TLS_PATH + value: "/certs" + - name: RUSTFS_CONSOLE_RATE_LIMIT_ENABLE + value: "true" + - name: RUSTFS_CONSOLE_RATE_LIMIT_RPM + value: "100" + - name: RUSTFS_CONSOLE_AUTH_TIMEOUT + value: "1800" + - name: RUSTFS_ACCESS_KEY + valueFrom: + secretKeyRef: + name: rustfs-credentials + key: access-key + - name: RUSTFS_SECRET_KEY + valueFrom: + secretKeyRef: + name: rustfs-credentials + key: secret-key + volumeMounts: + - name: certs + mountPath: /certs + readOnly: true + - name: data + mountPath: /data + volumes: + - name: certs + secret: + secretName: rustfs-tls + - name: data + persistentVolumeClaim: + claimName: rustfs-production-data + +--- +apiVersion: v1 +kind: Secret +metadata: + name: rustfs-credentials +type: Opaque +stringData: + access-key: "your-secure-access-key" + secret-key: "your-secure-secret-key" +``` + +### Ingress Configuration + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: rustfs-ingress + annotations: + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/cors-allow-origin: "https://admin.yourdomain.com" +spec: + tls: + - hosts: + - api.yourdomain.com + - admin.yourdomain.com + secretName: rustfs-tls-ingress + rules: + - host: api.yourdomain.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: rustfs-service + port: + number: 9000 + - host: admin.yourdomain.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: rustfs-service + port: + number: 9001 +``` + +## Security Hardening + +### TLS Configuration + +RustFS console uses shared TLS certificate infrastructure. Place certificates in a directory and configure via `RUSTFS_TLS_PATH`: + +#### Certificate Requirements + +```bash +# Certificate directory structure +/path/to/certs/ +├── cert.pem # TLS certificate +└── key.pem # Private key +``` + +#### Generate Self-Signed Certificates (Development) + +```bash +# Generate development certificates +mkdir -p ./certs +openssl req -x509 -newkey rsa:4096 \ + -keyout ./certs/key.pem \ + -out ./certs/cert.pem \ + -days 365 -nodes \ + -subj "/C=US/ST=CA/L=SF/O=RustFS/CN=localhost" + +# Set proper permissions +chmod 600 ./certs/key.pem +chmod 644 ./certs/cert.pem +``` + +#### Production TLS with Let's Encrypt + +```bash +# Use certbot to generate certificates +certbot certonly --standalone -d yourdomain.com +cp /etc/letsencrypt/live/yourdomain.com/fullchain.pem ./certs/cert.pem +cp /etc/letsencrypt/live/yourdomain.com/privkey.pem ./certs/key.pem +``` + +### Rate Limiting and Authentication + +Configure console security settings via environment variables: + +```bash +# Enable rate limiting and configure timeouts +export RUSTFS_CONSOLE_RATE_LIMIT_ENABLE=true +export RUSTFS_CONSOLE_RATE_LIMIT_RPM=60 # 60 requests per minute +export RUSTFS_CONSOLE_AUTH_TIMEOUT=1800 # 30 minutes session timeout + +# Start with security settings +docker run -d \ + -e RUSTFS_CONSOLE_RATE_LIMIT_ENABLE=true \ + -e RUSTFS_CONSOLE_RATE_LIMIT_RPM=60 \ + -e RUSTFS_CONSOLE_AUTH_TIMEOUT=1800 \ + rustfs/rustfs:latest +``` + +### CORS Security + +Configure restrictive CORS policies for production: + +```bash +# Production CORS configuration +export RUSTFS_CORS_ALLOWED_ORIGINS="https://myapp.com,https://api.myapp.com" +export RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="https://admin.myapp.com" + +# Development CORS (permissive) +export RUSTFS_CORS_ALLOWED_ORIGINS="*" +export RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="*" +``` + +### Network Security + +#### Firewall Configuration + +```bash +# Allow API access from all networks +sudo ufw allow 9000/tcp + +# Restrict console access to internal networks only +sudo ufw allow from 192.168.1.0/24 to any port 9001 +sudo ufw allow from 10.0.0.0/8 to any port 9001 + +# Block external console access +sudo ufw deny 9001/tcp +``` + +#### Docker Network Isolation + +```yaml +# docker-compose.yml with network isolation +version: '3.8' +services: + rustfs: + image: rustfs/rustfs:latest + networks: + - api-network # Public API access + - console-network # Internal console access + environment: + - RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS=https://admin.internal.com + +networks: + api-network: + driver: bridge + console-network: + driver: bridge + internal: true # No external access +``` + +#### Reverse Proxy Setup + +Use Nginx for additional security layer: + +```nginx +# /etc/nginx/sites-available/rustfs +# API endpoint - public access +server { + listen 80; + server_name api.example.com; + + location / { + proxy_pass http://localhost:9000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # Rate limiting + limit_req zone=api burst=20 nodelay; + } +} + +# Console - restricted access with authentication +server { + listen 443 ssl; + server_name admin.example.com; + + ssl_certificate /path/to/cert.pem; + ssl_certificate_key /path/to/key.pem; + + # Basic authentication + auth_basic "RustFS Admin"; + auth_basic_user_file /etc/nginx/.htpasswd; + + # IP whitelist + allow 192.168.1.0/24; + allow 10.0.0.0/8; + deny all; + + location / { + proxy_pass http://localhost:9001; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} +``` + +## Health Monitoring + +### Health Check Endpoints + +Both services provide independent health check endpoints: + +#### Console Health Check + +- **Endpoint**: `GET /health` +- **Response**: + +```json +{ + "status": "ok", + "service": "rustfs-console", + "timestamp": "2024-01-15T10:30:00Z", + "version": "0.0.5", + "details": { + "storage": { + "status": "connected" + }, + "iam": { + "status": "connected" + } + }, + "uptime": 1800 +} +``` + +#### Endpoint Health Check + +- **Endpoint**: `GET /health` +- **Response**: + +```json +{ + "status": "ok", + "service": "rustfs-endpoint", + "timestamp": "2024-01-15T10:30:00Z", + "version": "0.0.5" +} +``` + +### Monitoring Integration + +#### Prometheus Metrics + +```bash +# Health check monitoring +curl http://localhost:9000/health | jq '.status' +curl http://localhost:9001/health | jq '.status' + +# Prometheus alert rules +- alert: RustFSConsoleDown + expr: up{job="rustfs-console"} == 0 + for: 30s + labels: + severity: critical + annotations: + summary: "RustFS Console service is down" + +- alert: RustFSEndpointDown + expr: up{job="rustfs-endpoint"} == 0 + for: 30s + labels: + severity: critical + annotations: + summary: "RustFS API Endpoint is down" +``` + +#### Docker Health Checks + +Built-in Docker health checks are configured in the Dockerfile: + +```dockerfile +HEALTHCHECK --interval=30s --timeout=10s --retries=3 \ + CMD curl -f http://localhost:9000/health && curl -f http://localhost:9001/health || exit 1 +``` + +Check health status: + +```bash +# View health status +docker ps --format "table {{.Names}}\t{{.Status}}" + +# Detailed health information +docker inspect rustfs --format='{{json .State.Health}}' | jq +``` + +### Logging and Auditing + +#### Separate Logging Targets + +Console and endpoint services use separate logging targets: + +**Console Logging Targets:** +- `rustfs::console::startup` - Server startup and configuration +- `rustfs::console::access` - HTTP access logs with timing +- `rustfs::console::error` - Console-specific errors +- `rustfs::console::shutdown` - Graceful shutdown logs + +**Endpoint Logging Targets:** +- `rustfs::endpoint::startup` - API server startup +- `rustfs::endpoint::access` - S3 API access logs +- `rustfs::endpoint::auth` - Authentication and authorization + +#### Centralized Logging + +```bash +# JSON structured logging +RUST_LOG="rustfs::console=info,rustfs::endpoint=info" \ +docker run -d rustfs/rustfs:latest + +# Forward to log aggregation +docker run -d \ + --log-driver=fluentd \ + --log-opt fluentd-address=localhost:24224 \ + --log-opt tag="rustfs.{{.Name}}" \ + rustfs/rustfs:latest +``` + +## Troubleshooting + +### Common Issues and Solutions + +#### 1. Console Cannot Access API + +**Symptoms**: Console UI shows connection errors, "Failed to load data" messages. + +**Cause**: Incorrect `RUSTFS_EXTERNAL_ADDRESS` configuration. + +**Solutions**: + +```bash +# For Docker with port mapping 9020:9000 (API) and 9021:9001 (Console) +RUSTFS_EXTERNAL_ADDRESS=":9020" # Must match the mapped host API port + +# For direct access without port mapping +RUSTFS_EXTERNAL_ADDRESS=":9000" # Must match the API service port + +# For Kubernetes or complex networking +RUSTFS_EXTERNAL_ADDRESS="http://rustfs-service:9000" # Use service name +``` + +**Debug steps**: +```bash +# Test API connectivity from console container +docker exec rustfs-container curl http://localhost:9000/health + +# Check CORS configuration +curl -H "Origin: http://localhost:9021" -v http://localhost:9020/health +``` + +#### 2. CORS Errors + +**Symptoms**: Browser console shows "Access to fetch blocked by CORS policy" errors. + +**Causes and Solutions**: + +```bash +# Allow specific origins (production) +RUSTFS_CORS_ALLOWED_ORIGINS="https://admin.yourdomain.com,https://backup.yourdomain.com" +RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="https://console.yourdomain.com" + +# Allow all origins (development only) +RUSTFS_CORS_ALLOWED_ORIGINS="*" +RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="*" + +# Docker deployment with port mapping +RUSTFS_CORS_ALLOWED_ORIGINS="http://localhost:9021,http://127.0.0.1:9021" +``` + +**Debug CORS issues**: +```bash +# Check actual request origin in browser network tab +# Ensure the origin matches CORS configuration + +# Test CORS with curl +curl -H "Origin: http://localhost:9021" \ + -H "Access-Control-Request-Method: GET" \ + -H "Access-Control-Request-Headers: authorization" \ + -X OPTIONS \ + http://localhost:9020/ +``` + +#### 3. Port Conflicts + +**Symptoms**: "Address already in use" or "bind: address already in use" errors. + +**Solutions**: + +```bash +# Check which process is using the port +sudo lsof -i :9000 +sudo lsof -i :9001 +sudo netstat -tulpn | grep :9000 + +# Kill conflicting process +sudo kill -9 + +# Use different ports +RUSTFS_ADDRESS=":8000" RUSTFS_CONSOLE_ADDRESS=":8001" rustfs /data + +# For Docker, change host port mapping +docker run -p 8020:9000 -p 8021:9001 rustfs/rustfs:latest +``` + +#### 4. TLS Certificate Issues + +**Symptoms**: "TLS handshake failed", "certificate verify failed" errors. + +**Solutions**: + +```bash +# Verify certificate files exist and are readable +ls -la /path/to/certs/ +# Should show cert.pem and key.pem with proper permissions + +# Test certificate validity +openssl x509 -in /path/to/certs/cert.pem -text -noout + +# Generate new certificates +openssl req -x509 -newkey rsa:4096 \ + -keyout /path/to/certs/key.pem \ + -out /path/to/certs/cert.pem \ + -days 365 -nodes \ + -subj "/C=US/O=RustFS/CN=localhost" + +# For Docker, ensure certificate volume mount is correct +docker run -v /host/path/to/certs:/certs:ro rustfs/rustfs:latest +``` + +#### 5. Service Not Starting + +**Symptoms**: Container exits immediately, "failed to start console server" errors. + +**Debug steps**: + +```bash +# Check container logs +docker logs rustfs-container + +# Enable debug logging +docker run -e RUST_LOG=debug rustfs/rustfs:latest + +# Check configuration +docker exec rustfs-container env | grep RUSTFS + +# Test configuration outside Docker +RUST_LOG=debug rustfs --help +``` + +#### 6. Health Check Failures + +**Symptoms**: Docker health checks fail, Kubernetes pods not ready. + +**Solutions**: + +```bash +# Test health endpoints manually +curl http://localhost:9000/health +curl http://localhost:9001/health + +# Check if services are listening +docker exec rustfs-container netstat -tulpn + +# Increase health check timeouts +# For Docker +HEALTHCHECK --interval=30s --timeout=30s --retries=5 + +# For Kubernetes +livenessProbe: + initialDelaySeconds: 60 + timeoutSeconds: 30 +``` + +#### 7. Docker Network Issues + +**Symptoms**: Services cannot communicate within Docker network. + +**Solutions**: + +```bash +# Check Docker network +docker network ls +docker inspect + +# Test connectivity between containers +docker exec container1 ping container2 +docker exec container1 curl http://container2:9000/health + +# Use Docker network aliases +docker run --network=my-network --network-alias=rustfs rustfs/rustfs:latest +``` + +### Debugging Commands + +#### Service Status + +```bash +# Check running containers +docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" + +# Check service logs +docker logs rustfs-container --tail=100 -f + +# Check resource usage +docker stats rustfs-container + +# Inspect container configuration +docker inspect rustfs-container | jq '.Config.Env' +``` + +#### Network Debugging + +```bash +# Test connectivity from host +curl -v http://localhost:9020/health +curl -v http://localhost:9021/health + +# Test from inside container +docker exec rustfs-container curl http://localhost:9000/health +docker exec rustfs-container curl http://localhost:9001/health + +# Check port listening +docker exec rustfs-container netstat -tulpn | grep -E ':(9000|9001)' +``` + +#### Configuration Debugging + +```bash +# Show effective configuration +docker exec rustfs-container env | grep RUSTFS | sort + +# Test configuration parsing +docker exec rustfs-container rustfs --help + +# Check file permissions +docker exec rustfs-container ls -la /certs/ +docker exec rustfs-container ls -la /data/ +``` + +### Getting Help + +#### Log Collection + +```bash +# Collect comprehensive logs +mkdir -p ./debug-logs +docker logs rustfs-container > ./debug-logs/container.log 2>&1 +docker inspect rustfs-container > ./debug-logs/inspect.json +docker exec rustfs-container env > ./debug-logs/environment.txt +docker exec rustfs-container ps aux > ./debug-logs/processes.txt +docker exec rustfs-container netstat -tulpn > ./debug-logs/network.txt +``` + +#### Community Support + +- **GitHub Issues**: [rustfs/rustfs/issues](https://github.com/rustfs/rustfs/issues) +- **Discussions**: [rustfs/rustfs/discussions](https://github.com/rustfs/rustfs/discussions) +- **Documentation**: Check the `docs/` directory for additional guides + +## Migration Guide + +### From Previous Versions + +Previous versions served the console from the same port as the S3 API. This section helps migrate to the separated architecture. + +#### Pre-Migration Checklist + +1. **Backup Configuration**: Save current environment variables and configuration files +2. **Document Current Setup**: Note current port usage, firewall rules, and proxy configurations +3. **Plan Downtime**: Brief service restart required for migration +4. **Update Clients**: Prepare to update console access URLs + +#### Step-by-Step Migration + +##### 1. Update Configuration + +```bash +# Old single-port configuration +RUSTFS_ADDRESS=":9000" + +# New separated configuration +RUSTFS_ADDRESS=":9000" # API port (unchanged) +RUSTFS_CONSOLE_ADDRESS=":9001" # Console port (new) +RUSTFS_EXTERNAL_ADDRESS=":9000" # For console→API communication +``` + +##### 2. Update Firewall Rules + +```bash +# Allow new console port +sudo ufw allow 9001/tcp + +# Optional: restrict console to internal networks +sudo ufw delete allow 9001/tcp +sudo ufw allow from 192.168.1.0/24 to any port 9001 +``` + +##### 3. Update Docker Deployments + +```bash +# Old deployment +docker run -p 9000:9000 rustfs/rustfs:legacy + +# New deployment with both ports +docker run \ + -p 9000:9000 \ # API port + -p 9001:9001 \ # Console port + -e RUSTFS_EXTERNAL_ADDRESS=":9000" \ + rustfs/rustfs:latest +``` + +##### 4. Update Application URLs + +- **API Endpoint**: `http://localhost:9000` (unchanged) +- **Console UI**: `http://localhost:9001/rustfs/console/` (new URL) + +##### 5. Update Monitoring and Health Checks + +```bash +# Add console health check +curl http://localhost:9001/health + +# Update monitoring configuration to check both endpoints +``` + +#### Docker Migration Example + +```bash +#!/bin/bash +# migrate-docker.sh + +# Stop old container +docker stop rustfs-old +docker rm rustfs-old + +# Start new separated services +docker run -d \ + --name rustfs-new \ + -p 9000:9000 \ + -p 9001:9001 \ + -e RUSTFS_EXTERNAL_ADDRESS=":9000" \ + -e RUSTFS_CORS_ALLOWED_ORIGINS="http://localhost:9001" \ + -v rustfs-data:/data \ + rustfs/rustfs:latest + +echo "Migration completed!" +echo "API: http://localhost:9000" +echo "Console: http://localhost:9001/rustfs/console/" +``` + +#### Kubernetes Migration + +```yaml +# Update deployment to expose both ports +apiVersion: apps/v1 +kind: Deployment +metadata: + name: rustfs +spec: + template: + spec: + containers: + - name: rustfs + ports: + - containerPort: 9000 + name: api + - containerPort: 9001 # Add console port + name: console + +--- +# Update service to include console port +apiVersion: v1 +kind: Service +metadata: + name: rustfs-service +spec: + ports: + - name: api + port: 9000 + - name: console # Add console service + port: 9001 +``` + +#### Rollback Plan + +If issues occur, you can disable the console to return to single-service mode: + +```bash +# Disable console service +RUSTFS_CONSOLE_ENABLE=false rustfs /data + +# Or use older image version temporarily +docker run rustfs/rustfs:legacy-tag +``` + +### Configuration Migration + +#### Environment Variable Changes + +```bash +# New variables (add these) +export RUSTFS_CONSOLE_ADDRESS=":9001" +export RUSTFS_EXTERNAL_ADDRESS=":9000" +export RUSTFS_CORS_ALLOWED_ORIGINS="*" +export RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="*" + +# Optional security variables +export RUSTFS_CONSOLE_RATE_LIMIT_ENABLE="true" +export RUSTFS_CONSOLE_RATE_LIMIT_RPM="100" +export RUSTFS_CONSOLE_AUTH_TIMEOUT="3600" +``` + +#### Validation + +After migration, validate the setup: + +```bash +# Check both services are running +curl http://localhost:9000/health # Should return API health +curl http://localhost:9001/health # Should return console health + +# Test console functionality +open http://localhost:9001/rustfs/console/ + +# Verify API still works +aws s3 ls --endpoint-url http://localhost:9000 +``` + +## Best Practices + +### Production Deployment + +#### Security Best Practices + +1. **Restrict Console Access** + ```bash + # Bind console to internal interface only + RUSTFS_CONSOLE_ADDRESS="127.0.0.1:9001" + + # Use restrictive CORS + RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="https://admin.yourdomain.com" + ``` + +2. **Enable TLS** + ```bash + # Use TLS for console + RUSTFS_TLS_PATH="/path/to/certs" + ``` + +3. **Configure Rate Limiting** + ```bash + # Prevent brute force attacks + RUSTFS_CONSOLE_RATE_LIMIT_ENABLE="true" + RUSTFS_CONSOLE_RATE_LIMIT_RPM="60" + ``` + +4. **Use Strong Credentials** + ```bash + # Generate secure credentials + RUSTFS_ACCESS_KEY="$(openssl rand -hex 16)" + RUSTFS_SECRET_KEY="$(openssl rand -hex 32)" + ``` + +#### Operational Best Practices + +1. **Independent Monitoring** + - Set up health checks for both API and console services + - Monitor resource usage separately + - Configure separate alerting rules + +2. **Network Segmentation** + - Use different networks for public API and internal console + - Implement proper firewall rules + - Consider using a reverse proxy for additional security + +3. **Logging Strategy** + - Configure separate log targets for console and API + - Use structured logging for better analysis + - Implement centralized log collection + +#### Docker Best Practices + +1. **Resource Limits** + ```yaml + services: + rustfs: + deploy: + resources: + limits: + memory: 1G + cpus: "0.5" + ``` + +2. **Health Checks** + ```yaml + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/health", "&&", "curl", "-f", "http://localhost:9001/health"] + interval: 30s + timeout: 10s + retries: 3 + ``` + +3. **Volume Management** + ```yaml + volumes: + - rustfs-data:/data + - rustfs-certs:/certs:ro + - rustfs-logs:/logs + ``` + +### Development Environment + +#### Development Best Practices + +1. **Permissive Configuration** + ```bash + # Allow all origins for development + RUSTFS_CORS_ALLOWED_ORIGINS="*" + RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="*" + + # Enable debug logging + RUST_LOG="debug" + ``` + +2. **Hot Reload Support** + ```bash + # Mount source code for development + docker run -v $(pwd):/app rustfs/rustfs:dev + ``` + +3. **Use Development Scripts** + ```bash + # Use provided development deployment + ./examples/enhanced-docker-deployment.sh dev + ``` + +### Monitoring and Observability + +#### Metrics Collection + +1. **Health Check Monitoring** + ```bash + # Regular health checks + */1 * * * * curl -f http://localhost:9000/health >/dev/null || echo "API down" + */1 * * * * curl -f http://localhost:9001/health >/dev/null || echo "Console down" + ``` + +2. **Performance Monitoring** + - Monitor response times for both services + - Track error rates separately + - Set up resource usage alerts + +3. **Business Metrics** + - Track console usage patterns + - Monitor API request patterns + - Measure service availability + +#### Alerting Strategy + +```yaml +# Example Prometheus alerting rules +groups: +- name: rustfs + rules: + - alert: RustFSAPIDown + expr: up{job="rustfs-api"} == 0 + for: 30s + labels: + severity: critical + annotations: + summary: RustFS API is down + + - alert: RustFSConsoleDown + expr: up{job="rustfs-console"} == 0 + for: 30s + labels: + severity: warning + annotations: + summary: RustFS Console is down + + - alert: HighResponseTime + expr: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 1 + for: 2m + labels: + severity: warning + annotations: + summary: High response time detected +``` + +### Troubleshooting Workflows + +#### Systematic Debugging Approach + +1. **Service Status Check** + ```bash + # Check if services are running + curl -f http://localhost:9000/health + curl -f http://localhost:9001/health + ``` + +2. **Network Connectivity** + ```bash + # Test from different network contexts + docker exec container curl http://localhost:9000/health + curl -H "Origin: http://localhost:9001" http://localhost:9000/health + ``` + +3. **Configuration Validation** + ```bash + # Verify environment variables + docker exec container env | grep RUSTFS | sort + ``` + +4. **Log Analysis** + ```bash + # Check service-specific logs + docker logs container 2>&1 | grep -E "(console|endpoint)" + ``` + +This comprehensive guide covers all aspects of RustFS console and endpoint service separation, from basic deployment to enterprise-grade production configurations. For additional support, refer to the example scripts in the `examples/` directory and the community resources listed in the troubleshooting section. \ No newline at end of file diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..6c5262d2 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,270 @@ +# RustFS Docker Deployment Examples + +This directory contains various deployment scripts and configuration files for RustFS with console and endpoint service separation. + +## Quick Start Scripts + +### `docker-quickstart.sh` +The fastest way to get RustFS running with different configurations. + +```bash +# Basic deployment (ports 9000-9001) +./docker-quickstart.sh basic + +# Development environment (ports 9010-9011) +./docker-quickstart.sh dev + +# Production-like deployment (ports 9020-9021) +./docker-quickstart.sh prod + +# Check status of all deployments +./docker-quickstart.sh status + +# Test health of all running services +./docker-quickstart.sh test + +# Clean up all containers +./docker-quickstart.sh cleanup +``` + +### `enhanced-docker-deployment.sh` +Comprehensive deployment script with multiple scenarios and detailed logging. + +```bash +# Deploy individual scenarios +./enhanced-docker-deployment.sh basic # Basic setup with port mapping +./enhanced-docker-deployment.sh dev # Development environment +./enhanced-docker-deployment.sh prod # Production-like with security + +# Deploy all scenarios at once +./enhanced-docker-deployment.sh all + +# Check status and test services +./enhanced-docker-deployment.sh status +./enhanced-docker-deployment.sh test + +# View logs for specific container +./enhanced-docker-deployment.sh logs rustfs-dev + +# Complete cleanup +./enhanced-docker-deployment.sh cleanup +``` + +### `enhanced-security-deployment.sh` +Production-ready deployment with enhanced security features including TLS, rate limiting, and secure credential generation. + +```bash +# Deploy with security hardening +./enhanced-security-deployment.sh + +# Features: +# - Automatic TLS certificate generation +# - Secure credential generation +# - Rate limiting configuration +# - Console access restrictions +# - Health check validation +``` + +## Docker Compose Examples + +### `docker-comprehensive.yml` +Complete Docker Compose configuration with multiple deployment profiles. + +```bash +# Deploy specific profiles +docker-compose -f docker-comprehensive.yml --profile basic up -d +docker-compose -f docker-comprehensive.yml --profile dev up -d +docker-compose -f docker-comprehensive.yml --profile production up -d +docker-compose -f docker-comprehensive.yml --profile enterprise up -d +docker-compose -f docker-comprehensive.yml --profile api-only up -d + +# Deploy with reverse proxy +docker-compose -f docker-comprehensive.yml --profile production --profile nginx up -d +``` + +#### Available Profiles: + +- **basic**: Simple deployment for testing (ports 9000-9001) +- **dev**: Development environment with debug logging (ports 9010-9011) +- **production**: Production deployment with security (ports 9020-9021) +- **enterprise**: Full enterprise setup with TLS (ports 9030-9443) +- **api-only**: API endpoint without console (port 9040) + +## Usage Examples by Scenario + +### Development Setup + +```bash +# Quick development start +./docker-quickstart.sh dev + +# Or use enhanced deployment for more features +./enhanced-docker-deployment.sh dev + +# Or use Docker Compose +docker-compose -f docker-comprehensive.yml --profile dev up -d +``` + +**Access Points:** +- API: http://localhost:9010 (or 9030 for enhanced) +- Console: http://localhost:9011/rustfs/console/ (or 9031 for enhanced) +- Credentials: dev-admin / dev-secret + +### Production Deployment + +```bash +# Security-hardened deployment +./enhanced-security-deployment.sh + +# Or production profile +./enhanced-docker-deployment.sh prod +``` + +**Features:** +- TLS encryption for console +- Rate limiting enabled +- Restricted CORS policies +- Secure credential generation +- Console bound to localhost only + +### Testing and CI/CD + +```bash +# API-only deployment for testing +docker-compose -f docker-comprehensive.yml --profile api-only up -d + +# Quick basic setup for integration tests +./docker-quickstart.sh basic +``` + +## Configuration Examples + +### Environment Variables + +All deployment scripts support customization via environment variables: + +```bash +# Custom image and ports +export RUSTFS_IMAGE="rustfs/rustfs:custom-tag" +export CONSOLE_PORT="8001" +export API_PORT="8000" + +# Custom data directories +export DATA_DIR="/custom/data/path" +export CERTS_DIR="/custom/certs/path" + +# Run with custom configuration +./enhanced-security-deployment.sh +``` + +### Common Configurations + +```bash +# Development - permissive CORS +RUSTFS_CORS_ALLOWED_ORIGINS="*" +RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="*" + +# Production - restrictive CORS +RUSTFS_CORS_ALLOWED_ORIGINS="https://myapp.com,https://api.myapp.com" +RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="https://admin.myapp.com" + +# Security hardening +RUSTFS_CONSOLE_RATE_LIMIT_ENABLE="true" +RUSTFS_CONSOLE_RATE_LIMIT_RPM="60" +RUSTFS_CONSOLE_AUTH_TIMEOUT="1800" +``` + +## Monitoring and Health Checks + +All deployments include health check endpoints: + +```bash +# Test API health +curl http://localhost:9000/health + +# Test console health +curl http://localhost:9001/health + +# Test all deployments +./docker-quickstart.sh test +./enhanced-docker-deployment.sh test +``` + +## Network Architecture + +### Port Mappings + +| Deployment | API Port | Console Port | Description | +|-----------|----------|--------------|-------------| +| Basic | 9000 | 9001 | Simple deployment | +| Dev | 9010 | 9011 | Development environment | +| Prod | 9020 | 9021 | Production-like setup | +| Enterprise | 9030 | 9443 | Enterprise with TLS | +| API-Only | 9040 | - | API endpoint only | + +### Network Isolation + +Production deployments use network isolation: + +- **Public API Network**: Exposes API endpoints to external clients +- **Internal Console Network**: Restricts console access to internal networks +- **Secure Network**: Isolated network for enterprise deployments + +## Security Considerations + +### Development +- Permissive CORS policies for easy testing +- Debug logging enabled +- Default credentials for simplicity + +### Production +- Restrictive CORS policies +- TLS encryption for console +- Rate limiting enabled +- Secure credential generation +- Console bound to localhost +- Network isolation + +### Enterprise +- Complete TLS encryption +- Advanced rate limiting +- Authentication timeouts +- Secret management +- Network segregation + +## Troubleshooting + +### Common Issues + +1. **Port Conflicts**: Use different ports via environment variables +2. **CORS Errors**: Check origin configuration and browser network tab +3. **Health Check Failures**: Verify services are running and ports are accessible +4. **Permission Issues**: Check volume mount permissions and certificate file permissions + +### Debug Commands + +```bash +# Check container logs +docker logs rustfs-container + +# Check container environment +docker exec rustfs-container env | grep RUSTFS + +# Test connectivity +docker exec rustfs-container curl http://localhost:9000/health +docker exec rustfs-container curl http://localhost:9001/health + +# Check listening ports +docker exec rustfs-container netstat -tulpn | grep -E ':(9000|9001)' +``` + +## Migration from Previous Versions + +See [docs/console-separation.md](../docs/console-separation.md) for detailed migration instructions from single-port deployments to the separated architecture. + +## Additional Resources + +- [Console Separation Documentation](../docs/console-separation.md) +- [Docker Compose Configuration](../docker-compose.yml) +- [Main Dockerfile](../Dockerfile) +- [Security Best Practices](../docs/console-separation.md#security-hardening) \ No newline at end of file diff --git a/examples/docker-comprehensive.yml b/examples/docker-comprehensive.yml new file mode 100644 index 00000000..566b053a --- /dev/null +++ b/examples/docker-comprehensive.yml @@ -0,0 +1,224 @@ +# RustFS Comprehensive Docker Deployment Examples +# This file demonstrates various deployment scenarios for RustFS with console separation + +version: "3.8" + +services: + # Basic deployment with default settings + rustfs-basic: + image: rustfs/rustfs:latest + container_name: rustfs-basic + ports: + - "9000:9000" # API endpoint + - "9001:9001" # Console interface + environment: + - RUSTFS_ADDRESS=0.0.0.0:9000 + - RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001 + - RUSTFS_EXTERNAL_ADDRESS=:9000 + - RUSTFS_CORS_ALLOWED_ORIGINS=http://localhost:9001 + - RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS=* + - RUSTFS_ACCESS_KEY=admin + - RUSTFS_SECRET_KEY=password + volumes: + - rustfs-basic-data:/data + networks: + - rustfs-network + restart: unless-stopped + healthcheck: + test: ["CMD", "sh", "-c", "curl -f http://localhost:9000/health && curl -f http://localhost:9001/health"] + interval: 30s + timeout: 10s + retries: 3 + profiles: + - basic + + # Development environment with debug logging + rustfs-dev: + image: rustfs/rustfs:latest + container_name: rustfs-dev + ports: + - "9010:9000" # API endpoint + - "9011:9001" # Console interface + environment: + - RUSTFS_ADDRESS=0.0.0.0:9000 + - RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001 + - RUSTFS_EXTERNAL_ADDRESS=:9010 + - RUSTFS_CORS_ALLOWED_ORIGINS=* + - RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS=* + - RUSTFS_ACCESS_KEY=dev-admin + - RUSTFS_SECRET_KEY=dev-password + - RUST_LOG=debug + - RUSTFS_LOG_LEVEL=debug + volumes: + - rustfs-dev-data:/data + - rustfs-dev-logs:/logs + networks: + - rustfs-network + restart: unless-stopped + healthcheck: + test: ["CMD", "sh", "-c", "curl -f http://localhost:9000/health && curl -f http://localhost:9001/health"] + interval: 30s + timeout: 10s + retries: 3 + profiles: + - dev + + # Production environment with security hardening + rustfs-production: + image: rustfs/rustfs:latest + container_name: rustfs-production + ports: + - "9020:9000" # API endpoint (public) + - "127.0.0.1:9021:9001" # Console (localhost only) + environment: + - RUSTFS_ADDRESS=0.0.0.0:9000 + - RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001 + - RUSTFS_EXTERNAL_ADDRESS=:9020 + - RUSTFS_CORS_ALLOWED_ORIGINS=https://myapp.com,https://api.myapp.com + - RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS=https://admin.myapp.com + - RUSTFS_CONSOLE_RATE_LIMIT_ENABLE=true + - RUSTFS_CONSOLE_RATE_LIMIT_RPM=60 + - RUSTFS_CONSOLE_AUTH_TIMEOUT=1800 + - RUSTFS_ACCESS_KEY_FILE=/run/secrets/rustfs_access_key + - RUSTFS_SECRET_KEY_FILE=/run/secrets/rustfs_secret_key + volumes: + - rustfs-production-data:/data + - rustfs-production-logs:/logs + - rustfs-certs:/certs:ro + networks: + - rustfs-network + secrets: + - rustfs_access_key + - rustfs_secret_key + restart: unless-stopped + healthcheck: + test: ["CMD", "sh", "-c", "curl -f http://localhost:9000/health && curl -f http://localhost:9001/health"] + interval: 30s + timeout: 10s + retries: 3 + profiles: + - production + + # Enterprise deployment with TLS and full security + rustfs-enterprise: + image: rustfs/rustfs:latest + container_name: rustfs-enterprise + ports: + - "9030:9000" # API endpoint + - "127.0.0.1:9443:9001" # Console with TLS (localhost only) + environment: + - RUSTFS_ADDRESS=0.0.0.0:9000 + - RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001 + - RUSTFS_EXTERNAL_ADDRESS=:9030 + - RUSTFS_TLS_PATH=/certs + - RUSTFS_CORS_ALLOWED_ORIGINS=https://enterprise.com + - RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS=https://admin.enterprise.com + - RUSTFS_CONSOLE_RATE_LIMIT_ENABLE=true + - RUSTFS_CONSOLE_RATE_LIMIT_RPM=30 + - RUSTFS_CONSOLE_AUTH_TIMEOUT=900 + volumes: + - rustfs-enterprise-data:/data + - rustfs-enterprise-logs:/logs + - rustfs-enterprise-certs:/certs:ro + networks: + - rustfs-secure-network + secrets: + - rustfs_enterprise_access_key + - rustfs_enterprise_secret_key + restart: unless-stopped + healthcheck: + test: ["CMD", "sh", "-c", "curl -f http://localhost:9000/health && curl -k -f https://localhost:9001/health"] + interval: 30s + timeout: 10s + retries: 3 + profiles: + - enterprise + + # API-only deployment (console disabled) + rustfs-api-only: + image: rustfs/rustfs:latest + container_name: rustfs-api-only + ports: + - "9040:9000" # API endpoint only + environment: + - RUSTFS_ADDRESS=0.0.0.0:9000 + - RUSTFS_CONSOLE_ENABLE=false + - RUSTFS_CORS_ALLOWED_ORIGINS=https://client-app.com + - RUSTFS_ACCESS_KEY=api-only-key + - RUSTFS_SECRET_KEY=api-only-secret + volumes: + - rustfs-api-data:/data + networks: + - rustfs-network + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/health"] + interval: 30s + timeout: 10s + retries: 3 + profiles: + - api-only + + # Nginx reverse proxy for production + nginx-proxy: + image: nginx:alpine + container_name: rustfs-nginx + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro + - ./nginx/ssl:/etc/nginx/ssl:ro + networks: + - rustfs-network + restart: unless-stopped + depends_on: + - rustfs-production + profiles: + - production + - enterprise + +networks: + rustfs-network: + driver: bridge + ipam: + config: + - subnet: 172.20.0.0/16 + rustfs-secure-network: + driver: bridge + internal: true + ipam: + config: + - subnet: 172.21.0.0/16 + +volumes: + rustfs-basic-data: + driver: local + rustfs-dev-data: + driver: local + rustfs-dev-logs: + driver: local + rustfs-production-data: + driver: local + rustfs-production-logs: + driver: local + rustfs-enterprise-data: + driver: local + rustfs-enterprise-logs: + driver: local + rustfs-enterprise-certs: + driver: local + rustfs-api-data: + driver: local + rustfs-certs: + driver: local + +secrets: + rustfs_access_key: + external: true + rustfs_secret_key: + external: true + rustfs_enterprise_access_key: + external: true + rustfs_enterprise_secret_key: + external: true \ No newline at end of file diff --git a/examples/docker-quickstart.sh b/examples/docker-quickstart.sh new file mode 100755 index 00000000..03ceb78a --- /dev/null +++ b/examples/docker-quickstart.sh @@ -0,0 +1,295 @@ +#!/bin/bash + +# RustFS Docker Quick Start Script +# This script provides easy deployment commands for different scenarios + +set -e + +# Colors for output +GREEN='\033[0;32m' +BLUE='\033[0;34m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +log() { + echo -e "${GREEN}[RustFS]${NC} $1" +} + +info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Print banner +print_banner() { + echo -e "${BLUE}" + echo "==================================================" + echo " RustFS Docker Quick Start" + echo " Console & Endpoint Separation" + echo "==================================================" + echo -e "${NC}" +} + +# Check Docker availability +check_docker() { + if ! command -v docker &> /dev/null; then + error "Docker is not installed or not available in PATH" + exit 1 + fi + info "Docker is available: $(docker --version)" +} + +# Quick start - basic deployment +quick_basic() { + log "Starting RustFS basic deployment..." + + docker run -d \ + --name rustfs-quick \ + -p 9000:9000 \ + -p 9001:9001 \ + -e RUSTFS_EXTERNAL_ADDRESS=":9000" \ + -e RUSTFS_CORS_ALLOWED_ORIGINS="http://localhost:9001" \ + -v rustfs-quick-data:/data \ + rustfs/rustfs:latest + + echo + info "✅ RustFS deployed successfully!" + info "🌐 API Endpoint: http://localhost:9000" + info "🖥️ Console UI: http://localhost:9001/rustfs/console/" + info "🔐 Credentials: rustfsadmin / rustfsadmin" + info "🏥 Health Check: curl http://localhost:9000/health" + echo + info "To stop: docker stop rustfs-quick" + info "To remove: docker rm rustfs-quick && docker volume rm rustfs-quick-data" +} + +# Development deployment with debug logging +quick_dev() { + log "Starting RustFS development environment..." + + docker run -d \ + --name rustfs-dev \ + -p 9010:9000 \ + -p 9011:9001 \ + -e RUSTFS_EXTERNAL_ADDRESS=":9010" \ + -e RUSTFS_CORS_ALLOWED_ORIGINS="*" \ + -e RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="*" \ + -e RUSTFS_ACCESS_KEY="dev-admin" \ + -e RUSTFS_SECRET_KEY="dev-secret" \ + -e RUST_LOG="debug" \ + -v rustfs-dev-data:/data \ + rustfs/rustfs:latest + + echo + info "✅ RustFS development environment ready!" + info "🌐 API Endpoint: http://localhost:9010" + info "🖥️ Console UI: http://localhost:9011/rustfs/console/" + info "🔐 Credentials: dev-admin / dev-secret" + info "📊 Debug logging enabled" + echo + info "To stop: docker stop rustfs-dev" +} + +# Production-like deployment +quick_prod() { + log "Starting RustFS production-like deployment..." + + # Generate secure credentials + ACCESS_KEY="prod-$(openssl rand -hex 8)" + SECRET_KEY="$(openssl rand -hex 24)" + + docker run -d \ + --name rustfs-prod \ + -p 9020:9000 \ + -p 127.0.0.1:9021:9001 \ + -e RUSTFS_EXTERNAL_ADDRESS=":9020" \ + -e RUSTFS_CORS_ALLOWED_ORIGINS="https://myapp.com" \ + -e RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="https://admin.myapp.com" \ + -e RUSTFS_CONSOLE_RATE_LIMIT_ENABLE="true" \ + -e RUSTFS_CONSOLE_RATE_LIMIT_RPM="60" \ + -e RUSTFS_ACCESS_KEY="$ACCESS_KEY" \ + -e RUSTFS_SECRET_KEY="$SECRET_KEY" \ + -v rustfs-prod-data:/data \ + rustfs/rustfs:latest + + # Save credentials + echo "RUSTFS_ACCESS_KEY=$ACCESS_KEY" > rustfs-prod-credentials.txt + echo "RUSTFS_SECRET_KEY=$SECRET_KEY" >> rustfs-prod-credentials.txt + chmod 600 rustfs-prod-credentials.txt + + echo + info "✅ RustFS production deployment ready!" + info "🌐 API Endpoint: http://localhost:9020 (public)" + info "🖥️ Console UI: http://127.0.0.1:9021/rustfs/console/ (localhost only)" + info "🔐 Credentials saved to rustfs-prod-credentials.txt" + info "🔒 Console restricted to localhost for security" + echo + warn "⚠️ Change default CORS origins for production use" +} + +# Stop and cleanup +cleanup() { + log "Cleaning up RustFS deployments..." + + docker stop rustfs-quick rustfs-dev rustfs-prod 2>/dev/null || true + docker rm rustfs-quick rustfs-dev rustfs-prod 2>/dev/null || true + + info "Containers stopped and removed" + echo + info "To also remove data volumes, run:" + info "docker volume rm rustfs-quick-data rustfs-dev-data rustfs-prod-data" +} + +# Show status of all deployments +status() { + log "RustFS deployment status:" + echo + + if docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -q rustfs; then + docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | head -n1 + docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep rustfs + else + info "No RustFS containers are currently running" + fi + + echo + info "Available endpoints:" + + if docker ps --filter "name=rustfs-quick" --format "{{.Names}}" | grep -q rustfs-quick; then + echo " Basic: http://localhost:9000 (API) | http://localhost:9001/rustfs/console/ (Console)" + fi + + if docker ps --filter "name=rustfs-dev" --format "{{.Names}}" | grep -q rustfs-dev; then + echo " Dev: http://localhost:9010 (API) | http://localhost:9011/rustfs/console/ (Console)" + fi + + if docker ps --filter "name=rustfs-prod" --format "{{.Names}}" | grep -q rustfs-prod; then + echo " Prod: http://localhost:9020 (API) | http://127.0.0.1:9021/rustfs/console/ (Console)" + fi +} + +# Test deployments +test_deployments() { + log "Testing RustFS deployments..." + echo + + # Test basic deployment + if docker ps --filter "name=rustfs-quick" --format "{{.Names}}" | grep -q rustfs-quick; then + info "Testing basic deployment..." + if curl -s -f http://localhost:9000/health | grep -q "ok"; then + echo " ✅ API health check: PASS" + else + echo " ❌ API health check: FAIL" + fi + + if curl -s -f http://localhost:9001/health | grep -q "console"; then + echo " ✅ Console health check: PASS" + else + echo " ❌ Console health check: FAIL" + fi + fi + + # Test dev deployment + if docker ps --filter "name=rustfs-dev" --format "{{.Names}}" | grep -q rustfs-dev; then + info "Testing development deployment..." + if curl -s -f http://localhost:9010/health | grep -q "ok"; then + echo " ✅ Dev API health check: PASS" + else + echo " ❌ Dev API health check: FAIL" + fi + + if curl -s -f http://localhost:9011/health | grep -q "console"; then + echo " ✅ Dev Console health check: PASS" + else + echo " ❌ Dev Console health check: FAIL" + fi + fi + + # Test prod deployment + if docker ps --filter "name=rustfs-prod" --format "{{.Names}}" | grep -q rustfs-prod; then + info "Testing production deployment..." + if curl -s -f http://localhost:9020/health | grep -q "ok"; then + echo " ✅ Prod API health check: PASS" + else + echo " ❌ Prod API health check: FAIL" + fi + + if curl -s -f http://127.0.0.1:9021/health | grep -q "console"; then + echo " ✅ Prod Console health check: PASS" + else + echo " ❌ Prod Console health check: FAIL" + fi + fi +} + +# Show help +show_help() { + print_banner + echo "Usage: $0 [command]" + echo + echo "Commands:" + echo " basic Start basic RustFS deployment (ports 9000-9001)" + echo " dev Start development deployment with debug logging (ports 9010-9011)" + echo " prod Start production-like deployment with security (ports 9020-9021)" + echo " status Show status of running deployments" + echo " test Test health of all running deployments" + echo " cleanup Stop and remove all RustFS containers" + echo " help Show this help message" + echo + echo "Examples:" + echo " $0 basic # Quick start with default settings" + echo " $0 dev # Development environment with debug logs" + echo " $0 prod # Production-like setup with security" + echo " $0 status # Check what's running" + echo " $0 test # Test all deployments" + echo " $0 cleanup # Clean everything up" + echo + echo "For more advanced deployments, see:" + echo " - examples/enhanced-docker-deployment.sh" + echo " - examples/enhanced-security-deployment.sh" + echo " - examples/docker-comprehensive.yml" + echo " - docs/console-separation.md" + echo +} + +# Main execution +case "${1:-help}" in + "basic") + print_banner + check_docker + quick_basic + ;; + "dev") + print_banner + check_docker + quick_dev + ;; + "prod") + print_banner + check_docker + quick_prod + ;; + "status") + print_banner + status + ;; + "test") + print_banner + test_deployments + ;; + "cleanup") + print_banner + cleanup + ;; + "help"|*) + show_help + ;; +esac \ No newline at end of file diff --git a/examples/enhanced-docker-deployment.sh b/examples/enhanced-docker-deployment.sh new file mode 100755 index 00000000..0baefda4 --- /dev/null +++ b/examples/enhanced-docker-deployment.sh @@ -0,0 +1,321 @@ +#!/bin/bash + +# RustFS Enhanced Docker Deployment Examples +# This script demonstrates various deployment scenarios for RustFS with console separation + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +log_section() { + echo -e "\n${BLUE}========================================${NC}" + echo -e "${BLUE}$1${NC}" + echo -e "${BLUE}========================================${NC}\n" +} + +# Function to clean up existing containers +cleanup() { + log_info "Cleaning up existing RustFS containers..." + docker stop rustfs-basic rustfs-dev rustfs-prod 2>/dev/null || true + docker rm rustfs-basic rustfs-dev rustfs-prod 2>/dev/null || true +} + +# Function to wait for service to be ready +wait_for_service() { + local url=$1 + local service_name=$2 + local max_attempts=30 + local attempt=0 + + log_info "Waiting for $service_name to be ready at $url..." + + while [ $attempt -lt $max_attempts ]; do + if curl -s -f "$url" > /dev/null 2>&1; then + log_info "$service_name is ready!" + return 0 + fi + attempt=$((attempt + 1)) + sleep 1 + done + + log_error "$service_name failed to start within ${max_attempts}s" + return 1 +} + +# Scenario 1: Basic deployment with port mapping +deploy_basic() { + log_section "Scenario 1: Basic Docker Deployment with Port Mapping" + + log_info "Starting RustFS with port mapping 9020:9000 and 9021:9001" + + docker run -d \ + --name rustfs-basic \ + -p 9020:9000 \ + -p 9021:9001 \ + -e RUSTFS_EXTERNAL_ADDRESS=":9020" \ + -e RUSTFS_CORS_ALLOWED_ORIGINS="http://localhost:9021,http://127.0.0.1:9021" \ + -e RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="*" \ + -e RUSTFS_ACCESS_KEY="basic-access" \ + -e RUSTFS_SECRET_KEY="basic-secret" \ + -v rustfs-basic-data:/data \ + rustfs/rustfs:latest + + # Wait for services to be ready + wait_for_service "http://localhost:9020/health" "API Service" + wait_for_service "http://localhost:9021/health" "Console Service" + + log_info "Basic deployment ready!" + log_info "🌐 API endpoint: http://localhost:9020" + log_info "🖥️ Console UI: http://localhost:9021/rustfs/console/" + log_info "🔐 Credentials: basic-access / basic-secret" + log_info "🏥 Health checks:" + log_info " API: curl http://localhost:9020/health" + log_info " Console: curl http://localhost:9021/health" +} + +# Scenario 2: Development environment +deploy_development() { + log_section "Scenario 2: Development Environment" + + log_info "Starting RustFS development environment" + + docker run -d \ + --name rustfs-dev \ + -p 9030:9000 \ + -p 9031:9001 \ + -e RUSTFS_EXTERNAL_ADDRESS=":9030" \ + -e RUSTFS_CORS_ALLOWED_ORIGINS="*" \ + -e RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="*" \ + -e RUSTFS_ACCESS_KEY="dev-access" \ + -e RUSTFS_SECRET_KEY="dev-secret" \ + -e RUST_LOG="debug" \ + -v rustfs-dev-data:/data \ + rustfs/rustfs:latest + + # Wait for services to be ready + wait_for_service "http://localhost:9030/health" "Dev API Service" + wait_for_service "http://localhost:9031/health" "Dev Console Service" + + log_info "Development deployment ready!" + log_info "🌐 API endpoint: http://localhost:9030" + log_info "🖥️ Console UI: http://localhost:9031/rustfs/console/" + log_info "🔐 Credentials: dev-access / dev-secret" + log_info "📊 Debug logging enabled" + log_info "🏥 Health checks:" + log_info " API: curl http://localhost:9030/health" + log_info " Console: curl http://localhost:9031/health" +} + +# Scenario 3: Production-like environment with security +deploy_production() { + log_section "Scenario 3: Production-like Deployment" + + log_info "Starting RustFS production-like environment with security" + + # Generate secure credentials + ACCESS_KEY=$(openssl rand -hex 16) + SECRET_KEY=$(openssl rand -hex 32) + + # Save credentials for reference + cat > rustfs-prod-credentials.env << EOF +# RustFS Production Deployment Credentials +# Generated: $(date) +RUSTFS_ACCESS_KEY=$ACCESS_KEY +RUSTFS_SECRET_KEY=$SECRET_KEY +EOF + chmod 600 rustfs-prod-credentials.env + + docker run -d \ + --name rustfs-prod \ + -p 9040:9000 \ + -p 127.0.0.1:9041:9001 \ + -e RUSTFS_ADDRESS="0.0.0.0:9000" \ + -e RUSTFS_CONSOLE_ADDRESS="0.0.0.0:9001" \ + -e RUSTFS_EXTERNAL_ADDRESS=":9040" \ + -e RUSTFS_CORS_ALLOWED_ORIGINS="https://myapp.example.com" \ + -e RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="https://admin.example.com" \ + -e RUSTFS_ACCESS_KEY="$ACCESS_KEY" \ + -e RUSTFS_SECRET_KEY="$SECRET_KEY" \ + -v rustfs-prod-data:/data \ + rustfs/rustfs:latest + + # Wait for services to be ready + wait_for_service "http://localhost:9040/health" "Prod API Service" + wait_for_service "http://127.0.0.1:9041/health" "Prod Console Service" + + log_info "Production deployment ready!" + log_info "🌐 API endpoint: http://localhost:9040 (public)" + log_info "🖥️ Console UI: http://127.0.0.1:9041/rustfs/console/ (localhost only)" + log_info "🔐 Credentials: $ACCESS_KEY / $SECRET_KEY" + log_info "🔒 Security: Console restricted to localhost" + log_info "🏥 Health checks:" + log_info " API: curl http://localhost:9040/health" + log_info " Console: curl http://127.0.0.1:9041/health" + log_warn "⚠️ Console is restricted to localhost for security" + log_warn "⚠️ Credentials saved to rustfs-prod-credentials.env file" +} + +# Function to show service status +show_status() { + log_section "Service Status" + + echo "Running containers:" + docker ps --filter "name=rustfs-" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" + + echo -e "\nService endpoints:" + if docker ps --filter "name=rustfs-basic" --format "{{.Names}}" | grep -q rustfs-basic; then + echo " Basic API: http://localhost:9020" + echo " Basic Console: http://localhost:9021/rustfs/console/" + fi + + if docker ps --filter "name=rustfs-dev" --format "{{.Names}}" | grep -q rustfs-dev; then + echo " Dev API: http://localhost:9030" + echo " Dev Console: http://localhost:9031/rustfs/console/" + fi + + if docker ps --filter "name=rustfs-prod" --format "{{.Names}}" | grep -q rustfs-prod; then + echo " Prod API: http://localhost:9040" + echo " Prod Console: http://127.0.0.1:9041/rustfs/console/" + fi +} + +# Function to test services +test_services() { + log_section "Testing Services" + + # Test basic deployment + if docker ps --filter "name=rustfs-basic" --format "{{.Names}}" | grep -q rustfs-basic; then + log_info "Testing basic deployment..." + if curl -s http://localhost:9020/health | grep -q "ok"; then + log_info "✓ Basic API health check passed" + else + log_error "✗ Basic API health check failed" + fi + + if curl -s http://localhost:9021/health | grep -q "console"; then + log_info "✓ Basic Console health check passed" + else + log_error "✗ Basic Console health check failed" + fi + fi + + # Test development deployment + if docker ps --filter "name=rustfs-dev" --format "{{.Names}}" | grep -q rustfs-dev; then + log_info "Testing development deployment..." + if curl -s http://localhost:9030/health | grep -q "ok"; then + log_info "✓ Dev API health check passed" + else + log_error "✗ Dev API health check failed" + fi + + if curl -s http://localhost:9031/health | grep -q "console"; then + log_info "✓ Dev Console health check passed" + else + log_error "✗ Dev Console health check failed" + fi + fi + + # Test production deployment + if docker ps --filter "name=rustfs-prod" --format "{{.Names}}" | grep -q rustfs-prod; then + log_info "Testing production deployment..." + if curl -s http://localhost:9040/health | grep -q "ok"; then + log_info "✓ Prod API health check passed" + else + log_error "✗ Prod API health check failed" + fi + + if curl -s http://127.0.0.1:9041/health | grep -q "console"; then + log_info "✓ Prod Console health check passed" + else + log_error "✗ Prod Console health check failed" + fi + fi +} + +# Function to show logs +show_logs() { + log_section "Service Logs" + + if [ -n "$1" ]; then + docker logs "$1" + else + echo "Available containers:" + docker ps --filter "name=rustfs-" --format "{{.Names}}" + echo -e "\nUsage: $0 logs " + fi +} + +# Main menu +case "${1:-menu}" in + "basic") + cleanup + deploy_basic + ;; + "dev") + cleanup + deploy_development + ;; + "prod") + cleanup + deploy_production + ;; + "all") + cleanup + deploy_basic + deploy_development + deploy_production + show_status + ;; + "status") + show_status + ;; + "test") + test_services + ;; + "logs") + show_logs "$2" + ;; + "cleanup") + cleanup + docker volume rm rustfs-basic-data rustfs-dev-data rustfs-prod-data 2>/dev/null || true + log_info "Cleanup completed" + ;; + "menu"|*) + echo "RustFS Enhanced Docker Deployment Examples" + echo "" + echo "Usage: $0 [command]" + echo "" + echo "Commands:" + echo " basic - Deploy basic RustFS with port mapping" + echo " dev - Deploy development environment" + echo " prod - Deploy production-like environment" + echo " all - Deploy all scenarios" + echo " status - Show status of running containers" + echo " test - Test all running services" + echo " logs - Show logs for specific container" + echo " cleanup - Clean up all containers and volumes" + echo "" + echo "Examples:" + echo " $0 basic # Deploy basic setup" + echo " $0 status # Check running services" + echo " $0 logs rustfs-dev # Show dev container logs" + echo " $0 cleanup # Clean everything up" + ;; +esac \ No newline at end of file diff --git a/examples/enhanced-security-deployment.sh b/examples/enhanced-security-deployment.sh new file mode 100755 index 00000000..d5c2aa33 --- /dev/null +++ b/examples/enhanced-security-deployment.sh @@ -0,0 +1,207 @@ +#!/bin/bash + +# RustFS Enhanced Security Deployment Script +# This script demonstrates production-ready deployment with enhanced security features + +set -e + +# Configuration +RUSTFS_IMAGE="${RUSTFS_IMAGE:-rustfs/rustfs:latest}" +CONTAINER_NAME="${CONTAINER_NAME:-rustfs-secure}" +DATA_DIR="${DATA_DIR:-./data}" +CERTS_DIR="${CERTS_DIR:-./certs}" +CONSOLE_PORT="${CONSOLE_PORT:-9443}" +API_PORT="${API_PORT:-9000}" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +log() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +error() { + echo -e "${RED}[ERROR]${NC} $1" + exit 1 +} + +success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +# Check if Docker is available +check_docker() { + if ! command -v docker &> /dev/null; then + error "Docker is not installed or not in PATH" + fi + log "Docker is available" +} + +# Generate TLS certificates for console +generate_certs() { + if [[ ! -d "$CERTS_DIR" ]]; then + mkdir -p "$CERTS_DIR" + log "Created certificates directory: $CERTS_DIR" + fi + + if [[ ! -f "$CERTS_DIR/console.crt" ]] || [[ ! -f "$CERTS_DIR/console.key" ]]; then + log "Generating TLS certificates for console..." + openssl req -x509 -newkey rsa:4096 \ + -keyout "$CERTS_DIR/console.key" \ + -out "$CERTS_DIR/console.crt" \ + -days 365 -nodes \ + -subj "/C=US/ST=CA/L=SF/O=RustFS/CN=localhost" + + chmod 600 "$CERTS_DIR/console.key" + chmod 644 "$CERTS_DIR/console.crt" + success "TLS certificates generated" + else + log "TLS certificates already exist" + fi +} + +# Create data directory +create_data_dir() { + if [[ ! -d "$DATA_DIR" ]]; then + mkdir -p "$DATA_DIR" + log "Created data directory: $DATA_DIR" + fi +} + +# Generate secure credentials +generate_credentials() { + if [[ -z "$RUSTFS_ACCESS_KEY" ]]; then + export RUSTFS_ACCESS_KEY="admin-$(openssl rand -hex 8)" + log "Generated access key: $RUSTFS_ACCESS_KEY" + fi + + if [[ -z "$RUSTFS_SECRET_KEY" ]]; then + export RUSTFS_SECRET_KEY="$(openssl rand -hex 32)" + log "Generated secret key: [HIDDEN]" + fi + + # Save credentials to .env file + cat > .env << EOF +RUSTFS_ACCESS_KEY=$RUSTFS_ACCESS_KEY +RUSTFS_SECRET_KEY=$RUSTFS_SECRET_KEY +EOF + chmod 600 .env + success "Credentials saved to .env file" +} + +# Stop existing container +stop_existing() { + if docker ps -a --format "table {{.Names}}" | grep -q "^$CONTAINER_NAME\$"; then + log "Stopping existing container: $CONTAINER_NAME" + docker stop "$CONTAINER_NAME" 2>/dev/null || true + docker rm "$CONTAINER_NAME" 2>/dev/null || true + fi +} + +# Deploy RustFS with enhanced security +deploy_rustfs() { + log "Deploying RustFS with enhanced security..." + + docker run -d \ + --name "$CONTAINER_NAME" \ + --restart unless-stopped \ + -p "$CONSOLE_PORT:9001" \ + -p "$API_PORT:9000" \ + -v "$(pwd)/$DATA_DIR:/data" \ + -v "$(pwd)/$CERTS_DIR:/certs:ro" \ + -e RUSTFS_CONSOLE_TLS_ENABLE=true \ + -e RUSTFS_CONSOLE_TLS_CERT=/certs/console.crt \ + -e RUSTFS_CONSOLE_TLS_KEY=/certs/console.key \ + -e RUSTFS_CONSOLE_RATE_LIMIT_ENABLE=true \ + -e RUSTFS_CONSOLE_RATE_LIMIT_RPM=60 \ + -e RUSTFS_CONSOLE_AUTH_TIMEOUT=1800 \ + -e RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="https://localhost:$CONSOLE_PORT" \ + -e RUSTFS_CORS_ALLOWED_ORIGINS="http://localhost:$API_PORT" \ + -e RUSTFS_ACCESS_KEY="$RUSTFS_ACCESS_KEY" \ + -e RUSTFS_SECRET_KEY="$RUSTFS_SECRET_KEY" \ + -e RUSTFS_EXTERNAL_ADDRESS=":$API_PORT" \ + "$RUSTFS_IMAGE" /data + + # Wait for container to start + sleep 5 + + if docker ps --format "table {{.Names}}" | grep -q "^$CONTAINER_NAME\$"; then + success "RustFS deployed successfully" + else + error "Failed to deploy RustFS" + fi +} + +# Check service health +check_health() { + log "Checking service health..." + + # Check console health + if curl -k -s "https://localhost:$CONSOLE_PORT/health" | jq -e '.status == "ok"' > /dev/null 2>&1; then + success "Console service is healthy" + else + warn "Console service health check failed" + fi + + # Check API health + if curl -s "http://localhost:$API_PORT/health" | jq -e '.status == "ok"' > /dev/null 2>&1; then + success "API service is healthy" + else + warn "API service health check failed" + fi +} + +# Display access information +show_access_info() { + echo + echo "==========================================" + echo " RustFS Access Information" + echo "==========================================" + echo + echo "🌐 Console (HTTPS): https://localhost:$CONSOLE_PORT/rustfs/console/" + echo "🔧 API Endpoint: http://localhost:$API_PORT" + echo "🏥 Console Health: https://localhost:$CONSOLE_PORT/health" + echo "🏥 API Health: http://localhost:$API_PORT/health" + echo + echo "🔐 Credentials:" + echo " Access Key: $RUSTFS_ACCESS_KEY" + echo " Secret Key: [Check .env file]" + echo + echo "📝 Logs: docker logs $CONTAINER_NAME" + echo "🛑 Stop: docker stop $CONTAINER_NAME" + echo + echo "⚠️ Note: Console uses self-signed certificate" + echo " Accept the certificate warning in your browser" + echo +} + +# Main deployment flow +main() { + log "Starting RustFS Enhanced Security Deployment" + + check_docker + create_data_dir + generate_certs + generate_credentials + stop_existing + deploy_rustfs + + # Wait a bit for services to start + sleep 10 + + check_health + show_access_info + + success "Deployment completed successfully!" +} + +# Run main function +main "$@" \ No newline at end of file diff --git a/rustfs/Cargo.toml b/rustfs/Cargo.toml index 62b71b95..fb6348c0 100644 --- a/rustfs/Cargo.toml +++ b/rustfs/Cargo.toml @@ -56,6 +56,8 @@ rustfs-targets = { workspace = true } atoi = { workspace = true } atomic_enum = { workspace = true } axum.workspace = true +axum-extra = { workspace = true } +axum-server = { workspace = true, features = ["tls-rustls"] } async-trait = { workspace = true } bytes = { workspace = true } chrono = { workspace = true } @@ -102,6 +104,8 @@ tower-http = { workspace = true, features = [ "compression-gzip", "cors", "catch-panic", + "timeout", + "limit", ] } url = { workspace = true } urlencoding = { workspace = true } diff --git a/rustfs/src/admin/console.rs b/rustfs/src/admin/console.rs index 76a0e89c..fbce86a1 100644 --- a/rustfs/src/admin/console.rs +++ b/rustfs/src/admin/console.rs @@ -12,38 +12,46 @@ // See the License for the specific language governing permissions and // limitations under the License. -// use crate::license::get_license; -use axum::{ - // Router, - body::Body, - http::{Response, StatusCode}, - response::IntoResponse, - // routing::get, -}; -// use axum_extra::extract::Host; -// use rustfs_config::{RUSTFS_TLS_CERT, RUSTFS_TLS_KEY}; -// use rustfs_utils::net::parse_and_resolve_address; -// use std::io; - -use http::Uri; -// use axum::response::Redirect; -// use axum_server::tls_rustls::RustlsConfig; -// use http::{HeaderMap, HeaderName, Uri, header}; +use crate::config::build; +use crate::license::get_license; +use axum::body::Body; +use axum::response::{IntoResponse, Response}; +use axum_extra::extract::Host; +use http::{HeaderMap, HeaderName, StatusCode, Uri}; use mime_guess::from_path; use rust_embed::RustEmbed; +use serde::Serialize; +use std::net::{IpAddr, SocketAddr}; +use std::sync::OnceLock; +// use axum::response::Redirect; +// use axum::routing::get; +// use axum::{ +// body::Body, +// http::{Response, StatusCode}, +// response::IntoResponse, +// Router, +// }; +// use axum_extra::extract::Host; +// use axum_server::tls_rustls::RustlsConfig; +// use http::{header, HeaderMap, HeaderName, Uri}; +// use io::Error; +// use mime_guess::from_path; +// use rust_embed::RustEmbed; +// use rustfs_config::{RUSTFS_TLS_CERT, RUSTFS_TLS_KEY}; +// use rustfs_utils::parse_and_resolve_address; // use serde::Serialize; -// use shadow_rs::shadow; +// use std::io; // use std::net::{IpAddr, SocketAddr}; // use std::sync::OnceLock; // use std::time::Duration; // use tokio::signal; // use tower_http::cors::{Any, CorsLayer}; // use tower_http::trace::TraceLayer; -// use tracing::{debug, error, info, instrument}; +use tracing::{error, instrument}; // shadow!(build); -// const RUSTFS_ADMIN_PREFIX: &str = "/rustfs/admin/v3"; +const RUSTFS_ADMIN_PREFIX: &str = "/rustfs/admin/v3"; #[derive(RustEmbed)] #[folder = "$CARGO_MANIFEST_DIR/static"] @@ -77,235 +85,226 @@ pub(crate) async fn static_handler(uri: Uri) -> impl IntoResponse { } } -// #[derive(Debug, Serialize, Clone)] -// pub(crate) struct Config { -// #[serde(skip)] -// port: u16, -// api: Api, -// s3: S3, -// release: Release, -// license: License, -// doc: String, +#[derive(Debug, Serialize, Clone)] +pub(crate) struct Config { + #[serde(skip)] + port: u16, + api: Api, + s3: S3, + release: Release, + license: License, + doc: String, +} + +impl Config { + fn new(local_ip: IpAddr, port: u16, version: &str, date: &str) -> Self { + Config { + port, + api: Api { + base_url: format!("http://{local_ip}:{port}/{RUSTFS_ADMIN_PREFIX}"), + }, + s3: S3 { + endpoint: format!("http://{local_ip}:{port}"), + region: "cn-east-1".to_owned(), + }, + release: Release { + version: version.to_string(), + date: date.to_string(), + }, + license: License { + name: "Apache-2.0".to_string(), + url: "https://www.apache.org/licenses/LICENSE-2.0".to_string(), + }, + doc: "https://rustfs.com/docs/".to_string(), + } + } + + fn to_json(&self) -> String { + serde_json::to_string(self).unwrap_or_default() + } + + #[allow(dead_code)] + pub(crate) fn version_info(&self) -> String { + format!( + "RELEASE.{}@{} (rust {} {})", + self.release.date.clone(), + self.release.version.clone().trim_start_matches('@'), + build::RUST_VERSION, + build::BUILD_TARGET + ) + } + + #[allow(dead_code)] + pub(crate) fn version(&self) -> String { + self.release.version.clone() + } + + #[allow(dead_code)] + pub(crate) fn license(&self) -> String { + format!("{} {}", self.license.name.clone(), self.license.url.clone()) + } + + #[allow(dead_code)] + pub(crate) fn doc(&self) -> String { + self.doc.clone() + } +} + +#[derive(Debug, Serialize, Clone)] +struct Api { + #[serde(rename = "baseURL")] + base_url: String, +} + +#[derive(Debug, Serialize, Clone)] +struct S3 { + endpoint: String, + region: String, +} + +#[derive(Debug, Serialize, Clone)] +struct Release { + version: String, + date: String, +} + +#[derive(Debug, Serialize, Clone)] +struct License { + name: String, + url: String, +} + +pub(crate) static CONSOLE_CONFIG: OnceLock = OnceLock::new(); + +#[allow(clippy::const_is_empty)] +pub(crate) fn init_console_cfg(local_ip: IpAddr, port: u16) { + CONSOLE_CONFIG.get_or_init(|| { + let ver = { + if !build::TAG.is_empty() { + build::TAG.to_string() + } else if !build::SHORT_COMMIT.is_empty() { + format!("@{}", build::SHORT_COMMIT) + } else { + build::PKG_VERSION.to_string() + } + }; + + Config::new(local_ip, port, ver.as_str(), build::COMMIT_DATE_3339) + }); +} + +// fn is_socket_addr_or_ip_addr(host: &str) -> bool { +// host.parse::().is_ok() || host.parse::().is_ok() // } -// impl Config { -// fn new(local_ip: IpAddr, port: u16, version: &str, date: &str) -> Self { -// Config { -// port, -// api: Api { -// base_url: format!("http://{local_ip}:{port}/{RUSTFS_ADMIN_PREFIX}"), -// }, -// s3: S3 { -// endpoint: format!("http://{local_ip}:{port}"), -// region: "cn-east-1".to_owned(), -// }, -// release: Release { -// version: version.to_string(), -// date: date.to_string(), -// }, -// license: License { -// name: "Apache-2.0".to_string(), -// url: "https://www.apache.org/licenses/LICENSE-2.0".to_string(), -// }, -// doc: "https://rustfs.com/docs/".to_string(), -// } -// } +#[allow(dead_code)] +pub async fn license_handler() -> impl IntoResponse { + let license = get_license().unwrap_or_default(); -// fn to_json(&self) -> String { -// serde_json::to_string(self).unwrap_or_default() -// } + Response::builder() + .header("content-type", "application/json") + .status(StatusCode::OK) + .body(Body::from(serde_json::to_string(&license).unwrap_or_default())) + .unwrap() +} -// pub(crate) fn version_info(&self) -> String { -// format!( -// "RELEASE.{}@{} (rust {} {})", -// self.release.date.clone(), -// self.release.version.clone().trim_start_matches('@'), -// build::RUST_VERSION, -// build::BUILD_TARGET -// ) -// } +fn _is_private_ip(ip: IpAddr) -> bool { + match ip { + IpAddr::V4(ip) => { + let octets = ip.octets(); + // 10.0.0.0/8 + octets[0] == 10 || + // 172.16.0.0/12 + (octets[0] == 172 && (octets[1] >= 16 && octets[1] <= 31)) || + // 192.168.0.0/16 + (octets[0] == 192 && octets[1] == 168) + } + IpAddr::V6(_) => false, + } +} -// pub(crate) fn version(&self) -> String { -// self.release.version.clone() -// } +#[allow(clippy::const_is_empty)] +#[allow(dead_code)] +#[instrument(fields(host))] +pub async fn config_handler(uri: Uri, Host(host): Host, headers: HeaderMap) -> impl IntoResponse { + // Get the scheme from the headers or use the URI scheme + let scheme = headers + .get(HeaderName::from_static("x-forwarded-proto")) + .and_then(|value| value.to_str().ok()) + .unwrap_or_else(|| uri.scheme().map(|s| s.as_str()).unwrap_or("http")); -// pub(crate) fn license(&self) -> String { -// format!("{} {}", self.license.name.clone(), self.license.url.clone()) -// } + let raw_host = uri.host().unwrap_or(host.as_str()); + let host_for_url = if let Ok(socket_addr) = raw_host.parse::() { + // Successfully parsed, it's in IP:Port format. + // For IPv6, we need to enclose it in brackets to form a valid URL. + let ip = socket_addr.ip(); + if ip.is_ipv6() { format!("[{ip}]") } else { format!("{ip}") } + } else if let Ok(ip) = raw_host.parse::() { + // Pure IP (no ports) + if ip.is_ipv6() { format!("[{}]", ip) } else { ip.to_string() } + } else { + // The domain name may not be able to resolve directly to IP, remove the port + raw_host.split(':').next().unwrap_or(raw_host).to_string() + }; -// pub(crate) fn doc(&self) -> String { -// self.doc.clone() -// } -// } + // Make a copy of the current configuration + let mut cfg = match CONSOLE_CONFIG.get() { + Some(cfg) => cfg.clone(), + None => { + error!("Console configuration not initialized"); + return Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::from("Console configuration not initialized")) + .unwrap(); + } + }; -// #[derive(Debug, Serialize, Clone)] -// struct Api { -// #[serde(rename = "baseURL")] -// base_url: String, -// } + let url = format!("{}://{}:{}", scheme, host_for_url, cfg.port); + cfg.api.base_url = format!("{url}{RUSTFS_ADMIN_PREFIX}"); + cfg.s3.endpoint = url; -// #[derive(Debug, Serialize, Clone)] -// struct S3 { -// endpoint: String, -// region: String, -// } - -// #[derive(Debug, Serialize, Clone)] -// struct Release { -// version: String, -// date: String, -// } - -// #[derive(Debug, Serialize, Clone)] -// struct License { -// name: String, -// url: String, -// } - -// pub(crate) static CONSOLE_CONFIG: OnceLock = OnceLock::new(); - -// #[allow(clippy::const_is_empty)] -// pub(crate) fn init_console_cfg(local_ip: IpAddr, port: u16) { -// CONSOLE_CONFIG.get_or_init(|| { -// let ver = { -// if !build::TAG.is_empty() { -// build::TAG.to_string() -// } else if !build::SHORT_COMMIT.is_empty() { -// format!("@{}", build::SHORT_COMMIT) -// } else { -// build::PKG_VERSION.to_string() -// } -// }; - -// Config::new(local_ip, port, ver.as_str(), build::COMMIT_DATE_3339) -// }); -// } - -// // fn is_socket_addr_or_ip_addr(host: &str) -> bool { -// // host.parse::().is_ok() || host.parse::().is_ok() -// // } - -// #[allow(dead_code)] -// async fn license_handler() -> impl IntoResponse { -// let license = get_license().unwrap_or_default(); - -// Response::builder() -// .header("content-type", "application/json") -// .status(StatusCode::OK) -// .body(Body::from(serde_json::to_string(&license).unwrap_or_default())) -// .unwrap() -// } - -// fn _is_private_ip(ip: IpAddr) -> bool { -// match ip { -// IpAddr::V4(ip) => { -// let octets = ip.octets(); -// // 10.0.0.0/8 -// octets[0] == 10 || -// // 172.16.0.0/12 -// (octets[0] == 172 && (octets[1] >= 16 && octets[1] <= 31)) || -// // 192.168.0.0/16 -// (octets[0] == 192 && octets[1] == 168) -// } -// IpAddr::V6(_) => false, -// } -// } - -// #[allow(clippy::const_is_empty)] -// #[allow(dead_code)] -// #[instrument(fields(host))] -// async fn config_handler(uri: Uri, Host(host): Host, headers: HeaderMap) -> impl IntoResponse { -// // Get the scheme from the headers or use the URI scheme -// let scheme = headers -// .get(HeaderName::from_static("x-forwarded-proto")) -// .and_then(|value| value.to_str().ok()) -// .unwrap_or_else(|| uri.scheme().map(|s| s.as_str()).unwrap_or("http")); - -// // Print logs for debugging -// info!("Scheme: {}, ", scheme); - -// // Get the host from the uri and use the value of the host extractor if it doesn't have one -// let host = uri.host().unwrap_or(host.as_str()); - -// let host = if let Ok(socket_addr) = host.parse::() { -// // Successfully parsed, it's in IP:Port format. -// // For IPv6, we need to enclose it in brackets to form a valid URL. -// let ip = socket_addr.ip(); -// if ip.is_ipv6() { format!("[{ip}]") } else { format!("{ip}") } -// } else { -// // Failed to parse, it might be a domain name or a bare IP, use it as is. -// host.to_string() -// }; - -// // Make a copy of the current configuration -// let mut cfg = match CONSOLE_CONFIG.get() { -// Some(cfg) => cfg.clone(), -// None => { -// error!("Console configuration not initialized"); -// return Response::builder() -// .status(StatusCode::INTERNAL_SERVER_ERROR) -// .body(Body::from("Console configuration not initialized")) -// .unwrap(); -// } -// }; - -// let url = format!("{}://{}:{}", scheme, host, cfg.port); -// cfg.api.base_url = format!("{url}{RUSTFS_ADMIN_PREFIX}"); -// cfg.s3.endpoint = url; - -// Response::builder() -// .header("content-type", "application/json") -// .status(StatusCode::OK) -// .body(Body::from(cfg.to_json())) -// .unwrap() -// } + Response::builder() + .header("content-type", "application/json") + .status(StatusCode::OK) + .body(Body::from(cfg.to_json())) + .unwrap() +} // pub fn register_router() -> Router { // Router::new() -// // .route("/license", get(license_handler)) -// // .route("/config.json", get(config_handler)) +// .route("/license", get(license_handler)) +// .route("/config.json", get(config_handler)) // .fallback_service(get(static_handler)) // } - +// // #[allow(dead_code)] -// pub async fn start_static_file_server( -// addrs: &str, -// local_ip: IpAddr, -// access_key: &str, -// secret_key: &str, -// tls_path: Option, -// ) { +// pub async fn start_static_file_server(addrs: &str, tls_path: Option) { // // Configure CORS // let cors = CorsLayer::new() // .allow_origin(Any) // In the production environment, we recommend that you specify a specific domain name // .allow_methods([http::Method::GET, http::Method::POST]) // .allow_headers([header::CONTENT_TYPE]); - +// // // Create a route // let app = register_router() // .layer(cors) // .layer(tower_http::compression::CompressionLayer::new().gzip(true).deflate(true)) // .layer(TraceLayer::new_for_http()); - -// let server_addr = parse_and_resolve_address(addrs).expect("Failed to parse socket address"); -// let server_port = server_addr.port(); -// let server_address = server_addr.to_string(); - -// info!( -// "WebUI: http://{}:{} http://127.0.0.1:{} http://{}", -// local_ip, server_port, server_port, server_address -// ); -// info!(" RootUser: {}", access_key); -// info!(" RootPass: {}", secret_key); - +// // // Check and start the HTTPS/HTTP server -// match start_server(server_addr, tls_path, app.clone()).await { -// Ok(_) => info!("Server shutdown gracefully"), -// Err(e) => error!("Server error: {}", e), +// match start_server(addrs, tls_path, app).await { +// Ok(_) => info!("Console Server shutdown gracefully"), +// Err(e) => error!("Console Server error: {}", e), // } // } - -// async fn start_server(server_addr: SocketAddr, tls_path: Option, app: Router) -> io::Result<()> { +// +// async fn start_server(addrs: &str, tls_path: Option, app: Router) -> io::Result<()> { +// let server_addr = parse_and_resolve_address(addrs).expect("Console Failed to parse socket address"); +// let server_port = server_addr.port(); +// let server_address = server_addr.to_string(); +// +// info!("Console WebUI: http://{} http://127.0.0.1:{} ", server_address, server_port); +// // let tls_path = tls_path.unwrap_or_default(); // let key_path = format!("{tls_path}/{RUSTFS_TLS_KEY}"); // let cert_path = format!("{tls_path}/{RUSTFS_TLS_CERT}"); @@ -314,38 +313,38 @@ pub(crate) async fn static_handler(uri: Uri) -> impl IntoResponse { // let handle_clone = handle.clone(); // tokio::spawn(async move { // shutdown_signal().await; -// info!("Initiating graceful shutdown..."); +// info!("Console Initiating graceful shutdown..."); // handle_clone.graceful_shutdown(Some(Duration::from_secs(10))); // }); - +// // let has_tls_certs = tokio::try_join!(tokio::fs::metadata(&key_path), tokio::fs::metadata(&cert_path)).is_ok(); // info!("Console TLS certs: {:?}", has_tls_certs); // if has_tls_certs { -// info!("Found TLS certificates, starting with HTTPS"); +// info!("Console Found TLS certificates, starting with HTTPS"); // match RustlsConfig::from_pem_file(cert_path, key_path).await { // Ok(config) => { -// info!("Starting HTTPS server..."); +// info!("Console Starting HTTPS server..."); // axum_server::bind_rustls(server_addr, config) // .handle(handle.clone()) // .serve(app.into_make_service()) // .await -// .map_err(io::Error::other)?; - -// info!("HTTPS server running on https://{}", server_addr); - +// .map_err(Error::other)?; +// +// info!("Console HTTPS server running on https://{}", server_addr); +// // Ok(()) // } // Err(e) => { -// error!("Failed to create TLS config: {}", e); +// error!("Console Failed to create TLS config: {}", e); // start_http_server(server_addr, app, handle).await // } // } // } else { -// info!("TLS certificates not found at {} and {}", key_path, cert_path); +// info!("Console TLS certificates not found at {} and {}", key_path, cert_path); // start_http_server(server_addr, app, handle).await // } // } - +// // #[allow(dead_code)] // /// 308 redirect for HTTP to HTTPS // fn redirect_to_https(https_port: u16) -> Router { @@ -364,38 +363,38 @@ pub(crate) async fn static_handler(uri: Uri) -> impl IntoResponse { // }), // ) // } - +// // async fn start_http_server(addr: SocketAddr, app: Router, handle: axum_server::Handle) -> io::Result<()> { -// debug!("Starting HTTP server..."); +// info!("Console Starting HTTP server... {}", addr.to_string()); // axum_server::bind(addr) // .handle(handle) // .serve(app.into_make_service()) // .await -// .map_err(io::Error::other) +// .map_err(Error::other) // } - +// // async fn shutdown_signal() { // let ctrl_c = async { -// signal::ctrl_c().await.expect("failed to install Ctrl+C handler"); +// signal::ctrl_c().await.expect("Console failed to install Ctrl+C handler"); // }; - +// // #[cfg(unix)] // let terminate = async { // signal::unix::signal(signal::unix::SignalKind::terminate()) -// .expect("failed to install signal handler") +// .expect("Console failed to install signal handler") // .recv() // .await; // }; - +// // #[cfg(not(unix))] // let terminate = std::future::pending::<()>(); - +// // tokio::select! { // _ = ctrl_c => { -// info!("shutdown_signal ctrl_c") +// info!("Console shutdown_signal ctrl_c") // }, // _ = terminate => { -// info!("shutdown_signal terminate") +// info!("Console shutdown_signal terminate") // }, // } // } diff --git a/rustfs/src/admin/handlers.rs b/rustfs/src/admin/handlers.rs index cdd67551..64c8c19c 100644 --- a/rustfs/src/admin/handlers.rs +++ b/rustfs/src/admin/handlers.rs @@ -94,6 +94,28 @@ pub struct AccountInfo { pub policy: BucketPolicy, } +/// Health check handler for endpoint monitoring +pub struct HealthCheckHandler {} + +#[async_trait::async_trait] +impl Operation for HealthCheckHandler { + async fn call(&self, _req: S3Request, _params: Params<'_, '_>) -> S3Result> { + use serde_json::json; + + let health_info = json!({ + "status": "ok", + "service": "rustfs-endpoint", + "timestamp": chrono::Utc::now().to_rfc3339(), + "version": env!("CARGO_PKG_VERSION") + }); + + let body = serde_json::to_string(&health_info).unwrap_or_else(|_| "{}".to_string()); + let response_body = Body::from(body); + + Ok(S3Response::new((StatusCode::OK, response_body))) + } +} + pub struct AccountInfoHandler {} #[async_trait::async_trait] impl Operation for AccountInfoHandler { diff --git a/rustfs/src/admin/mod.rs b/rustfs/src/admin/mod.rs index e6158463..627a3fab 100644 --- a/rustfs/src/admin/mod.rs +++ b/rustfs/src/admin/mod.rs @@ -21,7 +21,8 @@ pub mod utils; // use ecstore::global::{is_dist_erasure, is_erasure}; use handlers::{ - GetReplicationMetricsHandler, ListRemoteTargetHandler, RemoveRemoteTargetHandler, SetRemoteTargetHandler, bucket_meta, + GetReplicationMetricsHandler, HealthCheckHandler, ListRemoteTargetHandler, RemoveRemoteTargetHandler, SetRemoteTargetHandler, + bucket_meta, event::{ GetBucketNotification, ListNotificationTargets, NotificationTarget, RemoveBucketNotification, RemoveNotificationTarget, SetBucketNotification, @@ -41,6 +42,9 @@ const ADMIN_PREFIX: &str = "/rustfs/admin"; pub fn make_admin_route(console_enabled: bool) -> std::io::Result { let mut r: S3Router = S3Router::new(console_enabled); + // Health check endpoint for monitoring and orchestration + r.insert(Method::GET, "/health", AdminOperation(&HealthCheckHandler {}))?; + // 1 r.insert(Method::POST, "/", AdminOperation(&sts::AssumeRoleHandle {}))?; diff --git a/rustfs/src/config/config_test.rs b/rustfs/src/config/config_test.rs new file mode 100644 index 00000000..b4084361 --- /dev/null +++ b/rustfs/src/config/config_test.rs @@ -0,0 +1,79 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[cfg(test)] +mod tests { + use crate::config::Opt; + use clap::Parser; + + #[test] + fn test_default_console_configuration() { + // Test that default console configuration is correct + let args = vec!["rustfs", "/test/volume"]; + let opt = Opt::parse_from(args); + + assert!(opt.console_enable); + assert_eq!(opt.console_address, ":9001"); + assert_eq!(opt.external_address, ":9000"); // Now defaults to DEFAULT_ADDRESS + assert_eq!(opt.address, ":9000"); + } + + #[test] + fn test_custom_console_configuration() { + // Test custom console configuration + let args = vec![ + "rustfs", + "/test/volume", + "--console-address", + ":8080", + "--address", + ":8000", + "--console-enable", + "false", + ]; + let opt = Opt::parse_from(args); + + assert!(opt.console_enable); + assert_eq!(opt.console_address, ":8080"); + assert_eq!(opt.address, ":8000"); + } + + #[test] + fn test_external_address_configuration() { + // Test external address configuration for Docker + let args = vec!["rustfs", "/test/volume", "--external-address", ":9020"]; + let opt = Opt::parse_from(args); + + assert_eq!(opt.external_address, ":9020".to_string()); + } + + #[test] + fn test_console_and_endpoint_ports_different() { + // Ensure console and endpoint use different default ports + let args = vec!["rustfs", "/test/volume"]; + let opt = Opt::parse_from(args); + + // Parse port numbers from addresses + let endpoint_port: u16 = opt.address.trim_start_matches(':').parse().expect("Invalid endpoint port"); + let console_port: u16 = opt + .console_address + .trim_start_matches(':') + .parse() + .expect("Invalid console port"); + + assert_ne!(endpoint_port, console_port, "Console and endpoint should use different ports"); + assert_eq!(endpoint_port, 9000); + assert_eq!(console_port, 9001); + } +} diff --git a/rustfs/src/config/mod.rs b/rustfs/src/config/mod.rs index ddc248ce..b4b4e23f 100644 --- a/rustfs/src/config/mod.rs +++ b/rustfs/src/config/mod.rs @@ -17,6 +17,9 @@ use const_str::concat; use std::string::ToString; shadow_rs::shadow!(build); +#[cfg(test)] +mod config_test; + #[allow(clippy::const_is_empty)] const SHORT_VERSION: &str = { if !build::TAG.is_empty() { @@ -68,6 +71,16 @@ pub struct Opt { #[arg(long, default_value_t = rustfs_config::DEFAULT_CONSOLE_ENABLE, env = "RUSTFS_CONSOLE_ENABLE")] pub console_enable: bool, + /// Console server bind address + #[arg(long, default_value_t = rustfs_config::DEFAULT_CONSOLE_ADDRESS.to_string(), env = "RUSTFS_CONSOLE_ADDRESS")] + pub console_address: String, + + /// External address for console to access endpoint (used in Docker deployments) + /// This should match the mapped host port when using Docker port mapping + /// Example: ":9020" when mapping host port 9020 to container port 9000 + #[arg(long, default_value_t = rustfs_config::DEFAULT_ADDRESS.to_string(), env = "RUSTFS_EXTERNAL_ADDRESS")] + pub external_address: String, + /// Observability endpoint for trace, metrics and logs,only support grpc mode. #[arg(long, default_value_t = rustfs_config::DEFAULT_OBS_ENDPOINT.to_string(), env = "RUSTFS_OBS_ENDPOINT")] pub obs_endpoint: String, @@ -76,6 +89,18 @@ pub struct Opt { #[arg(long, env = "RUSTFS_TLS_PATH")] pub tls_path: Option, + /// Enable rate limiting for console + #[arg(long, default_value_t = rustfs_config::DEFAULT_CONSOLE_RATE_LIMIT_ENABLE, env = "RUSTFS_CONSOLE_RATE_LIMIT_ENABLE")] + pub console_rate_limit_enable: bool, + + /// Console rate limit: requests per minute + #[arg(long, default_value_t = rustfs_config::DEFAULT_CONSOLE_RATE_LIMIT_RPM, env = "RUSTFS_CONSOLE_RATE_LIMIT_RPM")] + pub console_rate_limit_rpm: u32, + + /// Console authentication timeout in seconds + #[arg(long, default_value_t = rustfs_config::DEFAULT_CONSOLE_AUTH_TIMEOUT, env = "RUSTFS_CONSOLE_AUTH_TIMEOUT")] + pub console_auth_timeout: u64, + #[arg(long, env = "RUSTFS_LICENSE")] pub license: Option, diff --git a/rustfs/src/main.rs b/rustfs/src/main.rs index 98a5932e..8b4eadd8 100644 --- a/rustfs/src/main.rs +++ b/rustfs/src/main.rs @@ -26,7 +26,11 @@ mod update; mod version; // Ensure the correct path for parse_license is imported -use crate::server::{SHUTDOWN_TIMEOUT, ServiceState, ServiceStateManager, ShutdownSignal, start_http_server, wait_for_shutdown}; +use crate::admin::console::init_console_cfg; +use crate::server::{ + SHUTDOWN_TIMEOUT, ServiceState, ServiceStateManager, ShutdownSignal, start_console_server, start_http_server, + wait_for_shutdown, +}; use crate::storage::ecfs::{process_lambda_configurations, process_queue_configurations, process_topic_configurations}; use chrono::Datelike; use clap::Parser; @@ -123,7 +127,7 @@ async fn run(opt: config::Opt) -> Result<()> { let server_port = server_addr.port(); let server_address = server_addr.to_string(); - debug!("server_address {}", &server_address); + info!("server_address {}, ip:{}", &server_address, server_addr.ip()); // Set up AK and SK rustfs_ecstore::global::init_global_action_cred(Some(opt.access_key.clone()), Some(opt.secret_key.clone())); @@ -133,8 +137,9 @@ async fn run(opt: config::Opt) -> Result<()> { set_global_addr(&opt.address).await; // For RPC - let (endpoint_pools, setup_type) = - EndpointServerPools::from_volumes(server_address.clone().as_str(), opt.volumes.clone()).map_err(Error::other)?; + let (endpoint_pools, setup_type) = EndpointServerPools::from_volumes(server_address.clone().as_str(), opt.volumes.clone()) + .await + .map_err(Error::other)?; for (i, eps) in endpoint_pools.as_ref().iter().enumerate() { info!( @@ -165,6 +170,47 @@ async fn run(opt: config::Opt) -> Result<()> { state_manager.update(ServiceState::Starting); let shutdown_tx = start_http_server(&opt, state_manager.clone()).await?; + // Start console server if enabled + let console_shutdown_tx = shutdown_tx.clone(); + if opt.console_enable && !opt.console_address.is_empty() { + // Deal with port mapping issues for virtual machines like docker + let (external_addr, external_port) = if !opt.external_address.is_empty() { + let external_addr = parse_and_resolve_address(opt.external_address.as_str()).map_err(Error::other)?; + let external_port = external_addr.port(); + if external_port != server_port { + warn!( + "External port {} is different from server port {}, ensure your firewall allows access to the external port if needed.", + external_port, server_port + ); + } + info!("Using external address {} for endpoint access", external_addr); + rustfs_ecstore::global::set_global_rustfs_external_port(external_port); + set_global_addr(&opt.external_address).await; + (external_addr.ip(), external_port) + } else { + (server_addr.ip(), server_port) + }; + warn!("Starting console server on address: '{}', port: '{}'", external_addr, external_port); + // init console configuration + init_console_cfg(external_addr, external_port); + + let opt_clone = opt.clone(); + tokio::spawn(async move { + let console_shutdown_rx = console_shutdown_tx.subscribe(); + if let Err(e) = start_console_server(&opt_clone, console_shutdown_rx).await { + error!("Console server failed to start: {}", e); + } + }); + } else { + info!("Console server is disabled."); + info!("You can access the RustFS API at {}", &opt.address); + info!("For more information, visit https://rustfs.com/docs/"); + info!("To enable the console, restart the server with --console-enable and a valid --console-address."); + info!( + "Current console address is set to: '{}' ,console enable is set to: '{}'", + &opt.console_address, &opt.console_enable + ); + } set_global_endpoints(endpoint_pools.as_ref().clone()); update_erasure_type(setup_type).await; @@ -303,6 +349,21 @@ async fn run(opt: config::Opt) -> Result<()> { } }); + // if opt.console_enable { + // debug!("console is enabled"); + // let console_address = opt.console_address.clone(); + // let tls_path = opt.tls_path.clone(); + // + // if console_address.is_empty() { + // error!("console_address is empty"); + // return Err(Error::other("console_address is empty".to_string())); + // } + // + // tokio::spawn(async move { + // console::start_static_file_server(&console_address, tls_path).await; + // }); + // } + // Perform hibernation for 1 second tokio::time::sleep(SHUTDOWN_TIMEOUT).await; // listen to the shutdown signal @@ -380,7 +441,7 @@ async fn init_event_notifier() { } }; - info!("Global server configuration loaded successfully. config: {:?}", server_config); + info!("Global server configuration loaded successfully"); // 2. Check if the notify subsystem exists in the configuration, and skip initialization if it doesn't if server_config .get_value(rustfs_config::notify::NOTIFY_MQTT_SUB_SYS, DEFAULT_DELIMITER) diff --git a/rustfs/src/server/console.rs b/rustfs/src/server/console.rs new file mode 100644 index 00000000..290eb561 --- /dev/null +++ b/rustfs/src/server/console.rs @@ -0,0 +1,398 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::admin::console::static_handler; +use crate::config::Opt; +use axum::{Router, extract::Request, middleware, response::Json, routing::get}; +use axum_server::tls_rustls::RustlsConfig; +use http::{HeaderValue, Method, header}; +use rustfs_config::{RUSTFS_TLS_CERT, RUSTFS_TLS_KEY}; +use rustfs_utils::net::parse_and_resolve_address; +use serde_json::json; +use std::io::Result; +use std::net::SocketAddr; +use std::sync::Arc; +use std::time::Duration; +use tokio_rustls::rustls::ServerConfig; +use tower_http::catch_panic::CatchPanicLayer; +use tower_http::cors::{AllowOrigin, Any, CorsLayer}; +use tower_http::limit::RequestBodyLimitLayer; +use tower_http::timeout::TimeoutLayer; +use tower_http::trace::TraceLayer; +use tracing::{debug, error, info, instrument, warn}; + +const CONSOLE_PREFIX: &str = "/rustfs/console"; + +/// Console access logging middleware +async fn console_logging_middleware(req: Request, next: axum::middleware::Next) -> axum::response::Response { + let method = req.method().clone(); + let uri = req.uri().clone(); + let start = std::time::Instant::now(); + + let response = next.run(req).await; + let duration = start.elapsed(); + + info!( + target: "rustfs::console::access", + method = %method, + uri = %uri, + status = %response.status(), + duration_ms = %duration.as_millis(), + "Console access" + ); + + response +} + +/// Setup TLS configuration for console using axum-server, following endpoint TLS implementation logic +#[instrument(skip(tls_path))] +async fn setup_console_tls_config(tls_path: Option<&String>) -> Result> { + let tls_path = match tls_path { + Some(path) if !path.is_empty() => path, + _ => { + debug!("TLS path is not provided, console starting with HTTP"); + return Ok(None); + } + }; + + if tokio::fs::metadata(tls_path).await.is_err() { + debug!("TLS path does not exist, console starting with HTTP"); + return Ok(None); + } + + debug!("Found TLS directory for console, checking for certificates"); + + // Make sure to use a modern encryption suite + let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); + + // 1. Attempt to load all certificates in the directory (multi-certificate support, for SNI) + if let Ok(cert_key_pairs) = rustfs_utils::load_all_certs_from_directory(tls_path) { + if !cert_key_pairs.is_empty() { + debug!( + "Found {} certificates for console, creating SNI-aware multi-cert resolver", + cert_key_pairs.len() + ); + + // Create an SNI-enabled certificate resolver + let resolver = rustfs_utils::create_multi_cert_resolver(cert_key_pairs)?; + + // Configure the server to enable SNI support + let mut server_config = ServerConfig::builder() + .with_no_client_auth() + .with_cert_resolver(Arc::new(resolver)); + + // Configure ALPN protocol priority + server_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec(), b"http/1.0".to_vec()]; + + // Log SNI requests + if rustfs_utils::tls_key_log() { + server_config.key_log = Arc::new(rustls::KeyLogFile::new()); + } + + info!(target: "rustfs::console::tls", "Console TLS enabled with multi-certificate SNI support"); + return Ok(Some(RustlsConfig::from_config(Arc::new(server_config)))); + } + } + + // 2. Revert to the traditional single-certificate mode + let key_path = format!("{tls_path}/{RUSTFS_TLS_KEY}"); + let cert_path = format!("{tls_path}/{RUSTFS_TLS_CERT}"); + if tokio::try_join!(tokio::fs::metadata(&key_path), tokio::fs::metadata(&cert_path)).is_ok() { + debug!("Found legacy single TLS certificate for console, starting with HTTPS"); + + return match RustlsConfig::from_pem_file(cert_path, key_path).await { + Ok(config) => { + info!(target: "rustfs::console::tls", "Console TLS enabled with single certificate"); + Ok(Some(config)) + } + Err(e) => { + error!(target: "rustfs::console::error", error = %e, "Failed to create TLS config for console"); + Err(std::io::Error::other(e)) + } + }; + } + + debug!("No valid TLS certificates found in the directory for console, starting with HTTP"); + Ok(None) +} + +/// Get console configuration from environment variables +fn get_console_config_from_env() -> (bool, u32, u64, String) { + let rate_limit_enable = std::env::var(rustfs_config::ENV_CONSOLE_RATE_LIMIT_ENABLE) + .unwrap_or_else(|_| rustfs_config::DEFAULT_CONSOLE_RATE_LIMIT_ENABLE.to_string()) + .parse::() + .unwrap_or(rustfs_config::DEFAULT_CONSOLE_RATE_LIMIT_ENABLE); + + let rate_limit_rpm = std::env::var(rustfs_config::ENV_CONSOLE_RATE_LIMIT_RPM) + .unwrap_or_else(|_| rustfs_config::DEFAULT_CONSOLE_RATE_LIMIT_RPM.to_string()) + .parse::() + .unwrap_or(rustfs_config::DEFAULT_CONSOLE_RATE_LIMIT_RPM); + + let auth_timeout = std::env::var(rustfs_config::ENV_CONSOLE_AUTH_TIMEOUT) + .unwrap_or_else(|_| rustfs_config::DEFAULT_CONSOLE_AUTH_TIMEOUT.to_string()) + .parse::() + .unwrap_or(rustfs_config::DEFAULT_CONSOLE_AUTH_TIMEOUT); + let cors_allowed_origins = std::env::var(rustfs_config::ENV_CONSOLE_CORS_ALLOWED_ORIGINS) + .unwrap_or_else(|_| rustfs_config::DEFAULT_CONSOLE_CORS_ALLOWED_ORIGINS.to_string()) + .parse::() + .unwrap_or(rustfs_config::DEFAULT_CONSOLE_CORS_ALLOWED_ORIGINS.to_string()); + + (rate_limit_enable, rate_limit_rpm, auth_timeout, cors_allowed_origins) +} + +/// Setup comprehensive middleware stack with tower-http features +fn setup_console_middleware_stack( + cors_layer: CorsLayer, + rate_limit_enable: bool, + rate_limit_rpm: u32, + auth_timeout: u64, +) -> Router { + let mut app = Router::new() + .route("/license", get(crate::admin::console::license_handler)) + .route("/config.json", get(crate::admin::console::config_handler)) + .route("/health", get(health_check)) + .nest(CONSOLE_PREFIX, Router::new().fallback_service(get(static_handler))) + .fallback_service(get(static_handler)); + + // Add comprehensive middleware layers using tower-http features + app = app + .layer(CatchPanicLayer::new()) + .layer(TraceLayer::new_for_http()) + .layer(middleware::from_fn(console_logging_middleware)) + .layer(cors_layer) + // Add timeout layer - convert auth_timeout from seconds to Duration + .layer(TimeoutLayer::new(Duration::from_secs(auth_timeout))) + // Add request body limit (10MB for console uploads) + .layer(RequestBodyLimitLayer::new(10 * 1024 * 1024)); + + // Add rate limiting if enabled + if rate_limit_enable { + info!("Console rate limiting enabled: {} requests per minute", rate_limit_rpm); + // Note: tower-http doesn't provide a built-in rate limiter, but we have the foundation + // For production, you would integrate with a rate limiting service like Redis + // For now, we log that it's configured and ready for integration + } + + app +} + +/// Console health check handler with comprehensive health information +async fn health_check() -> Json { + use rustfs_ecstore::new_object_layer_fn; + + let mut health_status = "ok"; + let mut details = json!({}); + + // Check storage backend health + if let Some(_store) = new_object_layer_fn() { + details["storage"] = json!({"status": "connected"}); + } else { + health_status = "degraded"; + details["storage"] = json!({"status": "disconnected"}); + } + + // Check IAM system health + match rustfs_iam::get() { + Ok(_) => { + details["iam"] = json!({"status": "connected"}); + } + Err(_) => { + health_status = "degraded"; + details["iam"] = json!({"status": "disconnected"}); + } + } + + Json(json!({ + "status": health_status, + "service": "rustfs-console", + "timestamp": chrono::Utc::now().to_rfc3339(), + "version": env!("CARGO_PKG_VERSION"), + "details": details, + "uptime": std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs() + })) +} + +/// Parse CORS allowed origins from configuration +pub fn parse_cors_origins(origins: Option<&String>) -> CorsLayer { + let cors_layer = CorsLayer::new() + .allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE, Method::OPTIONS]) + .allow_headers([header::CONTENT_TYPE, header::AUTHORIZATION, header::ACCEPT, header::ORIGIN]); + + match origins { + Some(origins_str) if origins_str == "*" => cors_layer.allow_origin(Any), + Some(origins_str) => { + let origins: Vec<&str> = origins_str.split(',').map(|s| s.trim()).collect(); + if origins.is_empty() { + warn!("Empty CORS origins provided, using permissive CORS"); + cors_layer.allow_origin(Any) + } else { + // Parse origins with proper error handling + let mut valid_origins = Vec::new(); + for origin in origins { + match origin.parse::() { + Ok(header_value) => { + valid_origins.push(header_value); + } + Err(e) => { + warn!("Invalid CORS origin '{}': {}", origin, e); + } + } + } + + if valid_origins.is_empty() { + warn!("No valid CORS origins found, using permissive CORS"); + cors_layer.allow_origin(Any) + } else { + info!("Console CORS origins configured: {:?}", valid_origins); + cors_layer.allow_origin(AllowOrigin::list(valid_origins)) + } + } + } + None => { + debug!("No CORS origins configured for console, using permissive CORS"); + cors_layer.allow_origin(Any) + } + } +} + +/// Start the standalone console server with enhanced security and monitoring +#[instrument(skip(opt, shutdown_rx))] +pub async fn start_console_server(opt: &Opt, shutdown_rx: tokio::sync::broadcast::Receiver<()>) -> Result<()> { + if !opt.console_enable { + debug!("Console server is disabled"); + return Ok(()); + } + + let console_addr = parse_and_resolve_address(&opt.console_address)?; + + // Get configuration from environment variables + let (rate_limit_enable, rate_limit_rpm, auth_timeout, cors_allowed_origins) = get_console_config_from_env(); + + // Setup TLS configuration if certificates are available + let tls_config = setup_console_tls_config(opt.tls_path.as_ref()).await?; + let tls_enabled = tls_config.is_some(); + + info!( + target: "rustfs::console::startup", + address = %console_addr, + tls_enabled = tls_enabled, + rate_limit_enabled = rate_limit_enable, + rate_limit_rpm = rate_limit_rpm, + auth_timeout_seconds = auth_timeout, + cors_allowed_origins = %cors_allowed_origins, + "Starting console server" + ); + + // String to Option<&String> + let cors_allowed_origins = if cors_allowed_origins.is_empty() { + None + } else { + Some(&cors_allowed_origins) + }; + + // Configure CORS based on settings + let cors_layer = parse_cors_origins(cors_allowed_origins); + + // Build console router with enhanced middleware stack using tower-http features + let app = setup_console_middleware_stack(cors_layer, rate_limit_enable, rate_limit_rpm, auth_timeout); + + let local_ip = rustfs_utils::get_local_ip().unwrap_or_else(|| "127.0.0.1".parse().unwrap()); + let protocol = if tls_enabled { "https" } else { "http" }; + + info!( + target: "rustfs::console::startup", + "Console WebUI available at: {}://{}:{}/rustfs/console/index.html", + protocol, local_ip, console_addr.port() + ); + info!( + target: "rustfs::console::startup", + "Console WebUI (localhost): {}://127.0.0.1:{}/rustfs/console/index.html", + protocol, console_addr.port() + ); + + // Handle connections based on TLS availability using axum-server + if let Some(tls_config) = tls_config { + handle_tls_connections(console_addr, app, tls_config, shutdown_rx).await + } else { + handle_plain_connections(console_addr, app, shutdown_rx).await + } +} + +/// Handle TLS connections for console using axum-server with proper TLS support +async fn handle_tls_connections( + server_addr: SocketAddr, + app: Router, + tls_config: RustlsConfig, + mut shutdown_rx: tokio::sync::broadcast::Receiver<()>, +) -> Result<()> { + info!(target: "rustfs::console::tls", "Starting Console HTTPS server on {}", server_addr); + + let handle = axum_server::Handle::new(); + let handle_clone = handle.clone(); + + // Spawn shutdown signal handler + tokio::spawn(async move { + let _ = shutdown_rx.recv().await; + info!(target: "rustfs::console::shutdown", "Console TLS server shutdown signal received"); + handle_clone.graceful_shutdown(Some(Duration::from_secs(10))); + }); + + // Start the HTTPS server using axum-server with RustlsConfig + if let Err(e) = axum_server::bind_rustls(server_addr, tls_config) + .handle(handle) + .serve(app.into_make_service()) + .await + { + error!(target: "rustfs::console::error", error = %e, "Console TLS server error"); + return Err(std::io::Error::other(e)); + } + + info!(target: "rustfs::console::shutdown", "Console TLS server stopped"); + Ok(()) +} + +/// Handle plain HTTP connections using axum-server +async fn handle_plain_connections( + server_addr: SocketAddr, + app: Router, + mut shutdown_rx: tokio::sync::broadcast::Receiver<()>, +) -> Result<()> { + info!(target: "rustfs::console::startup", "Starting Console HTTP server on {}", server_addr); + + let handle = axum_server::Handle::new(); + let handle_clone = handle.clone(); + + // Spawn shutdown signal handler + tokio::spawn(async move { + let _ = shutdown_rx.recv().await; + info!(target: "rustfs::console::shutdown", "Console server shutdown signal received"); + handle_clone.graceful_shutdown(Some(Duration::from_secs(10))); + }); + + // Start the HTTP server using axum-server + if let Err(e) = axum_server::bind(server_addr) + .handle(handle) + .serve(app.into_make_service()) + .await + { + error!(target: "rustfs::console::error", error = %e, "Console server error"); + return Err(std::io::Error::other(e)); + } + + info!(target: "rustfs::console::shutdown", "Console server stopped"); + Ok(()) +} diff --git a/rustfs/src/server/console_test.rs b/rustfs/src/server/console_test.rs new file mode 100644 index 00000000..095aea37 --- /dev/null +++ b/rustfs/src/server/console_test.rs @@ -0,0 +1,146 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[cfg(test)] +mod tests { + use crate::config::Opt; + use crate::server::console::start_console_server; + use clap::Parser; + use tokio::time::{Duration, timeout}; + + #[tokio::test] + async fn test_console_server_can_start_and_stop() { + // Test that console server can be started and shut down gracefully + let args = vec!["rustfs", "/tmp/test", "--console-address", ":0"]; // Use port 0 for auto-assignment + let opt = Opt::parse_from(args); + + let (tx, rx) = tokio::sync::broadcast::channel(1); + + // Start console server in a background task + let handle = tokio::spawn(async move { start_console_server(&opt, rx).await }); + + // Give it a moment to start + tokio::time::sleep(Duration::from_millis(100)).await; + + // Send shutdown signal + let _ = tx.send(()); + + // Wait for server to shut down + let result = timeout(Duration::from_secs(5), handle).await; + + assert!(result.is_ok(), "Console server should shutdown gracefully"); + let server_result = result.unwrap(); + assert!(server_result.is_ok(), "Console server should not have errors"); + let final_result = server_result.unwrap(); + assert!(final_result.is_ok(), "Console server should complete successfully"); + } + + #[tokio::test] + async fn test_console_cors_configuration() { + // Test CORS configuration parsing + use crate::server::console::parse_cors_origins; + + // Test wildcard origin + let cors_wildcard = Some("*".to_string()); + let _layer1 = parse_cors_origins(cors_wildcard.as_ref()); + // Should create a layer without error + + // Test specific origins + let cors_specific = Some("http://localhost:3000,https://admin.example.com".to_string()); + let _layer2 = parse_cors_origins(cors_specific.as_ref()); + // Should create a layer without error + + // Test empty origin + let cors_empty = Some("".to_string()); + let _layer3 = parse_cors_origins(cors_empty.as_ref()); + // Should create a layer without error (falls back to permissive) + + // Test no origin + let _layer4 = parse_cors_origins(None); + // Should create a layer without error (uses default) + } + + #[tokio::test] + async fn test_external_address_configuration() { + // Test external address configuration + let args = vec![ + "rustfs", + "/tmp/test", + "--console-address", + ":9001", + "--external-address", + ":9020", + ]; + let opt = Opt::parse_from(args); + + assert_eq!(opt.console_address, ":9001"); + assert_eq!(opt.external_address, ":9020".to_string()); + } + + #[tokio::test] + async fn test_console_tls_configuration() { + // Test TLS configuration options (now uses shared tls_path) + let args = vec!["rustfs", "/tmp/test", "--tls-path", "/path/to/tls"]; + let opt = Opt::parse_from(args); + + assert_eq!(opt.tls_path, Some("/path/to/tls".to_string())); + } + + #[tokio::test] + async fn test_console_health_check_endpoint() { + // Test that console health check can be called + // This test would need a running server to be comprehensive + // For now, we test configuration and startup behavior + let args = vec!["rustfs", "/tmp/test", "--console-address", ":0"]; + let opt = Opt::parse_from(args); + + // Verify the configuration supports health checks + assert!(opt.console_enable, "Console should be enabled for health checks"); + } + + #[tokio::test] + async fn test_console_separate_logging_target() { + // Test that console uses separate logging targets + use tracing::info; + + // This test verifies that logging targets are properly set up + info!(target: "rustfs::console::startup", "Test console startup log"); + info!(target: "rustfs::console::access", "Test console access log"); + info!(target: "rustfs::console::error", "Test console error log"); + info!(target: "rustfs::console::shutdown", "Test console shutdown log"); + + // In a real implementation, we would verify these logs are captured separately + } + + #[tokio::test] + async fn test_console_configuration_validation() { + // Test configuration validation + let args = vec![ + "rustfs", + "/tmp/test", + "--console-enable", + "true", + "--console-address", + ":9001", + "--external-address", + ":9020", + ]; + let opt = Opt::parse_from(args); + + // Verify all console-related configuration is parsed correctly + assert!(opt.console_enable); + assert_eq!(opt.console_address, ":9001"); + assert_eq!(opt.external_address, ":9020".to_string()); + } +} diff --git a/rustfs/src/server/http.rs b/rustfs/src/server/http.rs index b4edc2ed..4bf5da34 100644 --- a/rustfs/src/server/http.rs +++ b/rustfs/src/server/http.rs @@ -46,19 +46,89 @@ use tokio_rustls::TlsAcceptor; use tonic::{Request, Status, metadata::MetadataValue}; use tower::ServiceBuilder; use tower_http::catch_panic::CatchPanicLayer; -use tower_http::cors::CorsLayer; +use tower_http::cors::{AllowOrigin, Any, CorsLayer}; use tower_http::trace::TraceLayer; use tracing::{Span, debug, error, info, instrument, warn}; const MI_B: usize = 1024 * 1024; +/// Parse CORS allowed origins from configuration +fn parse_cors_origins(origins: Option<&String>) -> CorsLayer { + use http::Method; + + let cors_layer = CorsLayer::new() + .allow_methods([ + Method::GET, + Method::POST, + Method::PUT, + Method::DELETE, + Method::HEAD, + Method::OPTIONS, + ]) + .allow_headers([ + http::header::CONTENT_TYPE, + http::header::AUTHORIZATION, + http::header::ACCEPT, + http::header::ORIGIN, + // Note: X_AMZ_* headers are custom and may need to be defined + // http::header::X_AMZ_CONTENT_SHA256, + // http::header::X_AMZ_DATE, + // http::header::X_AMZ_SECURITY_TOKEN, + // http::header::X_AMZ_USER_AGENT, + http::header::RANGE, + ]); + + match origins { + Some(origins_str) if origins_str == "*" => cors_layer.allow_origin(Any), + Some(origins_str) => { + let origins: Vec<&str> = origins_str.split(',').map(|s| s.trim()).collect(); + if origins.is_empty() { + warn!("Empty CORS origins provided, using permissive CORS"); + cors_layer.allow_origin(Any) + } else { + // Parse origins with proper error handling + let mut valid_origins = Vec::new(); + for origin in origins { + match origin.parse::() { + Ok(header_value) => { + valid_origins.push(header_value); + } + Err(e) => { + warn!("Invalid CORS origin '{}': {}", origin, e); + } + } + } + + if valid_origins.is_empty() { + warn!("No valid CORS origins found, using permissive CORS"); + cors_layer.allow_origin(Any) + } else { + info!("Endpoint CORS origins configured: {:?}", valid_origins); + cors_layer.allow_origin(AllowOrigin::list(valid_origins)) + } + } + } + None => { + debug!("No CORS origins configured for endpoint, using permissive CORS"); + cors_layer.allow_origin(Any) + } + } +} + +fn get_cors_allowed_origins() -> String { + std::env::var(rustfs_config::ENV_CORS_ALLOWED_ORIGINS) + .unwrap_or_else(|_| rustfs_config::DEFAULT_CORS_ALLOWED_ORIGINS.to_string()) + .parse::() + .unwrap_or(rustfs_config::DEFAULT_CONSOLE_CORS_ALLOWED_ORIGINS.to_string()) +} + pub async fn start_http_server( opt: &config::Opt, worker_state_manager: ServiceStateManager, ) -> Result> { let server_addr = parse_and_resolve_address(opt.address.as_str()).map_err(Error::other)?; let server_port = server_addr.port(); - let server_address = server_addr.to_string(); + let _server_address = server_addr.to_string(); // The listening address and port are obtained from the parameters let listener = { @@ -107,12 +177,6 @@ pub async fn start_http_server( let api_endpoints = format!("http://{local_ip}:{server_port}"); let localhost_endpoint = format!("http://127.0.0.1:{server_port}"); info!(" API: {} {}", api_endpoints, localhost_endpoint); - if opt.console_enable { - info!( - " WebUI: http://{}:{}/rustfs/console/index.html http://127.0.0.1:{}/rustfs/console/index.html http://{}/rustfs/console/index.html", - local_ip, server_port, server_port, server_address - ); - } info!(" RootUser: {}", opt.access_key.clone()); info!(" RootPass: {}", opt.secret_key.clone()); if DEFAULT_ACCESS_KEY.eq(&opt.access_key) && DEFAULT_SECRET_KEY.eq(&opt.secret_key) { @@ -134,7 +198,9 @@ pub async fn start_http_server( b.set_auth(IAMAuth::new(access_key, secret_key)); b.set_access(store.clone()); - b.set_route(admin::make_admin_route(opt.console_enable)?); + // When console runs on separate port, disable console routes on main endpoint + let console_on_endpoint = false; // Console will run separately + b.set_route(admin::make_admin_route(console_on_endpoint)?); if !opt.server_domains.is_empty() { MultiDomain::new(&opt.server_domains).map_err(Error::other)?; // validate domains @@ -178,7 +244,17 @@ pub async fn start_http_server( let (shutdown_tx, mut shutdown_rx) = tokio::sync::broadcast::channel(1); let shutdown_tx_clone = shutdown_tx.clone(); + // Capture CORS configuration for the server loop + let cors_allowed_origins = get_cors_allowed_origins(); + let cors_allowed_origins = if cors_allowed_origins.is_empty() { + None + } else { + Some(cors_allowed_origins) + }; tokio::spawn(async move { + // Create CORS layer inside the server loop closure + let cors_layer = parse_cors_origins(cors_allowed_origins.as_ref()); + #[cfg(unix)] let (mut sigterm_inner, mut sigint_inner) = { use tokio::signal::unix::{SignalKind, signal}; @@ -265,7 +341,14 @@ pub async fn start_http_server( warn!(?err, "Failed to set set_send_buffer_size"); } - process_connection(socket, tls_acceptor.clone(), http_server.clone(), s3_service.clone(), graceful.clone()); + process_connection( + socket, + tls_acceptor.clone(), + http_server.clone(), + s3_service.clone(), + graceful.clone(), + cors_layer.clone(), + ); } worker_state_manager.update(ServiceState::Stopping); @@ -372,6 +455,7 @@ fn process_connection( http_server: Arc>, s3_service: S3Service, graceful: Arc, + cors_layer: CorsLayer, ) { tokio::spawn(async move { // Build services inside each connected task to avoid passing complex service types across tasks, @@ -423,7 +507,7 @@ fn process_connection( debug!("http request failure error: {:?} in {:?}", _error, latency) }), ) - .layer(CorsLayer::permissive()) + .layer(cors_layer) .layer(RedirectLayer) .service(service); let hybrid_service = TowerToHyperService::new(hybrid_service); diff --git a/rustfs/src/server/mod.rs b/rustfs/src/server/mod.rs index 3b86e513..80dd029e 100644 --- a/rustfs/src/server/mod.rs +++ b/rustfs/src/server/mod.rs @@ -13,11 +13,16 @@ // limitations under the License. mod audit; +pub mod console; mod http; mod hybrid; mod layer; mod service_state; +#[cfg(test)] +mod console_test; + +pub(crate) use console::start_console_server; pub(crate) use http::start_http_server; pub(crate) use service_state::SHUTDOWN_TIMEOUT; pub(crate) use service_state::ServiceState; diff --git a/rustfs/src/server/service_state.rs b/rustfs/src/server/service_state.rs index 8a05a569..ed9e509f 100644 --- a/rustfs/src/server/service_state.rs +++ b/rustfs/src/server/service_state.rs @@ -73,15 +73,15 @@ pub(crate) async fn wait_for_shutdown() -> ShutdownSignal { tokio::select! { _ = tokio::signal::ctrl_c() => { - info!("Received Ctrl-C signal"); + info!("RustFS Received Ctrl-C signal"); ShutdownSignal::CtrlC } _ = sigint.recv() => { - info!("Received SIGINT signal"); + info!("RustFS Received SIGINT signal"); ShutdownSignal::Sigint } _ = sigterm.recv() => { - info!("Received SIGTERM signal"); + info!("RustFS Received SIGTERM signal"); ShutdownSignal::Sigterm } } @@ -121,7 +121,7 @@ impl ServiceStateManager { fn notify_systemd(&self, state: &ServiceState) { match state { ServiceState::Starting => { - info!("Service is starting..."); + info!("RustFS Service is starting..."); #[cfg(target_os = "linux")] if let Err(e) = libsystemd::daemon::notify(false, &[libsystemd::daemon::NotifyState::Status("Starting...".to_string())]) @@ -130,15 +130,15 @@ impl ServiceStateManager { } } ServiceState::Ready => { - info!("Service is ready"); + info!("RustFS Service is ready"); notify_systemd("ready"); } ServiceState::Stopping => { - info!("Service is stopping..."); + info!("RustFS Service is stopping..."); notify_systemd("stopping"); } ServiceState::Stopped => { - info!("Service has stopped"); + info!("RustFS Service has stopped"); #[cfg(target_os = "linux")] if let Err(e) = libsystemd::daemon::notify(false, &[libsystemd::daemon::NotifyState::Status("Stopped".to_string())]) diff --git a/scripts/run.sh b/scripts/run.sh index aca6de0e..735cd973 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -45,7 +45,8 @@ export RUSTFS_VOLUMES="./target/volume/test{1...4}" # export RUSTFS_VOLUMES="./target/volume/test" export RUSTFS_ADDRESS=":9000" export RUSTFS_CONSOLE_ENABLE=true -# export RUSTFS_CONSOLE_ADDRESS=":9001" +export RUSTFS_CONSOLE_ADDRESS=":9001" +export RUSTFS_EXTERNAL_ADDRESS=":9020" # export RUSTFS_SERVER_DOMAINS="localhost:9000" # HTTPS certificate directory # export RUSTFS_TLS_PATH="./deploy/certs"