VYPR
Moderate severityOSV Advisory· Published Jan 7, 2026· Updated Jan 7, 2026

RustFS gRPC GetMetrics deserialization panic enables remote DoS

CVE-2025-69255

Description

RustFS is a distributed object storage system built in Rust. In versions 1.0.0-alpha.13 to 1.0.0-alpha.77, a malformed gRPC GetMetrics request causes get_metrics to unwrap() failed deserialization of metric_type/opts, panicking the handler thread and enabling remote denial of service of the metrics endpoint. This issue has been patched in version 1.0.0-alpha.78.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
rustfscrates.io
>= 1.0.0-alpha.13, < 1.0.0-alpha.781.0.0-alpha.78

Affected products

1
  • Range: 1.0.0-alpha.13, 1.0.0-alpha.14, 1.0.0-alpha.15, …

Patches

1
eb33e82b56ed

fix: Prevent panic in GetMetrics gRPC handler on invalid input (#1291)

https://github.com/rustfs/rustfshousemeDec 28, 2025via ghsa
45 files changed · +985 563
  • Cargo.lock+80 53 modified
    @@ -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"
    
  • Cargo.toml+6 6 modified
    @@ -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"]
     
    
  • crates/common/Cargo.toml+1 1 modified
    @@ -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
    
  • crates/config/src/constants/app.rs+6 54 modified
    @@ -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
    
  • crates/credentials/Cargo.toml+21 0 added
    @@ -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
    
  • crates/credentials/README.md+44 0 added
    @@ -0,0 +1,44 @@
    +[![RustFS](https://rustfs.com/images/rustfs-github.png)](https://rustfs.com)
    +
    +# RustFS Credentials - Credential Management Module
    +
    +<p align="center">
    +  <strong>A module for managing credentials within the RustFS distributed object storage system.</strong>
    +</p>
    +
    +<p align="center">
    +  <a href="https://github.com/rustfs/rustfs/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/rustfs/rustfs/actions/workflows/ci.yml/badge.svg" /></a>
    +  <a href="https://docs.rustfs.com/">📖 Documentation</a>
    +  · <a href="https://github.com/rustfs/rustfs/issues">🐛 Bug Reports</a>
    +  · <a href="https://github.com/rustfs/rustfs/discussions">💬 Discussions</a>
    +</p>
    +
    +---
    +
    +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
    
  • crates/credentials/src/constants.rs+94 0 added
    @@ -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
    +    }
    +}
    
  • crates/credentials/src/credentials.rs+386 0 added
    @@ -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<Credentials> = OnceLock::new();
    +
    +/// Global gRPC authentication token
    +static GLOBAL_GRPC_AUTH_TOKEN: OnceLock<String> = OnceLock::new();
    +
    +/// Initialize the global action credentials
    +///
    +/// # Arguments
    +/// * `ak` - Optional access key
    +/// * `sk` - Optional secret key
    +///
    +/// # Returns
    +/// * `Result<(), Box<Credentials>>` - 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<String>, sk: Option<String>) -> Result<(), Box<Credentials>> {
    +    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<Credentials> {
    +    GLOBAL_ACTIVE_CRED.get().cloned()
    +}
    +
    +/// Get the global secret key
    +///
    +/// # Returns
    +/// * `Option<String>` - The global secret key, if set
    +///
    +pub fn get_global_secret_key_opt() -> Option<String> {
    +    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<String>` - The global access key, if set
    +///
    +pub fn get_global_access_key_opt() -> Option<String> {
    +    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<String>` - 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<String> {
    +    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<String>` - 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<String> {
    +    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<OffsetDateTime>,
    +    pub status: String,
    +    pub parent_user: String,
    +    pub groups: Option<Vec<String>>,
    +    pub claims: Option<HashMap<String, Value>>,
    +    pub name: Option<String>,
    +    pub description: Option<String>,
    +}
    +
    +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);
    +        }
    +    }
    +}
    
  • crates/credentials/src/lib.rs+19 0 added
    @@ -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::*;
    
  • crates/ecstore/Cargo.toml+9 8 modified
    @@ -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 }
    
  • crates/ecstore/README_cn.md+0 19 removed
    @@ -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.
    
  • crates/ecstore/src/global.rs+0 44 modified
    @@ -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<CancellationToken> = OnceLock::new();
     
    -/// Global active credentials
    -static GLOBAL_ACTIVE_CRED: OnceLock<Credentials> = 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<String>, sk: Option<String>) {
    -    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<Credentials> {
    -    GLOBAL_ACTIVE_CRED.get().cloned()
    -}
    -
     /// Get the global rustfs port
     ///
     /// # Returns
    
  • crates/ecstore/src/rpc/http_auth.rs+1 1 modified
    @@ -12,14 +12,14 @@
     // 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};
     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;
    
  • crates/filemeta/Cargo.toml+1 2 modified
    @@ -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]
    
  • crates/filemeta/src/replication.rs+2 3 modified
    @@ -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<Regex> = LazyLock::new(|| Regex::new(r"([^=].*?)=([^,].*?);").unwrap());
     
     impl ReplicateObjectInfo {
         /// Returns replication status of a target
    
  • crates/iam/Cargo.toml+1 0 modified
    @@ -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"] }
    
  • crates/iam/src/manager.rs+3 5 modified
    @@ -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};
    
  • crates/iam/src/store/object.rs+1 1 modified
    @@ -20,14 +20,14 @@ 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::{
         config::{
             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},
     };
    
  • crates/iam/src/sys.rs+2 3 modified
    @@ -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;
    
  • crates/policy/Cargo.toml+2 2 modified
    @@ -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 }
    
  • crates/policy/src/auth/credentials.rs+95 167 modified
    @@ -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,190 +33,94 @@ 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<Self, Self::Error> {
    -//         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<Vec<BorrowedFormatItem<'static>>> =
    -//                         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<OffsetDateTime>,
    -    pub status: String,
    -    pub parent_user: String,
    -    pub groups: Option<Vec<String>>,
    -    pub claims: Option<HashMap<String, Value>>,
    -    pub name: Option<String>,
    -    pub description: Option<String>,
    -}
    -
    -impl Credentials {
    -    // pub fn new(elem: &str) -> Result<Self> {
    -    //     let header: CredentialHeader = elem.try_into()?;
    -    //     Self::check_key_value(header)
    -    // }
    -
    -    // pub fn check_key_value(_header: CredentialHeader) -> Result<Self> {
    -    //     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<String, Value>, token_secret: &str) -> Result<Credentials> {
         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,
         claims: &HashMap<String, Value>,
         token_secret: &str,
     ) -> Result<Credentials> {
         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<T: Serialize>(claims: &T, token_secret: &str) -> Result<String> {
         let token = utils::generate_jwt(claims, token_secret)?;
         Ok(token)
    @@ -267,16 +182,29 @@ pub struct CredentialsBuilder {
         description: Option<String>,
         expiration: Option<OffsetDateTime>,
         allow_site_replicator_account: bool,
    -    claims: Option<serde_json::Value>,
    +    claims: Option<Value>,
         parent_user: String,
         groups: Option<Vec<String>>,
     }
     
     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<Policy>) -> 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<CredentialsBuilder> for Credentials {
         type Error = Error;
         fn try_from(mut value: CredentialsBuilder) -> std::result::Result<Self, Self::Error> {
             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<CredentialsBuilder> 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<CredentialsBuilder> 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);
    
  • crates/policy/src/auth/mod.rs+9 1 renamed
    @@ -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,
    
  • crates/policy/src/policy/policy.rs+1 1 modified
    @@ -258,7 +258,7 @@ pub fn get_policies_from_claims(claims: &HashMap<String, Value>, 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 {
    
  • crates/policy/src/policy.rs+0 4 modified
    @@ -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 {
    
  • crates/policy/src/utils.rs+1 57 modified
    @@ -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<String> {
    -    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<String> {
    -    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<T: Serialize>(claims: &T, secret: &str) -> std::result::Result<String, jsonwebtoken::errors::Error> {
         let header = Header::new(Algorithm::HS512);
    @@ -72,26 +33,9 @@ pub fn extract_claims<T: DeserializeOwned + Clone>(
     
     #[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,
    
  • crates/protos/Cargo.toml+1 0 modified
    @@ -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"] }
    
  • crates/protos/src/lib.rs+13 2 modified
    @@ -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<dyn Error>,
     > {
    -    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::<String>(),
    +        );
    +        e
    +    })?;
     
         // Try to get cached channel
         let cached_channel = { GLOBAL_CONN_MAP.read().await.get(addr).cloned() };
    
  • crates/utils/Cargo.toml+4 3 modified
    @@ -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
    
  • crates/utils/src/lib.rs+3 2 modified
    @@ -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;
    
  • crates/utils/src/string.rs+0 76 modified
    @@ -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<Vec<String>> {
         Ok(ret)
     }
     
    -/// Generates a random access key of the specified length.
    -///
    -/// # Arguments
    -/// * `length` - The length of the access key to generate
    -///
    -/// # Returns
    -/// * `Result<String>` - 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<String> {
    -    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<String>` - 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<String> {
    -    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
    
  • rustfs/Cargo.toml+1 0 modified
    @@ -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 }
    
  • rustfs/src/admin/auth.rs+3 3 modified
    @@ -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<Action>,
    @@ -49,7 +49,7 @@ pub async fn validate_admin_request(
     async fn check_admin_request_auth(
         iam_store: Arc<IamSys<ObjectStore>>,
         headers: &HeaderMap,
    -    cred: &auth::Credentials,
    +    cred: &Credentials,
         is_owner: bool,
         deny_only: bool,
         action: Action,
    
  • rustfs/src/admin/console.rs+10 5 modified
    @@ -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::<SocketAddr>() {
             // Successfully parsed, it's in IP:Port format.
             // For IPv6, we need to enclose it in brackets to form a valid URL.
    
  • rustfs/src/admin/handlers/group.rs+1 1 modified
    @@ -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};
    
  • rustfs/src/admin/handlers/policies.rs+1 1 modified
    @@ -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::{
    
  • rustfs/src/admin/handlers.rs+1 1 modified
    @@ -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;
    
  • rustfs/src/admin/handlers/service_account.rs+1 1 modified
    @@ -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::{
    
  • rustfs/src/admin/handlers/user.rs+1 1 modified
    @@ -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,
    
  • rustfs/src/auth.rs+76 16 modified
    @@ -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<HashMap<String, Value>> {
    +pub fn check_claims_from_token(token: &str, cred: &Credentials) -> S3Result<HashMap<String, Value>> {
         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<String, Vec<String>>` - 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<String, Vec<String>> {
    @@ -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,23 +522,41 @@ 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();
         }
         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();
         }
         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(),
    
  • rustfs/src/config/mod.rs+2 2 modified
    @@ -73,11 +73,11 @@ pub struct Opt {
         pub server_domains: Vec<String>,
     
         /// 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
    
  • rustfs/src/main.rs+11 1 modified
    @@ -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);
     
    
  • rustfs/src/server/http.rs+12 4 modified
    @@ -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<Request<()>, 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),
    
  • rustfs/src/storage/access.rs+2 3 modified
    @@ -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<auth::Credentials>,
    +    pub cred: Option<rustfs_credentials::Credentials>,
         pub is_owner: bool,
         pub bucket: Option<String>,
         pub object: Option<String>,
    @@ -107,7 +106,7 @@ pub async fn authorize_request<T>(req: &mut S3Request<T>, 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(),
             );
    
  • rustfs/src/storage/ecfs.rs+4 7 modified
    @@ -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,
    
  • rustfs/src/storage/tonic_service.rs+53 2 modified
    @@ -1774,11 +1774,34 @@ impl Node for NodeService {
     
         async fn get_metrics(&self, request: Request<GetMetricsRequest>) -> Result<Response<GetMetricsResponse>, Status> {
             let request = request.into_inner();
    +
    +        // Deserialize metric_type with error handling
             let mut buf_t = Deserializer::new(Cursor::new(request.metric_type));
    -        let t: MetricType = Deserialize::deserialize(&mut buf_t).unwrap();
    +        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());
    +    }
     }
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.