mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-26 08:18:18 +00:00
fix(iam): allow colons and dots in STS claim policy names (#3164)
`is_safe_claim_policy_name` rejected any character other than
`[a-zA-Z0-9_-]`, silently dropping policy names containing colons.
This breaks Kubernetes workload identity where `claim_name=sub`
resolves to `system:serviceaccount:<namespace>:<sa-name>` — a valid
policy name that can be created via the admin API but is then
unreachable during STS session authorization.
Add `:` and `.` to the allowed character set. These characters are:
- Used in K8s service account `sub` claims (colons)
- Used in Java/DNS-style group names from OIDC providers (dots)
- Already accepted by the `add-canned-policy` admin API endpoint
Require at least one alphanumeric character to prevent meaningless
names (`.`, `..`, `-`, `_`, `:`, etc.) from resolving.
Still rejected: `/`, `\`, whitespace, `$`, `;`, `{`, `}` and other
chars that could enable path traversal or injection.
Signed-off-by: Alexander Kharkevich <alex@mara.com>
Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: 安正超 <anzhengchao@gmail.com>
Co-authored-by: loverustfs <hello@rustfs.com>
Co-authored-by: GatewayJ <835269233@qq.com>
This commit is contained in:
committed by
GitHub
parent
45e3c01857
commit
528c3278b7
+80
-1
@@ -834,8 +834,27 @@ impl<T: Store> IamSys<T> {
|
||||
self.store.policy_db_get(name, groups).await
|
||||
}
|
||||
|
||||
/// Check whether a policy name from a JWT claim is safe to resolve against the IAM store.
|
||||
///
|
||||
/// Allowed characters: `[a-zA-Z0-9_:.-]`
|
||||
/// - Colons: K8s service account `sub` claims (`system:serviceaccount:ns:sa`)
|
||||
/// - Dots: OIDC group names from providers like Okta/Entra (`org.team.role`)
|
||||
///
|
||||
/// Requirements:
|
||||
/// - At least one ASCII alphanumeric character (prevents meaningless names
|
||||
/// like `.`, `..`, `-`, `___`, or `:.:`).
|
||||
/// - No characters outside the allowed set (helps mitigate path traversal
|
||||
/// and injection when names are used in storage keys or log output).
|
||||
fn is_safe_claim_policy_name(policy: &str) -> bool {
|
||||
!policy.is_empty() && policy.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
|
||||
let mut has_alnum = false;
|
||||
for c in policy.bytes() {
|
||||
if c.is_ascii_alphanumeric() {
|
||||
has_alnum = true;
|
||||
} else if !matches!(c, b'_' | b'-' | b':' | b'.') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
has_alnum
|
||||
}
|
||||
|
||||
// JWT policy claims carry canned policy names only; policy documents are resolved by IAM store.
|
||||
@@ -2667,4 +2686,64 @@ mod tests {
|
||||
policy_info.policy
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_safe_claim_policy_name_allows_k8s_sa_sub() {
|
||||
assert!(IamSys::<StsTestMockStore>::is_safe_claim_policy_name(
|
||||
"system:serviceaccount:my-namespace:my-sa"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_safe_claim_policy_name_allows_dotted_names() {
|
||||
assert!(IamSys::<StsTestMockStore>::is_safe_claim_policy_name("org.team.policy-name"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_safe_claim_policy_name_allows_simple_names() {
|
||||
assert!(IamSys::<StsTestMockStore>::is_safe_claim_policy_name("readwrite"));
|
||||
assert!(IamSys::<StsTestMockStore>::is_safe_claim_policy_name("my-custom_policy"));
|
||||
assert!(IamSys::<StsTestMockStore>::is_safe_claim_policy_name("Policy123"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_safe_claim_policy_name_rejects_empty() {
|
||||
assert!(!IamSys::<StsTestMockStore>::is_safe_claim_policy_name(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_safe_claim_policy_name_rejects_path_traversal() {
|
||||
assert!(!IamSys::<StsTestMockStore>::is_safe_claim_policy_name("../etc/passwd"));
|
||||
assert!(!IamSys::<StsTestMockStore>::is_safe_claim_policy_name("policy/name"));
|
||||
assert!(!IamSys::<StsTestMockStore>::is_safe_claim_policy_name("policy\\name"));
|
||||
assert!(!IamSys::<StsTestMockStore>::is_safe_claim_policy_name("..\\etc\\passwd"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_safe_claim_policy_name_rejects_whitespace() {
|
||||
assert!(!IamSys::<StsTestMockStore>::is_safe_claim_policy_name("my policy"));
|
||||
assert!(!IamSys::<StsTestMockStore>::is_safe_claim_policy_name("policy\tname"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_safe_claim_policy_name_rejects_special_chars() {
|
||||
assert!(!IamSys::<StsTestMockStore>::is_safe_claim_policy_name("policy;drop"));
|
||||
assert!(!IamSys::<StsTestMockStore>::is_safe_claim_policy_name("pol$icy"));
|
||||
assert!(!IamSys::<StsTestMockStore>::is_safe_claim_policy_name("name{bad}"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_safe_claim_policy_name_rejects_no_alphanumeric() {
|
||||
assert!(!IamSys::<StsTestMockStore>::is_safe_claim_policy_name("."));
|
||||
assert!(!IamSys::<StsTestMockStore>::is_safe_claim_policy_name(".."));
|
||||
assert!(!IamSys::<StsTestMockStore>::is_safe_claim_policy_name(":"));
|
||||
assert!(!IamSys::<StsTestMockStore>::is_safe_claim_policy_name("..."));
|
||||
assert!(!IamSys::<StsTestMockStore>::is_safe_claim_policy_name(".:.:"));
|
||||
assert!(!IamSys::<StsTestMockStore>::is_safe_claim_policy_name("-"));
|
||||
assert!(!IamSys::<StsTestMockStore>::is_safe_claim_policy_name("_"));
|
||||
assert!(!IamSys::<StsTestMockStore>::is_safe_claim_policy_name("__"));
|
||||
assert!(!IamSys::<StsTestMockStore>::is_safe_claim_policy_name("---"));
|
||||
assert!(!IamSys::<StsTestMockStore>::is_safe_claim_policy_name("-_-"));
|
||||
assert!(!IamSys::<StsTestMockStore>::is_safe_claim_policy_name("-.:_"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user