mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-15 09:33:13 +00:00
chore(rustfs): remove dead keystone shadow auth path (#5988)
This commit is contained in:
@@ -516,39 +516,6 @@ fn check_claims_from_token_with_context(
|
||||
Ok(HashMap::new())
|
||||
}
|
||||
|
||||
/// Check for Keystone authentication headers and authenticate if present
|
||||
/// Returns Some((Credentials, is_owner)) if Keystone authentication succeeds
|
||||
/// Returns None if no Keystone headers present (fall back to standard auth)
|
||||
///
|
||||
/// Reserved for future use (alternative Keystone auth path)
|
||||
#[allow(dead_code)]
|
||||
pub async fn try_keystone_auth(headers: &HeaderMap) -> S3Result<Option<(Credentials, bool)>> {
|
||||
use crate::auth_keystone;
|
||||
|
||||
if !auth_keystone::is_keystone_enabled() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
match auth_keystone::authenticate_keystone(headers).await? {
|
||||
Some(cred) => {
|
||||
// Keystone credentials are never "owner" in the traditional sense
|
||||
// unless they have admin role
|
||||
let is_owner = cred
|
||||
.groups
|
||||
.as_ref()
|
||||
.map(|groups| {
|
||||
groups
|
||||
.iter()
|
||||
.any(|g| g.eq_ignore_ascii_case("admin") || g.eq_ignore_ascii_case("reseller_admin"))
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
Ok(Some((cred, is_owner)))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_session_token<'a>(uri: &'a Uri, hds: &'a HeaderMap) -> Option<&'a str> {
|
||||
let token = hds
|
||||
.get("x-amz-security-token")
|
||||
|
||||
+1
-237
@@ -14,13 +14,9 @@
|
||||
|
||||
//! OpenStack Keystone authentication integration for RustFS
|
||||
|
||||
use http::HeaderMap;
|
||||
use rustfs_credentials::Credentials;
|
||||
use rustfs_keystone::{KeystoneAuthProvider, KeystoneClient, KeystoneConfig, KeystoneIdentityMapper};
|
||||
use rustfs_utils::MaskedAccessKey;
|
||||
use s3s::{S3Result, s3_error};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use tracing::{error, info};
|
||||
use tracing::info;
|
||||
|
||||
static KEYSTONE_AUTH: OnceLock<Arc<KeystoneAuthProvider>> = OnceLock::new();
|
||||
static KEYSTONE_MAPPER: OnceLock<Arc<KeystoneIdentityMapper>> = OnceLock::new();
|
||||
@@ -112,239 +108,7 @@ pub fn get_keystone_auth() -> Option<Arc<KeystoneAuthProvider>> {
|
||||
KEYSTONE_AUTH.get().cloned()
|
||||
}
|
||||
|
||||
/// Get Keystone identity mapper
|
||||
///
|
||||
/// Reserved for future use (Swift API, tenant prefixing)
|
||||
#[allow(dead_code)]
|
||||
pub fn get_keystone_mapper() -> Option<Arc<KeystoneIdentityMapper>> {
|
||||
KEYSTONE_MAPPER.get().cloned()
|
||||
}
|
||||
|
||||
/// Get Keystone configuration
|
||||
///
|
||||
/// Reserved for future use (dynamic configuration updates)
|
||||
#[allow(dead_code)]
|
||||
pub fn get_keystone_config() -> Option<&'static KeystoneConfig> {
|
||||
KEYSTONE_CONFIG.get()
|
||||
}
|
||||
|
||||
/// Check if Keystone is enabled
|
||||
pub fn is_keystone_enabled() -> bool {
|
||||
KEYSTONE_CONFIG.get().map(|c| c.enable).unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Authenticate request with Keystone
|
||||
///
|
||||
/// Checks for:
|
||||
/// 1. X-Auth-Token header (Keystone token)
|
||||
/// 2. X-Storage-Token header (Swift compatibility)
|
||||
///
|
||||
/// Returns Some(Credentials) if authenticated via Keystone,
|
||||
/// None if Keystone is disabled or no Keystone headers present
|
||||
///
|
||||
/// Reserved for future use (alternative auth path, Swift API)
|
||||
#[allow(dead_code)]
|
||||
pub async fn authenticate_keystone(headers: &HeaderMap) -> S3Result<Option<Credentials>> {
|
||||
let auth_provider = match get_keystone_auth() {
|
||||
Some(provider) => provider,
|
||||
None => return Ok(None), // Keystone not enabled
|
||||
};
|
||||
|
||||
// Check for X-Auth-Token header (Keystone v3)
|
||||
if let Some(token) = headers.get("X-Auth-Token").and_then(|v| v.to_str().ok()) {
|
||||
return match auth_provider.authenticate_with_token(token).await {
|
||||
Ok(cred) => {
|
||||
info!(
|
||||
component = LOG_COMPONENT_AUTH,
|
||||
subsystem = LOG_SUBSYSTEM_KEYSTONE,
|
||||
event = "keystone_token_auth",
|
||||
token_type = "x_auth_token",
|
||||
principal = %MaskedAccessKey(&cred.parent_user),
|
||||
result = "success",
|
||||
"Keystone token authentication completed"
|
||||
);
|
||||
Ok(Some(cred))
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
component = LOG_COMPONENT_AUTH,
|
||||
subsystem = LOG_SUBSYSTEM_KEYSTONE,
|
||||
event = "keystone_token_auth",
|
||||
token_type = "x_auth_token",
|
||||
result = "failed",
|
||||
error = %e,
|
||||
"Keystone token authentication completed"
|
||||
);
|
||||
Err(s3_error!(InvalidToken, "Invalid Keystone token: {}", e))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Check for X-Storage-Token header (Swift compatibility)
|
||||
if let Some(token) = headers.get("X-Storage-Token").and_then(|v| v.to_str().ok()) {
|
||||
return match auth_provider.authenticate_with_token(token).await {
|
||||
Ok(cred) => {
|
||||
info!(
|
||||
component = LOG_COMPONENT_AUTH,
|
||||
subsystem = LOG_SUBSYSTEM_KEYSTONE,
|
||||
event = "keystone_token_auth",
|
||||
token_type = "x_storage_token",
|
||||
principal = %MaskedAccessKey(&cred.parent_user),
|
||||
result = "success",
|
||||
"Keystone token authentication completed"
|
||||
);
|
||||
Ok(Some(cred))
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
component = LOG_COMPONENT_AUTH,
|
||||
subsystem = LOG_SUBSYSTEM_KEYSTONE,
|
||||
event = "keystone_token_auth",
|
||||
token_type = "x_storage_token",
|
||||
result = "failed",
|
||||
error = %e,
|
||||
"Keystone token authentication completed"
|
||||
);
|
||||
Err(s3_error!(InvalidToken, "Invalid Keystone token: {}", e))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// No Keystone headers found
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Apply tenant prefix to bucket name
|
||||
///
|
||||
/// Reserved for future use (multi-tenancy feature)
|
||||
#[allow(dead_code)]
|
||||
pub fn apply_tenant_prefix(bucket: &str, cred: &Credentials) -> String {
|
||||
let mapper = match get_keystone_mapper() {
|
||||
Some(m) => m,
|
||||
None => return bucket.to_string(),
|
||||
};
|
||||
|
||||
// Extract project_id from claims
|
||||
let project_id = cred
|
||||
.claims
|
||||
.as_ref()
|
||||
.and_then(|claims| claims.get("keystone_project_id"))
|
||||
.and_then(|v| v.as_str());
|
||||
|
||||
mapper.apply_tenant_prefix(bucket, project_id)
|
||||
}
|
||||
|
||||
/// Remove tenant prefix from bucket name
|
||||
///
|
||||
/// Reserved for future use (multi-tenancy feature)
|
||||
#[allow(dead_code)]
|
||||
pub fn remove_tenant_prefix(prefixed_bucket: &str, cred: &Credentials) -> String {
|
||||
let mapper = match get_keystone_mapper() {
|
||||
Some(m) => m,
|
||||
None => return prefixed_bucket.to_string(),
|
||||
};
|
||||
|
||||
let project_id = cred
|
||||
.claims
|
||||
.as_ref()
|
||||
.and_then(|claims| claims.get("keystone_project_id"))
|
||||
.and_then(|v| v.as_str());
|
||||
|
||||
mapper.remove_tenant_prefix(prefixed_bucket, project_id)
|
||||
}
|
||||
|
||||
/// Check if bucket belongs to user's project
|
||||
///
|
||||
/// Reserved for future use (multi-tenancy feature)
|
||||
#[allow(dead_code)]
|
||||
pub fn is_user_bucket(bucket: &str, cred: &Credentials) -> bool {
|
||||
let mapper = match get_keystone_mapper() {
|
||||
Some(m) => m,
|
||||
None => return true,
|
||||
};
|
||||
|
||||
let project_id = cred
|
||||
.claims
|
||||
.as_ref()
|
||||
.and_then(|claims| claims.get("keystone_project_id"))
|
||||
.and_then(|v| v.as_str());
|
||||
|
||||
mapper.is_project_bucket(bucket, project_id)
|
||||
}
|
||||
|
||||
/// Filter bucket list to only show user's project buckets
|
||||
///
|
||||
/// Reserved for future use (multi-tenancy feature)
|
||||
#[allow(dead_code)]
|
||||
pub fn filter_bucket_list(buckets: Vec<String>, cred: &Credentials) -> Vec<String> {
|
||||
let mapper = match get_keystone_mapper() {
|
||||
Some(m) => m,
|
||||
None => return buckets,
|
||||
};
|
||||
|
||||
if !mapper.is_tenant_prefix_enabled() {
|
||||
return buckets;
|
||||
}
|
||||
|
||||
let project_id = cred
|
||||
.claims
|
||||
.as_ref()
|
||||
.and_then(|claims| claims.get("keystone_project_id"))
|
||||
.and_then(|v| v.as_str());
|
||||
|
||||
if let Some(proj_id) = project_id {
|
||||
let prefix = format!("{}:", proj_id);
|
||||
buckets
|
||||
.into_iter()
|
||||
.filter(|b| b.starts_with(&prefix))
|
||||
.map(|b| b[prefix.len()..].to_string())
|
||||
.collect()
|
||||
} else {
|
||||
// No project ID, return unprefixed buckets only
|
||||
buckets.into_iter().filter(|b| !b.contains(':')).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if credential is from Keystone
|
||||
///
|
||||
/// Reserved for future use (credential type detection)
|
||||
#[allow(dead_code)]
|
||||
pub fn is_keystone_credential(cred: &Credentials) -> bool {
|
||||
cred.claims
|
||||
.as_ref()
|
||||
.and_then(|claims| claims.get("auth_source"))
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s == "keystone")
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn create_test_credentials(project_id: Option<&str>) -> Credentials {
|
||||
let mut claims = HashMap::new();
|
||||
claims.insert("auth_source".to_string(), json!("keystone"));
|
||||
if let Some(proj_id) = project_id {
|
||||
claims.insert("keystone_project_id".to_string(), json!(proj_id));
|
||||
}
|
||||
|
||||
Credentials {
|
||||
access_key: "test-access".to_string(),
|
||||
secret_key: "test-secret".to_string(),
|
||||
claims: Some(claims),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_keystone_credential() {
|
||||
let cred = create_test_credentials(Some("proj123"));
|
||||
assert!(is_keystone_credential(&cred));
|
||||
|
||||
let non_keystone_cred = Credentials::default();
|
||||
assert!(!is_keystone_credential(&non_keystone_cred));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user