diff --git a/Cargo.lock b/Cargo.lock index e96b2da1..f823f798 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,7 +24,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03d2d54c4d9e7006f132f615a167865bff927a79ca63d8f637237575ce0a9795" dependencies = [ "crypto-common 0.2.0-rc.5", - "inout 0.2.1", + "inout 0.2.2", ] [[package]] @@ -1068,9 +1068,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", @@ -1087,9 +1087,9 @@ dependencies = [ [[package]] name = "axum-extra" -version = "0.12.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dfbd6109d91702d55fc56df06aae7ed85c465a7a451db6c0e54a4b9ca5983d1" +checksum = "fef252edff26ddba56bbcdf2ee3307b8129acb86f5749b68990c168a6fcc9c76" dependencies = [ "axum", "axum-core", @@ -1185,9 +1185,9 @@ checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" [[package]] name = "bigdecimal" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560f42649de9fa436b73517378a147ec21f6c997a546581df4b4b31677828934" +checksum = "4d6867f1565b3aad85681f1015055b087fcfd840d6aeee6eee7f2da317603695" dependencies = [ "autocfg", "libm", @@ -1459,9 +1459,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.50" +version = "1.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" dependencies = [ "find-msvc-tools", "jobserver", @@ -1574,7 +1574,7 @@ checksum = "155e4a260750fa4f7754649f049748aacc31db238a358d85fd721002f230f92f" dependencies = [ "block-buffer 0.11.0", "crypto-common 0.2.0-rc.5", - "inout 0.2.1", + "inout 0.2.2", ] [[package]] @@ -3367,9 +3367,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" [[package]] name = "findshlibs" @@ -3473,9 +3473,9 @@ dependencies = [ [[package]] name = "fs-err" -version = "3.2.1" +version = "3.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824f08d01d0f496b3eca4f001a13cf17690a6ee930043d20817f547455fd98f8" +checksum = "baf68cef89750956493a66a10f512b9e58d9db21f2a573c079c0bdf1207a54a7" dependencies = [ "autocfg", "tokio", @@ -3629,6 +3629,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b99f0d993a2b9b97b9a201193aa8ad21305cde06a3be9a7e1f8f4201e5cc27e" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + [[package]] name = "getset" version = "0.1.6" @@ -4546,9 +4558,9 @@ dependencies = [ [[package]] name = "inout" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7357b6e7aa75618c7864ebd0634b115a7218b0615f4cb1df33ac3eca23943d4" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" dependencies = [ "hybrid-array", ] @@ -4627,9 +4639,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jemalloc_pprof" @@ -4817,13 +4829,13 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags 2.10.0", "libc", - "redox_syscall 0.6.0", + "redox_syscall 0.7.0", ] [[package]] @@ -5007,9 +5019,9 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "matchit" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea5f97102eb9e54ab99fb70bb175589073f554bdadfb74d9bd656482ea73e2a" +checksum = "b3eede3bdf92f3b4f9dc04072a9ce5ab557d5ec9038773bf9ffcd5588b3cc05b" [[package]] name = "md-5" @@ -5761,11 +5773,11 @@ dependencies = [ [[package]] name = "password-hash" -version = "0.6.0-rc.6" +version = "0.6.0-rc.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "383d290055c99f2dd7dece082088d89494dff6d79277fbac4a7da21c1bf2ab6b" +checksum = "c351143b5ab27b1f1d24712f21ea4d0458fe74f60dd5839297dabcc2ecd24d58" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.0-rc.0", "phc", ] @@ -5883,12 +5895,12 @@ dependencies = [ [[package]] name = "phc" -version = "0.6.0-rc.0" +version = "0.6.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f960577aaac5c259bc0866d685ba315c0ed30793c602d7287f54980913863" +checksum = "71d390c5fe8d102c2c18ff39f1e72b9ad5996de282c2d831b0312f56910f5508" dependencies = [ "base64ct", - "getrandom 0.3.4", + "getrandom 0.4.0-rc.0", "subtle", ] @@ -6098,9 +6110,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" [[package]] name = "potential_utf" @@ -6233,9 +6245,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" dependencies = [ "unicode-ident", ] @@ -6635,9 +6647,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5" +checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" dependencies = [ "bitflags 2.10.0", ] @@ -6830,7 +6842,7 @@ dependencies = [ "pastey", "pin-project-lite", "rmcp-macros", - "schemars 1.1.0", + "schemars 1.2.0", "serde", "serde_json", "thiserror 2.0.17", @@ -7045,7 +7057,7 @@ dependencies = [ "hyper-util", "jemalloc_pprof", "libsystemd", - "matchit 0.9.0", + "matchit 0.9.1", "md5", "metrics", "mimalloc", @@ -7061,6 +7073,7 @@ dependencies = [ "rustfs-audit", "rustfs-common", "rustfs-config", + "rustfs-credentials", "rustfs-ecstore", "rustfs-filemeta", "rustfs-iam", @@ -7210,6 +7223,17 @@ dependencies = [ "const-str", ] +[[package]] +name = "rustfs-credentials" +version = "0.0.5" +dependencies = [ + "base64-simd", + "rand 0.10.0-rc.5", + "serde", + "serde_json", + "time", +] + [[package]] name = "rustfs-crypto" version = "0.0.5" @@ -7276,6 +7300,7 @@ dependencies = [ "rustfs-checksums", "rustfs-common", "rustfs-config", + "rustfs-credentials", "rustfs-filemeta", "rustfs-lock", "rustfs-madmin", @@ -7318,7 +7343,6 @@ dependencies = [ "bytes", "crc-fast", "criterion", - "lazy_static", "regex", "rmp", "rmp-serde", @@ -7344,6 +7368,7 @@ dependencies = [ "jsonwebtoken", "pollster", "rand 0.10.0-rc.5", + "rustfs-credentials", "rustfs-crypto", "rustfs-ecstore", "rustfs-madmin", @@ -7427,7 +7452,7 @@ dependencies = [ "clap", "mime_guess", "rmcp", - "schemars 1.1.0", + "schemars 1.2.0", "serde", "serde_json", "tokio", @@ -7505,10 +7530,10 @@ dependencies = [ "jsonwebtoken", "moka", "pollster", - "rand 0.10.0-rc.5", "regex", "reqwest", "rustfs-config", + "rustfs-credentials", "rustfs-crypto", "serde", "serde_json", @@ -7528,6 +7553,7 @@ dependencies = [ "flatbuffers", "prost 0.14.1", "rustfs-common", + "rustfs-credentials", "tonic", "tonic-prost", "tonic-prost-build", @@ -7865,9 +7891,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "s3s" @@ -7959,9 +7985,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" +checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" dependencies = [ "chrono", "dyn-clone", @@ -7973,9 +7999,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301858a4023d78debd2353c7426dc486001bddc91ae31a76fb1f55132f7e2633" +checksum = "4908ad288c5035a8eb12cfdf0d49270def0a268ee162b75eeee0f85d155a7c45" dependencies = [ "proc-macro2", "quote", @@ -8124,9 +8150,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.147" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af14725505314343e673e9ecb7cd7e8a36aa9791eb936235a3567cc31447ae4" +checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" dependencies = [ "itoa", "memchr", @@ -8179,7 +8205,7 @@ dependencies = [ "indexmap 1.9.3", "indexmap 2.12.1", "schemars 0.9.0", - "schemars 1.1.0", + "schemars 1.2.0", "serde_core", "serde_json", "serde_with_macros", @@ -8317,10 +8343,11 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -10432,9 +10459,9 @@ checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" [[package]] name = "zmij" -version = "0.1.7" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e404bcd8afdaf006e529269d3e85a743f9480c3cef60034d77860d02964f3ba" +checksum = "f5858cd3a46fff31e77adea2935e357e3a2538d870741617bfb7c943e218fee6" [[package]] name = "zopfli" diff --git a/Cargo.toml b/Cargo.toml index ae85bdc5..6eed163c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ members = [ "crates/audit", # Audit target management system with multi-target fan-out "crates/common", # Shared utilities and data structures "crates/config", # Configuration management + "crates/credentials", # Credential management system "crates/crypto", # Cryptography and security features "crates/ecstore", # Erasure coding storage implementation "crates/e2e_test", # End-to-end test suite @@ -71,6 +72,7 @@ rustfs-audit = { path = "crates/audit", version = "0.0.5" } rustfs-checksums = { path = "crates/checksums", version = "0.0.5" } rustfs-common = { path = "crates/common", version = "0.0.5" } rustfs-config = { path = "./crates/config", version = "0.0.5" } +rustfs-credentials = { path = "crates/credentials", version = "0.0.5" } rustfs-crypto = { path = "crates/crypto", version = "0.0.5" } rustfs-ecstore = { path = "crates/ecstore", version = "0.0.5" } rustfs-filemeta = { path = "crates/filemeta", version = "0.0.5" } @@ -98,7 +100,7 @@ async-compression = { version = "0.4.19" } async-recursion = "1.1.1" async-trait = "0.1.89" axum = "0.8.8" -axum-extra = "0.12.3" +axum-extra = "0.12.5" axum-server = { version = "0.8.0", features = ["tls-rustls-no-provider"], default-features = false } futures = "0.3.31" futures-core = "0.3.31" @@ -135,9 +137,9 @@ rmcp = { version = "0.12.0" } rmp = { version = "0.8.15" } rmp-serde = { version = "1.3.1" } serde = { version = "1.0.228", features = ["derive"] } -serde_json = { version = "1.0.147", features = ["raw_value"] } +serde_json = { version = "1.0.148", features = ["raw_value"] } serde_urlencoded = "0.7.1" -schemars = "1.1.0" +schemars = "1.2.0" # Cryptography and Security aes-gcm = { version = "0.11.0-rc.2", features = ["rand_core"] } @@ -200,7 +202,7 @@ libc = "0.2.178" libsystemd = "0.7.2" local-ip-address = "0.6.8" lz4 = "1.28.1" -matchit = "0.9.0" +matchit = "0.9.1" md-5 = "0.11.0-rc.3" md5 = "0.8.0" mime_guess = "2.0.5" @@ -276,8 +278,6 @@ jemalloc_pprof = { version = "0.8.1", features = ["symbolize", "flamegraph"] } # Used to generate CPU performance analysis data and flame diagrams pprof = { version = "0.15.0", features = ["flamegraph", "protobuf-codec"] } - - [workspace.metadata.cargo-shear] ignored = ["rustfs", "rustfs-mcp"] diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index cea4e0a9..e9ed0caf 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -39,4 +39,4 @@ path-clean = { workspace = true } rmp-serde = { workspace = true } async-trait = { workspace = true } s3s = { workspace = true } -tracing = { workspace = true } +tracing = { workspace = true } \ No newline at end of file diff --git a/crates/config/src/constants/app.rs b/crates/config/src/constants/app.rs index 0610319e..0b2035e4 100644 --- a/crates/config/src/constants/app.rs +++ b/crates/config/src/constants/app.rs @@ -49,21 +49,6 @@ pub const SERVICE_VERSION: &str = "1.0.0"; /// Default value: production pub const ENVIRONMENT: &str = "production"; -/// Default Access Key -/// Default value: rustfsadmin -/// Environment variable: RUSTFS_ACCESS_KEY -/// Command line argument: --access-key -/// Example: RUSTFS_ACCESS_KEY=rustfsadmin -/// Example: --access-key rustfsadmin -pub const DEFAULT_ACCESS_KEY: &str = "rustfsadmin"; -/// Default Secret Key -/// Default value: rustfsadmin -/// Environment variable: RUSTFS_SECRET_KEY -/// Command line argument: --secret-key -/// Example: RUSTFS_SECRET_KEY=rustfsadmin -/// Example: --secret-key rustfsadmin -pub const DEFAULT_SECRET_KEY: &str = "rustfsadmin"; - /// Default console enable /// This is the default value for the console server. /// It is used to enable or disable the console server. @@ -185,6 +170,12 @@ pub const KI_B: usize = 1024; /// Default value: 1048576 pub const MI_B: usize = 1024 * 1024; +/// Environment variable for gRPC authentication token +/// Used to set the authentication token for gRPC communication +/// Example: RUSTFS_GRPC_AUTH_TOKEN=your_token_here +/// Default value: No default value. RUSTFS_SECRET_KEY value is recommended. +pub const ENV_GRPC_AUTH_TOKEN: &str = "RUSTFS_GRPC_AUTH_TOKEN"; + #[cfg(test)] mod tests { use super::*; @@ -225,20 +216,6 @@ mod tests { ); } - #[test] - fn test_security_constants() { - // Test security related constants - assert_eq!(DEFAULT_ACCESS_KEY, "rustfsadmin"); - assert!(DEFAULT_ACCESS_KEY.len() >= 8, "Access key should be at least 8 characters"); - - assert_eq!(DEFAULT_SECRET_KEY, "rustfsadmin"); - assert!(DEFAULT_SECRET_KEY.len() >= 8, "Secret key should be at least 8 characters"); - - // In production environment, access key and secret key should be different - // These are default values, so being the same is acceptable, but should be warned in documentation - println!("Warning: Default access key and secret key are the same. Change them in production!"); - } - #[test] fn test_file_path_constants() { assert_eq!(RUSTFS_TLS_KEY, "rustfs_key.pem"); @@ -300,8 +277,6 @@ mod tests { DEFAULT_LOG_LEVEL, SERVICE_VERSION, ENVIRONMENT, - DEFAULT_ACCESS_KEY, - DEFAULT_SECRET_KEY, RUSTFS_TLS_KEY, RUSTFS_TLS_CERT, DEFAULT_ADDRESS, @@ -331,29 +306,6 @@ mod tests { assert_ne!(DEFAULT_CONSOLE_PORT, 0, "Console port should not be zero"); } - #[test] - fn test_security_best_practices() { - // Test security best practices - - // These are default values, should be changed in production environments - println!("Security Warning: Default credentials detected!"); - println!("Access Key: {DEFAULT_ACCESS_KEY}"); - println!("Secret Key: {DEFAULT_SECRET_KEY}"); - println!("These should be changed in production environments!"); - - // Verify that key lengths meet minimum security requirements - assert!(DEFAULT_ACCESS_KEY.len() >= 8, "Access key should be at least 8 characters"); - assert!(DEFAULT_SECRET_KEY.len() >= 8, "Secret key should be at least 8 characters"); - - // Check if default credentials contain common insecure patterns - let _insecure_patterns = ["admin", "password", "123456", "default"]; - let _access_key_lower = DEFAULT_ACCESS_KEY.to_lowercase(); - let _secret_key_lower = DEFAULT_SECRET_KEY.to_lowercase(); - - // Note: More security check logic can be added here - // For example, check if keys contain insecure patterns - } - #[test] fn test_configuration_consistency() { // Test configuration consistency diff --git a/crates/credentials/Cargo.toml b/crates/credentials/Cargo.toml new file mode 100644 index 00000000..051644e7 --- /dev/null +++ b/crates/credentials/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "rustfs-credentials" +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true +version.workspace = true +homepage.workspace = true +description = "Credentials management utilities for RustFS, enabling secure handling of authentication and authorization data." +keywords = ["rustfs", "Minio", "credentials", "authentication", "authorization"] +categories = ["web-programming", "development-tools", "data-structures", "security"] + +[dependencies] +base64-simd = { workspace = true } +rand = { workspace = true } +serde = { workspace = true } +serde_json.workspace = true +time = { workspace = true, features = ["serde-human-readable"] } + +[lints] +workspace = true diff --git a/crates/credentials/README.md b/crates/credentials/README.md new file mode 100644 index 00000000..1643b121 --- /dev/null +++ b/crates/credentials/README.md @@ -0,0 +1,44 @@ +[![RustFS](https://rustfs.com/images/rustfs-github.png)](https://rustfs.com) + +# RustFS Credentials - Credential Management Module + +

+ A module for managing credentials within the RustFS distributed object storage system. +

+ +

+ CI + ๐Ÿ“– Documentation + ยท ๐Ÿ› Bug Reports + ยท ๐Ÿ’ฌ Discussions +

+ +--- + +This module provides a secure and efficient way to handle various types of credentials, +such as API keys, access tokens, and cryptographic keys, required for interacting with +the RustFS ecosystem and external services. + +## ๐Ÿ“– Overview + +**RustFS Credentials** is a module dedicated to managing credentials for the [RustFS](https://rustfs.com) distributed +object storage system. For the complete RustFS experience, +please visit the [main RustFS repository](https://github.com/rustfs/rustfs) + +## โœจ Features + +- Secure storage and retrieval of credentials +- Support for multiple credential types (API keys, tokens, etc.) +- Encryption of sensitive credential data +- Integration with external secret management systems +- Easy-to-use API for credential management +- Credential rotation and expiration handling + +## ๐Ÿ“š Documentation + +For comprehensive documentation, examples, and usage guides, please visit the +main [RustFS repository](https://github.com/rustfs/rustfs). + +## ๐Ÿ“„ License + +This project is licensed under the Apache License 2.0 - see the [LICENSE](../../LICENSE) file for details. \ No newline at end of file diff --git a/crates/credentials/src/constants.rs b/crates/credentials/src/constants.rs new file mode 100644 index 00000000..442e2833 --- /dev/null +++ b/crates/credentials/src/constants.rs @@ -0,0 +1,94 @@ +// 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. + +/// Default Access Key +/// Default value: rustfsadmin +/// Environment variable: RUSTFS_ACCESS_KEY +/// Command line argument: --access-key +/// Example: RUSTFS_ACCESS_KEY=rustfsadmin +/// Example: --access-key rustfsadmin +pub const DEFAULT_ACCESS_KEY: &str = "rustfsadmin"; +/// Default Secret Key +/// Default value: rustfsadmin +/// Environment variable: RUSTFS_SECRET_KEY +/// Command line argument: --secret-key +/// Example: RUSTFS_SECRET_KEY=rustfsadmin +/// Example: --secret-key rustfsadmin +pub const DEFAULT_SECRET_KEY: &str = "rustfsadmin"; + +/// Environment variable for gRPC authentication token +/// Used to set the authentication token for gRPC communication +/// Example: RUSTFS_GRPC_AUTH_TOKEN=your_token_here +/// Default value: No default value. RUSTFS_SECRET_KEY value is recommended. +pub const ENV_GRPC_AUTH_TOKEN: &str = "RUSTFS_GRPC_AUTH_TOKEN"; + +/// IAM Policy Types +/// Used to differentiate between embedded and inherited policies +/// Example: "embedded-policy" or "inherited-policy" +/// Default value: "embedded-policy" +pub const EMBEDDED_POLICY_TYPE: &str = "embedded-policy"; + +/// IAM Policy Types +/// Used to differentiate between embedded and inherited policies +/// Example: "embedded-policy" or "inherited-policy" +/// Default value: "inherited-policy" +pub const INHERITED_POLICY_TYPE: &str = "inherited-policy"; + +/// IAM Policy Claim Name for Service Account +/// Used to identify the service account policy claim in JWT tokens +/// Example: "sa-policy" +/// Default value: "sa-policy" +pub const IAM_POLICY_CLAIM_NAME_SA: &str = "sa-policy"; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_security_constants() { + // Test security related constants + assert_eq!(DEFAULT_ACCESS_KEY, "rustfsadmin"); + assert!(DEFAULT_ACCESS_KEY.len() >= 8, "Access key should be at least 8 characters"); + + assert_eq!(DEFAULT_SECRET_KEY, "rustfsadmin"); + assert!(DEFAULT_SECRET_KEY.len() >= 8, "Secret key should be at least 8 characters"); + + // In production environment, access key and secret key should be different + // These are default values, so being the same is acceptable, but should be warned in documentation + println!("Warning: Default access key and secret key are the same. Change them in production!"); + } + + #[test] + fn test_security_best_practices() { + // Test security best practices + + // These are default values, should be changed in production environments + println!("Security Warning: Default credentials detected!"); + println!("Access Key: {DEFAULT_ACCESS_KEY}"); + println!("Secret Key: {DEFAULT_SECRET_KEY}"); + println!("These should be changed in production environments!"); + + // Verify that key lengths meet minimum security requirements + assert!(DEFAULT_ACCESS_KEY.len() >= 8, "Access key should be at least 8 characters"); + assert!(DEFAULT_SECRET_KEY.len() >= 8, "Secret key should be at least 8 characters"); + + // Check if default credentials contain common insecure patterns + let _insecure_patterns = ["admin", "password", "123456", "default"]; + let _access_key_lower = DEFAULT_ACCESS_KEY.to_lowercase(); + let _secret_key_lower = DEFAULT_SECRET_KEY.to_lowercase(); + + // Note: More security check logic can be added here + // For example, check if keys contain insecure patterns + } +} diff --git a/crates/credentials/src/credentials.rs b/crates/credentials/src/credentials.rs new file mode 100644 index 00000000..34aa0fe9 --- /dev/null +++ b/crates/credentials/src/credentials.rs @@ -0,0 +1,386 @@ +// 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::{DEFAULT_SECRET_KEY, ENV_GRPC_AUTH_TOKEN, IAM_POLICY_CLAIM_NAME_SA, INHERITED_POLICY_TYPE}; +use rand::{Rng, RngCore}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap; +use std::env; +use std::io::Error; +use std::sync::OnceLock; +use time::OffsetDateTime; + +/// Global active credentials +static GLOBAL_ACTIVE_CRED: OnceLock = OnceLock::new(); + +/// Global gRPC authentication token +static GLOBAL_GRPC_AUTH_TOKEN: OnceLock = OnceLock::new(); + +/// Initialize the global action credentials +/// +/// # Arguments +/// * `ak` - Optional access key +/// * `sk` - Optional secret key +/// +/// # Returns +/// * `Result<(), Box>` - Ok if successful, Err with existing credentials if already initialized +/// +/// # Panics +/// This function panics if automatic credential generation fails when `ak` or `sk` +/// are `None`, for example if the random number generator fails while calling +/// `gen_access_key` or `gen_secret_key`. +pub fn init_global_action_credentials(ak: Option, sk: Option) -> Result<(), Box> { + let ak = ak.unwrap_or_else(|| gen_access_key(20).expect("Failed to generate access key")); + let sk = sk.unwrap_or_else(|| gen_secret_key(32).expect("Failed to generate secret key")); + + let cred = Credentials { + access_key: ak, + secret_key: sk, + ..Default::default() + }; + + GLOBAL_ACTIVE_CRED.set(cred).map_err(|e| { + Box::new(Credentials { + access_key: e.access_key.clone(), + ..Default::default() + }) + }) +} + +/// Get the global action credentials +pub fn get_global_action_cred() -> Option { + GLOBAL_ACTIVE_CRED.get().cloned() +} + +/// Get the global secret key +/// +/// # Returns +/// * `Option` - The global secret key, if set +/// +pub fn get_global_secret_key_opt() -> Option { + GLOBAL_ACTIVE_CRED.get().map(|cred| cred.secret_key.clone()) +} + +/// Get the global secret key +/// +/// # Returns +/// * `String` - The global secret key, or empty string if not set +/// +pub fn get_global_secret_key() -> String { + GLOBAL_ACTIVE_CRED + .get() + .map(|cred| cred.secret_key.clone()) + .unwrap_or_default() +} + +/// Get the global access key +/// +/// # Returns +/// * `Option` - The global access key, if set +/// +pub fn get_global_access_key_opt() -> Option { + GLOBAL_ACTIVE_CRED.get().map(|cred| cred.access_key.clone()) +} + +/// Get the global access key +/// +/// # Returns +/// * `String` - The global access key, or empty string if not set +/// +pub fn get_global_access_key() -> String { + GLOBAL_ACTIVE_CRED + .get() + .map(|cred| cred.access_key.clone()) + .unwrap_or_default() +} + +/// Generates a random access key of the specified length. +/// +/// # Arguments +/// * `length` - The length of the access key to generate +/// +/// # Returns +/// * `Result` - A result containing the generated access key or an error if the length is too short +/// +/// # Errors +/// This function will return an error if the specified length is less than 3. +/// +/// Examples +/// ```no_run +/// use rustfs_credentials::gen_access_key; +/// +/// let access_key = gen_access_key(16).unwrap(); +/// println!("Generated access key: {}", access_key); +/// ``` +/// +pub fn gen_access_key(length: usize) -> std::io::Result { + const ALPHA_NUMERIC_TABLE: [char; 36] = [ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + ]; + + if length < 3 { + return Err(Error::other("access key length is too short")); + } + + let mut result = String::with_capacity(length); + let mut rng = rand::rng(); + + for _ in 0..length { + result.push(ALPHA_NUMERIC_TABLE[rng.random_range(0..ALPHA_NUMERIC_TABLE.len())]); + } + + Ok(result) +} + +/// Generates a random secret key of the specified length. +/// +/// # Arguments +/// * `length` - The length of the secret key to generate +/// +/// # Returns +/// * `Result` - A result containing the generated secret key or an error if the length is too short +/// +/// # Errors +/// This function will return an error if the specified length is less than 8. +/// +/// # Examples +/// ```no_run +/// use rustfs_credentials::gen_secret_key; +/// +/// let secret_key = gen_secret_key(32).unwrap(); +/// println!("Generated secret key: {}", secret_key); +/// ``` +/// +pub fn gen_secret_key(length: usize) -> std::io::Result { + use base64_simd::URL_SAFE_NO_PAD; + + if length < 8 { + return Err(Error::other("secret key length is too short")); + } + let mut rng = rand::rng(); + + let mut key = vec![0u8; URL_SAFE_NO_PAD.estimated_decoded_length(length)]; + rng.fill_bytes(&mut key); + + let encoded = URL_SAFE_NO_PAD.encode_to_string(&key); + let key_str = encoded.replace("/", "+"); + + Ok(key_str) +} + +/// Get the gRPC authentication token from environment variable +/// +/// # Returns +/// * `String` - The gRPC authentication token +/// +pub fn get_grpc_token() -> String { + GLOBAL_GRPC_AUTH_TOKEN + .get_or_init(|| { + env::var(ENV_GRPC_AUTH_TOKEN) + .unwrap_or_else(|_| get_global_secret_key_opt().unwrap_or_else(|| DEFAULT_SECRET_KEY.to_string())) + }) + .clone() +} + +/// Credentials structure +/// +/// Fields: +/// - access_key: Access key string +/// - secret_key: Secret key string +/// - session_token: Session token string +/// - expiration: Optional expiration time as OffsetDateTime +/// - status: Status string (e.g., "active", "off") +/// - parent_user: Parent user string +/// - groups: Optional list of groups +/// - claims: Optional map of claims +/// - name: Optional name string +/// - description: Optional description string +/// +#[derive(Serialize, Deserialize, Clone, Default, Debug)] +pub struct Credentials { + pub access_key: String, + pub secret_key: String, + pub session_token: String, + pub expiration: Option, + pub status: String, + pub parent_user: String, + pub groups: Option>, + pub claims: Option>, + pub name: Option, + pub description: Option, +} + +impl Credentials { + pub fn is_expired(&self) -> bool { + if self.expiration.is_none() { + return false; + } + + self.expiration + .as_ref() + .map(|e| OffsetDateTime::now_utc() > *e) + .unwrap_or(false) + } + + pub fn is_temp(&self) -> bool { + !self.session_token.is_empty() && !self.is_expired() + } + + pub fn is_service_account(&self) -> bool { + self.claims + .as_ref() + .map(|x| x.get(IAM_POLICY_CLAIM_NAME_SA).is_some_and(|_| !self.parent_user.is_empty())) + .unwrap_or_default() + } + + pub fn is_implied_policy(&self) -> bool { + if self.is_service_account() { + return self + .claims + .as_ref() + .map(|x| x.get(IAM_POLICY_CLAIM_NAME_SA).is_some_and(|v| v == INHERITED_POLICY_TYPE)) + .unwrap_or_default(); + } + + false + } + + pub fn is_valid(&self) -> bool { + if self.status == "off" { + return false; + } + + self.access_key.len() >= 3 && self.secret_key.len() >= 8 && !self.is_expired() + } + + pub fn is_owner(&self) -> bool { + false + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{IAM_POLICY_CLAIM_NAME_SA, INHERITED_POLICY_TYPE}; + use time::Duration; + + #[test] + fn test_credentials_is_expired() { + let mut cred = Credentials::default(); + assert!(!cred.is_expired()); + + cred.expiration = Some(OffsetDateTime::now_utc() + Duration::hours(1)); + assert!(!cred.is_expired()); + + cred.expiration = Some(OffsetDateTime::now_utc() - Duration::hours(1)); + assert!(cred.is_expired()); + } + + #[test] + fn test_credentials_is_temp() { + let mut cred = Credentials::default(); + assert!(!cred.is_temp()); + + cred.session_token = "token".to_string(); + assert!(cred.is_temp()); + + cred.expiration = Some(OffsetDateTime::now_utc() - Duration::hours(1)); + assert!(!cred.is_temp()); + } + + #[test] + fn test_credentials_is_service_account() { + let mut cred = Credentials::default(); + assert!(!cred.is_service_account()); + + let mut claims = HashMap::new(); + claims.insert(IAM_POLICY_CLAIM_NAME_SA.to_string(), Value::String("policy".to_string())); + cred.claims = Some(claims); + cred.parent_user = "parent".to_string(); + assert!(cred.is_service_account()); + } + + #[test] + fn test_credentials_is_implied_policy() { + let mut cred = Credentials::default(); + assert!(!cred.is_implied_policy()); + + let mut claims = HashMap::new(); + claims.insert(IAM_POLICY_CLAIM_NAME_SA.to_string(), Value::String(INHERITED_POLICY_TYPE.to_string())); + cred.claims = Some(claims); + cred.parent_user = "parent".to_string(); + assert!(cred.is_implied_policy()); + } + + #[test] + fn test_credentials_is_valid() { + let mut cred = Credentials::default(); + assert!(!cred.is_valid()); + + cred.access_key = "abc".to_string(); + cred.secret_key = "12345678".to_string(); + assert!(cred.is_valid()); + + cred.status = "off".to_string(); + assert!(!cred.is_valid()); + } + + #[test] + fn test_credentials_is_owner() { + let cred = Credentials::default(); + assert!(!cred.is_owner()); + } + + #[test] + fn test_global_credentials_flow() { + // Since OnceLock can only be set once, we put together all globally related tests + // If it has already been initialized (possibly from other tests), we verify the results directly + if get_global_action_cred().is_none() { + // Verify that the initial state is empty + assert!(get_global_access_key_opt().is_none()); + assert_eq!(get_global_access_key(), ""); + assert!(get_global_secret_key_opt().is_none()); + assert_eq!(get_global_secret_key(), ""); + + // Initialize + let test_ak = "test_access_key".to_string(); + let test_sk = "test_secret_key_123456".to_string(); + init_global_action_credentials(Some(test_ak.clone()), Some(test_sk.clone())).ok(); + } + + // Verify the state after initialization + let cred = get_global_action_cred().expect("Global credentials should be set"); + assert!(!cred.access_key.is_empty()); + assert!(!cred.secret_key.is_empty()); + + assert!(get_global_access_key_opt().is_some()); + assert!(!get_global_access_key().is_empty()); + assert!(get_global_secret_key_opt().is_some()); + assert!(!get_global_secret_key().is_empty()); + } + + #[test] + fn test_init_global_credentials_auto_gen() { + // If it hasn't already been initialized, the test automatically generates logic + if get_global_action_cred().is_none() { + init_global_action_credentials(None, None).ok(); + let ak = get_global_access_key(); + let sk = get_global_secret_key(); + assert_eq!(ak.len(), 20); + assert_eq!(sk.len(), 32); + } + } +} diff --git a/crates/credentials/src/lib.rs b/crates/credentials/src/lib.rs new file mode 100644 index 00000000..876d61b8 --- /dev/null +++ b/crates/credentials/src/lib.rs @@ -0,0 +1,19 @@ +// 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. + +mod constants; +mod credentials; + +pub use constants::*; +pub use credentials::*; diff --git a/crates/ecstore/Cargo.toml b/crates/ecstore/Cargo.toml index bd021c19..d4fe7d4c 100644 --- a/crates/ecstore/Cargo.toml +++ b/crates/ecstore/Cargo.toml @@ -34,12 +34,19 @@ workspace = true default = [] [dependencies] +rustfs-filemeta.workspace = true +rustfs-utils = { workspace = true, features = ["full"] } +rustfs-rio.workspace = true +rustfs-signer.workspace = true +rustfs-checksums.workspace = true rustfs-config = { workspace = true, features = ["constants", "notify", "audit"] } +rustfs-credentials = { workspace = true } +rustfs-common.workspace = true +rustfs-policy.workspace = true +rustfs-protos.workspace = true async-trait.workspace = true bytes.workspace = true byteorder = { workspace = true } -rustfs-common.workspace = true -rustfs-policy.workspace = true chrono.workspace = true glob = { workspace = true } thiserror.workspace = true @@ -60,7 +67,6 @@ lazy_static.workspace = true rustfs-lock.workspace = true regex = { workspace = true } path-absolutize = { workspace = true } -rustfs-protos.workspace = true rmp.workspace = true rmp-serde.workspace = true tokio-util = { workspace = true, features = ["io", "compat"] } @@ -91,11 +97,6 @@ aws-sdk-s3 = { workspace = true } urlencoding = { workspace = true } smallvec = { workspace = true } shadow-rs.workspace = true -rustfs-filemeta.workspace = true -rustfs-utils = { workspace = true, features = ["full"] } -rustfs-rio.workspace = true -rustfs-signer.workspace = true -rustfs-checksums.workspace = true async-recursion.workspace = true aws-credential-types = { workspace = true } aws-smithy-types = { workspace = true } diff --git a/crates/ecstore/README_cn.md b/crates/ecstore/README_cn.md deleted file mode 100644 index 5bd11b23..00000000 --- a/crates/ecstore/README_cn.md +++ /dev/null @@ -1,19 +0,0 @@ -# ECStore - Erasure Coding Storage - -ECStore provides erasure coding functionality for the RustFS project, using high-performance Reed-Solomon SIMD implementation for optimal performance. - -## Features - -- **Reed-Solomon Implementation**: High-performance SIMD-optimized erasure coding -- **Cross-Platform Compatibility**: Support for x86_64, aarch64, and other architectures -- **Performance Optimized**: SIMD instructions for maximum throughput -- **Thread Safety**: Safe concurrent access with caching optimizations -- **Scalable**: Excellent performance for high-throughput scenarios - -## Documentation - -For complete documentation, examples, and usage information, please visit the main [RustFS repository](https://github.com/rustfs/rustfs). - -## License - -This project is licensed under the Apache License, Version 2.0. diff --git a/crates/ecstore/src/global.rs b/crates/ecstore/src/global.rs index 9c45f7b4..0a6621e9 100644 --- a/crates/ecstore/src/global.rs +++ b/crates/ecstore/src/global.rs @@ -21,7 +21,6 @@ use crate::{ tier::tier::TierConfigMgr, }; use lazy_static::lazy_static; -use rustfs_policy::auth::Credentials; use std::{ collections::HashMap, sync::{Arc, OnceLock}, @@ -61,49 +60,6 @@ lazy_static! { /// Global cancellation token for background services (data scanner and auto heal) static GLOBAL_BACKGROUND_SERVICES_CANCEL_TOKEN: OnceLock = OnceLock::new(); -/// Global active credentials -static GLOBAL_ACTIVE_CRED: OnceLock = OnceLock::new(); - -/// Initialize the global action credentials -/// -/// # Arguments -/// * `ak` - Optional access key -/// * `sk` - Optional secret key -/// -/// # Returns -/// * None -/// -pub fn init_global_action_credentials(ak: Option, sk: Option) { - let ak = { - if let Some(k) = ak { - k - } else { - rustfs_utils::string::gen_access_key(20).unwrap_or_default() - } - }; - - let sk = { - if let Some(k) = sk { - k - } else { - rustfs_utils::string::gen_secret_key(32).unwrap_or_default() - } - }; - - GLOBAL_ACTIVE_CRED - .set(Credentials { - access_key: ak, - secret_key: sk, - ..Default::default() - }) - .unwrap(); -} - -/// Get the global action credentials -pub fn get_global_action_cred() -> Option { - GLOBAL_ACTIVE_CRED.get().cloned() -} - /// Get the global rustfs port /// /// # Returns diff --git a/crates/ecstore/src/rpc/http_auth.rs b/crates/ecstore/src/rpc/http_auth.rs index faad926e..b283b3d1 100644 --- a/crates/ecstore/src/rpc/http_auth.rs +++ b/crates/ecstore/src/rpc/http_auth.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::global::get_global_action_cred; use base64::Engine as _; use base64::engine::general_purpose; use hmac::{Hmac, KeyInit, Mac}; @@ -20,6 +19,7 @@ use http::HeaderMap; use http::HeaderValue; use http::Method; use http::Uri; +use rustfs_credentials::get_global_action_cred; use sha2::Sha256; use time::OffsetDateTime; use tracing::error; diff --git a/crates/filemeta/Cargo.toml b/crates/filemeta/Cargo.toml index 915ec574..d80db2c3 100644 --- a/crates/filemeta/Cargo.toml +++ b/crates/filemeta/Cargo.toml @@ -35,12 +35,11 @@ uuid = { workspace = true, features = ["v4", "fast-rng", "serde"] } tokio = { workspace = true, features = ["io-util", "macros", "sync"] } xxhash-rust = { workspace = true, features = ["xxh64"] } bytes.workspace = true -rustfs-utils = { workspace = true, features = ["hash","http"] } +rustfs-utils = { workspace = true, features = ["hash", "http"] } byteorder = { workspace = true } tracing.workspace = true thiserror.workspace = true s3s.workspace = true -lazy_static.workspace = true regex.workspace = true [dev-dependencies] diff --git a/crates/filemeta/src/replication.rs b/crates/filemeta/src/replication.rs index 1f3358c8..b7038362 100644 --- a/crates/filemeta/src/replication.rs +++ b/crates/filemeta/src/replication.rs @@ -19,6 +19,7 @@ use rustfs_utils::http::RESERVED_METADATA_PREFIX_LOWER; use serde::{Deserialize, Serialize}; use std::any::Any; use std::collections::HashMap; +use std::sync::LazyLock; use std::time::Duration; use time::OffsetDateTime; use uuid::Uuid; @@ -773,9 +774,7 @@ impl ReplicationWorkerOperation for ReplicateObjectInfo { } } -lazy_static::lazy_static! { - static ref REPL_STATUS_REGEX: Regex = Regex::new(r"([^=].*?)=([^,].*?);").unwrap(); -} +static REPL_STATUS_REGEX: LazyLock = LazyLock::new(|| Regex::new(r"([^=].*?)=([^,].*?);").unwrap()); impl ReplicateObjectInfo { /// Returns replication status of a target diff --git a/crates/iam/Cargo.toml b/crates/iam/Cargo.toml index 104f1613..09770a4a 100644 --- a/crates/iam/Cargo.toml +++ b/crates/iam/Cargo.toml @@ -29,6 +29,7 @@ documentation = "https://docs.rs/rustfs-iam/latest/rustfs_iam/" workspace = true [dependencies] +rustfs-credentials = { workspace = true } tokio.workspace = true time = { workspace = true, features = ["serde-human-readable"] } serde = { workspace = true, features = ["derive", "rc"] } diff --git a/crates/iam/src/manager.rs b/crates/iam/src/manager.rs index 5fa5220b..c4b8ef71 100644 --- a/crates/iam/src/manager.rs +++ b/crates/iam/src/manager.rs @@ -24,15 +24,13 @@ use crate::{ }, }; use futures::future::join_all; -use rustfs_ecstore::global::get_global_action_cred; +use rustfs_credentials::{Credentials, EMBEDDED_POLICY_TYPE, INHERITED_POLICY_TYPE, get_global_action_cred}; use rustfs_madmin::{AccountStatus, AddOrUpdateUserReq, GroupDesc}; use rustfs_policy::{ arn::ARN, - auth::{self, Credentials, UserIdentity, is_secret_key_valid, jwt_sign}, + auth::{self, UserIdentity, is_secret_key_valid, jwt_sign}, format::Format, - policy::{ - EMBEDDED_POLICY_TYPE, INHERITED_POLICY_TYPE, Policy, PolicyDoc, default::DEFAULT_POLICIES, iam_policy_claim_name_sa, - }, + policy::{Policy, PolicyDoc, default::DEFAULT_POLICIES, iam_policy_claim_name_sa}, }; use rustfs_utils::path::path_join_buf; use serde::{Deserialize, Serialize}; diff --git a/crates/iam/src/store/object.rs b/crates/iam/src/store/object.rs index 5479465b..af53aa69 100644 --- a/crates/iam/src/store/object.rs +++ b/crates/iam/src/store/object.rs @@ -20,6 +20,7 @@ use crate::{ manager::{extract_jwt_claims, get_default_policyes}, }; use futures::future::join_all; +use rustfs_credentials::get_global_action_cred; use rustfs_ecstore::StorageAPI as _; use rustfs_ecstore::store_api::{ObjectInfoOrErr, WalkOptions}; use rustfs_ecstore::{ @@ -27,7 +28,6 @@ use rustfs_ecstore::{ RUSTFS_CONFIG_PREFIX, com::{delete_config, read_config, read_config_with_metadata, save_config}, }, - global::get_global_action_cred, store::ECStore, store_api::{ObjectInfo, ObjectOptions}, }; diff --git a/crates/iam/src/sys.rs b/crates/iam/src/sys.rs index a05cdb6b..6b6648c1 100644 --- a/crates/iam/src/sys.rs +++ b/crates/iam/src/sys.rs @@ -24,19 +24,18 @@ use crate::store::MappedPolicy; use crate::store::Store; use crate::store::UserType; use crate::utils::extract_claims; -use rustfs_ecstore::global::get_global_action_cred; +use rustfs_credentials::{Credentials, EMBEDDED_POLICY_TYPE, INHERITED_POLICY_TYPE, get_global_action_cred}; use rustfs_ecstore::notification_sys::get_global_notification_sys; use rustfs_madmin::AddOrUpdateUserReq; use rustfs_madmin::GroupDesc; use rustfs_policy::arn::ARN; -use rustfs_policy::auth::Credentials; use rustfs_policy::auth::{ ACCOUNT_ON, UserIdentity, contains_reserved_chars, create_new_credentials_with_metadata, generate_credentials, is_access_key_valid, is_secret_key_valid, }; use rustfs_policy::policy::Args; use rustfs_policy::policy::opa; -use rustfs_policy::policy::{EMBEDDED_POLICY_TYPE, INHERITED_POLICY_TYPE, Policy, PolicyDoc, iam_policy_claim_name_sa}; +use rustfs_policy::policy::{Policy, PolicyDoc, iam_policy_claim_name_sa}; use serde_json::Value; use serde_json::json; use std::collections::HashMap; diff --git a/crates/policy/Cargo.toml b/crates/policy/Cargo.toml index b66e2031..a1aaaf37 100644 --- a/crates/policy/Cargo.toml +++ b/crates/policy/Cargo.toml @@ -29,7 +29,8 @@ documentation = "https://docs.rs/rustfs-policy/latest/rustfs_policy/" workspace = true [dependencies] -rustfs-config = { workspace = true, features = ["constants","opa"] } +rustfs-credentials = { workspace = true } +rustfs-config = { workspace = true, features = ["constants", "opa"] } tokio = { workspace = true, features = ["full"] } time = { workspace = true, features = ["serde-human-readable"] } serde = { workspace = true, features = ["derive", "rc"] } @@ -38,7 +39,6 @@ thiserror.workspace = true strum = { workspace = true, features = ["derive"] } rustfs-crypto = { workspace = true } ipnetwork = { workspace = true, features = ["serde"] } -rand.workspace = true base64-simd = { workspace = true } jsonwebtoken = { workspace = true } regex = { workspace = true } diff --git a/crates/policy/src/auth/credentials.rs b/crates/policy/src/auth/credentials.rs index 9813f6c4..98133082 100644 --- a/crates/policy/src/auth/credentials.rs +++ b/crates/policy/src/auth/credentials.rs @@ -12,13 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::error::Error as IamError; use crate::error::{Error, Result}; -use crate::policy::{INHERITED_POLICY_TYPE, Policy, Validator, iam_policy_claim_name_sa}; +use crate::policy::{Policy, Validator}; use crate::utils; -use serde::{Deserialize, Serialize}; +use rustfs_credentials::Credentials; +use serde::Serialize; use serde_json::{Value, json}; use std::collections::HashMap; +use std::convert::TryFrom; use time::OffsetDateTime; use tracing::warn; @@ -32,178 +33,82 @@ pub const ACCOUNT_OFF: &str = "off"; const RESERVED_CHARS: &str = "=,"; -// ContainsReservedChars - returns whether the input string contains reserved characters. +/// ContainsReservedChars - returns whether the input string contains reserved characters. +/// +/// # Arguments +/// * `s` - input string to check. +/// +/// # Returns +/// * `bool` - true if contains reserved characters, false otherwise. +/// pub fn contains_reserved_chars(s: &str) -> bool { s.contains(RESERVED_CHARS) } -// IsAccessKeyValid - validate access key for right length. +/// IsAccessKeyValid - validate access key for right length. +/// +/// # Arguments +/// * `access_key` - access key to validate. +/// +/// # Returns +/// * `bool` - true if valid, false otherwise. +/// pub fn is_access_key_valid(access_key: &str) -> bool { access_key.len() >= ACCESS_KEY_MIN_LEN } -// IsSecretKeyValid - validate secret key for right length. +/// IsSecretKeyValid - validate secret key for right length. +/// +/// # Arguments +/// * `secret_key` - secret key to validate. +/// +/// # Returns +/// * `bool` - true if valid, false otherwise. +/// pub fn is_secret_key_valid(secret_key: &str) -> bool { secret_key.len() >= SECRET_KEY_MIN_LEN } -// #[cfg_attr(test, derive(PartialEq, Eq, Debug))] -// struct CredentialHeader { -// access_key: String, -// scop: CredentialHeaderScope, -// } - -// #[cfg_attr(test, derive(PartialEq, Eq, Debug))] -// struct CredentialHeaderScope { -// date: Date, -// region: String, -// service: ServiceType, -// request: String, -// } - -// impl TryFrom<&str> for CredentialHeader { -// type Error = Error; -// fn try_from(value: &str) -> Result { -// let mut elem = value.trim().splitn(2, '='); -// let (Some(h), Some(cred_elems)) = (elem.next(), elem.next()) else { -// return Err(IamError::ErrCredMalformed)); -// }; - -// if h != "Credential" { -// return Err(IamError::ErrCredMalformed)); -// } - -// let mut cred_elems = cred_elems.trim().rsplitn(5, '/'); - -// let Some(request) = cred_elems.next() else { -// return Err(IamError::ErrCredMalformed)); -// }; - -// let Some(service) = cred_elems.next() else { -// return Err(IamError::ErrCredMalformed)); -// }; - -// let Some(region) = cred_elems.next() else { -// return Err(IamError::ErrCredMalformed)); -// }; - -// let Some(date) = cred_elems.next() else { -// return Err(IamError::ErrCredMalformed)); -// }; - -// let Some(ak) = cred_elems.next() else { -// return Err(IamError::ErrCredMalformed)); -// }; - -// if ak.len() < 3 { -// return Err(IamError::ErrCredMalformed)); -// } - -// if request != "aws4_request" { -// return Err(IamError::ErrCredMalformed)); -// } - -// Ok(CredentialHeader { -// access_key: ak.to_owned(), -// scop: CredentialHeaderScope { -// date: { -// const FORMATTER: LazyCell>> = -// LazyCell::new(|| time::format_description::parse("[year][month][day]").unwrap()); - -// Date::parse(date, &FORMATTER).map_err(|_| IamError::ErrCredMalformed))? -// }, -// region: region.to_owned(), -// service: service.try_into()?, -// request: request.to_owned(), -// }, -// }) -// } -// } - -#[derive(Serialize, Deserialize, Clone, Default, Debug)] -pub struct Credentials { - pub access_key: String, - pub secret_key: String, - pub session_token: String, - pub expiration: Option, - pub status: String, - pub parent_user: String, - pub groups: Option>, - pub claims: Option>, - pub name: Option, - pub description: Option, -} - -impl Credentials { - // pub fn new(elem: &str) -> Result { - // let header: CredentialHeader = elem.try_into()?; - // Self::check_key_value(header) - // } - - // pub fn check_key_value(_header: CredentialHeader) -> Result { - // todo!() - // } - - pub fn is_expired(&self) -> bool { - if self.expiration.is_none() { - return false; - } - - self.expiration - .as_ref() - .map(|e| time::OffsetDateTime::now_utc() > *e) - .unwrap_or(false) - } - - pub fn is_temp(&self) -> bool { - !self.session_token.is_empty() && !self.is_expired() - } - - pub fn is_service_account(&self) -> bool { - const IAM_POLICY_CLAIM_NAME_SA: &str = "sa-policy"; - self.claims - .as_ref() - .map(|x| x.get(IAM_POLICY_CLAIM_NAME_SA).is_some_and(|_| !self.parent_user.is_empty())) - .unwrap_or_default() - } - - pub fn is_implied_policy(&self) -> bool { - if self.is_service_account() { - return self - .claims - .as_ref() - .map(|x| x.get(&iam_policy_claim_name_sa()).is_some_and(|v| v == INHERITED_POLICY_TYPE)) - .unwrap_or_default(); - } - - false - } - - pub fn is_valid(&self) -> bool { - if self.status == "off" { - return false; - } - - self.access_key.len() >= 3 && self.secret_key.len() >= 8 && !self.is_expired() - } - - pub fn is_owner(&self) -> bool { - false - } -} - +/// GenerateCredentials - generate a new access key and secret key pair. +/// +/// # Returns +/// * `Ok((String, String))` - access key and secret key pair. +/// * `Err(Error)` - if an error occurs during generation. +/// pub fn generate_credentials() -> Result<(String, String)> { - let ak = utils::gen_access_key(20)?; - let sk = utils::gen_secret_key(40)?; + let ak = rustfs_credentials::gen_access_key(20)?; + let sk = rustfs_credentials::gen_secret_key(40)?; Ok((ak, sk)) } +/// GetNewCredentialsWithMetadata - generate new credentials with metadata claims and token secret. +/// +/// # Arguments +/// * `claims` - metadata claims to be included in the token. +/// * `token_secret` - secret used to sign the token. +/// +/// # Returns +/// * `Ok(Credentials)` - newly generated credentials. +/// * `Err(Error)` - if an error occurs during generation. +/// pub fn get_new_credentials_with_metadata(claims: &HashMap, token_secret: &str) -> Result { let (ak, sk) = generate_credentials()?; create_new_credentials_with_metadata(&ak, &sk, claims, token_secret) } +/// CreateNewCredentialsWithMetadata - create new credentials with provided access key, secret key, metadata claims, and token secret. +/// +/// # Arguments +/// * `ak` - access key. +/// * `sk` - secret key. +/// * `claims` - metadata claims to be included in the token. +/// * `token_secret` - secret used to sign the token. +/// +/// # Returns +/// * `Ok(Credentials)` - newly created credentials. +/// * `Err(Error)` - if an error occurs during creation. +/// pub fn create_new_credentials_with_metadata( ak: &str, sk: &str, @@ -211,11 +116,11 @@ pub fn create_new_credentials_with_metadata( token_secret: &str, ) -> Result { if ak.len() < ACCESS_KEY_MIN_LEN || ak.len() > ACCESS_KEY_MAX_LEN { - return Err(IamError::InvalidAccessKeyLength); + return Err(Error::InvalidAccessKeyLength); } if sk.len() < SECRET_KEY_MIN_LEN || sk.len() > SECRET_KEY_MAX_LEN { - return Err(IamError::InvalidAccessKeyLength); + return Err(Error::InvalidAccessKeyLength); } if token_secret.is_empty() { @@ -253,6 +158,16 @@ pub fn create_new_credentials_with_metadata( }) } +/// JWTSign - sign the provided claims with the given token secret to generate a JWT token. +/// +/// # Arguments +/// * `claims` - claims to be included in the token. +/// * `token_secret` - secret used to sign the token. +/// +/// # Returns +/// * `Ok(String)` - generated JWT token. +/// * `Err(Error)` - if an error occurs during signing. +/// pub fn jwt_sign(claims: &T, token_secret: &str) -> Result { let token = utils::generate_jwt(claims, token_secret)?; Ok(token) @@ -267,16 +182,29 @@ pub struct CredentialsBuilder { description: Option, expiration: Option, allow_site_replicator_account: bool, - claims: Option, + claims: Option, parent_user: String, groups: Option>, } impl CredentialsBuilder { + /// Create a new CredentialsBuilder instance. + /// + /// # Returns + /// * `CredentialsBuilder` - a new instance of CredentialsBuilder. + /// pub fn new() -> Self { Self::default() } + /// Set the session policy for the credentials. + /// + /// # Arguments + /// * `policy` - an optional Policy to set as the session policy. + /// + /// # Returns + /// * `Self` - the updated CredentialsBuilder instance. + /// pub fn session_policy(mut self, policy: Option) -> Self { self.session_policy = policy; self @@ -312,7 +240,7 @@ impl CredentialsBuilder { self } - pub fn claims(mut self, claims: serde_json::Value) -> Self { + pub fn claims(mut self, claims: Value) -> Self { self.claims = Some(claims); self } @@ -336,7 +264,7 @@ impl TryFrom for Credentials { type Error = Error; fn try_from(mut value: CredentialsBuilder) -> std::result::Result { if value.parent_user.is_empty() { - return Err(IamError::InvalidArgument); + return Err(Error::InvalidArgument); } if (value.access_key.is_empty() && !value.secret_key.is_empty()) @@ -346,27 +274,27 @@ impl TryFrom for Credentials { } if value.parent_user == value.access_key.as_str() { - return Err(IamError::InvalidArgument); + return Err(Error::InvalidArgument); } if value.access_key == "site-replicator-0" && !value.allow_site_replicator_account { - return Err(IamError::InvalidArgument); + return Err(Error::InvalidArgument); } - let mut claim = serde_json::json!({ + let mut claim = json!({ "parent": value.parent_user }); if let Some(p) = value.session_policy { p.is_valid()?; - let policy_buf = serde_json::to_vec(&p).map_err(|_| IamError::InvalidArgument)?; + let policy_buf = serde_json::to_vec(&p).map_err(|_| Error::InvalidArgument)?; if policy_buf.len() > 4096 { return Err(Error::other("session policy is too large")); } - claim["sessionPolicy"] = serde_json::json!(base64_simd::STANDARD.encode_to_string(&policy_buf)); - claim["sa-policy"] = serde_json::json!("embedded-policy"); + claim["sessionPolicy"] = json!(base64_simd::STANDARD.encode_to_string(&policy_buf)); + claim[rustfs_credentials::IAM_POLICY_CLAIM_NAME_SA] = json!(rustfs_credentials::EMBEDDED_POLICY_TYPE); } else { - claim["sa-policy"] = serde_json::json!("inherited-policy"); + claim[rustfs_credentials::IAM_POLICY_CLAIM_NAME_SA] = json!(rustfs_credentials::INHERITED_POLICY_TYPE); } if let Some(Value::Object(obj)) = value.claims { @@ -379,11 +307,11 @@ impl TryFrom for Credentials { } if value.access_key.is_empty() { - value.access_key = utils::gen_access_key(20)?; + value.access_key = rustfs_credentials::gen_access_key(20)?; } if value.secret_key.is_empty() { - value.access_key = utils::gen_secret_key(40)?; + value.secret_key = rustfs_credentials::gen_secret_key(40)?; } claim["accessKey"] = json!(&value.access_key); diff --git a/crates/policy/src/auth.rs b/crates/policy/src/auth/mod.rs similarity index 87% rename from crates/policy/src/auth.rs rename to crates/policy/src/auth/mod.rs index 22e3db84..cfd5b8e1 100644 --- a/crates/policy/src/auth.rs +++ b/crates/policy/src/auth/mod.rs @@ -14,8 +14,9 @@ mod credentials; -pub use credentials::Credentials; pub use credentials::*; + +use rustfs_credentials::Credentials; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; @@ -27,6 +28,13 @@ pub struct UserIdentity { } impl UserIdentity { + /// Create a new UserIdentity + /// + /// # Arguments + /// * `credentials` - Credentials object + /// + /// # Returns + /// * UserIdentity pub fn new(credentials: Credentials) -> Self { UserIdentity { version: 1, diff --git a/crates/policy/src/policy.rs b/crates/policy/src/policy.rs index 8733a859..0a034a86 100644 --- a/crates/policy/src/policy.rs +++ b/crates/policy/src/policy.rs @@ -28,7 +28,6 @@ pub mod variables; pub use action::ActionSet; pub use doc::PolicyDoc; - pub use effect::Effect; pub use function::Functions; pub use id::ID; @@ -37,9 +36,6 @@ pub use principal::Principal; pub use resource::ResourceSet; pub use statement::Statement; -pub const EMBEDDED_POLICY_TYPE: &str = "embedded-policy"; -pub const INHERITED_POLICY_TYPE: &str = "inherited-policy"; - #[derive(thiserror::Error, Debug)] #[cfg_attr(test, derive(Eq, PartialEq))] pub enum Error { diff --git a/crates/policy/src/policy/policy.rs b/crates/policy/src/policy/policy.rs index 45d368d8..6ddf6294 100644 --- a/crates/policy/src/policy/policy.rs +++ b/crates/policy/src/policy/policy.rs @@ -258,7 +258,7 @@ pub fn get_policies_from_claims(claims: &HashMap, policy_claim_na } pub fn iam_policy_claim_name_sa() -> String { - "sa-policy".to_string() + rustfs_credentials::IAM_POLICY_CLAIM_NAME_SA.to_string() } pub mod default { diff --git a/crates/policy/src/utils.rs b/crates/policy/src/utils.rs index 6a1e7fff..3698dc4d 100644 --- a/crates/policy/src/utils.rs +++ b/crates/policy/src/utils.rs @@ -13,46 +13,7 @@ // limitations under the License. use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header}; -use rand::{Rng, RngCore}; use serde::{Serialize, de::DeserializeOwned}; -use std::io::{Error, Result}; - -pub fn gen_access_key(length: usize) -> Result { - const ALPHA_NUMERIC_TABLE: [char; 36] = [ - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', - 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - ]; - - if length < 3 { - return Err(Error::other("access key length is too short")); - } - - let mut result = String::with_capacity(length); - let mut rng = rand::rng(); - - for _ in 0..length { - result.push(ALPHA_NUMERIC_TABLE[rng.random_range(0..ALPHA_NUMERIC_TABLE.len())]); - } - - Ok(result) -} - -pub fn gen_secret_key(length: usize) -> Result { - use base64_simd::URL_SAFE_NO_PAD; - - if length < 8 { - return Err(Error::other("secret key length is too short")); - } - let mut rng = rand::rng(); - - let mut key = vec![0u8; URL_SAFE_NO_PAD.estimated_decoded_length(length)]; - rng.fill_bytes(&mut key); - - let encoded = URL_SAFE_NO_PAD.encode_to_string(&key); - let key_str = encoded.replace("/", "+"); - - Ok(key_str) -} pub fn generate_jwt(claims: &T, secret: &str) -> std::result::Result { let header = Header::new(Algorithm::HS512); @@ -72,26 +33,9 @@ pub fn extract_claims( #[cfg(test)] mod tests { - use super::{gen_access_key, gen_secret_key, generate_jwt}; + use super::generate_jwt; use serde::{Deserialize, Serialize}; - #[test] - fn test_gen_access_key() { - let a = gen_access_key(10).unwrap(); - let b = gen_access_key(10).unwrap(); - - assert_eq!(a.len(), 10); - assert_eq!(b.len(), 10); - assert_ne!(a, b); - } - - #[test] - fn test_gen_secret_key() { - let a = gen_secret_key(10).unwrap(); - let b = gen_secret_key(10).unwrap(); - assert_ne!(a, b); - } - #[derive(Debug, Serialize, Deserialize, PartialEq)] struct Claims { sub: String, diff --git a/crates/protos/Cargo.toml b/crates/protos/Cargo.toml index 86031828..29ada67a 100644 --- a/crates/protos/Cargo.toml +++ b/crates/protos/Cargo.toml @@ -34,6 +34,7 @@ path = "src/main.rs" [dependencies] rustfs-common.workspace = true +rustfs-credentials = { workspace = true } flatbuffers = { workspace = true } prost = { workspace = true } tonic = { workspace = true, features = ["transport"] } diff --git a/crates/protos/src/lib.rs b/crates/protos/src/lib.rs index e54f1edf..607eb1b2 100644 --- a/crates/protos/src/lib.rs +++ b/crates/protos/src/lib.rs @@ -24,7 +24,7 @@ use tonic::{ service::interceptor::InterceptedService, transport::{Certificate, Channel, ClientTlsConfig, Endpoint}, }; -use tracing::{debug, warn}; +use tracing::{debug, error, warn}; // Type alias for the complex client type pub type NodeServiceClientType = NodeServiceClient< @@ -150,7 +150,18 @@ pub async fn node_service_time_out_client( >, Box, > { - let token: MetadataValue<_> = "rustfs rpc".parse()?; + debug!("Obtaining gRPC client for NodeService at: {}", addr); + let token_str = rustfs_credentials::get_grpc_token(); + let token: MetadataValue<_> = token_str.parse().map_err(|e| { + error!( + "Failed to parse gRPC auth token into MetadataValue: {:?}; env={} token_len={} token_prefix={}", + e, + rustfs_credentials::ENV_GRPC_AUTH_TOKEN, + token_str.len(), + token_str.chars().take(2).collect::(), + ); + e + })?; // Try to get cached channel let cached_channel = { GLOBAL_CONN_MAP.read().await.get(addr).cloned() }; diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index 9b05e84e..7295c43b 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -86,11 +86,12 @@ io = ["dep:tokio"] path = [] notify = ["dep:hyper", "dep:s3s", "dep:hashbrown", "dep:thiserror", "dep:serde", "dep:libc", "dep:url", "dep:regex"] # file system notification features compress = ["dep:flate2", "dep:brotli", "dep:snap", "dep:lz4", "dep:zstd"] -string = ["dep:regex", "dep:rand"] +string = ["dep:regex"] crypto = ["dep:base64-simd", "dep:hex-simd", "dep:hmac", "dep:hyper", "dep:sha1"] -hash = ["dep:highway", "dep:md-5", "dep:sha2", "dep:blake3", "dep:serde", "dep:siphasher", "dep:hex-simd", "dep:base64-simd", "dep:crc-fast"] +hash = ["dep:highway", "dep:md-5", "dep:sha2", "dep:blake3", "dep:serde", "dep:siphasher", "dep:hex-simd", "dep:crc-fast"] os = ["dep:nix", "dep:tempfile", "winapi"] # operating system utilities integration = [] # integration test features sys = ["dep:sysinfo"] # system information features http = ["dep:convert_case", "dep:http", "dep:regex"] -full = ["ip", "tls", "net", "io", "hash", "os", "integration", "path", "crypto", "string", "compress", "sys", "notify", "http"] # all features +obj = ["http"] # object storage features +full = ["ip", "tls", "net", "io", "hash", "os", "integration", "path", "crypto", "string", "compress", "sys", "notify", "http", "obj"] # all features diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs index cb23e249..4c2396bc 100644 --- a/crates/utils/src/lib.rs +++ b/crates/utils/src/lib.rs @@ -82,7 +82,8 @@ pub use sys::user_agent::*; #[cfg(feature = "notify")] pub use notify::*; +#[cfg(feature = "obj")] +pub mod obj; + mod envs; pub use envs::*; - -pub mod obj; diff --git a/crates/utils/src/string.rs b/crates/utils/src/string.rs index 8d3879d1..24675db6 100644 --- a/crates/utils/src/string.rs +++ b/crates/utils/src/string.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use rand::{Rng, RngCore}; use regex::Regex; use std::io::{Error, Result}; use std::sync::LazyLock; @@ -488,81 +487,6 @@ pub fn parse_ellipses_range(pattern: &str) -> Result> { Ok(ret) } -/// Generates a random access key of the specified length. -/// -/// # Arguments -/// * `length` - The length of the access key to generate -/// -/// # Returns -/// * `Result` - A result containing the generated access key or an error if the length is too short -/// -/// # Errors -/// This function will return an error if the specified length is less than 3. -/// -/// Examples -/// ```no_run -/// use rustfs_utils::string::gen_access_key; -/// -/// let access_key = gen_access_key(16).unwrap(); -/// println!("Generated access key: {}", access_key); -/// ``` -/// -pub fn gen_access_key(length: usize) -> Result { - const ALPHA_NUMERIC_TABLE: [char; 36] = [ - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', - 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - ]; - - if length < 3 { - return Err(Error::other("access key length is too short")); - } - - let mut result = String::with_capacity(length); - let mut rng = rand::rng(); - - for _ in 0..length { - result.push(ALPHA_NUMERIC_TABLE[rng.random_range(0..ALPHA_NUMERIC_TABLE.len())]); - } - - Ok(result) -} - -/// Generates a random secret key of the specified length. -/// -/// # Arguments -/// * `length` - The length of the secret key to generate -/// -/// # Returns -/// * `Result` - A result containing the generated secret key or an error if the length is too short -/// -/// # Errors -/// This function will return an error if the specified length is less than 8. -/// -/// # Examples -/// ```no_run -/// use rustfs_utils::string::gen_secret_key; -/// -/// let secret_key = gen_secret_key(32).unwrap(); -/// println!("Generated secret key: {}", secret_key); -/// ``` -/// -pub fn gen_secret_key(length: usize) -> Result { - use base64_simd::URL_SAFE_NO_PAD; - - if length < 8 { - return Err(Error::other("secret key length is too short")); - } - let mut rng = rand::rng(); - - let mut key = vec![0u8; URL_SAFE_NO_PAD.estimated_decoded_length(length)]; - rng.fill_bytes(&mut key); - - let encoded = URL_SAFE_NO_PAD.encode_to_string(&key); - let key_str = encoded.replace("/", "+"); - - Ok(key_str) -} - /// Tests whether the string s begins with prefix ignoring case /// /// # Arguments diff --git a/rustfs/Cargo.toml b/rustfs/Cargo.toml index bac807ec..cd5ba113 100644 --- a/rustfs/Cargo.toml +++ b/rustfs/Cargo.toml @@ -44,6 +44,7 @@ rustfs-appauth = { workspace = true } rustfs-audit = { workspace = true } rustfs-common = { workspace = true } rustfs-config = { workspace = true, features = ["constants", "notify"] } +rustfs-credentials = { workspace = true } rustfs-ecstore = { workspace = true } rustfs-filemeta.workspace = true rustfs-iam = { workspace = true } diff --git a/rustfs/src/admin/auth.rs b/rustfs/src/admin/auth.rs index 2f101099..63d4ab65 100644 --- a/rustfs/src/admin/auth.rs +++ b/rustfs/src/admin/auth.rs @@ -14,9 +14,9 @@ use crate::auth::get_condition_values; use http::HeaderMap; +use rustfs_credentials::Credentials; use rustfs_iam::store::object::ObjectStore; use rustfs_iam::sys::IamSys; -use rustfs_policy::auth; use rustfs_policy::policy::Args; use rustfs_policy::policy::action::Action; use s3s::S3Result; @@ -26,7 +26,7 @@ use std::sync::Arc; pub async fn validate_admin_request( headers: &HeaderMap, - cred: &auth::Credentials, + cred: &Credentials, is_owner: bool, deny_only: bool, actions: Vec, @@ -49,7 +49,7 @@ pub async fn validate_admin_request( async fn check_admin_request_auth( iam_store: Arc>, headers: &HeaderMap, - cred: &auth::Credentials, + cred: &Credentials, is_owner: bool, deny_only: bool, action: Action, diff --git a/rustfs/src/admin/console.rs b/rustfs/src/admin/console.rs index b541edf1..5bd0bbd0 100644 --- a/rustfs/src/admin/console.rs +++ b/rustfs/src/admin/console.rs @@ -23,7 +23,6 @@ use axum::{ response::{IntoResponse, Response}, routing::get, }; -use axum_extra::extract::Host; use axum_server::tls_rustls::RustlsConfig; use http::{HeaderMap, HeaderName, HeaderValue, Method, StatusCode, Uri}; use mime_guess::from_path; @@ -264,21 +263,27 @@ async fn version_handler() -> impl IntoResponse { /// /// # Arguments: /// - `uri`: The request URI. -/// - `Host(host)`: The host extracted from the request. /// - `headers`: The request headers. /// /// # Returns: /// - 200 OK with JSON body containing the console configuration if initialized. /// - 500 Internal Server Error if configuration is not initialized. -#[instrument(fields(host))] -async fn config_handler(uri: Uri, Host(host): Host, headers: HeaderMap) -> impl IntoResponse { +#[instrument(fields(uri))] +async fn config_handler(uri: Uri, 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")); - let raw_host = uri.host().unwrap_or(host.as_str()); + // Prefer URI host, fallback to `Host` header + let header_host = headers + .get(http::header::HOST) + .and_then(|v| v.to_str().ok()) + .unwrap_or_default(); + + let raw_host = uri.host().unwrap_or(header_host); + 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. diff --git a/rustfs/src/admin/handlers.rs b/rustfs/src/admin/handlers.rs index 821e948f..f1a8b212 100644 --- a/rustfs/src/admin/handlers.rs +++ b/rustfs/src/admin/handlers.rs @@ -25,6 +25,7 @@ use hyper::StatusCode; use matchit::Params; use rustfs_common::heal_channel::HealOpts; use rustfs_config::{MAX_ADMIN_REQUEST_BODY_SIZE, MAX_HEAL_REQUEST_SIZE}; +use rustfs_credentials::get_global_action_cred; use rustfs_ecstore::admin_server_info::get_server_info; use rustfs_ecstore::bucket::bucket_target_sys::BucketTargetSys; use rustfs_ecstore::bucket::metadata::BUCKET_TARGETS_FILE; @@ -35,7 +36,6 @@ use rustfs_ecstore::data_usage::{ aggregate_local_snapshots, compute_bucket_usage, load_data_usage_from_backend, store_data_usage_in_backend, }; use rustfs_ecstore::error::StorageError; -use rustfs_ecstore::global::get_global_action_cred; use rustfs_ecstore::global::global_rustfs_port; use rustfs_ecstore::metrics_realtime::{CollectMetricsOpts, MetricType, collect_local_metrics}; use rustfs_ecstore::new_object_layer_fn; diff --git a/rustfs/src/admin/handlers/group.rs b/rustfs/src/admin/handlers/group.rs index c7866a81..07ae9baf 100644 --- a/rustfs/src/admin/handlers/group.rs +++ b/rustfs/src/admin/handlers/group.rs @@ -19,7 +19,7 @@ use crate::{ use http::{HeaderMap, StatusCode}; use matchit::Params; use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE; -use rustfs_ecstore::global::get_global_action_cred; +use rustfs_credentials::get_global_action_cred; use rustfs_iam::error::{is_err_no_such_group, is_err_no_such_user}; use rustfs_madmin::GroupAddRemove; use rustfs_policy::policy::action::{Action, AdminAction}; diff --git a/rustfs/src/admin/handlers/policies.rs b/rustfs/src/admin/handlers/policies.rs index 76915be0..7587c6ae 100644 --- a/rustfs/src/admin/handlers/policies.rs +++ b/rustfs/src/admin/handlers/policies.rs @@ -19,7 +19,7 @@ use crate::{ use http::{HeaderMap, StatusCode}; use matchit::Params; use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE; -use rustfs_ecstore::global::get_global_action_cred; +use rustfs_credentials::get_global_action_cred; use rustfs_iam::error::is_err_no_such_user; use rustfs_iam::store::MappedPolicy; use rustfs_policy::policy::{ diff --git a/rustfs/src/admin/handlers/service_account.rs b/rustfs/src/admin/handlers/service_account.rs index 1340d13f..76e59a90 100644 --- a/rustfs/src/admin/handlers/service_account.rs +++ b/rustfs/src/admin/handlers/service_account.rs @@ -19,7 +19,7 @@ use http::HeaderMap; use hyper::StatusCode; use matchit::Params; use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE; -use rustfs_ecstore::global::get_global_action_cred; +use rustfs_credentials::get_global_action_cred; use rustfs_iam::error::is_err_no_such_service_account; use rustfs_iam::sys::{NewServiceAccountOpts, UpdateServiceAccountOpts}; use rustfs_madmin::{ diff --git a/rustfs/src/admin/handlers/user.rs b/rustfs/src/admin/handlers/user.rs index 0ab6a128..92c39d18 100644 --- a/rustfs/src/admin/handlers/user.rs +++ b/rustfs/src/admin/handlers/user.rs @@ -19,7 +19,7 @@ use crate::{ use http::{HeaderMap, StatusCode}; use matchit::Params; use rustfs_config::{MAX_ADMIN_REQUEST_BODY_SIZE, MAX_IAM_IMPORT_SIZE}; -use rustfs_ecstore::global::get_global_action_cred; +use rustfs_credentials::get_global_action_cred; use rustfs_iam::{ store::{GroupInfo, MappedPolicy, UserType}, sys::NewServiceAccountOpts, diff --git a/rustfs/src/auth.rs b/rustfs/src/auth.rs index 79cb2922..97a329f0 100644 --- a/rustfs/src/auth.rs +++ b/rustfs/src/auth.rs @@ -14,11 +14,10 @@ use http::HeaderMap; use http::Uri; -use rustfs_ecstore::global::get_global_action_cred; +use rustfs_credentials::{Credentials, get_global_action_cred}; use rustfs_iam::error::Error as IamError; use rustfs_iam::sys::SESSION_POLICY_NAME; use rustfs_iam::sys::get_claims_from_token_with_secret; -use rustfs_policy::auth; use rustfs_utils::http::ip::get_source_ip_raw; use s3s::S3Error; use s3s::S3ErrorCode; @@ -129,7 +128,7 @@ impl S3Auth for IAMAuth { } // check_key_valid checks the key is valid or not. return the user's credentials and if the user is the owner. -pub async fn check_key_valid(session_token: &str, access_key: &str) -> S3Result<(auth::Credentials, bool)> { +pub async fn check_key_valid(session_token: &str, access_key: &str) -> S3Result<(Credentials, bool)> { let Some(mut cred) = get_global_action_cred() else { return Err(S3Error::with_message( S3ErrorCode::InternalError, @@ -187,7 +186,7 @@ pub async fn check_key_valid(session_token: &str, access_key: &str) -> S3Result< Ok((cred, owner)) } -pub fn check_claims_from_token(token: &str, cred: &auth::Credentials) -> S3Result> { +pub fn check_claims_from_token(token: &str, cred: &Credentials) -> S3Result> { if !token.is_empty() && cred.access_key.is_empty() { return Err(s3_error!(InvalidRequest, "no access key")); } @@ -235,9 +234,20 @@ pub fn get_session_token<'a>(uri: &'a Uri, hds: &'a HeaderMap) -> Option<&'a str .or_else(|| get_query_param(uri.query().unwrap_or_default(), "x-amz-security-token")) } +/// Get condition values for policy evaluation +/// +/// # Arguments +/// * `header` - HTTP headers of the request +/// * `cred` - User credentials +/// * `version_id` - Optional version ID of the object +/// * `region` - Optional region/location constraint +/// +/// # Returns +/// * `HashMap>` - Condition values for policy evaluation +/// pub fn get_condition_values( header: &HeaderMap, - cred: &auth::Credentials, + cred: &Credentials, version_id: Option<&str>, region: Option<&str>, ) -> HashMap> { @@ -403,7 +413,14 @@ pub fn get_condition_values( args } -// Get request authentication type +/// Get request authentication type +/// +/// # Arguments +/// * `header` - HTTP headers of the request +/// +/// # Returns +/// * `AuthType` - The determined authentication type +/// pub fn get_request_auth_type(header: &HeaderMap) -> AuthType { if is_request_signature_v2(header) { AuthType::SignedV2 @@ -432,7 +449,14 @@ pub fn get_request_auth_type(header: &HeaderMap) -> AuthType { } } -// Helper function to determine auth type and signature version +/// Helper function to determine auth type and signature version +/// +/// # Arguments +/// * `header` - HTTP headers of the request +/// +/// # Returns +/// * `(String, String)` - Tuple of auth type and signature version +/// fn determine_auth_type_and_version(header: &HeaderMap) -> (String, String) { match get_request_auth_type(header) { AuthType::JWT => ("JWT".to_string(), String::new()), @@ -450,7 +474,13 @@ fn determine_auth_type_and_version(header: &HeaderMap) -> (String, String) { } } -// Verify if request has JWT +/// Verify if request has JWT +/// +/// # Arguments +/// * `header` - HTTP headers of the request +/// +/// # Returns +/// * `bool` - True if request has JWT, false otherwise fn is_request_jwt(header: &HeaderMap) -> bool { if let Some(auth) = header.get("authorization") { if let Ok(auth_str) = auth.to_str() { @@ -460,7 +490,13 @@ fn is_request_jwt(header: &HeaderMap) -> bool { false } -// Verify if request has AWS Signature Version '4' +/// Verify if request has AWS Signature Version '4' +/// +/// # Arguments +/// * `header` - HTTP headers of the request +/// +/// # Returns +/// * `bool` - True if request has AWS Signature Version '4', false otherwise fn is_request_signature_v4(header: &HeaderMap) -> bool { if let Some(auth) = header.get("authorization") { if let Ok(auth_str) = auth.to_str() { @@ -470,7 +506,13 @@ fn is_request_signature_v4(header: &HeaderMap) -> bool { false } -// Verify if request has AWS Signature Version '2' +/// Verify if request has AWS Signature Version '2' +/// +/// # Arguments +/// * `header` - HTTP headers of the request +/// +/// # Returns +/// * `bool` - True if request has AWS Signature Version '2', false otherwise fn is_request_signature_v2(header: &HeaderMap) -> bool { if let Some(auth) = header.get("authorization") { if let Ok(auth_str) = auth.to_str() { @@ -480,7 +522,13 @@ fn is_request_signature_v2(header: &HeaderMap) -> bool { false } -// Verify if request has AWS PreSign Version '4' +/// Verify if request has AWS PreSign Version '4' +/// +/// # Arguments +/// * `header` - HTTP headers of the request +/// +/// # Returns +/// * `bool` - True if request has AWS PreSign Version '4', false otherwise pub(crate) fn is_request_presigned_signature_v4(header: &HeaderMap) -> bool { if let Some(credential) = header.get(AMZ_CREDENTIAL) { return !credential.to_str().unwrap_or("").is_empty(); @@ -488,7 +536,13 @@ pub(crate) fn is_request_presigned_signature_v4(header: &HeaderMap) -> bool { false } -// Verify request has AWS PreSign Version '2' +/// Verify request has AWS PreSign Version '2' +/// +/// # Arguments +/// * `header` - HTTP headers of the request +/// +/// # Returns +/// * `bool` - True if request has AWS PreSign Version '2', false otherwise fn is_request_presigned_signature_v2(header: &HeaderMap) -> bool { if let Some(access_key) = header.get(AMZ_ACCESS_KEY_ID) { return !access_key.to_str().unwrap_or("").is_empty(); @@ -496,7 +550,13 @@ fn is_request_presigned_signature_v2(header: &HeaderMap) -> bool { false } -// Verify if request has AWS Post policy Signature Version '4' +/// Verify if request has AWS Post policy Signature Version '4' +/// +/// # Arguments +/// * `header` - HTTP headers of the request +/// +/// # Returns +/// * `bool` - True if request has AWS Post policy Signature Version '4', false otherwise fn is_request_post_policy_signature_v4(header: &HeaderMap) -> bool { if let Some(content_type) = header.get("content-type") { if let Ok(ct) = content_type.to_str() { @@ -506,7 +566,7 @@ fn is_request_post_policy_signature_v4(header: &HeaderMap) -> bool { false } -// Verify if the request has AWS Streaming Signature Version '4' +/// Verify if the request has AWS Streaming Signature Version '4' fn is_request_sign_streaming_v4(header: &HeaderMap) -> bool { if let Some(content_sha256) = header.get("x-amz-content-sha256") { if let Ok(sha256_str) = content_sha256.to_str() { @@ -567,7 +627,7 @@ pub fn get_query_param<'a>(query: &'a str, param_name: &str) -> Option<&'a str> mod tests { use super::*; use http::{HeaderMap, HeaderValue, Uri}; - use rustfs_policy::auth::Credentials; + use rustfs_credentials::Credentials; use s3s::auth::SecretKey; use serde_json::json; use std::collections::HashMap; @@ -605,7 +665,7 @@ mod tests { fn create_service_account_credentials() -> Credentials { let mut claims = HashMap::new(); - claims.insert("sa-policy".to_string(), json!("test-policy")); + claims.insert(rustfs_credentials::IAM_POLICY_CLAIM_NAME_SA.to_string(), json!("test-policy")); Credentials { access_key: "service-access-key".to_string(), diff --git a/rustfs/src/config/mod.rs b/rustfs/src/config/mod.rs index 14923522..00d068ba 100644 --- a/rustfs/src/config/mod.rs +++ b/rustfs/src/config/mod.rs @@ -73,11 +73,11 @@ pub struct Opt { pub server_domains: Vec, /// Access key used for authentication. - #[arg(long, default_value_t = rustfs_config::DEFAULT_ACCESS_KEY.to_string(), env = "RUSTFS_ACCESS_KEY")] + #[arg(long, default_value_t = rustfs_credentials::DEFAULT_ACCESS_KEY.to_string(), env = "RUSTFS_ACCESS_KEY")] pub access_key: String, /// Secret key used for authentication. - #[arg(long, default_value_t = rustfs_config::DEFAULT_SECRET_KEY.to_string(), env = "RUSTFS_SECRET_KEY")] + #[arg(long, default_value_t = rustfs_credentials::DEFAULT_SECRET_KEY.to_string(), env = "RUSTFS_SECRET_KEY")] pub secret_key: String, /// Enable console server diff --git a/rustfs/src/main.rs b/rustfs/src/main.rs index 38a85518..438181e1 100644 --- a/rustfs/src/main.rs +++ b/rustfs/src/main.rs @@ -39,6 +39,7 @@ use rustfs_ahm::{ scanner::data_scanner::ScannerConfig, shutdown_ahm_services, }; use rustfs_common::{GlobalReadiness, SystemStage, set_global_addr}; +use rustfs_credentials::init_global_action_credentials; use rustfs_ecstore::{ StorageAPI, bucket::metadata_sys::init_bucket_metadata_sys, @@ -147,7 +148,16 @@ async fn run(opt: config::Opt) -> Result<()> { ); // Set up AK and SK - rustfs_ecstore::global::init_global_action_credentials(Some(opt.access_key.clone()), Some(opt.secret_key.clone())); + match init_global_action_credentials(Some(opt.access_key.clone()), Some(opt.secret_key.clone())) { + Ok(_) => { + info!(target: "rustfs::main::run", "Global action credentials initialized successfully."); + } + Err(e) => { + let msg = format!("init_global_action_credentials failed: {e:?}"); + error!("{msg}"); + return Err(Error::other(msg)); + } + }; set_global_rustfs_port(server_port); diff --git a/rustfs/src/server/http.rs b/rustfs/src/server/http.rs index 53a03bca..10d5011a 100644 --- a/rustfs/src/server/http.rs +++ b/rustfs/src/server/http.rs @@ -30,7 +30,7 @@ use hyper_util::{ }; use metrics::{counter, histogram}; use rustfs_common::GlobalReadiness; -use rustfs_config::{DEFAULT_ACCESS_KEY, DEFAULT_SECRET_KEY, MI_B, RUSTFS_TLS_CERT, RUSTFS_TLS_KEY}; +use rustfs_config::{MI_B, RUSTFS_TLS_CERT, RUSTFS_TLS_KEY}; use rustfs_protos::proto_gen::node_service::node_service_server::NodeServiceServer; use rustfs_utils::net::parse_and_resolve_address; use rustls::ServerConfig; @@ -212,10 +212,13 @@ pub async fn start_http_server( info!(target: "rustfs::main::startup","RustFS API: {api_endpoints} {localhost_endpoint}"); println!("RustFS Http API: {api_endpoints} {localhost_endpoint}"); println!("RustFS Start Time: {now_time}"); - if DEFAULT_ACCESS_KEY.eq(&opt.access_key) && DEFAULT_SECRET_KEY.eq(&opt.secret_key) { + if rustfs_credentials::DEFAULT_ACCESS_KEY.eq(&opt.access_key) + && rustfs_credentials::DEFAULT_SECRET_KEY.eq(&opt.secret_key) + { warn!( "Detected default credentials '{}:{}', we recommend that you change these values with 'RUSTFS_ACCESS_KEY' and 'RUSTFS_SECRET_KEY' environment variables", - DEFAULT_ACCESS_KEY, DEFAULT_SECRET_KEY + rustfs_credentials::DEFAULT_ACCESS_KEY, + rustfs_credentials::DEFAULT_SECRET_KEY ); } info!(target: "rustfs::main::startup","For more information, visit https://rustfs.com/docs/"); @@ -685,7 +688,12 @@ fn handle_connection_error(err: &(dyn std::error::Error + 'static)) { #[allow(clippy::result_large_err)] fn check_auth(req: Request<()>) -> std::result::Result, Status> { - let token: MetadataValue<_> = "rustfs rpc".parse().unwrap(); + let token_str = rustfs_credentials::get_grpc_token(); + + let token: MetadataValue<_> = token_str.parse().map_err(|e| { + error!("Failed to parse RUSTFS_GRPC_AUTH_TOKEN into gRPC metadata value: {}", e); + Status::internal("Invalid auth token configuration") + })?; match req.metadata().get("authorization") { Some(t) if token == t => Ok(req), diff --git a/rustfs/src/storage/access.rs b/rustfs/src/storage/access.rs index 7d664585..736f386e 100644 --- a/rustfs/src/storage/access.rs +++ b/rustfs/src/storage/access.rs @@ -17,7 +17,6 @@ use crate::auth::{check_key_valid, get_condition_values, get_session_token}; use crate::license::license_check; use rustfs_ecstore::bucket::policy_sys::PolicySys; use rustfs_iam::error::Error as IamError; -use rustfs_policy::auth; use rustfs_policy::policy::action::{Action, S3Action}; use rustfs_policy::policy::{Args, BucketPolicyArgs}; use s3s::access::{S3Access, S3AccessContext}; @@ -27,7 +26,7 @@ use std::collections::HashMap; #[allow(dead_code)] #[derive(Default, Clone)] pub(crate) struct ReqInfo { - pub cred: Option, + pub cred: Option, pub is_owner: bool, pub bucket: Option, pub object: Option, @@ -107,7 +106,7 @@ pub async fn authorize_request(req: &mut S3Request, action: Action) -> S3R } else { let conditions = get_condition_values( &req.headers, - &auth::Credentials::default(), + &rustfs_credentials::Credentials::default(), req_info.version_id.as_deref(), req.region.as_deref(), ); diff --git a/rustfs/src/storage/ecfs.rs b/rustfs/src/storage/ecfs.rs index ca8af9ed..d9675ff4 100644 --- a/rustfs/src/storage/ecfs.rs +++ b/rustfs/src/storage/ecfs.rs @@ -93,12 +93,9 @@ use rustfs_kms::{ types::{EncryptionMetadata, ObjectEncryptionContext}, }; use rustfs_notify::{EventArgsBuilder, notifier_global}; -use rustfs_policy::{ - auth, - policy::{ - action::{Action, S3Action}, - {BucketPolicy, BucketPolicyArgs, Validator}, - }, +use rustfs_policy::policy::{ + action::{Action, S3Action}, + {BucketPolicy, BucketPolicyArgs, Validator}, }; use rustfs_rio::{CompressReader, DecryptReader, EncryptReader, EtagReader, HardLimitReader, HashReader, Reader, WarpReader}; use rustfs_s3select_api::{ @@ -4692,7 +4689,7 @@ impl S3 for FS { .await .map_err(ApiError::from)?; - let conditions = get_condition_values(&req.headers, &auth::Credentials::default(), None, None); + let conditions = get_condition_values(&req.headers, &rustfs_credentials::Credentials::default(), None, None); let read_only = PolicySys::is_allowed(&BucketPolicyArgs { bucket: &bucket, diff --git a/rustfs/src/storage/tonic_service.rs b/rustfs/src/storage/tonic_service.rs index c0f86cb4..1f173da5 100644 --- a/rustfs/src/storage/tonic_service.rs +++ b/rustfs/src/storage/tonic_service.rs @@ -1774,11 +1774,34 @@ impl Node for NodeService { async fn get_metrics(&self, request: Request) -> Result, Status> { let request = request.into_inner(); - let mut buf_t = Deserializer::new(Cursor::new(request.metric_type)); - let t: MetricType = Deserialize::deserialize(&mut buf_t).unwrap(); + // Deserialize metric_type with error handling + let mut buf_t = Deserializer::new(Cursor::new(request.metric_type)); + let t: MetricType = match Deserialize::deserialize(&mut buf_t) { + Ok(t) => t, + Err(err) => { + error!("Failed to deserialize metric_type: {}", err); + return Ok(Response::new(GetMetricsResponse { + success: false, + realtime_metrics: Bytes::new(), + error_info: Some(format!("Invalid metric_type: {}", err)), + })); + } + }; + + // Deserialize opts with error handling let mut buf_o = Deserializer::new(Cursor::new(request.opts)); - let opts: CollectMetricsOpts = Deserialize::deserialize(&mut buf_o).unwrap(); + let opts: CollectMetricsOpts = match Deserialize::deserialize(&mut buf_o) { + Ok(opts) => opts, + Err(err) => { + error!("Failed to deserialize opts: {}", err); + return Ok(Response::new(GetMetricsResponse { + success: false, + realtime_metrics: Bytes::new(), + error_info: Some(format!("Invalid opts: {}", err)), + })); + } + }; let info = collect_local_metrics(t, &opts).await; @@ -3648,4 +3671,32 @@ mod tests { // Should return None for non-existent disk assert!(disk.is_none()); } + + #[tokio::test] + async fn test_get_metrics_invalid_metric_type() { + let service = create_test_node_service(); + let request = Request::new(GetMetricsRequest { + metric_type: Bytes::from(vec![0x00u8, 0x01u8]), // Invalid rmp data + opts: Bytes::new(), // Valid or invalid + }); + let response = service.get_metrics(request).await.unwrap().into_inner(); + assert!(!response.success); + assert!(response.error_info.is_some()); + } + + #[tokio::test] + async fn test_get_metrics_invalid_opts() { + let service = create_test_node_service(); + // Serialize a valid MetricType + let metric_type = MetricType::DISK; + let metric_type_bytes = rmp_serde::to_vec(&metric_type).unwrap(); + + let request = Request::new(GetMetricsRequest { + metric_type: Bytes::from(metric_type_bytes), + opts: Bytes::from(vec![0x00u8, 0x01u8]), // Invalid rmp data + }); + let response = service.get_metrics(request).await.unwrap().into_inner(); + assert!(!response.success); + assert!(response.error_info.is_some()); + } }