use std::collections::HashSet;
use std::fs;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use cheetah_string::CheetahString;
use rocketmq_common::common::mix_all::ACL_CONF_TOOLS_FILE;
use rocketmq_common::utils::env_utils::EnvUtils;
use rocketmq_error::RocketMQError;
use rocketmq_error::RocketMQResult;
use serde_yaml;
use crate::migration::alc::acl_config::AclConfig;
use crate::migration::alc::plain_access_config::PlainAccessConfig;
use crate::migration::alc::plain_access_data::PlainAccessData;
pub struct PlainPermissionManager {
pub file_home: String,
pub default_acl_dir: String,
pub default_acl_file: String,
pub file_list: Vec<String>,
}
impl PlainPermissionManager {
pub fn new() -> Self {
let file_home = EnvUtils::get_rocketmq_home();
let default_acl_dir = Path::new(&file_home)
.join("conf")
.join("acl")
.to_string_lossy()
.to_string();
let acl_file_prop =
std::env::var("rocketmq.acl.plain.file").unwrap_or_else(|_| "conf/plain_acl.yml".to_string());
let default_acl_file = Path::new(&file_home).join(acl_file_prop).to_string_lossy().to_string();
let mut manager = Self {
file_home,
default_acl_dir,
default_acl_file,
file_list: Vec::new(),
};
if let Err(e) = manager.load() {
tracing::error!("Failed to load ACL manager: {:#?}", e);
}
manager
}
pub fn get_all_acl_files<P: AsRef<Path>>(&self, path: P) -> Vec<String> {
let path = path.as_ref();
if !path.exists() {
tracing::info!("The default acl dir {} is not exist", path.to_string_lossy());
return Vec::new();
}
let mut all_acl_files = Vec::new();
if let Ok(read_dir) = fs::read_dir(path) {
for entry in read_dir.flatten() {
let file_path = entry.path();
let file_name = file_path.to_string_lossy().to_string();
let tools_file = Path::new(&self.file_home)
.join(ACL_CONF_TOOLS_FILE.trim_start_matches('/'))
.to_string_lossy()
.to_string();
if file_name == tools_file {
continue;
} else if file_name.ends_with(".yml") || file_name.ends_with(".yaml") {
all_acl_files.push(file_name);
} else if file_path.is_dir() {
let mut nested = self.get_all_acl_files(file_path);
all_acl_files.append(&mut nested);
}
}
}
all_acl_files
}
pub fn load(&mut self) -> Result<(), RocketMQError> {
if self.file_home.is_empty() {
return Ok(());
}
self.assure_acl_config_files_exist()?;
self.file_list = self.get_all_acl_files(&self.default_acl_dir);
if Path::new(&self.default_acl_file).exists() && !self.file_list.contains(&self.default_acl_file) {
self.file_list.push(self.default_acl_file.clone());
}
Ok(())
}
fn assure_acl_config_files_exist(&self) -> RocketMQResult<()> {
let default_acl_path = Path::new(&self.default_acl_file);
if !default_acl_path.exists() {
match File::create(default_acl_path) {
Ok(_) => {}
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => { }
Err(e) => {
tracing::error!("Error in creating {}: {:?}", self.default_acl_file, e);
return Err(e.into());
}
}
}
Ok(())
}
pub fn get_all_acl_config(&self) -> RocketMQResult<AclConfig> {
let mut acl_config = AclConfig::new();
let mut configs: Vec<PlainAccessConfig> = Vec::new();
let mut white_addrs: Vec<CheetahString> = Vec::new();
let mut access_key_sets: HashSet<String> = HashSet::new();
for path in &self.file_list {
let mut f = File::open(path).map_err(|e| {
tracing::error!("ACL file {} cannot be opened: {:?}", path, e);
RocketMQError::IO(e)
})?;
let mut content = String::new();
f.read_to_string(&mut content).map_err(|e| {
tracing::error!("Failed to read ACL file {}: {:?}", path, e);
RocketMQError::IO(e)
})?;
let plain_acl_conf_data: PlainAccessData = serde_yaml::from_str(&content).map_err(|e| {
tracing::error!("Failed to parse YAML {}: {:?}", path, e);
rocketmq_error::RocketMQError::Serialization(rocketmq_error::SerializationError::decode_failed(
"YAML",
e.to_string(),
))
})?;
let global_white_addrs = plain_acl_conf_data.global_white_remote_addresses();
if !global_white_addrs.is_empty() {
white_addrs.extend(global_white_addrs.iter().cloned());
}
let plain_access_configs = plain_acl_conf_data.accounts();
if !plain_access_configs.is_empty() {
for access_config in plain_access_configs {
if let Some(key_cs) = access_config.access_key() {
let key = key_cs.as_str().to_string();
if !access_key_sets.contains(&key) {
access_key_sets.insert(key);
configs.push(access_config.clone());
}
}
}
}
}
if !configs.is_empty() {
acl_config.set_plain_access_configs(configs);
}
if !white_addrs.is_empty() {
acl_config.set_global_white_addrs(white_addrs);
}
Ok(acl_config)
}
}
impl Default for PlainPermissionManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use std::fs;
use tempfile::TempDir;
use super::*;
#[test]
fn test_get_all_acl_files_and_load() {
let tmp = TempDir::new().unwrap();
let root = tmp.path();
let conf_acl = root.join("conf").join("acl");
fs::create_dir_all(&conf_acl).unwrap();
let tools = root.join("conf").join("tools.yml");
fs::write(&tools, "").unwrap();
let f1 = conf_acl.join("a.yml");
fs::write(
&f1,
"
global_white_remote_addresses: [\"1.2.3.4\"]
accounts:
- access_key: \"ak\"
secret_key: \"sk\"
white_remote_address: \"1.2.3.4\"
admin: true
default_topic_perm: \"DENY\"
default_group_perm: \"DENY\"
topic_perms: [\"t1\"]
group_perms: [\"g1\"]
",
)
.unwrap();
let nested = conf_acl.join("nested");
fs::create_dir_all(&nested).unwrap();
let f2 = nested.join("b.yaml");
fs::write(&f2, "accounts: []\n").unwrap();
let mut mgr = PlainPermissionManager::new();
mgr.file_home = root.to_string_lossy().to_string();
mgr.default_acl_dir = conf_acl.to_string_lossy().to_string();
mgr.default_acl_file = root.join("conf").join("plain_acl.yml").to_string_lossy().to_string();
let files = mgr.get_all_acl_files(&mgr.default_acl_dir);
assert!(files.iter().any(|p| p.ends_with("a.yml")));
assert!(files
.iter()
.any(|p| p.ends_with("nested\\b.yaml") || p.ends_with("nested/b.yaml")));
assert!(!files.iter().any(|p| p.ends_with("tools.yml")));
mgr.file_list = files;
let acl = mgr.get_all_acl_config().unwrap();
let plain = acl.plain_access_configs().unwrap();
assert_eq!(plain.len(), 1);
assert_eq!(plain[0].access_key().unwrap().as_str(), "ak");
let whites = acl.global_white_addrs().unwrap();
assert_eq!(whites.len(), 1);
assert_eq!(whites[0].as_str(), "1.2.3.4");
}
#[test]
fn test_get_all_acl_config_parse_error() {
let tmp = TempDir::new().unwrap();
let root = tmp.path();
let conf_acl = root.join("conf").join("acl");
fs::create_dir_all(&conf_acl).unwrap();
let f1 = conf_acl.join("bad.yml");
fs::write(&f1, "accounts: [").unwrap();
let mut mgr = PlainPermissionManager::new();
mgr.file_home = root.to_string_lossy().to_string();
mgr.default_acl_dir = conf_acl.to_string_lossy().to_string();
mgr.file_list = mgr.get_all_acl_files(&mgr.default_acl_dir);
let res = mgr.get_all_acl_config();
assert!(res.is_err());
match res.err().unwrap() {
rocketmq_error::RocketMQError::Serialization(_) => {}
other => panic!("expected serialization error, got {other:?}"),
}
}
#[test]
fn test_get_all_acl_config_accepts_java_camel_case_yaml() {
let tmp = TempDir::new().unwrap();
let root = tmp.path();
let conf_acl = root.join("conf").join("acl");
fs::create_dir_all(&conf_acl).unwrap();
let acl_file = conf_acl.join("java.yml");
fs::write(
&acl_file,
r#"
globalWhiteRemoteAddresses:
- "10.10.*.*"
accounts:
- accessKey: "ak"
secretKey: "sk"
whiteRemoteAddress: "192.168.0.*"
admin: true
defaultTopicPerm: "PUB|SUB"
defaultGroupPerm: "SUB"
topicPerms: ["TopicA=PUB"]
groupPerms: ["GroupA=SUB"]
"#,
)
.unwrap();
let mut mgr = PlainPermissionManager::new();
mgr.file_home = root.to_string_lossy().to_string();
mgr.default_acl_dir = conf_acl.to_string_lossy().to_string();
mgr.file_list = mgr.get_all_acl_files(&mgr.default_acl_dir);
let acl = mgr.get_all_acl_config().unwrap();
let plain = acl.plain_access_configs().unwrap();
assert_eq!(plain.len(), 1);
assert_eq!(plain[0].access_key().unwrap().as_str(), "ak");
assert_eq!(plain[0].secret_key().unwrap().as_str(), "sk");
assert_eq!(plain[0].white_remote_address().unwrap().as_str(), "192.168.0.*");
assert_eq!(plain[0].default_topic_perm().unwrap().as_str(), "PUB|SUB");
assert_eq!(plain[0].default_group_perm().unwrap().as_str(), "SUB");
assert_eq!(plain[0].topic_perms().unwrap()[0].as_str(), "TopicA=PUB");
assert_eq!(plain[0].group_perms().unwrap()[0].as_str(), "GroupA=SUB");
let whites = acl.global_white_addrs().unwrap();
assert_eq!(whites.len(), 1);
assert_eq!(whites[0].as_str(), "10.10.*.*");
}
}