Skip to content

Commit cf349c2

Browse files
feat(security): add BASIC keywords for security protection tools
Add security_protection.rs with 8 new BASIC keywords: - SECURITY TOOL STATUS - Check if tool is installed/running - SECURITY RUN SCAN - Execute security scan - SECURITY GET REPORT - Get latest scan report - SECURITY UPDATE DEFINITIONS - Update signatures - SECURITY START SERVICE - Start security service - SECURITY STOP SERVICE - Stop security service - SECURITY INSTALL TOOL - Install security tool - SECURITY HARDENING SCORE - Get Lynis hardening index Also: - Registered protection routes in main.rs - Added Security Protection category to keywords list - All functions use proper error handling (no unwrap/expect)
1 parent d2175a5 commit cf349c2

File tree

8 files changed

+947
-5
lines changed

8 files changed

+947
-5
lines changed

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,11 @@ qrcode = { version = "0.14", default-features = false }
207207
# Excel/Spreadsheet Support
208208
calamine = "0.26"
209209
rust_xlsxwriter = "0.79"
210+
spreadsheet-ods = "1.0"
211+
212+
# Word/PowerPoint Support
213+
docx-rs = "0.4"
214+
ppt-rs = { version = "0.2", default-features = false }
210215

211216
# Error handling
212217
thiserror = "2.0"

src/basic/keywords/mod.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ pub mod procedures;
5555
pub mod qrcode;
5656
pub mod remember;
5757
pub mod save_from_unstructured;
58+
pub mod security_protection;
5859
pub mod send_mail;
5960
pub mod send_template;
6061
pub mod set;
@@ -85,6 +86,12 @@ pub mod webhook;
8586
pub use app_server::configure_app_server_routes;
8687
pub use db_api::configure_db_routes;
8788
pub use mcp_client::{McpClient, McpRequest, McpResponse, McpServer, McpTool};
89+
pub use security_protection::{
90+
security_get_report, security_hardening_score, security_install_tool, security_run_scan,
91+
security_service_is_running, security_start_service, security_stop_service,
92+
security_tool_is_installed, security_tool_status, security_update_definitions,
93+
SecurityScanResult, SecurityToolResult,
94+
};
8895
pub use mcp_directory::{McpDirectoryScanResult, McpDirectoryScanner, McpServerConfig};
8996
pub use table_access::{
9097
check_field_write_access, check_table_access, filter_fields_by_role, load_table_access_info,
@@ -201,6 +208,14 @@ pub fn get_all_keywords() -> Vec<String> {
201208
"OPTION A OR B".to_string(),
202209
"DECIDE".to_string(),
203210
"ESCALATE".to_string(),
211+
"SECURITY TOOL STATUS".to_string(),
212+
"SECURITY RUN SCAN".to_string(),
213+
"SECURITY GET REPORT".to_string(),
214+
"SECURITY UPDATE DEFINITIONS".to_string(),
215+
"SECURITY START SERVICE".to_string(),
216+
"SECURITY STOP SERVICE".to_string(),
217+
"SECURITY INSTALL TOOL".to_string(),
218+
"SECURITY HARDENING SCORE".to_string(),
204219
]
205220
}
206221

@@ -325,5 +340,19 @@ pub fn get_keyword_categories() -> std::collections::HashMap<String, Vec<String>
325340
],
326341
);
327342

