eris: move lua API to standalone module
Easier to work with now, yay.
This commit is contained in:
parent
a2fc2bf2bc
commit
54f858aee9
5 changed files with 297 additions and 127 deletions
135
src/main.rs
135
src/main.rs
|
@ -1,7 +1,6 @@
|
|||
use actix_web::{App, HttpResponse, HttpServer, web};
|
||||
use clap::Parser;
|
||||
use ipnetwork::IpNetwork;
|
||||
use rlua::{Function, Lua};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::env;
|
||||
|
@ -17,9 +16,11 @@ use tokio::process::Command;
|
|||
use tokio::sync::RwLock;
|
||||
use tokio::time::sleep;
|
||||
|
||||
mod lua;
|
||||
mod markov;
|
||||
mod metrics;
|
||||
|
||||
use lua::ScriptManager;
|
||||
use markov::MarkovGenerator;
|
||||
use metrics::{
|
||||
ACTIVE_CONNECTIONS, BLOCKED_IPS, HITS_COUNTER, PATH_HITS, UA_HITS, metrics_handler,
|
||||
|
@ -356,10 +357,11 @@ impl BotState {
|
|||
}
|
||||
|
||||
let hit_cache_file = format!("{}/hit_counters.json", self.cache_dir);
|
||||
let mut hit_map = HashMap::new();
|
||||
for (ip, count) in &self.hits {
|
||||
hit_map.insert(ip.to_string(), *count);
|
||||
}
|
||||
let hit_map: HashMap<String, u32> = self
|
||||
.hits
|
||||
.iter()
|
||||
.map(|(ip, count)| (ip.to_string(), *count))
|
||||
.collect();
|
||||
|
||||
match fs::File::create(&hit_cache_file) {
|
||||
Ok(file) => {
|
||||
|
@ -376,116 +378,11 @@ impl BotState {
|
|||
}
|
||||
}
|
||||
|
||||
// Lua scripts for response generation and customization
|
||||
struct ScriptManager {
|
||||
script_content: String,
|
||||
scripts_loaded: bool,
|
||||
}
|
||||
|
||||
impl ScriptManager {
|
||||
fn new(scripts_dir: &str) -> Self {
|
||||
let mut script_content = String::new();
|
||||
let mut scripts_loaded = false;
|
||||
|
||||
// Try to load scripts from directory
|
||||
let script_dir = Path::new(scripts_dir);
|
||||
if script_dir.exists() {
|
||||
log::debug!("Loading Lua scripts from directory: {scripts_dir}");
|
||||
if let Ok(entries) = fs::read_dir(script_dir) {
|
||||
for entry in entries {
|
||||
if let Ok(entry) = entry {
|
||||
let path = entry.path();
|
||||
if path.extension().and_then(|ext| ext.to_str()) == Some("lua") {
|
||||
if let Ok(content) = fs::read_to_string(&path) {
|
||||
log::debug!("Loaded Lua script: {}", path.display());
|
||||
script_content.push_str(&content);
|
||||
script_content.push('\n');
|
||||
scripts_loaded = true;
|
||||
} else {
|
||||
log::warn!("Failed to read Lua script: {}", path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::warn!("Lua scripts directory does not exist: {scripts_dir}");
|
||||
}
|
||||
|
||||
// If no scripts were loaded, use a default script
|
||||
if !scripts_loaded {
|
||||
log::info!("No Lua scripts found, loading default scripts");
|
||||
script_content = r#"
|
||||
function generate_honeytoken(token)
|
||||
local token_types = {"API_KEY", "AUTH_TOKEN", "SESSION_ID", "SECRET_KEY"}
|
||||
local prefix = token_types[math.random(#token_types)]
|
||||
local suffix = string.format("%08x", math.random(0xffffff))
|
||||
return prefix .. "_" .. token .. "_" .. suffix
|
||||
end
|
||||
|
||||
function enhance_response(text, response_type, path, token)
|
||||
local result = text
|
||||
local honeytoken = generate_honeytoken(token)
|
||||
|
||||
-- Add some fake sensitive data
|
||||
result = result .. "\n<!-- DEBUG: " .. honeytoken .. " -->"
|
||||
result = result .. "\n<div style='display:none'>Server ID: " .. token .. "</div>"
|
||||
|
||||
return result
|
||||
end
|
||||
"#
|
||||
.to_string();
|
||||
scripts_loaded = true;
|
||||
}
|
||||
|
||||
Self {
|
||||
script_content,
|
||||
scripts_loaded,
|
||||
}
|
||||
}
|
||||
|
||||
// Lua is a powerful configuration language we can use to expand functionality of
|
||||
// Eris, e.g., with fake tokens or honeytrap content.
|
||||
fn expand_response(&self, text: &str, response_type: &str, path: &str, token: &str) -> String {
|
||||
if !self.scripts_loaded {
|
||||
return format!("{text}\n<!-- Token: {token} -->");
|
||||
}
|
||||
|
||||
let lua = Lua::new();
|
||||
if let Err(e) = lua.load(&self.script_content).exec() {
|
||||
log::warn!("Error loading Lua script: {e}");
|
||||
return format!("{text}\n<!-- Error: Failed to load Lua script -->");
|
||||
}
|
||||
|
||||
let globals = lua.globals();
|
||||
match globals.get::<_, Function>("enhance_response") {
|
||||
Ok(enhance_func) => {
|
||||
match enhance_func.call::<_, String>((text, response_type, path, token)) {
|
||||
Ok(result) => result,
|
||||
Err(e) => {
|
||||
log::warn!("Error calling Lua function enhance_response: {e}");
|
||||
format!("{text}\n<!-- Error calling Lua enhance_response -->")
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("Lua enhance_response function not found: {e}");
|
||||
format!("{text}\n<!-- Lua enhance_response function not found -->")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find end of HTTP headers
|
||||
// XXX: I'm sure this could be made less fragile.
|
||||
fn find_header_end(data: &[u8]) -> Option<usize> {
|
||||
for i in 0..data.len().saturating_sub(3) {
|
||||
if data[i] == b'\r' && data[i + 1] == b'\n' && data[i + 2] == b'\r' && data[i + 3] == b'\n'
|
||||
{
|
||||
return Some(i + 4);
|
||||
}
|
||||
}
|
||||
None
|
||||
data.windows(4)
|
||||
.position(|window| window == b"\r\n\r\n")
|
||||
.map(|pos| pos + 4)
|
||||
}
|
||||
|
||||
// Extract path from raw request data
|
||||
|
@ -1379,18 +1276,6 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_script_manager_default_script() {
|
||||
let script_manager = ScriptManager::new("/nonexistent_directory");
|
||||
assert!(script_manager.scripts_loaded);
|
||||
assert!(
|
||||
script_manager
|
||||
.script_content
|
||||
.contains("generate_honeytoken")
|
||||
);
|
||||
assert!(script_manager.script_content.contains("enhance_response"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_bot_state() {
|
||||
let state = BotState::new("/tmp/eris_test", "/tmp/eris_test_cache");
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue