mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-16 18:08:21 +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())
|
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> {
|
pub fn get_session_token<'a>(uri: &'a Uri, hds: &'a HeaderMap) -> Option<&'a str> {
|
||||||
let token = hds
|
let token = hds
|
||||||
.get("x-amz-security-token")
|
.get("x-amz-security-token")
|
||||||
|
|||||||
+1
-237
@@ -14,13 +14,9 @@
|
|||||||
|
|
||||||
//! OpenStack Keystone authentication integration for RustFS
|
//! OpenStack Keystone authentication integration for RustFS
|
||||||
|
|
||||||
use http::HeaderMap;
|
|
||||||
use rustfs_credentials::Credentials;
|
|
||||||
use rustfs_keystone::{KeystoneAuthProvider, KeystoneClient, KeystoneConfig, KeystoneIdentityMapper};
|
use rustfs_keystone::{KeystoneAuthProvider, KeystoneClient, KeystoneConfig, KeystoneIdentityMapper};
|
||||||
use rustfs_utils::MaskedAccessKey;
|
|
||||||
use s3s::{S3Result, s3_error};
|
|
||||||
use std::sync::{Arc, OnceLock};
|
use std::sync::{Arc, OnceLock};
|
||||||
use tracing::{error, info};
|
use tracing::info;
|
||||||
|
|
||||||
static KEYSTONE_AUTH: OnceLock<Arc<KeystoneAuthProvider>> = OnceLock::new();
|
static KEYSTONE_AUTH: OnceLock<Arc<KeystoneAuthProvider>> = OnceLock::new();
|
||||||
static KEYSTONE_MAPPER: OnceLock<Arc<KeystoneIdentityMapper>> = 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()
|
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
|
/// Check if Keystone is enabled
|
||||||
pub fn is_keystone_enabled() -> bool {
|
pub fn is_keystone_enabled() -> bool {
|
||||||
KEYSTONE_CONFIG.get().map(|c| c.enable).unwrap_or(false)
|
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