eris: move lua API to standalone module

Easier to work with now, yay.
This commit is contained in:
raf 2025-05-02 05:45:14 +03:00
commit 54f858aee9
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
5 changed files with 297 additions and 127 deletions

View file

@ -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");