diff --git a/Cargo.lock b/Cargo.lock
index e96b2da1..f823f798 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -24,7 +24,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03d2d54c4d9e7006f132f615a167865bff927a79ca63d8f637237575ce0a9795"
dependencies = [
"crypto-common 0.2.0-rc.5",
- "inout 0.2.1",
+ "inout 0.2.2",
]
[[package]]
@@ -1068,9 +1068,9 @@ dependencies = [
[[package]]
name = "axum-core"
-version = "0.5.5"
+version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22"
+checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
dependencies = [
"bytes",
"futures-core",
@@ -1087,9 +1087,9 @@ dependencies = [
[[package]]
name = "axum-extra"
-version = "0.12.3"
+version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6dfbd6109d91702d55fc56df06aae7ed85c465a7a451db6c0e54a4b9ca5983d1"
+checksum = "fef252edff26ddba56bbcdf2ee3307b8129acb86f5749b68990c168a6fcc9c76"
dependencies = [
"axum",
"axum-core",
@@ -1185,9 +1185,9 @@ checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a"
[[package]]
name = "bigdecimal"
-version = "0.4.9"
+version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "560f42649de9fa436b73517378a147ec21f6c997a546581df4b4b31677828934"
+checksum = "4d6867f1565b3aad85681f1015055b087fcfd840d6aeee6eee7f2da317603695"
dependencies = [
"autocfg",
"libm",
@@ -1459,9 +1459,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
-version = "1.2.50"
+version = "1.2.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c"
+checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -1574,7 +1574,7 @@ checksum = "155e4a260750fa4f7754649f049748aacc31db238a358d85fd721002f230f92f"
dependencies = [
"block-buffer 0.11.0",
"crypto-common 0.2.0-rc.5",
- "inout 0.2.1",
+ "inout 0.2.2",
]
[[package]]
@@ -3367,9 +3367,9 @@ dependencies = [
[[package]]
name = "find-msvc-tools"
-version = "0.1.5"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
+checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff"
[[package]]
name = "findshlibs"
@@ -3473,9 +3473,9 @@ dependencies = [
[[package]]
name = "fs-err"
-version = "3.2.1"
+version = "3.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "824f08d01d0f496b3eca4f001a13cf17690a6ee930043d20817f547455fd98f8"
+checksum = "baf68cef89750956493a66a10f512b9e58d9db21f2a573c079c0bdf1207a54a7"
dependencies = [
"autocfg",
"tokio",
@@ -3629,6 +3629,18 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "getrandom"
+version = "0.4.0-rc.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b99f0d993a2b9b97b9a201193aa8ad21305cde06a3be9a7e1f8f4201e5cc27e"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasip2",
+]
+
[[package]]
name = "getset"
version = "0.1.6"
@@ -4546,9 +4558,9 @@ dependencies = [
[[package]]
name = "inout"
-version = "0.2.1"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7357b6e7aa75618c7864ebd0634b115a7218b0615f4cb1df33ac3eca23943d4"
+checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
dependencies = [
"hybrid-array",
]
@@ -4627,9 +4639,9 @@ dependencies = [
[[package]]
name = "itoa"
-version = "1.0.16"
+version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010"
+checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]]
name = "jemalloc_pprof"
@@ -4817,13 +4829,13 @@ dependencies = [
[[package]]
name = "libredox"
-version = "0.1.11"
+version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50"
+checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
dependencies = [
"bitflags 2.10.0",
"libc",
- "redox_syscall 0.6.0",
+ "redox_syscall 0.7.0",
]
[[package]]
@@ -5007,9 +5019,9 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]]
name = "matchit"
-version = "0.9.0"
+version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ea5f97102eb9e54ab99fb70bb175589073f554bdadfb74d9bd656482ea73e2a"
+checksum = "b3eede3bdf92f3b4f9dc04072a9ce5ab557d5ec9038773bf9ffcd5588b3cc05b"
[[package]]
name = "md-5"
@@ -5761,11 +5773,11 @@ dependencies = [
[[package]]
name = "password-hash"
-version = "0.6.0-rc.6"
+version = "0.6.0-rc.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "383d290055c99f2dd7dece082088d89494dff6d79277fbac4a7da21c1bf2ab6b"
+checksum = "c351143b5ab27b1f1d24712f21ea4d0458fe74f60dd5839297dabcc2ecd24d58"
dependencies = [
- "getrandom 0.3.4",
+ "getrandom 0.4.0-rc.0",
"phc",
]
@@ -5883,12 +5895,12 @@ dependencies = [
[[package]]
name = "phc"
-version = "0.6.0-rc.0"
+version = "0.6.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c61f960577aaac5c259bc0866d685ba315c0ed30793c602d7287f54980913863"
+checksum = "71d390c5fe8d102c2c18ff39f1e72b9ad5996de282c2d831b0312f56910f5508"
dependencies = [
"base64ct",
- "getrandom 0.3.4",
+ "getrandom 0.4.0-rc.0",
"subtle",
]
@@ -6098,9 +6110,9 @@ dependencies = [
[[package]]
name = "portable-atomic"
-version = "1.12.0"
+version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd"
+checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950"
[[package]]
name = "potential_utf"
@@ -6233,9 +6245,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.103"
+version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
+checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0"
dependencies = [
"unicode-ident",
]
@@ -6635,9 +6647,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
-version = "0.6.0"
+version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5"
+checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27"
dependencies = [
"bitflags 2.10.0",
]
@@ -6830,7 +6842,7 @@ dependencies = [
"pastey",
"pin-project-lite",
"rmcp-macros",
- "schemars 1.1.0",
+ "schemars 1.2.0",
"serde",
"serde_json",
"thiserror 2.0.17",
@@ -7045,7 +7057,7 @@ dependencies = [
"hyper-util",
"jemalloc_pprof",
"libsystemd",
- "matchit 0.9.0",
+ "matchit 0.9.1",
"md5",
"metrics",
"mimalloc",
@@ -7061,6 +7073,7 @@ dependencies = [
"rustfs-audit",
"rustfs-common",
"rustfs-config",
+ "rustfs-credentials",
"rustfs-ecstore",
"rustfs-filemeta",
"rustfs-iam",
@@ -7210,6 +7223,17 @@ dependencies = [
"const-str",
]
+[[package]]
+name = "rustfs-credentials"
+version = "0.0.5"
+dependencies = [
+ "base64-simd",
+ "rand 0.10.0-rc.5",
+ "serde",
+ "serde_json",
+ "time",
+]
+
[[package]]
name = "rustfs-crypto"
version = "0.0.5"
@@ -7276,6 +7300,7 @@ dependencies = [
"rustfs-checksums",
"rustfs-common",
"rustfs-config",
+ "rustfs-credentials",
"rustfs-filemeta",
"rustfs-lock",
"rustfs-madmin",
@@ -7318,7 +7343,6 @@ dependencies = [
"bytes",
"crc-fast",
"criterion",
- "lazy_static",
"regex",
"rmp",
"rmp-serde",
@@ -7344,6 +7368,7 @@ dependencies = [
"jsonwebtoken",
"pollster",
"rand 0.10.0-rc.5",
+ "rustfs-credentials",
"rustfs-crypto",
"rustfs-ecstore",
"rustfs-madmin",
@@ -7427,7 +7452,7 @@ dependencies = [
"clap",
"mime_guess",
"rmcp",
- "schemars 1.1.0",
+ "schemars 1.2.0",
"serde",
"serde_json",
"tokio",
@@ -7505,10 +7530,10 @@ dependencies = [
"jsonwebtoken",
"moka",
"pollster",
- "rand 0.10.0-rc.5",
"regex",
"reqwest",
"rustfs-config",
+ "rustfs-credentials",
"rustfs-crypto",
"serde",
"serde_json",
@@ -7528,6 +7553,7 @@ dependencies = [
"flatbuffers",
"prost 0.14.1",
"rustfs-common",
+ "rustfs-credentials",
"tonic",
"tonic-prost",
"tonic-prost-build",
@@ -7865,9 +7891,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
-version = "1.0.21"
+version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea"
+checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
[[package]]
name = "s3s"
@@ -7959,9 +7985,9 @@ dependencies = [
[[package]]
name = "schemars"
-version = "1.1.0"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289"
+checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2"
dependencies = [
"chrono",
"dyn-clone",
@@ -7973,9 +7999,9 @@ dependencies = [
[[package]]
name = "schemars_derive"
-version = "1.1.0"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "301858a4023d78debd2353c7426dc486001bddc91ae31a76fb1f55132f7e2633"
+checksum = "4908ad288c5035a8eb12cfdf0d49270def0a268ee162b75eeee0f85d155a7c45"
dependencies = [
"proc-macro2",
"quote",
@@ -8124,9 +8150,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.147"
+version = "1.0.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6af14725505314343e673e9ecb7cd7e8a36aa9791eb936235a3567cc31447ae4"
+checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da"
dependencies = [
"itoa",
"memchr",
@@ -8179,7 +8205,7 @@ dependencies = [
"indexmap 1.9.3",
"indexmap 2.12.1",
"schemars 0.9.0",
- "schemars 1.1.0",
+ "schemars 1.2.0",
"serde_core",
"serde_json",
"serde_with_macros",
@@ -8317,10 +8343,11 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
-version = "1.4.7"
+version = "1.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad"
+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
dependencies = [
+ "errno",
"libc",
]
@@ -10432,9 +10459,9 @@ checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3"
[[package]]
name = "zmij"
-version = "0.1.7"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e404bcd8afdaf006e529269d3e85a743f9480c3cef60034d77860d02964f3ba"
+checksum = "f5858cd3a46fff31e77adea2935e357e3a2538d870741617bfb7c943e218fee6"
[[package]]
name = "zopfli"
diff --git a/Cargo.toml b/Cargo.toml
index ae85bdc5..6eed163c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,6 +19,7 @@ members = [
"crates/audit", # Audit target management system with multi-target fan-out
"crates/common", # Shared utilities and data structures
"crates/config", # Configuration management
+ "crates/credentials", # Credential management system
"crates/crypto", # Cryptography and security features
"crates/ecstore", # Erasure coding storage implementation
"crates/e2e_test", # End-to-end test suite
@@ -71,6 +72,7 @@ rustfs-audit = { path = "crates/audit", version = "0.0.5" }
rustfs-checksums = { path = "crates/checksums", version = "0.0.5" }
rustfs-common = { path = "crates/common", version = "0.0.5" }
rustfs-config = { path = "./crates/config", version = "0.0.5" }
+rustfs-credentials = { path = "crates/credentials", version = "0.0.5" }
rustfs-crypto = { path = "crates/crypto", version = "0.0.5" }
rustfs-ecstore = { path = "crates/ecstore", version = "0.0.5" }
rustfs-filemeta = { path = "crates/filemeta", version = "0.0.5" }
@@ -98,7 +100,7 @@ async-compression = { version = "0.4.19" }
async-recursion = "1.1.1"
async-trait = "0.1.89"
axum = "0.8.8"
-axum-extra = "0.12.3"
+axum-extra = "0.12.5"
axum-server = { version = "0.8.0", features = ["tls-rustls-no-provider"], default-features = false }
futures = "0.3.31"
futures-core = "0.3.31"
@@ -135,9 +137,9 @@ rmcp = { version = "0.12.0" }
rmp = { version = "0.8.15" }
rmp-serde = { version = "1.3.1" }
serde = { version = "1.0.228", features = ["derive"] }
-serde_json = { version = "1.0.147", features = ["raw_value"] }
+serde_json = { version = "1.0.148", features = ["raw_value"] }
serde_urlencoded = "0.7.1"
-schemars = "1.1.0"
+schemars = "1.2.0"
# Cryptography and Security
aes-gcm = { version = "0.11.0-rc.2", features = ["rand_core"] }
@@ -200,7 +202,7 @@ libc = "0.2.178"
libsystemd = "0.7.2"
local-ip-address = "0.6.8"
lz4 = "1.28.1"
-matchit = "0.9.0"
+matchit = "0.9.1"
md-5 = "0.11.0-rc.3"
md5 = "0.8.0"
mime_guess = "2.0.5"
@@ -276,8 +278,6 @@ jemalloc_pprof = { version = "0.8.1", features = ["symbolize", "flamegraph"] }
# Used to generate CPU performance analysis data and flame diagrams
pprof = { version = "0.15.0", features = ["flamegraph", "protobuf-codec"] }
-
-
[workspace.metadata.cargo-shear]
ignored = ["rustfs", "rustfs-mcp"]
diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml
index cea4e0a9..e9ed0caf 100644
--- a/crates/common/Cargo.toml
+++ b/crates/common/Cargo.toml
@@ -39,4 +39,4 @@ path-clean = { workspace = true }
rmp-serde = { workspace = true }
async-trait = { workspace = true }
s3s = { workspace = true }
-tracing = { workspace = true }
+tracing = { workspace = true }
\ No newline at end of file
diff --git a/crates/config/src/constants/app.rs b/crates/config/src/constants/app.rs
index 0610319e..0b2035e4 100644
--- a/crates/config/src/constants/app.rs
+++ b/crates/config/src/constants/app.rs
@@ -49,21 +49,6 @@ pub const SERVICE_VERSION: &str = "1.0.0";
/// Default value: production
pub const ENVIRONMENT: &str = "production";
-/// Default Access Key
-/// Default value: rustfsadmin
-/// Environment variable: RUSTFS_ACCESS_KEY
-/// Command line argument: --access-key
-/// Example: RUSTFS_ACCESS_KEY=rustfsadmin
-/// Example: --access-key rustfsadmin
-pub const DEFAULT_ACCESS_KEY: &str = "rustfsadmin";
-/// Default Secret Key
-/// Default value: rustfsadmin
-/// Environment variable: RUSTFS_SECRET_KEY
-/// Command line argument: --secret-key
-/// Example: RUSTFS_SECRET_KEY=rustfsadmin
-/// Example: --secret-key rustfsadmin
-pub const DEFAULT_SECRET_KEY: &str = "rustfsadmin";
-
/// Default console enable
/// This is the default value for the console server.
/// It is used to enable or disable the console server.
@@ -185,6 +170,12 @@ pub const KI_B: usize = 1024;
/// Default value: 1048576
pub const MI_B: usize = 1024 * 1024;
+/// Environment variable for gRPC authentication token
+/// Used to set the authentication token for gRPC communication
+/// Example: RUSTFS_GRPC_AUTH_TOKEN=your_token_here
+/// Default value: No default value. RUSTFS_SECRET_KEY value is recommended.
+pub const ENV_GRPC_AUTH_TOKEN: &str = "RUSTFS_GRPC_AUTH_TOKEN";
+
#[cfg(test)]
mod tests {
use super::*;
@@ -225,20 +216,6 @@ mod tests {
);
}
- #[test]
- fn test_security_constants() {
- // Test security related constants
- assert_eq!(DEFAULT_ACCESS_KEY, "rustfsadmin");
- assert!(DEFAULT_ACCESS_KEY.len() >= 8, "Access key should be at least 8 characters");
-
- assert_eq!(DEFAULT_SECRET_KEY, "rustfsadmin");
- assert!(DEFAULT_SECRET_KEY.len() >= 8, "Secret key should be at least 8 characters");
-
- // In production environment, access key and secret key should be different
- // These are default values, so being the same is acceptable, but should be warned in documentation
- println!("Warning: Default access key and secret key are the same. Change them in production!");
- }
-
#[test]
fn test_file_path_constants() {
assert_eq!(RUSTFS_TLS_KEY, "rustfs_key.pem");
@@ -300,8 +277,6 @@ mod tests {
DEFAULT_LOG_LEVEL,
SERVICE_VERSION,
ENVIRONMENT,
- DEFAULT_ACCESS_KEY,
- DEFAULT_SECRET_KEY,
RUSTFS_TLS_KEY,
RUSTFS_TLS_CERT,
DEFAULT_ADDRESS,
@@ -331,29 +306,6 @@ mod tests {
assert_ne!(DEFAULT_CONSOLE_PORT, 0, "Console port should not be zero");
}
- #[test]
- fn test_security_best_practices() {
- // Test security best practices
-
- // These are default values, should be changed in production environments
- println!("Security Warning: Default credentials detected!");
- println!("Access Key: {DEFAULT_ACCESS_KEY}");
- println!("Secret Key: {DEFAULT_SECRET_KEY}");
- println!("These should be changed in production environments!");
-
- // Verify that key lengths meet minimum security requirements
- assert!(DEFAULT_ACCESS_KEY.len() >= 8, "Access key should be at least 8 characters");
- assert!(DEFAULT_SECRET_KEY.len() >= 8, "Secret key should be at least 8 characters");
-
- // Check if default credentials contain common insecure patterns
- let _insecure_patterns = ["admin", "password", "123456", "default"];
- let _access_key_lower = DEFAULT_ACCESS_KEY.to_lowercase();
- let _secret_key_lower = DEFAULT_SECRET_KEY.to_lowercase();
-
- // Note: More security check logic can be added here
- // For example, check if keys contain insecure patterns
- }
-
#[test]
fn test_configuration_consistency() {
// Test configuration consistency
diff --git a/crates/credentials/Cargo.toml b/crates/credentials/Cargo.toml
new file mode 100644
index 00000000..051644e7
--- /dev/null
+++ b/crates/credentials/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "rustfs-credentials"
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+rust-version.workspace = true
+version.workspace = true
+homepage.workspace = true
+description = "Credentials management utilities for RustFS, enabling secure handling of authentication and authorization data."
+keywords = ["rustfs", "Minio", "credentials", "authentication", "authorization"]
+categories = ["web-programming", "development-tools", "data-structures", "security"]
+
+[dependencies]
+base64-simd = { workspace = true }
+rand = { workspace = true }
+serde = { workspace = true }
+serde_json.workspace = true
+time = { workspace = true, features = ["serde-human-readable"] }
+
+[lints]
+workspace = true
diff --git a/crates/credentials/README.md b/crates/credentials/README.md
new file mode 100644
index 00000000..1643b121
--- /dev/null
+++ b/crates/credentials/README.md
@@ -0,0 +1,44 @@
+[](https://rustfs.com)
+
+# RustFS Credentials - Credential Management Module
+
+
+ A module for managing credentials within the RustFS distributed object storage system.
+
+
+
+
+ ๐ Documentation
+ ยท ๐ Bug Reports
+ ยท ๐ฌ Discussions
+
+
+---
+
+This module provides a secure and efficient way to handle various types of credentials,
+such as API keys, access tokens, and cryptographic keys, required for interacting with
+the RustFS ecosystem and external services.
+
+## ๐ Overview
+
+**RustFS Credentials** is a module dedicated to managing credentials for the [RustFS](https://rustfs.com) distributed
+object storage system. For the complete RustFS experience,
+please visit the [main RustFS repository](https://github.com/rustfs/rustfs)
+
+## โจ Features
+
+- Secure storage and retrieval of credentials
+- Support for multiple credential types (API keys, tokens, etc.)
+- Encryption of sensitive credential data
+- Integration with external secret management systems
+- Easy-to-use API for credential management
+- Credential rotation and expiration handling
+
+## ๐ Documentation
+
+For comprehensive documentation, examples, and usage guides, please visit the
+main [RustFS repository](https://github.com/rustfs/rustfs).
+
+## ๐ License
+
+This project is licensed under the Apache License 2.0 - see the [LICENSE](../../LICENSE) file for details.
\ No newline at end of file
diff --git a/crates/credentials/src/constants.rs b/crates/credentials/src/constants.rs
new file mode 100644
index 00000000..442e2833
--- /dev/null
+++ b/crates/credentials/src/constants.rs
@@ -0,0 +1,94 @@
+// Copyright 2024 RustFS Team
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/// Default Access Key
+/// Default value: rustfsadmin
+/// Environment variable: RUSTFS_ACCESS_KEY
+/// Command line argument: --access-key
+/// Example: RUSTFS_ACCESS_KEY=rustfsadmin
+/// Example: --access-key rustfsadmin
+pub const DEFAULT_ACCESS_KEY: &str = "rustfsadmin";
+/// Default Secret Key
+/// Default value: rustfsadmin
+/// Environment variable: RUSTFS_SECRET_KEY
+/// Command line argument: --secret-key
+/// Example: RUSTFS_SECRET_KEY=rustfsadmin
+/// Example: --secret-key rustfsadmin
+pub const DEFAULT_SECRET_KEY: &str = "rustfsadmin";
+
+/// Environment variable for gRPC authentication token
+/// Used to set the authentication token for gRPC communication
+/// Example: RUSTFS_GRPC_AUTH_TOKEN=your_token_here
+/// Default value: No default value. RUSTFS_SECRET_KEY value is recommended.
+pub const ENV_GRPC_AUTH_TOKEN: &str = "RUSTFS_GRPC_AUTH_TOKEN";
+
+/// IAM Policy Types
+/// Used to differentiate between embedded and inherited policies
+/// Example: "embedded-policy" or "inherited-policy"
+/// Default value: "embedded-policy"
+pub const EMBEDDED_POLICY_TYPE: &str = "embedded-policy";
+
+/// IAM Policy Types
+/// Used to differentiate between embedded and inherited policies
+/// Example: "embedded-policy" or "inherited-policy"
+/// Default value: "inherited-policy"
+pub const INHERITED_POLICY_TYPE: &str = "inherited-policy";
+
+/// IAM Policy Claim Name for Service Account
+/// Used to identify the service account policy claim in JWT tokens
+/// Example: "sa-policy"
+/// Default value: "sa-policy"
+pub const IAM_POLICY_CLAIM_NAME_SA: &str = "sa-policy";
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_security_constants() {
+ // Test security related constants
+ assert_eq!(DEFAULT_ACCESS_KEY, "rustfsadmin");
+ assert!(DEFAULT_ACCESS_KEY.len() >= 8, "Access key should be at least 8 characters");
+
+ assert_eq!(DEFAULT_SECRET_KEY, "rustfsadmin");
+ assert!(DEFAULT_SECRET_KEY.len() >= 8, "Secret key should be at least 8 characters");
+
+ // In production environment, access key and secret key should be different
+ // These are default values, so being the same is acceptable, but should be warned in documentation
+ println!("Warning: Default access key and secret key are the same. Change them in production!");
+ }
+
+ #[test]
+ fn test_security_best_practices() {
+ // Test security best practices
+
+ // These are default values, should be changed in production environments
+ println!("Security Warning: Default credentials detected!");
+ println!("Access Key: {DEFAULT_ACCESS_KEY}");
+ println!("Secret Key: {DEFAULT_SECRET_KEY}");
+ println!("These should be changed in production environments!");
+
+ // Verify that key lengths meet minimum security requirements
+ assert!(DEFAULT_ACCESS_KEY.len() >= 8, "Access key should be at least 8 characters");
+ assert!(DEFAULT_SECRET_KEY.len() >= 8, "Secret key should be at least 8 characters");
+
+ // Check if default credentials contain common insecure patterns
+ let _insecure_patterns = ["admin", "password", "123456", "default"];
+ let _access_key_lower = DEFAULT_ACCESS_KEY.to_lowercase();
+ let _secret_key_lower = DEFAULT_SECRET_KEY.to_lowercase();
+
+ // Note: More security check logic can be added here
+ // For example, check if keys contain insecure patterns
+ }
+}
diff --git a/crates/credentials/src/credentials.rs b/crates/credentials/src/credentials.rs
new file mode 100644
index 00000000..34aa0fe9
--- /dev/null
+++ b/crates/credentials/src/credentials.rs
@@ -0,0 +1,386 @@
+// Copyright 2024 RustFS Team
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::{DEFAULT_SECRET_KEY, ENV_GRPC_AUTH_TOKEN, IAM_POLICY_CLAIM_NAME_SA, INHERITED_POLICY_TYPE};
+use rand::{Rng, RngCore};
+use serde::{Deserialize, Serialize};
+use serde_json::Value;
+use std::collections::HashMap;
+use std::env;
+use std::io::Error;
+use std::sync::OnceLock;
+use time::OffsetDateTime;
+
+/// Global active credentials
+static GLOBAL_ACTIVE_CRED: OnceLock = OnceLock::new();
+
+/// Global gRPC authentication token
+static GLOBAL_GRPC_AUTH_TOKEN: OnceLock = OnceLock::new();
+
+/// Initialize the global action credentials
+///
+/// # Arguments
+/// * `ak` - Optional access key
+/// * `sk` - Optional secret key
+///
+/// # Returns
+/// * `Result<(), Box>` - Ok if successful, Err with existing credentials if already initialized
+///
+/// # Panics
+/// This function panics if automatic credential generation fails when `ak` or `sk`
+/// are `None`, for example if the random number generator fails while calling
+/// `gen_access_key` or `gen_secret_key`.
+pub fn init_global_action_credentials(ak: Option, sk: Option) -> Result<(), Box> {
+ let ak = ak.unwrap_or_else(|| gen_access_key(20).expect("Failed to generate access key"));
+ let sk = sk.unwrap_or_else(|| gen_secret_key(32).expect("Failed to generate secret key"));
+
+ let cred = Credentials {
+ access_key: ak,
+ secret_key: sk,
+ ..Default::default()
+ };
+
+ GLOBAL_ACTIVE_CRED.set(cred).map_err(|e| {
+ Box::new(Credentials {
+ access_key: e.access_key.clone(),
+ ..Default::default()
+ })
+ })
+}
+
+/// Get the global action credentials
+pub fn get_global_action_cred() -> Option {
+ GLOBAL_ACTIVE_CRED.get().cloned()
+}
+
+/// Get the global secret key
+///
+/// # Returns
+/// * `Option` - The global secret key, if set
+///
+pub fn get_global_secret_key_opt() -> Option {
+ GLOBAL_ACTIVE_CRED.get().map(|cred| cred.secret_key.clone())
+}
+
+/// Get the global secret key
+///
+/// # Returns
+/// * `String` - The global secret key, or empty string if not set
+///
+pub fn get_global_secret_key() -> String {
+ GLOBAL_ACTIVE_CRED
+ .get()
+ .map(|cred| cred.secret_key.clone())
+ .unwrap_or_default()
+}
+
+/// Get the global access key
+///
+/// # Returns
+/// * `Option` - The global access key, if set
+///
+pub fn get_global_access_key_opt() -> Option {
+ GLOBAL_ACTIVE_CRED.get().map(|cred| cred.access_key.clone())
+}
+
+/// Get the global access key
+///
+/// # Returns
+/// * `String` - The global access key, or empty string if not set
+///
+pub fn get_global_access_key() -> String {
+ GLOBAL_ACTIVE_CRED
+ .get()
+ .map(|cred| cred.access_key.clone())
+ .unwrap_or_default()
+}
+
+/// Generates a random access key of the specified length.
+///
+/// # Arguments
+/// * `length` - The length of the access key to generate
+///
+/// # Returns
+/// * `Result` - A result containing the generated access key or an error if the length is too short
+///
+/// # Errors
+/// This function will return an error if the specified length is less than 3.
+///
+/// Examples
+/// ```no_run
+/// use rustfs_credentials::gen_access_key;
+///
+/// let access_key = gen_access_key(16).unwrap();
+/// println!("Generated access key: {}", access_key);
+/// ```
+///
+pub fn gen_access_key(length: usize) -> std::io::Result {
+ const ALPHA_NUMERIC_TABLE: [char; 36] = [
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
+ 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ ];
+
+ if length < 3 {
+ return Err(Error::other("access key length is too short"));
+ }
+
+ let mut result = String::with_capacity(length);
+ let mut rng = rand::rng();
+
+ for _ in 0..length {
+ result.push(ALPHA_NUMERIC_TABLE[rng.random_range(0..ALPHA_NUMERIC_TABLE.len())]);
+ }
+
+ Ok(result)
+}
+
+/// Generates a random secret key of the specified length.
+///
+/// # Arguments
+/// * `length` - The length of the secret key to generate
+///
+/// # Returns
+/// * `Result` - A result containing the generated secret key or an error if the length is too short
+///
+/// # Errors
+/// This function will return an error if the specified length is less than 8.
+///
+/// # Examples
+/// ```no_run
+/// use rustfs_credentials::gen_secret_key;
+///
+/// let secret_key = gen_secret_key(32).unwrap();
+/// println!("Generated secret key: {}", secret_key);
+/// ```
+///
+pub fn gen_secret_key(length: usize) -> std::io::Result {
+ use base64_simd::URL_SAFE_NO_PAD;
+
+ if length < 8 {
+ return Err(Error::other("secret key length is too short"));
+ }
+ let mut rng = rand::rng();
+
+ let mut key = vec![0u8; URL_SAFE_NO_PAD.estimated_decoded_length(length)];
+ rng.fill_bytes(&mut key);
+
+ let encoded = URL_SAFE_NO_PAD.encode_to_string(&key);
+ let key_str = encoded.replace("/", "+");
+
+ Ok(key_str)
+}
+
+/// Get the gRPC authentication token from environment variable
+///
+/// # Returns
+/// * `String` - The gRPC authentication token
+///
+pub fn get_grpc_token() -> String {
+ GLOBAL_GRPC_AUTH_TOKEN
+ .get_or_init(|| {
+ env::var(ENV_GRPC_AUTH_TOKEN)
+ .unwrap_or_else(|_| get_global_secret_key_opt().unwrap_or_else(|| DEFAULT_SECRET_KEY.to_string()))
+ })
+ .clone()
+}
+
+/// Credentials structure
+///
+/// Fields:
+/// - access_key: Access key string
+/// - secret_key: Secret key string
+/// - session_token: Session token string
+/// - expiration: Optional expiration time as OffsetDateTime
+/// - status: Status string (e.g., "active", "off")
+/// - parent_user: Parent user string
+/// - groups: Optional list of groups
+/// - claims: Optional map of claims
+/// - name: Optional name string
+/// - description: Optional description string
+///
+#[derive(Serialize, Deserialize, Clone, Default, Debug)]
+pub struct Credentials {
+ pub access_key: String,
+ pub secret_key: String,
+ pub session_token: String,
+ pub expiration: Option,
+ pub status: String,
+ pub parent_user: String,
+ pub groups: Option>,
+ pub claims: Option>,
+ pub name: Option,
+ pub description: Option,
+}
+
+impl Credentials {
+ pub fn is_expired(&self) -> bool {
+ if self.expiration.is_none() {
+ return false;
+ }
+
+ self.expiration
+ .as_ref()
+ .map(|e| OffsetDateTime::now_utc() > *e)
+ .unwrap_or(false)
+ }
+
+ pub fn is_temp(&self) -> bool {
+ !self.session_token.is_empty() && !self.is_expired()
+ }
+
+ pub fn is_service_account(&self) -> bool {
+ self.claims
+ .as_ref()
+ .map(|x| x.get(IAM_POLICY_CLAIM_NAME_SA).is_some_and(|_| !self.parent_user.is_empty()))
+ .unwrap_or_default()
+ }
+
+ pub fn is_implied_policy(&self) -> bool {
+ if self.is_service_account() {
+ return self
+ .claims
+ .as_ref()
+ .map(|x| x.get(IAM_POLICY_CLAIM_NAME_SA).is_some_and(|v| v == INHERITED_POLICY_TYPE))
+ .unwrap_or_default();
+ }
+
+ false
+ }
+
+ pub fn is_valid(&self) -> bool {
+ if self.status == "off" {
+ return false;
+ }
+
+ self.access_key.len() >= 3 && self.secret_key.len() >= 8 && !self.is_expired()
+ }
+
+ pub fn is_owner(&self) -> bool {
+ false
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{IAM_POLICY_CLAIM_NAME_SA, INHERITED_POLICY_TYPE};
+ use time::Duration;
+
+ #[test]
+ fn test_credentials_is_expired() {
+ let mut cred = Credentials::default();
+ assert!(!cred.is_expired());
+
+ cred.expiration = Some(OffsetDateTime::now_utc() + Duration::hours(1));
+ assert!(!cred.is_expired());
+
+ cred.expiration = Some(OffsetDateTime::now_utc() - Duration::hours(1));
+ assert!(cred.is_expired());
+ }
+
+ #[test]
+ fn test_credentials_is_temp() {
+ let mut cred = Credentials::default();
+ assert!(!cred.is_temp());
+
+ cred.session_token = "token".to_string();
+ assert!(cred.is_temp());
+
+ cred.expiration = Some(OffsetDateTime::now_utc() - Duration::hours(1));
+ assert!(!cred.is_temp());
+ }
+
+ #[test]
+ fn test_credentials_is_service_account() {
+ let mut cred = Credentials::default();
+ assert!(!cred.is_service_account());
+
+ let mut claims = HashMap::new();
+ claims.insert(IAM_POLICY_CLAIM_NAME_SA.to_string(), Value::String("policy".to_string()));
+ cred.claims = Some(claims);
+ cred.parent_user = "parent".to_string();
+ assert!(cred.is_service_account());
+ }
+
+ #[test]
+ fn test_credentials_is_implied_policy() {
+ let mut cred = Credentials::default();
+ assert!(!cred.is_implied_policy());
+
+ let mut claims = HashMap::new();
+ claims.insert(IAM_POLICY_CLAIM_NAME_SA.to_string(), Value::String(INHERITED_POLICY_TYPE.to_string()));
+ cred.claims = Some(claims);
+ cred.parent_user = "parent".to_string();
+ assert!(cred.is_implied_policy());
+ }
+
+ #[test]
+ fn test_credentials_is_valid() {
+ let mut cred = Credentials::default();
+ assert!(!cred.is_valid());
+
+ cred.access_key = "abc".to_string();
+ cred.secret_key = "12345678".to_string();
+ assert!(cred.is_valid());
+
+ cred.status = "off".to_string();
+ assert!(!cred.is_valid());
+ }
+
+ #[test]
+ fn test_credentials_is_owner() {
+ let cred = Credentials::default();
+ assert!(!cred.is_owner());
+ }
+
+ #[test]
+ fn test_global_credentials_flow() {
+ // Since OnceLock can only be set once, we put together all globally related tests
+ // If it has already been initialized (possibly from other tests), we verify the results directly
+ if get_global_action_cred().is_none() {
+ // Verify that the initial state is empty
+ assert!(get_global_access_key_opt().is_none());
+ assert_eq!(get_global_access_key(), "");
+ assert!(get_global_secret_key_opt().is_none());
+ assert_eq!(get_global_secret_key(), "");
+
+ // Initialize
+ let test_ak = "test_access_key".to_string();
+ let test_sk = "test_secret_key_123456".to_string();
+ init_global_action_credentials(Some(test_ak.clone()), Some(test_sk.clone())).ok();
+ }
+
+ // Verify the state after initialization
+ let cred = get_global_action_cred().expect("Global credentials should be set");
+ assert!(!cred.access_key.is_empty());
+ assert!(!cred.secret_key.is_empty());
+
+ assert!(get_global_access_key_opt().is_some());
+ assert!(!get_global_access_key().is_empty());
+ assert!(get_global_secret_key_opt().is_some());
+ assert!(!get_global_secret_key().is_empty());
+ }
+
+ #[test]
+ fn test_init_global_credentials_auto_gen() {
+ // If it hasn't already been initialized, the test automatically generates logic
+ if get_global_action_cred().is_none() {
+ init_global_action_credentials(None, None).ok();
+ let ak = get_global_access_key();
+ let sk = get_global_secret_key();
+ assert_eq!(ak.len(), 20);
+ assert_eq!(sk.len(), 32);
+ }
+ }
+}
diff --git a/crates/credentials/src/lib.rs b/crates/credentials/src/lib.rs
new file mode 100644
index 00000000..876d61b8
--- /dev/null
+++ b/crates/credentials/src/lib.rs
@@ -0,0 +1,19 @@
+// Copyright 2024 RustFS Team
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+mod constants;
+mod credentials;
+
+pub use constants::*;
+pub use credentials::*;
diff --git a/crates/ecstore/Cargo.toml b/crates/ecstore/Cargo.toml
index bd021c19..d4fe7d4c 100644
--- a/crates/ecstore/Cargo.toml
+++ b/crates/ecstore/Cargo.toml
@@ -34,12 +34,19 @@ workspace = true
default = []
[dependencies]
+rustfs-filemeta.workspace = true
+rustfs-utils = { workspace = true, features = ["full"] }
+rustfs-rio.workspace = true
+rustfs-signer.workspace = true
+rustfs-checksums.workspace = true
rustfs-config = { workspace = true, features = ["constants", "notify", "audit"] }
+rustfs-credentials = { workspace = true }
+rustfs-common.workspace = true
+rustfs-policy.workspace = true
+rustfs-protos.workspace = true
async-trait.workspace = true
bytes.workspace = true
byteorder = { workspace = true }
-rustfs-common.workspace = true
-rustfs-policy.workspace = true
chrono.workspace = true
glob = { workspace = true }
thiserror.workspace = true
@@ -60,7 +67,6 @@ lazy_static.workspace = true
rustfs-lock.workspace = true
regex = { workspace = true }
path-absolutize = { workspace = true }
-rustfs-protos.workspace = true
rmp.workspace = true
rmp-serde.workspace = true
tokio-util = { workspace = true, features = ["io", "compat"] }
@@ -91,11 +97,6 @@ aws-sdk-s3 = { workspace = true }
urlencoding = { workspace = true }
smallvec = { workspace = true }
shadow-rs.workspace = true
-rustfs-filemeta.workspace = true
-rustfs-utils = { workspace = true, features = ["full"] }
-rustfs-rio.workspace = true
-rustfs-signer.workspace = true
-rustfs-checksums.workspace = true
async-recursion.workspace = true
aws-credential-types = { workspace = true }
aws-smithy-types = { workspace = true }
diff --git a/crates/ecstore/README_cn.md b/crates/ecstore/README_cn.md
deleted file mode 100644
index 5bd11b23..00000000
--- a/crates/ecstore/README_cn.md
+++ /dev/null
@@ -1,19 +0,0 @@
-# ECStore - Erasure Coding Storage
-
-ECStore provides erasure coding functionality for the RustFS project, using high-performance Reed-Solomon SIMD implementation for optimal performance.
-
-## Features
-
-- **Reed-Solomon Implementation**: High-performance SIMD-optimized erasure coding
-- **Cross-Platform Compatibility**: Support for x86_64, aarch64, and other architectures
-- **Performance Optimized**: SIMD instructions for maximum throughput
-- **Thread Safety**: Safe concurrent access with caching optimizations
-- **Scalable**: Excellent performance for high-throughput scenarios
-
-## Documentation
-
-For complete documentation, examples, and usage information, please visit the main [RustFS repository](https://github.com/rustfs/rustfs).
-
-## License
-
-This project is licensed under the Apache License, Version 2.0.
diff --git a/crates/ecstore/src/global.rs b/crates/ecstore/src/global.rs
index 9c45f7b4..0a6621e9 100644
--- a/crates/ecstore/src/global.rs
+++ b/crates/ecstore/src/global.rs
@@ -21,7 +21,6 @@ use crate::{
tier::tier::TierConfigMgr,
};
use lazy_static::lazy_static;
-use rustfs_policy::auth::Credentials;
use std::{
collections::HashMap,
sync::{Arc, OnceLock},
@@ -61,49 +60,6 @@ lazy_static! {
/// Global cancellation token for background services (data scanner and auto heal)
static GLOBAL_BACKGROUND_SERVICES_CANCEL_TOKEN: OnceLock = OnceLock::new();
-/// Global active credentials
-static GLOBAL_ACTIVE_CRED: OnceLock = OnceLock::new();
-
-/// Initialize the global action credentials
-///
-/// # Arguments
-/// * `ak` - Optional access key
-/// * `sk` - Optional secret key
-///
-/// # Returns
-/// * None
-///
-pub fn init_global_action_credentials(ak: Option, sk: Option) {
- let ak = {
- if let Some(k) = ak {
- k
- } else {
- rustfs_utils::string::gen_access_key(20).unwrap_or_default()
- }
- };
-
- let sk = {
- if let Some(k) = sk {
- k
- } else {
- rustfs_utils::string::gen_secret_key(32).unwrap_or_default()
- }
- };
-
- GLOBAL_ACTIVE_CRED
- .set(Credentials {
- access_key: ak,
- secret_key: sk,
- ..Default::default()
- })
- .unwrap();
-}
-
-/// Get the global action credentials
-pub fn get_global_action_cred() -> Option {
- GLOBAL_ACTIVE_CRED.get().cloned()
-}
-
/// Get the global rustfs port
///
/// # Returns
diff --git a/crates/ecstore/src/rpc/http_auth.rs b/crates/ecstore/src/rpc/http_auth.rs
index faad926e..b283b3d1 100644
--- a/crates/ecstore/src/rpc/http_auth.rs
+++ b/crates/ecstore/src/rpc/http_auth.rs
@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::global::get_global_action_cred;
use base64::Engine as _;
use base64::engine::general_purpose;
use hmac::{Hmac, KeyInit, Mac};
@@ -20,6 +19,7 @@ use http::HeaderMap;
use http::HeaderValue;
use http::Method;
use http::Uri;
+use rustfs_credentials::get_global_action_cred;
use sha2::Sha256;
use time::OffsetDateTime;
use tracing::error;
diff --git a/crates/filemeta/Cargo.toml b/crates/filemeta/Cargo.toml
index 915ec574..d80db2c3 100644
--- a/crates/filemeta/Cargo.toml
+++ b/crates/filemeta/Cargo.toml
@@ -35,12 +35,11 @@ uuid = { workspace = true, features = ["v4", "fast-rng", "serde"] }
tokio = { workspace = true, features = ["io-util", "macros", "sync"] }
xxhash-rust = { workspace = true, features = ["xxh64"] }
bytes.workspace = true
-rustfs-utils = { workspace = true, features = ["hash","http"] }
+rustfs-utils = { workspace = true, features = ["hash", "http"] }
byteorder = { workspace = true }
tracing.workspace = true
thiserror.workspace = true
s3s.workspace = true
-lazy_static.workspace = true
regex.workspace = true
[dev-dependencies]
diff --git a/crates/filemeta/src/replication.rs b/crates/filemeta/src/replication.rs
index 1f3358c8..b7038362 100644
--- a/crates/filemeta/src/replication.rs
+++ b/crates/filemeta/src/replication.rs
@@ -19,6 +19,7 @@ use rustfs_utils::http::RESERVED_METADATA_PREFIX_LOWER;
use serde::{Deserialize, Serialize};
use std::any::Any;
use std::collections::HashMap;
+use std::sync::LazyLock;
use std::time::Duration;
use time::OffsetDateTime;
use uuid::Uuid;
@@ -773,9 +774,7 @@ impl ReplicationWorkerOperation for ReplicateObjectInfo {
}
}
-lazy_static::lazy_static! {
- static ref REPL_STATUS_REGEX: Regex = Regex::new(r"([^=].*?)=([^,].*?);").unwrap();
-}
+static REPL_STATUS_REGEX: LazyLock = LazyLock::new(|| Regex::new(r"([^=].*?)=([^,].*?);").unwrap());
impl ReplicateObjectInfo {
/// Returns replication status of a target
diff --git a/crates/iam/Cargo.toml b/crates/iam/Cargo.toml
index 104f1613..09770a4a 100644
--- a/crates/iam/Cargo.toml
+++ b/crates/iam/Cargo.toml
@@ -29,6 +29,7 @@ documentation = "https://docs.rs/rustfs-iam/latest/rustfs_iam/"
workspace = true
[dependencies]
+rustfs-credentials = { workspace = true }
tokio.workspace = true
time = { workspace = true, features = ["serde-human-readable"] }
serde = { workspace = true, features = ["derive", "rc"] }
diff --git a/crates/iam/src/manager.rs b/crates/iam/src/manager.rs
index 5fa5220b..c4b8ef71 100644
--- a/crates/iam/src/manager.rs
+++ b/crates/iam/src/manager.rs
@@ -24,15 +24,13 @@ use crate::{
},
};
use futures::future::join_all;
-use rustfs_ecstore::global::get_global_action_cred;
+use rustfs_credentials::{Credentials, EMBEDDED_POLICY_TYPE, INHERITED_POLICY_TYPE, get_global_action_cred};
use rustfs_madmin::{AccountStatus, AddOrUpdateUserReq, GroupDesc};
use rustfs_policy::{
arn::ARN,
- auth::{self, Credentials, UserIdentity, is_secret_key_valid, jwt_sign},
+ auth::{self, UserIdentity, is_secret_key_valid, jwt_sign},
format::Format,
- policy::{
- EMBEDDED_POLICY_TYPE, INHERITED_POLICY_TYPE, Policy, PolicyDoc, default::DEFAULT_POLICIES, iam_policy_claim_name_sa,
- },
+ policy::{Policy, PolicyDoc, default::DEFAULT_POLICIES, iam_policy_claim_name_sa},
};
use rustfs_utils::path::path_join_buf;
use serde::{Deserialize, Serialize};
diff --git a/crates/iam/src/store/object.rs b/crates/iam/src/store/object.rs
index 5479465b..af53aa69 100644
--- a/crates/iam/src/store/object.rs
+++ b/crates/iam/src/store/object.rs
@@ -20,6 +20,7 @@ use crate::{
manager::{extract_jwt_claims, get_default_policyes},
};
use futures::future::join_all;
+use rustfs_credentials::get_global_action_cred;
use rustfs_ecstore::StorageAPI as _;
use rustfs_ecstore::store_api::{ObjectInfoOrErr, WalkOptions};
use rustfs_ecstore::{
@@ -27,7 +28,6 @@ use rustfs_ecstore::{
RUSTFS_CONFIG_PREFIX,
com::{delete_config, read_config, read_config_with_metadata, save_config},
},
- global::get_global_action_cred,
store::ECStore,
store_api::{ObjectInfo, ObjectOptions},
};
diff --git a/crates/iam/src/sys.rs b/crates/iam/src/sys.rs
index a05cdb6b..6b6648c1 100644
--- a/crates/iam/src/sys.rs
+++ b/crates/iam/src/sys.rs
@@ -24,19 +24,18 @@ use crate::store::MappedPolicy;
use crate::store::Store;
use crate::store::UserType;
use crate::utils::extract_claims;
-use rustfs_ecstore::global::get_global_action_cred;
+use rustfs_credentials::{Credentials, EMBEDDED_POLICY_TYPE, INHERITED_POLICY_TYPE, get_global_action_cred};
use rustfs_ecstore::notification_sys::get_global_notification_sys;
use rustfs_madmin::AddOrUpdateUserReq;
use rustfs_madmin::GroupDesc;
use rustfs_policy::arn::ARN;
-use rustfs_policy::auth::Credentials;
use rustfs_policy::auth::{
ACCOUNT_ON, UserIdentity, contains_reserved_chars, create_new_credentials_with_metadata, generate_credentials,
is_access_key_valid, is_secret_key_valid,
};
use rustfs_policy::policy::Args;
use rustfs_policy::policy::opa;
-use rustfs_policy::policy::{EMBEDDED_POLICY_TYPE, INHERITED_POLICY_TYPE, Policy, PolicyDoc, iam_policy_claim_name_sa};
+use rustfs_policy::policy::{Policy, PolicyDoc, iam_policy_claim_name_sa};
use serde_json::Value;
use serde_json::json;
use std::collections::HashMap;
diff --git a/crates/policy/Cargo.toml b/crates/policy/Cargo.toml
index b66e2031..a1aaaf37 100644
--- a/crates/policy/Cargo.toml
+++ b/crates/policy/Cargo.toml
@@ -29,7 +29,8 @@ documentation = "https://docs.rs/rustfs-policy/latest/rustfs_policy/"
workspace = true
[dependencies]
-rustfs-config = { workspace = true, features = ["constants","opa"] }
+rustfs-credentials = { workspace = true }
+rustfs-config = { workspace = true, features = ["constants", "opa"] }
tokio = { workspace = true, features = ["full"] }
time = { workspace = true, features = ["serde-human-readable"] }
serde = { workspace = true, features = ["derive", "rc"] }
@@ -38,7 +39,6 @@ thiserror.workspace = true
strum = { workspace = true, features = ["derive"] }
rustfs-crypto = { workspace = true }
ipnetwork = { workspace = true, features = ["serde"] }
-rand.workspace = true
base64-simd = { workspace = true }
jsonwebtoken = { workspace = true }
regex = { workspace = true }
diff --git a/crates/policy/src/auth/credentials.rs b/crates/policy/src/auth/credentials.rs
index 9813f6c4..98133082 100644
--- a/crates/policy/src/auth/credentials.rs
+++ b/crates/policy/src/auth/credentials.rs
@@ -12,13 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::error::Error as IamError;
use crate::error::{Error, Result};
-use crate::policy::{INHERITED_POLICY_TYPE, Policy, Validator, iam_policy_claim_name_sa};
+use crate::policy::{Policy, Validator};
use crate::utils;
-use serde::{Deserialize, Serialize};
+use rustfs_credentials::Credentials;
+use serde::Serialize;
use serde_json::{Value, json};
use std::collections::HashMap;
+use std::convert::TryFrom;
use time::OffsetDateTime;
use tracing::warn;
@@ -32,178 +33,82 @@ pub const ACCOUNT_OFF: &str = "off";
const RESERVED_CHARS: &str = "=,";
-// ContainsReservedChars - returns whether the input string contains reserved characters.
+/// ContainsReservedChars - returns whether the input string contains reserved characters.
+///
+/// # Arguments
+/// * `s` - input string to check.
+///
+/// # Returns
+/// * `bool` - true if contains reserved characters, false otherwise.
+///
pub fn contains_reserved_chars(s: &str) -> bool {
s.contains(RESERVED_CHARS)
}
-// IsAccessKeyValid - validate access key for right length.
+/// IsAccessKeyValid - validate access key for right length.
+///
+/// # Arguments
+/// * `access_key` - access key to validate.
+///
+/// # Returns
+/// * `bool` - true if valid, false otherwise.
+///
pub fn is_access_key_valid(access_key: &str) -> bool {
access_key.len() >= ACCESS_KEY_MIN_LEN
}
-// IsSecretKeyValid - validate secret key for right length.
+/// IsSecretKeyValid - validate secret key for right length.
+///
+/// # Arguments
+/// * `secret_key` - secret key to validate.
+///
+/// # Returns
+/// * `bool` - true if valid, false otherwise.
+///
pub fn is_secret_key_valid(secret_key: &str) -> bool {
secret_key.len() >= SECRET_KEY_MIN_LEN
}
-// #[cfg_attr(test, derive(PartialEq, Eq, Debug))]
-// struct CredentialHeader {
-// access_key: String,
-// scop: CredentialHeaderScope,
-// }
-
-// #[cfg_attr(test, derive(PartialEq, Eq, Debug))]
-// struct CredentialHeaderScope {
-// date: Date,
-// region: String,
-// service: ServiceType,
-// request: String,
-// }
-
-// impl TryFrom<&str> for CredentialHeader {
-// type Error = Error;
-// fn try_from(value: &str) -> Result {
-// let mut elem = value.trim().splitn(2, '=');
-// let (Some(h), Some(cred_elems)) = (elem.next(), elem.next()) else {
-// return Err(IamError::ErrCredMalformed));
-// };
-
-// if h != "Credential" {
-// return Err(IamError::ErrCredMalformed));
-// }
-
-// let mut cred_elems = cred_elems.trim().rsplitn(5, '/');
-
-// let Some(request) = cred_elems.next() else {
-// return Err(IamError::ErrCredMalformed));
-// };
-
-// let Some(service) = cred_elems.next() else {
-// return Err(IamError::ErrCredMalformed));
-// };
-
-// let Some(region) = cred_elems.next() else {
-// return Err(IamError::ErrCredMalformed));
-// };
-
-// let Some(date) = cred_elems.next() else {
-// return Err(IamError::ErrCredMalformed));
-// };
-
-// let Some(ak) = cred_elems.next() else {
-// return Err(IamError::ErrCredMalformed));
-// };
-
-// if ak.len() < 3 {
-// return Err(IamError::ErrCredMalformed));
-// }
-
-// if request != "aws4_request" {
-// return Err(IamError::ErrCredMalformed));
-// }
-
-// Ok(CredentialHeader {
-// access_key: ak.to_owned(),
-// scop: CredentialHeaderScope {
-// date: {
-// const FORMATTER: LazyCell>> =
-// LazyCell::new(|| time::format_description::parse("[year][month][day]").unwrap());
-
-// Date::parse(date, &FORMATTER).map_err(|_| IamError::ErrCredMalformed))?
-// },
-// region: region.to_owned(),
-// service: service.try_into()?,
-// request: request.to_owned(),
-// },
-// })
-// }
-// }
-
-#[derive(Serialize, Deserialize, Clone, Default, Debug)]
-pub struct Credentials {
- pub access_key: String,
- pub secret_key: String,
- pub session_token: String,
- pub expiration: Option,
- pub status: String,
- pub parent_user: String,
- pub groups: Option>,
- pub claims: Option>,
- pub name: Option,
- pub description: Option,
-}
-
-impl Credentials {
- // pub fn new(elem: &str) -> Result {
- // let header: CredentialHeader = elem.try_into()?;
- // Self::check_key_value(header)
- // }
-
- // pub fn check_key_value(_header: CredentialHeader) -> Result {
- // todo!()
- // }
-
- pub fn is_expired(&self) -> bool {
- if self.expiration.is_none() {
- return false;
- }
-
- self.expiration
- .as_ref()
- .map(|e| time::OffsetDateTime::now_utc() > *e)
- .unwrap_or(false)
- }
-
- pub fn is_temp(&self) -> bool {
- !self.session_token.is_empty() && !self.is_expired()
- }
-
- pub fn is_service_account(&self) -> bool {
- const IAM_POLICY_CLAIM_NAME_SA: &str = "sa-policy";
- self.claims
- .as_ref()
- .map(|x| x.get(IAM_POLICY_CLAIM_NAME_SA).is_some_and(|_| !self.parent_user.is_empty()))
- .unwrap_or_default()
- }
-
- pub fn is_implied_policy(&self) -> bool {
- if self.is_service_account() {
- return self
- .claims
- .as_ref()
- .map(|x| x.get(&iam_policy_claim_name_sa()).is_some_and(|v| v == INHERITED_POLICY_TYPE))
- .unwrap_or_default();
- }
-
- false
- }
-
- pub fn is_valid(&self) -> bool {
- if self.status == "off" {
- return false;
- }
-
- self.access_key.len() >= 3 && self.secret_key.len() >= 8 && !self.is_expired()
- }
-
- pub fn is_owner(&self) -> bool {
- false
- }
-}
-
+/// GenerateCredentials - generate a new access key and secret key pair.
+///
+/// # Returns
+/// * `Ok((String, String))` - access key and secret key pair.
+/// * `Err(Error)` - if an error occurs during generation.
+///
pub fn generate_credentials() -> Result<(String, String)> {
- let ak = utils::gen_access_key(20)?;
- let sk = utils::gen_secret_key(40)?;
+ let ak = rustfs_credentials::gen_access_key(20)?;
+ let sk = rustfs_credentials::gen_secret_key(40)?;
Ok((ak, sk))
}
+/// GetNewCredentialsWithMetadata - generate new credentials with metadata claims and token secret.
+///
+/// # Arguments
+/// * `claims` - metadata claims to be included in the token.
+/// * `token_secret` - secret used to sign the token.
+///
+/// # Returns
+/// * `Ok(Credentials)` - newly generated credentials.
+/// * `Err(Error)` - if an error occurs during generation.
+///
pub fn get_new_credentials_with_metadata(claims: &HashMap, token_secret: &str) -> Result {
let (ak, sk) = generate_credentials()?;
create_new_credentials_with_metadata(&ak, &sk, claims, token_secret)
}
+/// CreateNewCredentialsWithMetadata - create new credentials with provided access key, secret key, metadata claims, and token secret.
+///
+/// # Arguments
+/// * `ak` - access key.
+/// * `sk` - secret key.
+/// * `claims` - metadata claims to be included in the token.
+/// * `token_secret` - secret used to sign the token.
+///
+/// # Returns
+/// * `Ok(Credentials)` - newly created credentials.
+/// * `Err(Error)` - if an error occurs during creation.
+///
pub fn create_new_credentials_with_metadata(
ak: &str,
sk: &str,
@@ -211,11 +116,11 @@ pub fn create_new_credentials_with_metadata(
token_secret: &str,
) -> Result {
if ak.len() < ACCESS_KEY_MIN_LEN || ak.len() > ACCESS_KEY_MAX_LEN {
- return Err(IamError::InvalidAccessKeyLength);
+ return Err(Error::InvalidAccessKeyLength);
}
if sk.len() < SECRET_KEY_MIN_LEN || sk.len() > SECRET_KEY_MAX_LEN {
- return Err(IamError::InvalidAccessKeyLength);
+ return Err(Error::InvalidAccessKeyLength);
}
if token_secret.is_empty() {
@@ -253,6 +158,16 @@ pub fn create_new_credentials_with_metadata(
})
}
+/// JWTSign - sign the provided claims with the given token secret to generate a JWT token.
+///
+/// # Arguments
+/// * `claims` - claims to be included in the token.
+/// * `token_secret` - secret used to sign the token.
+///
+/// # Returns
+/// * `Ok(String)` - generated JWT token.
+/// * `Err(Error)` - if an error occurs during signing.
+///
pub fn jwt_sign(claims: &T, token_secret: &str) -> Result {
let token = utils::generate_jwt(claims, token_secret)?;
Ok(token)
@@ -267,16 +182,29 @@ pub struct CredentialsBuilder {
description: Option,
expiration: Option,
allow_site_replicator_account: bool,
- claims: Option,
+ claims: Option,
parent_user: String,
groups: Option>,
}
impl CredentialsBuilder {
+ /// Create a new CredentialsBuilder instance.
+ ///
+ /// # Returns
+ /// * `CredentialsBuilder` - a new instance of CredentialsBuilder.
+ ///
pub fn new() -> Self {
Self::default()
}
+ /// Set the session policy for the credentials.
+ ///
+ /// # Arguments
+ /// * `policy` - an optional Policy to set as the session policy.
+ ///
+ /// # Returns
+ /// * `Self` - the updated CredentialsBuilder instance.
+ ///
pub fn session_policy(mut self, policy: Option) -> Self {
self.session_policy = policy;
self
@@ -312,7 +240,7 @@ impl CredentialsBuilder {
self
}
- pub fn claims(mut self, claims: serde_json::Value) -> Self {
+ pub fn claims(mut self, claims: Value) -> Self {
self.claims = Some(claims);
self
}
@@ -336,7 +264,7 @@ impl TryFrom for Credentials {
type Error = Error;
fn try_from(mut value: CredentialsBuilder) -> std::result::Result {
if value.parent_user.is_empty() {
- return Err(IamError::InvalidArgument);
+ return Err(Error::InvalidArgument);
}
if (value.access_key.is_empty() && !value.secret_key.is_empty())
@@ -346,27 +274,27 @@ impl TryFrom for Credentials {
}
if value.parent_user == value.access_key.as_str() {
- return Err(IamError::InvalidArgument);
+ return Err(Error::InvalidArgument);
}
if value.access_key == "site-replicator-0" && !value.allow_site_replicator_account {
- return Err(IamError::InvalidArgument);
+ return Err(Error::InvalidArgument);
}
- let mut claim = serde_json::json!({
+ let mut claim = json!({
"parent": value.parent_user
});
if let Some(p) = value.session_policy {
p.is_valid()?;
- let policy_buf = serde_json::to_vec(&p).map_err(|_| IamError::InvalidArgument)?;
+ let policy_buf = serde_json::to_vec(&p).map_err(|_| Error::InvalidArgument)?;
if policy_buf.len() > 4096 {
return Err(Error::other("session policy is too large"));
}
- claim["sessionPolicy"] = serde_json::json!(base64_simd::STANDARD.encode_to_string(&policy_buf));
- claim["sa-policy"] = serde_json::json!("embedded-policy");
+ claim["sessionPolicy"] = json!(base64_simd::STANDARD.encode_to_string(&policy_buf));
+ claim[rustfs_credentials::IAM_POLICY_CLAIM_NAME_SA] = json!(rustfs_credentials::EMBEDDED_POLICY_TYPE);
} else {
- claim["sa-policy"] = serde_json::json!("inherited-policy");
+ claim[rustfs_credentials::IAM_POLICY_CLAIM_NAME_SA] = json!(rustfs_credentials::INHERITED_POLICY_TYPE);
}
if let Some(Value::Object(obj)) = value.claims {
@@ -379,11 +307,11 @@ impl TryFrom for Credentials {
}
if value.access_key.is_empty() {
- value.access_key = utils::gen_access_key(20)?;
+ value.access_key = rustfs_credentials::gen_access_key(20)?;
}
if value.secret_key.is_empty() {
- value.access_key = utils::gen_secret_key(40)?;
+ value.secret_key = rustfs_credentials::gen_secret_key(40)?;
}
claim["accessKey"] = json!(&value.access_key);
diff --git a/crates/policy/src/auth.rs b/crates/policy/src/auth/mod.rs
similarity index 87%
rename from crates/policy/src/auth.rs
rename to crates/policy/src/auth/mod.rs
index 22e3db84..cfd5b8e1 100644
--- a/crates/policy/src/auth.rs
+++ b/crates/policy/src/auth/mod.rs
@@ -14,8 +14,9 @@
mod credentials;
-pub use credentials::Credentials;
pub use credentials::*;
+
+use rustfs_credentials::Credentials;
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
@@ -27,6 +28,13 @@ pub struct UserIdentity {
}
impl UserIdentity {
+ /// Create a new UserIdentity
+ ///
+ /// # Arguments
+ /// * `credentials` - Credentials object
+ ///
+ /// # Returns
+ /// * UserIdentity
pub fn new(credentials: Credentials) -> Self {
UserIdentity {
version: 1,
diff --git a/crates/policy/src/policy.rs b/crates/policy/src/policy.rs
index 8733a859..0a034a86 100644
--- a/crates/policy/src/policy.rs
+++ b/crates/policy/src/policy.rs
@@ -28,7 +28,6 @@ pub mod variables;
pub use action::ActionSet;
pub use doc::PolicyDoc;
-
pub use effect::Effect;
pub use function::Functions;
pub use id::ID;
@@ -37,9 +36,6 @@ pub use principal::Principal;
pub use resource::ResourceSet;
pub use statement::Statement;
-pub const EMBEDDED_POLICY_TYPE: &str = "embedded-policy";
-pub const INHERITED_POLICY_TYPE: &str = "inherited-policy";
-
#[derive(thiserror::Error, Debug)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub enum Error {
diff --git a/crates/policy/src/policy/policy.rs b/crates/policy/src/policy/policy.rs
index 45d368d8..6ddf6294 100644
--- a/crates/policy/src/policy/policy.rs
+++ b/crates/policy/src/policy/policy.rs
@@ -258,7 +258,7 @@ pub fn get_policies_from_claims(claims: &HashMap, policy_claim_na
}
pub fn iam_policy_claim_name_sa() -> String {
- "sa-policy".to_string()
+ rustfs_credentials::IAM_POLICY_CLAIM_NAME_SA.to_string()
}
pub mod default {
diff --git a/crates/policy/src/utils.rs b/crates/policy/src/utils.rs
index 6a1e7fff..3698dc4d 100644
--- a/crates/policy/src/utils.rs
+++ b/crates/policy/src/utils.rs
@@ -13,46 +13,7 @@
// limitations under the License.
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header};
-use rand::{Rng, RngCore};
use serde::{Serialize, de::DeserializeOwned};
-use std::io::{Error, Result};
-
-pub fn gen_access_key(length: usize) -> Result {
- const ALPHA_NUMERIC_TABLE: [char; 36] = [
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
- 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
- ];
-
- if length < 3 {
- return Err(Error::other("access key length is too short"));
- }
-
- let mut result = String::with_capacity(length);
- let mut rng = rand::rng();
-
- for _ in 0..length {
- result.push(ALPHA_NUMERIC_TABLE[rng.random_range(0..ALPHA_NUMERIC_TABLE.len())]);
- }
-
- Ok(result)
-}
-
-pub fn gen_secret_key(length: usize) -> Result {
- use base64_simd::URL_SAFE_NO_PAD;
-
- if length < 8 {
- return Err(Error::other("secret key length is too short"));
- }
- let mut rng = rand::rng();
-
- let mut key = vec![0u8; URL_SAFE_NO_PAD.estimated_decoded_length(length)];
- rng.fill_bytes(&mut key);
-
- let encoded = URL_SAFE_NO_PAD.encode_to_string(&key);
- let key_str = encoded.replace("/", "+");
-
- Ok(key_str)
-}
pub fn generate_jwt(claims: &T, secret: &str) -> std::result::Result {
let header = Header::new(Algorithm::HS512);
@@ -72,26 +33,9 @@ pub fn extract_claims(
#[cfg(test)]
mod tests {
- use super::{gen_access_key, gen_secret_key, generate_jwt};
+ use super::generate_jwt;
use serde::{Deserialize, Serialize};
- #[test]
- fn test_gen_access_key() {
- let a = gen_access_key(10).unwrap();
- let b = gen_access_key(10).unwrap();
-
- assert_eq!(a.len(), 10);
- assert_eq!(b.len(), 10);
- assert_ne!(a, b);
- }
-
- #[test]
- fn test_gen_secret_key() {
- let a = gen_secret_key(10).unwrap();
- let b = gen_secret_key(10).unwrap();
- assert_ne!(a, b);
- }
-
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Claims {
sub: String,
diff --git a/crates/protos/Cargo.toml b/crates/protos/Cargo.toml
index 86031828..29ada67a 100644
--- a/crates/protos/Cargo.toml
+++ b/crates/protos/Cargo.toml
@@ -34,6 +34,7 @@ path = "src/main.rs"
[dependencies]
rustfs-common.workspace = true
+rustfs-credentials = { workspace = true }
flatbuffers = { workspace = true }
prost = { workspace = true }
tonic = { workspace = true, features = ["transport"] }
diff --git a/crates/protos/src/lib.rs b/crates/protos/src/lib.rs
index e54f1edf..607eb1b2 100644
--- a/crates/protos/src/lib.rs
+++ b/crates/protos/src/lib.rs
@@ -24,7 +24,7 @@ use tonic::{
service::interceptor::InterceptedService,
transport::{Certificate, Channel, ClientTlsConfig, Endpoint},
};
-use tracing::{debug, warn};
+use tracing::{debug, error, warn};
// Type alias for the complex client type
pub type NodeServiceClientType = NodeServiceClient<
@@ -150,7 +150,18 @@ pub async fn node_service_time_out_client(
>,
Box,
> {
- let token: MetadataValue<_> = "rustfs rpc".parse()?;
+ debug!("Obtaining gRPC client for NodeService at: {}", addr);
+ let token_str = rustfs_credentials::get_grpc_token();
+ let token: MetadataValue<_> = token_str.parse().map_err(|e| {
+ error!(
+ "Failed to parse gRPC auth token into MetadataValue: {:?}; env={} token_len={} token_prefix={}",
+ e,
+ rustfs_credentials::ENV_GRPC_AUTH_TOKEN,
+ token_str.len(),
+ token_str.chars().take(2).collect::(),
+ );
+ e
+ })?;
// Try to get cached channel
let cached_channel = { GLOBAL_CONN_MAP.read().await.get(addr).cloned() };
diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml
index 9b05e84e..7295c43b 100644
--- a/crates/utils/Cargo.toml
+++ b/crates/utils/Cargo.toml
@@ -86,11 +86,12 @@ io = ["dep:tokio"]
path = []
notify = ["dep:hyper", "dep:s3s", "dep:hashbrown", "dep:thiserror", "dep:serde", "dep:libc", "dep:url", "dep:regex"] # file system notification features
compress = ["dep:flate2", "dep:brotli", "dep:snap", "dep:lz4", "dep:zstd"]
-string = ["dep:regex", "dep:rand"]
+string = ["dep:regex"]
crypto = ["dep:base64-simd", "dep:hex-simd", "dep:hmac", "dep:hyper", "dep:sha1"]
-hash = ["dep:highway", "dep:md-5", "dep:sha2", "dep:blake3", "dep:serde", "dep:siphasher", "dep:hex-simd", "dep:base64-simd", "dep:crc-fast"]
+hash = ["dep:highway", "dep:md-5", "dep:sha2", "dep:blake3", "dep:serde", "dep:siphasher", "dep:hex-simd", "dep:crc-fast"]
os = ["dep:nix", "dep:tempfile", "winapi"] # operating system utilities
integration = [] # integration test features
sys = ["dep:sysinfo"] # system information features
http = ["dep:convert_case", "dep:http", "dep:regex"]
-full = ["ip", "tls", "net", "io", "hash", "os", "integration", "path", "crypto", "string", "compress", "sys", "notify", "http"] # all features
+obj = ["http"] # object storage features
+full = ["ip", "tls", "net", "io", "hash", "os", "integration", "path", "crypto", "string", "compress", "sys", "notify", "http", "obj"] # all features
diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs
index cb23e249..4c2396bc 100644
--- a/crates/utils/src/lib.rs
+++ b/crates/utils/src/lib.rs
@@ -82,7 +82,8 @@ pub use sys::user_agent::*;
#[cfg(feature = "notify")]
pub use notify::*;
+#[cfg(feature = "obj")]
+pub mod obj;
+
mod envs;
pub use envs::*;
-
-pub mod obj;
diff --git a/crates/utils/src/string.rs b/crates/utils/src/string.rs
index 8d3879d1..24675db6 100644
--- a/crates/utils/src/string.rs
+++ b/crates/utils/src/string.rs
@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use rand::{Rng, RngCore};
use regex::Regex;
use std::io::{Error, Result};
use std::sync::LazyLock;
@@ -488,81 +487,6 @@ pub fn parse_ellipses_range(pattern: &str) -> Result> {
Ok(ret)
}
-/// Generates a random access key of the specified length.
-///
-/// # Arguments
-/// * `length` - The length of the access key to generate
-///
-/// # Returns
-/// * `Result` - A result containing the generated access key or an error if the length is too short
-///
-/// # Errors
-/// This function will return an error if the specified length is less than 3.
-///
-/// Examples
-/// ```no_run
-/// use rustfs_utils::string::gen_access_key;
-///
-/// let access_key = gen_access_key(16).unwrap();
-/// println!("Generated access key: {}", access_key);
-/// ```
-///
-pub fn gen_access_key(length: usize) -> Result {
- const ALPHA_NUMERIC_TABLE: [char; 36] = [
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
- 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
- ];
-
- if length < 3 {
- return Err(Error::other("access key length is too short"));
- }
-
- let mut result = String::with_capacity(length);
- let mut rng = rand::rng();
-
- for _ in 0..length {
- result.push(ALPHA_NUMERIC_TABLE[rng.random_range(0..ALPHA_NUMERIC_TABLE.len())]);
- }
-
- Ok(result)
-}
-
-/// Generates a random secret key of the specified length.
-///
-/// # Arguments
-/// * `length` - The length of the secret key to generate
-///
-/// # Returns
-/// * `Result` - A result containing the generated secret key or an error if the length is too short
-///
-/// # Errors
-/// This function will return an error if the specified length is less than 8.
-///
-/// # Examples
-/// ```no_run
-/// use rustfs_utils::string::gen_secret_key;
-///
-/// let secret_key = gen_secret_key(32).unwrap();
-/// println!("Generated secret key: {}", secret_key);
-/// ```
-///
-pub fn gen_secret_key(length: usize) -> Result {
- use base64_simd::URL_SAFE_NO_PAD;
-
- if length < 8 {
- return Err(Error::other("secret key length is too short"));
- }
- let mut rng = rand::rng();
-
- let mut key = vec![0u8; URL_SAFE_NO_PAD.estimated_decoded_length(length)];
- rng.fill_bytes(&mut key);
-
- let encoded = URL_SAFE_NO_PAD.encode_to_string(&key);
- let key_str = encoded.replace("/", "+");
-
- Ok(key_str)
-}
-
/// Tests whether the string s begins with prefix ignoring case
///
/// # Arguments
diff --git a/rustfs/Cargo.toml b/rustfs/Cargo.toml
index bac807ec..cd5ba113 100644
--- a/rustfs/Cargo.toml
+++ b/rustfs/Cargo.toml
@@ -44,6 +44,7 @@ rustfs-appauth = { workspace = true }
rustfs-audit = { workspace = true }
rustfs-common = { workspace = true }
rustfs-config = { workspace = true, features = ["constants", "notify"] }
+rustfs-credentials = { workspace = true }
rustfs-ecstore = { workspace = true }
rustfs-filemeta.workspace = true
rustfs-iam = { workspace = true }
diff --git a/rustfs/src/admin/auth.rs b/rustfs/src/admin/auth.rs
index 2f101099..63d4ab65 100644
--- a/rustfs/src/admin/auth.rs
+++ b/rustfs/src/admin/auth.rs
@@ -14,9 +14,9 @@
use crate::auth::get_condition_values;
use http::HeaderMap;
+use rustfs_credentials::Credentials;
use rustfs_iam::store::object::ObjectStore;
use rustfs_iam::sys::IamSys;
-use rustfs_policy::auth;
use rustfs_policy::policy::Args;
use rustfs_policy::policy::action::Action;
use s3s::S3Result;
@@ -26,7 +26,7 @@ use std::sync::Arc;
pub async fn validate_admin_request(
headers: &HeaderMap,
- cred: &auth::Credentials,
+ cred: &Credentials,
is_owner: bool,
deny_only: bool,
actions: Vec,
@@ -49,7 +49,7 @@ pub async fn validate_admin_request(
async fn check_admin_request_auth(
iam_store: Arc>,
headers: &HeaderMap,
- cred: &auth::Credentials,
+ cred: &Credentials,
is_owner: bool,
deny_only: bool,
action: Action,
diff --git a/rustfs/src/admin/console.rs b/rustfs/src/admin/console.rs
index b541edf1..5bd0bbd0 100644
--- a/rustfs/src/admin/console.rs
+++ b/rustfs/src/admin/console.rs
@@ -23,7 +23,6 @@ use axum::{
response::{IntoResponse, Response},
routing::get,
};
-use axum_extra::extract::Host;
use axum_server::tls_rustls::RustlsConfig;
use http::{HeaderMap, HeaderName, HeaderValue, Method, StatusCode, Uri};
use mime_guess::from_path;
@@ -264,21 +263,27 @@ async fn version_handler() -> impl IntoResponse {
///
/// # Arguments:
/// - `uri`: The request URI.
-/// - `Host(host)`: The host extracted from the request.
/// - `headers`: The request headers.
///
/// # Returns:
/// - 200 OK with JSON body containing the console configuration if initialized.
/// - 500 Internal Server Error if configuration is not initialized.
-#[instrument(fields(host))]
-async fn config_handler(uri: Uri, Host(host): Host, headers: HeaderMap) -> impl IntoResponse {
+#[instrument(fields(uri))]
+async fn config_handler(uri: Uri, headers: HeaderMap) -> impl IntoResponse {
// Get the scheme from the headers or use the URI scheme
let scheme = headers
.get(HeaderName::from_static("x-forwarded-proto"))
.and_then(|value| value.to_str().ok())
.unwrap_or_else(|| uri.scheme().map(|s| s.as_str()).unwrap_or("http"));
- let raw_host = uri.host().unwrap_or(host.as_str());
+ // Prefer URI host, fallback to `Host` header
+ let header_host = headers
+ .get(http::header::HOST)
+ .and_then(|v| v.to_str().ok())
+ .unwrap_or_default();
+
+ let raw_host = uri.host().unwrap_or(header_host);
+
let host_for_url = if let Ok(socket_addr) = raw_host.parse::() {
// Successfully parsed, it's in IP:Port format.
// For IPv6, we need to enclose it in brackets to form a valid URL.
diff --git a/rustfs/src/admin/handlers.rs b/rustfs/src/admin/handlers.rs
index 821e948f..f1a8b212 100644
--- a/rustfs/src/admin/handlers.rs
+++ b/rustfs/src/admin/handlers.rs
@@ -25,6 +25,7 @@ use hyper::StatusCode;
use matchit::Params;
use rustfs_common::heal_channel::HealOpts;
use rustfs_config::{MAX_ADMIN_REQUEST_BODY_SIZE, MAX_HEAL_REQUEST_SIZE};
+use rustfs_credentials::get_global_action_cred;
use rustfs_ecstore::admin_server_info::get_server_info;
use rustfs_ecstore::bucket::bucket_target_sys::BucketTargetSys;
use rustfs_ecstore::bucket::metadata::BUCKET_TARGETS_FILE;
@@ -35,7 +36,6 @@ use rustfs_ecstore::data_usage::{
aggregate_local_snapshots, compute_bucket_usage, load_data_usage_from_backend, store_data_usage_in_backend,
};
use rustfs_ecstore::error::StorageError;
-use rustfs_ecstore::global::get_global_action_cred;
use rustfs_ecstore::global::global_rustfs_port;
use rustfs_ecstore::metrics_realtime::{CollectMetricsOpts, MetricType, collect_local_metrics};
use rustfs_ecstore::new_object_layer_fn;
diff --git a/rustfs/src/admin/handlers/group.rs b/rustfs/src/admin/handlers/group.rs
index c7866a81..07ae9baf 100644
--- a/rustfs/src/admin/handlers/group.rs
+++ b/rustfs/src/admin/handlers/group.rs
@@ -19,7 +19,7 @@ use crate::{
use http::{HeaderMap, StatusCode};
use matchit::Params;
use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE;
-use rustfs_ecstore::global::get_global_action_cred;
+use rustfs_credentials::get_global_action_cred;
use rustfs_iam::error::{is_err_no_such_group, is_err_no_such_user};
use rustfs_madmin::GroupAddRemove;
use rustfs_policy::policy::action::{Action, AdminAction};
diff --git a/rustfs/src/admin/handlers/policies.rs b/rustfs/src/admin/handlers/policies.rs
index 76915be0..7587c6ae 100644
--- a/rustfs/src/admin/handlers/policies.rs
+++ b/rustfs/src/admin/handlers/policies.rs
@@ -19,7 +19,7 @@ use crate::{
use http::{HeaderMap, StatusCode};
use matchit::Params;
use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE;
-use rustfs_ecstore::global::get_global_action_cred;
+use rustfs_credentials::get_global_action_cred;
use rustfs_iam::error::is_err_no_such_user;
use rustfs_iam::store::MappedPolicy;
use rustfs_policy::policy::{
diff --git a/rustfs/src/admin/handlers/service_account.rs b/rustfs/src/admin/handlers/service_account.rs
index 1340d13f..76e59a90 100644
--- a/rustfs/src/admin/handlers/service_account.rs
+++ b/rustfs/src/admin/handlers/service_account.rs
@@ -19,7 +19,7 @@ use http::HeaderMap;
use hyper::StatusCode;
use matchit::Params;
use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE;
-use rustfs_ecstore::global::get_global_action_cred;
+use rustfs_credentials::get_global_action_cred;
use rustfs_iam::error::is_err_no_such_service_account;
use rustfs_iam::sys::{NewServiceAccountOpts, UpdateServiceAccountOpts};
use rustfs_madmin::{
diff --git a/rustfs/src/admin/handlers/user.rs b/rustfs/src/admin/handlers/user.rs
index 0ab6a128..92c39d18 100644
--- a/rustfs/src/admin/handlers/user.rs
+++ b/rustfs/src/admin/handlers/user.rs
@@ -19,7 +19,7 @@ use crate::{
use http::{HeaderMap, StatusCode};
use matchit::Params;
use rustfs_config::{MAX_ADMIN_REQUEST_BODY_SIZE, MAX_IAM_IMPORT_SIZE};
-use rustfs_ecstore::global::get_global_action_cred;
+use rustfs_credentials::get_global_action_cred;
use rustfs_iam::{
store::{GroupInfo, MappedPolicy, UserType},
sys::NewServiceAccountOpts,
diff --git a/rustfs/src/auth.rs b/rustfs/src/auth.rs
index 79cb2922..97a329f0 100644
--- a/rustfs/src/auth.rs
+++ b/rustfs/src/auth.rs
@@ -14,11 +14,10 @@
use http::HeaderMap;
use http::Uri;
-use rustfs_ecstore::global::get_global_action_cred;
+use rustfs_credentials::{Credentials, get_global_action_cred};
use rustfs_iam::error::Error as IamError;
use rustfs_iam::sys::SESSION_POLICY_NAME;
use rustfs_iam::sys::get_claims_from_token_with_secret;
-use rustfs_policy::auth;
use rustfs_utils::http::ip::get_source_ip_raw;
use s3s::S3Error;
use s3s::S3ErrorCode;
@@ -129,7 +128,7 @@ impl S3Auth for IAMAuth {
}
// check_key_valid checks the key is valid or not. return the user's credentials and if the user is the owner.
-pub async fn check_key_valid(session_token: &str, access_key: &str) -> S3Result<(auth::Credentials, bool)> {
+pub async fn check_key_valid(session_token: &str, access_key: &str) -> S3Result<(Credentials, bool)> {
let Some(mut cred) = get_global_action_cred() else {
return Err(S3Error::with_message(
S3ErrorCode::InternalError,
@@ -187,7 +186,7 @@ pub async fn check_key_valid(session_token: &str, access_key: &str) -> S3Result<
Ok((cred, owner))
}
-pub fn check_claims_from_token(token: &str, cred: &auth::Credentials) -> S3Result> {
+pub fn check_claims_from_token(token: &str, cred: &Credentials) -> S3Result> {
if !token.is_empty() && cred.access_key.is_empty() {
return Err(s3_error!(InvalidRequest, "no access key"));
}
@@ -235,9 +234,20 @@ pub fn get_session_token<'a>(uri: &'a Uri, hds: &'a HeaderMap) -> Option<&'a str
.or_else(|| get_query_param(uri.query().unwrap_or_default(), "x-amz-security-token"))
}
+/// Get condition values for policy evaluation
+///
+/// # Arguments
+/// * `header` - HTTP headers of the request
+/// * `cred` - User credentials
+/// * `version_id` - Optional version ID of the object
+/// * `region` - Optional region/location constraint
+///
+/// # Returns
+/// * `HashMap>` - Condition values for policy evaluation
+///
pub fn get_condition_values(
header: &HeaderMap,
- cred: &auth::Credentials,
+ cred: &Credentials,
version_id: Option<&str>,
region: Option<&str>,
) -> HashMap> {
@@ -403,7 +413,14 @@ pub fn get_condition_values(
args
}
-// Get request authentication type
+/// Get request authentication type
+///
+/// # Arguments
+/// * `header` - HTTP headers of the request
+///
+/// # Returns
+/// * `AuthType` - The determined authentication type
+///
pub fn get_request_auth_type(header: &HeaderMap) -> AuthType {
if is_request_signature_v2(header) {
AuthType::SignedV2
@@ -432,7 +449,14 @@ pub fn get_request_auth_type(header: &HeaderMap) -> AuthType {
}
}
-// Helper function to determine auth type and signature version
+/// Helper function to determine auth type and signature version
+///
+/// # Arguments
+/// * `header` - HTTP headers of the request
+///
+/// # Returns
+/// * `(String, String)` - Tuple of auth type and signature version
+///
fn determine_auth_type_and_version(header: &HeaderMap) -> (String, String) {
match get_request_auth_type(header) {
AuthType::JWT => ("JWT".to_string(), String::new()),
@@ -450,7 +474,13 @@ fn determine_auth_type_and_version(header: &HeaderMap) -> (String, String) {
}
}
-// Verify if request has JWT
+/// Verify if request has JWT
+///
+/// # Arguments
+/// * `header` - HTTP headers of the request
+///
+/// # Returns
+/// * `bool` - True if request has JWT, false otherwise
fn is_request_jwt(header: &HeaderMap) -> bool {
if let Some(auth) = header.get("authorization") {
if let Ok(auth_str) = auth.to_str() {
@@ -460,7 +490,13 @@ fn is_request_jwt(header: &HeaderMap) -> bool {
false
}
-// Verify if request has AWS Signature Version '4'
+/// Verify if request has AWS Signature Version '4'
+///
+/// # Arguments
+/// * `header` - HTTP headers of the request
+///
+/// # Returns
+/// * `bool` - True if request has AWS Signature Version '4', false otherwise
fn is_request_signature_v4(header: &HeaderMap) -> bool {
if let Some(auth) = header.get("authorization") {
if let Ok(auth_str) = auth.to_str() {
@@ -470,7 +506,13 @@ fn is_request_signature_v4(header: &HeaderMap) -> bool {
false
}
-// Verify if request has AWS Signature Version '2'
+/// Verify if request has AWS Signature Version '2'
+///
+/// # Arguments
+/// * `header` - HTTP headers of the request
+///
+/// # Returns
+/// * `bool` - True if request has AWS Signature Version '2', false otherwise
fn is_request_signature_v2(header: &HeaderMap) -> bool {
if let Some(auth) = header.get("authorization") {
if let Ok(auth_str) = auth.to_str() {
@@ -480,7 +522,13 @@ fn is_request_signature_v2(header: &HeaderMap) -> bool {
false
}
-// Verify if request has AWS PreSign Version '4'
+/// Verify if request has AWS PreSign Version '4'
+///
+/// # Arguments
+/// * `header` - HTTP headers of the request
+///
+/// # Returns
+/// * `bool` - True if request has AWS PreSign Version '4', false otherwise
pub(crate) fn is_request_presigned_signature_v4(header: &HeaderMap) -> bool {
if let Some(credential) = header.get(AMZ_CREDENTIAL) {
return !credential.to_str().unwrap_or("").is_empty();
@@ -488,7 +536,13 @@ pub(crate) fn is_request_presigned_signature_v4(header: &HeaderMap) -> bool {
false
}
-// Verify request has AWS PreSign Version '2'
+/// Verify request has AWS PreSign Version '2'
+///
+/// # Arguments
+/// * `header` - HTTP headers of the request
+///
+/// # Returns
+/// * `bool` - True if request has AWS PreSign Version '2', false otherwise
fn is_request_presigned_signature_v2(header: &HeaderMap) -> bool {
if let Some(access_key) = header.get(AMZ_ACCESS_KEY_ID) {
return !access_key.to_str().unwrap_or("").is_empty();
@@ -496,7 +550,13 @@ fn is_request_presigned_signature_v2(header: &HeaderMap) -> bool {
false
}
-// Verify if request has AWS Post policy Signature Version '4'
+/// Verify if request has AWS Post policy Signature Version '4'
+///
+/// # Arguments
+/// * `header` - HTTP headers of the request
+///
+/// # Returns
+/// * `bool` - True if request has AWS Post policy Signature Version '4', false otherwise
fn is_request_post_policy_signature_v4(header: &HeaderMap) -> bool {
if let Some(content_type) = header.get("content-type") {
if let Ok(ct) = content_type.to_str() {
@@ -506,7 +566,7 @@ fn is_request_post_policy_signature_v4(header: &HeaderMap) -> bool {
false
}
-// Verify if the request has AWS Streaming Signature Version '4'
+/// Verify if the request has AWS Streaming Signature Version '4'
fn is_request_sign_streaming_v4(header: &HeaderMap) -> bool {
if let Some(content_sha256) = header.get("x-amz-content-sha256") {
if let Ok(sha256_str) = content_sha256.to_str() {
@@ -567,7 +627,7 @@ pub fn get_query_param<'a>(query: &'a str, param_name: &str) -> Option<&'a str>
mod tests {
use super::*;
use http::{HeaderMap, HeaderValue, Uri};
- use rustfs_policy::auth::Credentials;
+ use rustfs_credentials::Credentials;
use s3s::auth::SecretKey;
use serde_json::json;
use std::collections::HashMap;
@@ -605,7 +665,7 @@ mod tests {
fn create_service_account_credentials() -> Credentials {
let mut claims = HashMap::new();
- claims.insert("sa-policy".to_string(), json!("test-policy"));
+ claims.insert(rustfs_credentials::IAM_POLICY_CLAIM_NAME_SA.to_string(), json!("test-policy"));
Credentials {
access_key: "service-access-key".to_string(),
diff --git a/rustfs/src/config/mod.rs b/rustfs/src/config/mod.rs
index 14923522..00d068ba 100644
--- a/rustfs/src/config/mod.rs
+++ b/rustfs/src/config/mod.rs
@@ -73,11 +73,11 @@ pub struct Opt {
pub server_domains: Vec,
/// Access key used for authentication.
- #[arg(long, default_value_t = rustfs_config::DEFAULT_ACCESS_KEY.to_string(), env = "RUSTFS_ACCESS_KEY")]
+ #[arg(long, default_value_t = rustfs_credentials::DEFAULT_ACCESS_KEY.to_string(), env = "RUSTFS_ACCESS_KEY")]
pub access_key: String,
/// Secret key used for authentication.
- #[arg(long, default_value_t = rustfs_config::DEFAULT_SECRET_KEY.to_string(), env = "RUSTFS_SECRET_KEY")]
+ #[arg(long, default_value_t = rustfs_credentials::DEFAULT_SECRET_KEY.to_string(), env = "RUSTFS_SECRET_KEY")]
pub secret_key: String,
/// Enable console server
diff --git a/rustfs/src/main.rs b/rustfs/src/main.rs
index 38a85518..438181e1 100644
--- a/rustfs/src/main.rs
+++ b/rustfs/src/main.rs
@@ -39,6 +39,7 @@ use rustfs_ahm::{
scanner::data_scanner::ScannerConfig, shutdown_ahm_services,
};
use rustfs_common::{GlobalReadiness, SystemStage, set_global_addr};
+use rustfs_credentials::init_global_action_credentials;
use rustfs_ecstore::{
StorageAPI,
bucket::metadata_sys::init_bucket_metadata_sys,
@@ -147,7 +148,16 @@ async fn run(opt: config::Opt) -> Result<()> {
);
// Set up AK and SK
- rustfs_ecstore::global::init_global_action_credentials(Some(opt.access_key.clone()), Some(opt.secret_key.clone()));
+ match init_global_action_credentials(Some(opt.access_key.clone()), Some(opt.secret_key.clone())) {
+ Ok(_) => {
+ info!(target: "rustfs::main::run", "Global action credentials initialized successfully.");
+ }
+ Err(e) => {
+ let msg = format!("init_global_action_credentials failed: {e:?}");
+ error!("{msg}");
+ return Err(Error::other(msg));
+ }
+ };
set_global_rustfs_port(server_port);
diff --git a/rustfs/src/server/http.rs b/rustfs/src/server/http.rs
index 53a03bca..10d5011a 100644
--- a/rustfs/src/server/http.rs
+++ b/rustfs/src/server/http.rs
@@ -30,7 +30,7 @@ use hyper_util::{
};
use metrics::{counter, histogram};
use rustfs_common::GlobalReadiness;
-use rustfs_config::{DEFAULT_ACCESS_KEY, DEFAULT_SECRET_KEY, MI_B, RUSTFS_TLS_CERT, RUSTFS_TLS_KEY};
+use rustfs_config::{MI_B, RUSTFS_TLS_CERT, RUSTFS_TLS_KEY};
use rustfs_protos::proto_gen::node_service::node_service_server::NodeServiceServer;
use rustfs_utils::net::parse_and_resolve_address;
use rustls::ServerConfig;
@@ -212,10 +212,13 @@ pub async fn start_http_server(
info!(target: "rustfs::main::startup","RustFS API: {api_endpoints} {localhost_endpoint}");
println!("RustFS Http API: {api_endpoints} {localhost_endpoint}");
println!("RustFS Start Time: {now_time}");
- if DEFAULT_ACCESS_KEY.eq(&opt.access_key) && DEFAULT_SECRET_KEY.eq(&opt.secret_key) {
+ if rustfs_credentials::DEFAULT_ACCESS_KEY.eq(&opt.access_key)
+ && rustfs_credentials::DEFAULT_SECRET_KEY.eq(&opt.secret_key)
+ {
warn!(
"Detected default credentials '{}:{}', we recommend that you change these values with 'RUSTFS_ACCESS_KEY' and 'RUSTFS_SECRET_KEY' environment variables",
- DEFAULT_ACCESS_KEY, DEFAULT_SECRET_KEY
+ rustfs_credentials::DEFAULT_ACCESS_KEY,
+ rustfs_credentials::DEFAULT_SECRET_KEY
);
}
info!(target: "rustfs::main::startup","For more information, visit https://rustfs.com/docs/");
@@ -685,7 +688,12 @@ fn handle_connection_error(err: &(dyn std::error::Error + 'static)) {
#[allow(clippy::result_large_err)]
fn check_auth(req: Request<()>) -> std::result::Result, Status> {
- let token: MetadataValue<_> = "rustfs rpc".parse().unwrap();
+ let token_str = rustfs_credentials::get_grpc_token();
+
+ let token: MetadataValue<_> = token_str.parse().map_err(|e| {
+ error!("Failed to parse RUSTFS_GRPC_AUTH_TOKEN into gRPC metadata value: {}", e);
+ Status::internal("Invalid auth token configuration")
+ })?;
match req.metadata().get("authorization") {
Some(t) if token == t => Ok(req),
diff --git a/rustfs/src/storage/access.rs b/rustfs/src/storage/access.rs
index 7d664585..736f386e 100644
--- a/rustfs/src/storage/access.rs
+++ b/rustfs/src/storage/access.rs
@@ -17,7 +17,6 @@ use crate::auth::{check_key_valid, get_condition_values, get_session_token};
use crate::license::license_check;
use rustfs_ecstore::bucket::policy_sys::PolicySys;
use rustfs_iam::error::Error as IamError;
-use rustfs_policy::auth;
use rustfs_policy::policy::action::{Action, S3Action};
use rustfs_policy::policy::{Args, BucketPolicyArgs};
use s3s::access::{S3Access, S3AccessContext};
@@ -27,7 +26,7 @@ use std::collections::HashMap;
#[allow(dead_code)]
#[derive(Default, Clone)]
pub(crate) struct ReqInfo {
- pub cred: Option,
+ pub cred: Option,
pub is_owner: bool,
pub bucket: Option,
pub object: Option,
@@ -107,7 +106,7 @@ pub async fn authorize_request(req: &mut S3Request, action: Action) -> S3R
} else {
let conditions = get_condition_values(
&req.headers,
- &auth::Credentials::default(),
+ &rustfs_credentials::Credentials::default(),
req_info.version_id.as_deref(),
req.region.as_deref(),
);
diff --git a/rustfs/src/storage/ecfs.rs b/rustfs/src/storage/ecfs.rs
index ca8af9ed..d9675ff4 100644
--- a/rustfs/src/storage/ecfs.rs
+++ b/rustfs/src/storage/ecfs.rs
@@ -93,12 +93,9 @@ use rustfs_kms::{
types::{EncryptionMetadata, ObjectEncryptionContext},
};
use rustfs_notify::{EventArgsBuilder, notifier_global};
-use rustfs_policy::{
- auth,
- policy::{
- action::{Action, S3Action},
- {BucketPolicy, BucketPolicyArgs, Validator},
- },
+use rustfs_policy::policy::{
+ action::{Action, S3Action},
+ {BucketPolicy, BucketPolicyArgs, Validator},
};
use rustfs_rio::{CompressReader, DecryptReader, EncryptReader, EtagReader, HardLimitReader, HashReader, Reader, WarpReader};
use rustfs_s3select_api::{
@@ -4692,7 +4689,7 @@ impl S3 for FS {
.await
.map_err(ApiError::from)?;
- let conditions = get_condition_values(&req.headers, &auth::Credentials::default(), None, None);
+ let conditions = get_condition_values(&req.headers, &rustfs_credentials::Credentials::default(), None, None);
let read_only = PolicySys::is_allowed(&BucketPolicyArgs {
bucket: &bucket,
diff --git a/rustfs/src/storage/tonic_service.rs b/rustfs/src/storage/tonic_service.rs
index c0f86cb4..1f173da5 100644
--- a/rustfs/src/storage/tonic_service.rs
+++ b/rustfs/src/storage/tonic_service.rs
@@ -1774,11 +1774,34 @@ impl Node for NodeService {
async fn get_metrics(&self, request: Request) -> Result, Status> {
let request = request.into_inner();
- let mut buf_t = Deserializer::new(Cursor::new(request.metric_type));
- let t: MetricType = Deserialize::deserialize(&mut buf_t).unwrap();
+ // Deserialize metric_type with error handling
+ let mut buf_t = Deserializer::new(Cursor::new(request.metric_type));
+ let t: MetricType = match Deserialize::deserialize(&mut buf_t) {
+ Ok(t) => t,
+ Err(err) => {
+ error!("Failed to deserialize metric_type: {}", err);
+ return Ok(Response::new(GetMetricsResponse {
+ success: false,
+ realtime_metrics: Bytes::new(),
+ error_info: Some(format!("Invalid metric_type: {}", err)),
+ }));
+ }
+ };
+
+ // Deserialize opts with error handling
let mut buf_o = Deserializer::new(Cursor::new(request.opts));
- let opts: CollectMetricsOpts = Deserialize::deserialize(&mut buf_o).unwrap();
+ let opts: CollectMetricsOpts = match Deserialize::deserialize(&mut buf_o) {
+ Ok(opts) => opts,
+ Err(err) => {
+ error!("Failed to deserialize opts: {}", err);
+ return Ok(Response::new(GetMetricsResponse {
+ success: false,
+ realtime_metrics: Bytes::new(),
+ error_info: Some(format!("Invalid opts: {}", err)),
+ }));
+ }
+ };
let info = collect_local_metrics(t, &opts).await;
@@ -3648,4 +3671,32 @@ mod tests {
// Should return None for non-existent disk
assert!(disk.is_none());
}
+
+ #[tokio::test]
+ async fn test_get_metrics_invalid_metric_type() {
+ let service = create_test_node_service();
+ let request = Request::new(GetMetricsRequest {
+ metric_type: Bytes::from(vec![0x00u8, 0x01u8]), // Invalid rmp data
+ opts: Bytes::new(), // Valid or invalid
+ });
+ let response = service.get_metrics(request).await.unwrap().into_inner();
+ assert!(!response.success);
+ assert!(response.error_info.is_some());
+ }
+
+ #[tokio::test]
+ async fn test_get_metrics_invalid_opts() {
+ let service = create_test_node_service();
+ // Serialize a valid MetricType
+ let metric_type = MetricType::DISK;
+ let metric_type_bytes = rmp_serde::to_vec(&metric_type).unwrap();
+
+ let request = Request::new(GetMetricsRequest {
+ metric_type: Bytes::from(metric_type_bytes),
+ opts: Bytes::from(vec![0x00u8, 0x01u8]), // Invalid rmp data
+ });
+ let response = service.get_metrics(request).await.unwrap().into_inner();
+ assert!(!response.success);
+ assert!(response.error_info.is_some());
+ }
}