343+
categories.insert(
344+
"Security Protection".to_string(),
345+
vec![
346+
"SECURITY TOOL STATUS".to_string(),
347+
"SECURITY RUN SCAN".to_string(),
348+
"SECURITY GET REPORT".to_string(),
349+
"SECURITY UPDATE DEFINITIONS".to_string(),
350+
"SECURITY START SERVICE".to_string(),
351+
"SECURITY STOP SERVICE".to_string(),
352+
"SECURITY INSTALL TOOL".to_string(),
353+
"SECURITY HARDENING SCORE".to_string(),
354+
],
355+
);
356+
328357
categories
329358
}
Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
use crate::security::protection::{ProtectionManager, ProtectionTool, ProtectionConfig};
2+
use crate::shared::state::AppState;
3+
use serde::{Deserialize, Serialize};
4+
use std::sync::Arc;
5+
6+
#[derive(Debug, Clone, Serialize, Deserialize)]
7+
pub struct SecurityToolResult {
8+
pub tool: String,
9+
pub success: bool,
10+
pub installed: bool,
11+
pub version: Option<String>,
12+
pub running: Option<bool>,
13+
pub message: String,
14+
}
15+
16+
#[derive(Debug, Clone, Serialize, Deserialize)]
17+
pub struct SecurityScanResult {
18+
pub tool: String,
19+
pub success: bool,
20+
pub status: String,
21+
pub findings_count: usize,
22+
pub warnings_count: usize,
23+
pub score: Option<i32>,
24+
pub report_path: Option<String>,
25+
}
26+
27+
pub async fn security_tool_status(
28+
_state: Arc<AppState>,
29+
tool_name: &str,
30+
) -> Result<SecurityToolResult, String> {
31+
let tool = parse_tool_name(tool_name)?;
32+
let manager = ProtectionManager::new(ProtectionConfig::default());
33+
34+
match manager.check_tool_status(tool).await {
35+
Ok(status) => Ok(SecurityToolResult {
36+
tool: tool_name.to_lowercase(),
37+
success: true,
38+
installed: status.installed,
39+
version: status.version,
40+
running: status.service_running,
41+
message: if status.installed {
42+
"Tool is installed".to_string()
43+
} else {
44+
"Tool is not installed".to_string()
45+
},
46+
}),
47+
Err(e) => Ok(SecurityToolResult {
48+
tool: tool_name.to_lowercase(),
49+
success: false,
50+
installed: false,
51+
version: None,
52+
running: None,
53+
message: format!("Failed to check status: {e}"),
54+
}),
55+
}
56+
}
57+
58+
pub async fn security_run_scan(
59+
_state: Arc<AppState>,
60+
tool_name: &str,
61+
) -> Result<SecurityScanResult, String> {
62+
let tool = parse_tool_name(tool_name)?;
63+
let manager = ProtectionManager::new(ProtectionConfig::default());
64+
65+
match manager.run_scan(tool).await {
66+
Ok(result) => Ok(SecurityScanResult {
67+
tool: tool_name.to_lowercase(),
68+
success: true,
69+
status: result.status,
70+
findings_count: result.findings.len(),
71+
warnings_count: result.warnings,
72+
score: result.score,
73+
report_path: result.report_path,
74+
}),
75+
Err(e) => Ok(SecurityScanResult {
76+
tool: tool_name.to_lowercase(),
77+
success: false,
78+
status: "error".to_string(),
79+
findings_count: 0,
80+
warnings_count: 0,
81+
score: None,
82+
report_path: None,
83+
}),
84+
}
85+
}
86+
87+
pub async fn security_get_report(
88+
_state: Arc<AppState>,
89+
tool_name: &str,
90+
) -> Result<String, String> {
91+
let tool = parse_tool_name(tool_name)?;
92+
let manager = ProtectionManager::new(ProtectionConfig::default());
93+
94+
manager
95+
.get_report(tool)
96+
.await
97+
.map_err(|e| format!("Failed to get report: {e}"))
98+
}
99+
100+
pub async fn security_update_definitions(
101+
_state: Arc<AppState>,
102+
tool_name: &str,
103+
) -> Result<SecurityToolResult, String> {
104+
let tool = parse_tool_name(tool_name)?;
105+
let manager = ProtectionManager::new(ProtectionConfig::default());
106+
107+
match manager.update_definitions(tool).await {
108+
Ok(()) => Ok(SecurityToolResult {
109+
tool: tool_name.to_lowercase(),
110+
success: true,
111+
installed: true,
112+
version: None,
113+
running: None,
114+
message: "Definitions updated successfully".to_string(),
115+
}),
116+
Err(e) => Ok(SecurityToolResult {
117+
tool: tool_name.to_lowercase(),
118+
success: false,
119+
installed: true,
120+
version: None,
121+
running: None,
122+
message: format!("Failed to update definitions: {e}"),
123+
}),
124+
}
125+
}
126+
127+
pub async fn security_start_service(
128+
_state: Arc<AppState>,
129+
tool_name: &str,
130+
) -> Result<SecurityToolResult, String> {
131+
let tool = parse_tool_name(tool_name)?;
132+
let manager = ProtectionManager::new(ProtectionConfig::default());
133+
134+
match manager.start_service(tool).await {
135+
Ok(()) => Ok(SecurityToolResult {
136+
tool: tool_name.to_lowercase(),
137+
success: true,
138+
installed: true,
139+
version: None,
140+
running: Some(true),
141+
message: "Service started successfully".to_string(),
142+
}),
143+
Err(e) => Ok(SecurityToolResult {
144+
tool: tool_name.to_lowercase(),
145+
success: false,
146+
installed: true,
147+
version: None,
148+
running: Some(false),
149+
message: format!("Failed to start service: {e}"),
150+
}),
151+
}
152+
}
153+
154+
pub async fn security_stop_service(
155+
_state: Arc<AppState>,
156+
tool_name: &str,
157+
) -> Result<SecurityToolResult, String> {
158+
let tool = parse_tool_name(tool_name)?;
159+
let manager = ProtectionManager::new(ProtectionConfig::default());
160+
161+
match manager.stop_service(tool).await {
162+
Ok(()) => Ok(SecurityToolResult {
163+
tool: tool_name.to_lowercase(),
164+
success: true,
165+
installed: true,
166+
version: None,
167+
running: Some(false),
168+
message: "Service stopped successfully".to_string(),
169+
}),
170+
Err(e) => Ok(SecurityToolResult {
171+
tool: tool_name.to_lowercase(),
172+
success: false,
173+
installed: true,
174+
version: None,
175+
running: None,
176+
message: format!("Failed to stop service: {e}"),
177+
}),
178+
}
179+
}
180+
181+
pub async fn security_install_tool(
182+
_state: Arc<AppState>,
183+
tool_name: &str,
184+
) -> Result<SecurityToolResult, String> {
185+
let tool = parse_tool_name(tool_name)?;
186+
let manager = ProtectionManager::new(ProtectionConfig::default());
187+
188+
match manager.install_tool(tool).await {
189+
Ok(()) => Ok(SecurityToolResult {
190+
tool: tool_name.to_lowercase(),
191+
success: true,
192+
installed: true,
193+
version: None,
194+
running: None,
195+
message: "Tool installed successfully".to_string(),
196+
}),
197+
Err(e) => Ok(SecurityToolResult {
198+
tool: tool_name.to_lowercase(),
199+
success: false,
200+
installed: false,
201+
version: None,
202+
running: None,
203+
message: format!("Failed to install tool: {e}"),
204+
}),
205+
}
206+
}
207+
208+
pub async fn security_hardening_score(_state: Arc<AppState>) -> Result<i32, String> {
209+
let manager = ProtectionManager::new(ProtectionConfig::default());
210+
211+
match manager.run_scan(ProtectionTool::Lynis).await {
212+
Ok(result) => result.score.ok_or_else(|| "No hardening score available".to_string()),
213+
Err(e) => Err(format!("Failed to get hardening score: {e}")),
214+
}
215+
}
216+
217+
pub fn security_tool_is_installed(status: &SecurityToolResult) -> bool {
218+
status.installed
219+
}
220+
221+
pub fn security_service_is_running(status: &SecurityToolResult) -> bool {
222+
status.running.unwrap_or(false)
223+
}
224+
225+
fn parse_tool_name(name: &str) -> Result<ProtectionTool, String> {
226+
ProtectionTool::from_str(name)
227+
.ok_or_else(|| format!("Unknown security tool: {name}. Valid tools: lynis, rkhunter, chkrootkit, suricata, lmd, clamav"))
228+
}
229+
230+
#[cfg(test)]
231+
mod tests {
232+
use super::*;
233+
234+
#[test]
235+
fn test_parse_tool_name_valid() {
236+
assert!(parse_tool_name("lynis").is_ok());
237+
assert!(parse_tool_name("LYNIS").is_ok());
238+
assert!(parse_tool_name("Lynis").is_ok());
239+
assert!(parse_tool_name("rkhunter").is_ok());
240+
assert!(parse_tool_name("chkrootkit").is_ok());
241+
assert!(parse_tool_name("suricata").is_ok());
242+
assert!(parse_tool_name("lmd").is_ok());
243+
assert!(parse_tool_name("clamav").is_ok());
244+
}
245+
246+
#[test]
247+
fn test_parse_tool_name_invalid() {
248+
assert!(parse_tool_name("unknown").is_err());
249+
assert!(parse_tool_name("").is_err());
250+
assert!(parse_tool_name("invalid_tool").is_err());
251+
}
252+
253+
#[test]
254+
fn test_security_tool_is_installed() {
255+
let installed = SecurityToolResult {
256+
tool: "lynis".to_string(),
257+
success: true,
258+
installed: true,
259+
version: Some("3.0.9".to_string()),
260+
running: None,
261+
message: "Tool is installed".to_string(),
262+
};
263+
assert!(security_tool_is_installed(&installed));
264+
265+
let not_installed = SecurityToolResult {
266+
tool: "lynis".to_string(),
267+
success: true,
268+
installed: false,
269+
version: None,
270+
running: None,
271+
message: "Tool is not installed".to_string(),
272+
};
273+
assert!(!security_tool_is_installed(&not_installed));
274+
}
275+
276+
#[test]
277+
fn test_security_service_is_running() {
278+
let running = SecurityToolResult {
279+
tool: "suricata".to_string(),
280+
success: true,
281+
installed: true,
282+
version: None,
283+
running: Some(true),
284+
message: "Service running".to_string(),
285+
};
286+
assert!(security_service_is_running(&running));
287+
288+
let stopped = SecurityToolResult {
289+
tool: "suricata".to_string(),
290+
success: true,
291+
installed: true,
292+
version: None,
293+
running: Some(false),
294+
message: "Service stopped".to_string(),
295+
};
296+
assert!(!security_service_is_running(&stopped));
297+
298+
let unknown = SecurityToolResult {
299+
tool: "lynis".to_string(),
300+
success: true,
301+
installed: true,
302+
version: None,
303+
running: None,
304+
message: "No service".to_string(),
305+
};
306+
assert!(!security_service_is_running(&unknown));
307+
}
308+
309+
#[test]
310+
fn test_security_scan_result_serialization() {
311+
let result = SecurityScanResult {
312+
tool: "lynis".to_string(),
313+
success: true,
314+
status: "completed".to_string(),
315+
findings_count: 5,
316+
warnings_count: 12,
317+
score: Some(78),
318+
report_path: Some("/var/log/lynis-report.dat".to_string()),
319+
};
320+
321+
let json = serde_json::to_string(&result).expect("Failed to serialize");
322+
assert!(json.contains("\"tool\":\"lynis\""));
323+
assert!(json.contains("\"score\":78"));
324+
}
325+
}

0 commit comments

Comments
 (0)