Files
apache--hertzbeat/mcp-servers/mcp-bash-server/src/common/config.rs
T
wehub-resource-sync c8cebdfeee
MCP Bash Server CI / Test MCP Bash Server (dev) (push) Has been cancelled
MCP Bash Server CI / Test MCP Bash Server (release) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:11:39 +08:00

191 lines
6.0 KiB
Rust

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
//! Configuration management for the MCP Bash Server
//!
//! This module handles reading and parsing configuration from TOML files,
//! including server settings and security blacklists for command validation.
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::fs;
#[derive(Debug, Deserialize, Serialize)]
pub struct Config {
pub settings: Settings,
pub blacklist: Blacklist,
pub whitelist: Whitelist,
}
/// Security whitelist configuration for command validation
/// Contains lists of allowed commands and operations
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Whitelist {
/// List of command names that are allowed to be executed
pub commands: Vec<String>,
/// List of regular expressions for commands that are allowed
/// These patterns are matched against the full command line
pub regex: Vec<String>,
}
/// Security blacklist configuration for command validation
/// Contains lists of forbidden commands and regex patterns
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Blacklist {
/// List of command names that are not allowed to be executed
pub commands: Vec<String>,
/// List of regular expressions for commands that are not allowed
/// These patterns are matched against the full command line
pub regex: Vec<String>,
}
/// Server runtime settings including network configuration
#[derive(Debug, Deserialize, Serialize)]
pub struct Settings {
pub port: u16,
pub host: String,
pub env: Option<String>, // "development" or "production"
}
impl Config {
/// Read and parse configuration from a TOML file
/// Returns parsed Config structure or error if file cannot be read/parsed
pub fn read_config(file: &str) -> Result<Config> {
let toml_str = fs::read_to_string(file)?;
let config: Config = toml::from_str(&toml_str)?;
Ok(config)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::NamedTempFile;
#[test]
fn test_config_parsing() {
let config_content = r#"
[settings]
port = 4000
host = "127.0.0.1"
env = "development"
[whitelist]
commands = ["echo hello", "ls -la"]
regex = ["echo.*", "ls.*"]
[blacklist]
commands = ["rm", "dd"]
regex = [".*[|&].*", "^sudo .*"]
"#;
let mut temp_file = NamedTempFile::new().unwrap();
write!(temp_file, "{config_content}").unwrap();
let temp_path = temp_file.path().to_str().unwrap();
let config = Config::read_config(temp_path).unwrap();
assert_eq!(config.settings.port, 4000);
assert_eq!(config.settings.host, "127.0.0.1");
assert_eq!(config.settings.env, Some("development".to_string()));
assert_eq!(config.whitelist.commands.len(), 2);
assert!(
config
.whitelist
.commands
.contains(&"echo hello".to_string())
);
assert!(config.whitelist.commands.contains(&"ls -la".to_string()));
assert_eq!(config.whitelist.regex.len(), 2);
assert!(config.whitelist.regex.contains(&"echo.*".to_string()));
assert!(config.whitelist.regex.contains(&"ls.*".to_string()));
assert_eq!(config.blacklist.commands.len(), 2);
assert!(config.blacklist.commands.contains(&"rm".to_string()));
assert!(config.blacklist.commands.contains(&"dd".to_string()));
assert_eq!(config.blacklist.regex.len(), 2);
assert!(config.blacklist.regex.contains(&".*[|&].*".to_string()));
assert!(config.blacklist.regex.contains(&"^sudo .*".to_string()));
}
#[test]
fn test_config_invalid_file() {
let result = Config::read_config("non_existent_file.toml");
assert!(result.is_err());
}
#[test]
fn test_config_invalid_toml() {
let invalid_config = r#"
[settings
port = 4000
"#;
let mut temp_file = NamedTempFile::new().unwrap();
write!(temp_file, "{invalid_config}").unwrap();
let temp_path = temp_file.path().to_str().unwrap();
let result = Config::read_config(temp_path);
assert!(result.is_err());
}
#[test]
fn test_whitelist_creation() {
let commands = vec!["echo".to_string(), "ls".to_string()];
let regex = vec!["test.*".to_string()];
let whitelist = Whitelist {
commands: commands.clone(),
regex: regex.clone(),
};
assert_eq!(whitelist.commands, commands);
assert_eq!(whitelist.regex, regex);
}
#[test]
fn test_blacklist_creation() {
let commands = vec!["rm".to_string(), "dd".to_string()];
let regex = vec![".*[|&].*".to_string(), "^sudo .*".to_string()];
let blacklist = Blacklist {
commands: commands.clone(),
regex: regex.clone(),
};
assert_eq!(blacklist.commands, commands);
assert_eq!(blacklist.regex, regex);
}
#[test]
fn test_settings_creation() {
let settings = Settings {
port: 8080,
host: "localhost".to_string(),
env: Some("production".to_string()),
};
assert_eq!(settings.port, 8080);
assert_eq!(settings.host, "localhost");
assert_eq!(settings.env, Some("production".to_string()));
}
}