use coraza::{Error, WafConfig};
#[test]
fn test_full_waf_lifecycle() {
let waf = WafConfig::new()
.unwrap()
.with_directives("SecRuleEngine DetectionOnly")
.build()
.unwrap();
let mut tx = waf.new_transaction();
tx.process_connection("127.0.0.1", 8080, "localhost", 80)
.unwrap();
tx.process_uri("/test", "GET", "HTTP/1.1").unwrap();
tx.add_request_header("Host", "localhost");
tx.process_request_headers().unwrap();
tx.process_response_headers(200, "HTTP/1.1").unwrap();
tx.process_response_body().unwrap();
tx.process_logging();
assert!(tx.intervention().is_none());
tx.close().unwrap();
}
#[test]
fn test_waf_blocks_bad_request() {
let waf = WafConfig::new()
.unwrap()
.with_directives(r#"SecRule REMOTE_ADDR "127.0.0.1" "id:1,phase:1,deny,log,status:403""#)
.build()
.unwrap();
let mut tx = waf.new_transaction();
tx.process_connection("127.0.0.1", 8080, "localhost", 80)
.unwrap();
tx.process_uri("/test", "GET", "HTTP/1.1").unwrap();
let result = tx.process_request_headers();
assert!(result.is_err());
tx.process_logging();
let intervention = tx.intervention().unwrap();
assert_eq!(intervention.status, 403);
assert!(intervention.is_disruptive());
}
#[test]
fn test_waf_redirect() {
let waf = WafConfig::new()
.unwrap()
.with_directives(
r#"SecRule ARGS:trigger "@streq yes" "id:10,phase:1,status:302,redirect:http://example.com""#,
)
.build()
.unwrap();
let mut tx = waf.new_transaction();
tx.process_connection("10.0.0.1", 12345, "localhost", 80)
.unwrap();
tx.add_get_argument("trigger", "yes");
tx.process_uri("/?trigger=yes", "GET", "HTTP/1.1").unwrap();
let result = tx.process_request_headers();
assert!(result.is_err());
tx.process_logging();
let intervention = tx.intervention().unwrap();
assert_eq!(intervention.status, 302);
assert!(intervention.is_redirect());
assert_eq!(intervention.data.as_deref(), Some("http://example.com"));
}
#[test]
fn test_multiple_transactions() {
let waf = WafConfig::new()
.unwrap()
.with_directives("SecRuleEngine DetectionOnly")
.build()
.unwrap();
for i in 0..5 {
let id = format!("tx-{}", i);
let mut tx = waf.new_transaction_with_id(&id);
tx.process_connection("127.0.0.1", 8080, "localhost", 80)
.unwrap();
tx.process_uri("/test", "GET", "HTTP/1.1").unwrap();
tx.process_request_headers().unwrap();
tx.process_logging();
assert!(tx.intervention().is_none());
}
}
#[test]
fn test_waf_rules_count() {
let waf = WafConfig::new()
.unwrap()
.with_directives("SecRuleEngine DetectionOnly")
.with_directives(r#"SecRule REMOTE_ADDR "127.0.0.1" "id:1,phase:1,pass""#)
.with_directives(r#"SecRule REMOTE_ADDR "10.0.0.1" "id:2,phase:1,pass""#)
.build()
.unwrap();
assert!(waf.rules_count() >= 2);
}
#[test]
fn test_waf_build_error() {
let result = WafConfig::new()
.unwrap()
.with_directives_from_file("/nonexistent/path/rules.conf")
.build();
assert!(result.is_err());
match result.unwrap_err() {
Error::WafCreation(_) => {} other => panic!("expected WafCreation error, got: {:?}", other),
}
}
#[test]
fn test_debug_log_callback() {
let log_count = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
let log_count_clone = log_count.clone();
let _waf = WafConfig::new()
.unwrap()
.with_directives("SecRuleEngine DetectionOnly")
.with_debug_log_callback(move |_level, _msg, _fields| {
log_count_clone.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
})
.build()
.unwrap();
}
#[test]
fn test_error_callback() {
let matched = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
let matched_clone = matched.clone();
let waf = WafConfig::new()
.unwrap()
.with_directives(
r#"SecRule REMOTE_ADDR "127.0.0.1" "id:1,phase:1,deny,log,msg:'test block',status:403""#,
)
.with_error_callback(move |rule| {
matched_clone
.lock()
.unwrap()
.push((rule.rule_id, rule.message.clone()));
})
.build()
.unwrap();
let mut tx = waf.new_transaction();
tx.process_connection("127.0.0.1", 8080, "localhost", 80)
.unwrap();
tx.process_uri("/test", "GET", "HTTP/1.1").unwrap();
let _ = tx.process_request_headers();
tx.process_logging();
let rules = matched.lock().unwrap();
assert!(!rules.is_empty(), "expected at least one matched rule");
assert_eq!(rules[0].0, 1); }
#[test]
fn test_transaction_with_id() {
let waf = WafConfig::new()
.unwrap()
.with_directives("SecRuleEngine DetectionOnly")
.build()
.unwrap();
let tx = waf.new_transaction_with_id("my-custom-id");
assert!(!tx.is_closed());
}
#[test]
fn test_intervention_none_when_no_match() {
let waf = WafConfig::new()
.unwrap()
.with_directives("SecRuleEngine DetectionOnly")
.build()
.unwrap();
let mut tx = waf.new_transaction();
tx.process_connection("127.0.0.1", 8080, "localhost", 80)
.unwrap();
tx.process_uri("/test", "GET", "HTTP/1.1").unwrap();
tx.process_request_headers().unwrap();
assert!(tx.intervention().is_none());
}
#[test]
fn test_request_body_processing() {
let waf = WafConfig::new()
.unwrap()
.with_directives("SecRuleEngine DetectionOnly")
.with_directives("SecRequestBodyAccess On")
.build()
.unwrap();
let mut tx = waf.new_transaction();
tx.process_connection("127.0.0.1", 8080, "localhost", 80)
.unwrap();
tx.process_uri("/submit", "POST", "HTTP/1.1").unwrap();
tx.add_request_header("Content-Type", "application/x-www-form-urlencoded");
tx.process_request_headers().unwrap();
tx.append_request_body(b"hello=world&foo=bar").unwrap();
tx.process_request_body().unwrap();
tx.process_response_headers(200, "HTTP/1.1").unwrap();
tx.process_response_body().unwrap();
tx.process_logging();
assert!(tx.intervention().is_none());
}
#[test]
fn test_response_body_processing() {
let waf = WafConfig::new()
.unwrap()
.with_directives("SecRuleEngine DetectionOnly")
.with_directives("SecResponseBodyAccess On")
.with_directives("SecResponseBodyMimeType text/html text/plain")
.build()
.unwrap();
let mut tx = waf.new_transaction();
tx.process_connection("127.0.0.1", 8080, "localhost", 80)
.unwrap();
tx.process_uri("/page", "GET", "HTTP/1.1").unwrap();
tx.process_request_headers().unwrap();
tx.process_response_headers(200, "HTTP/1.1").unwrap();
let _processable = tx.is_response_body_processable();
tx.append_response_body(b"<html><body>Hello</body></html>")
.unwrap();
tx.process_response_body().unwrap();
tx.process_logging();
assert!(tx.intervention().is_none());
}
#[test]
fn test_add_request_headers_batch() {
let waf = WafConfig::new()
.unwrap()
.with_directives("SecRuleEngine DetectionOnly")
.build()
.unwrap();
let mut tx = waf.new_transaction();
tx.process_connection("127.0.0.1", 8080, "localhost", 80)
.unwrap();
tx.process_uri("/test", "GET", "HTTP/1.1").unwrap();
let headers = vec![
("Host", "localhost"),
("User-Agent", "test-agent"),
("Accept", "text/html"),
];
tx.add_request_headers(&headers);
tx.process_request_headers().unwrap();
tx.process_logging();
assert!(tx.intervention().is_none());
}
#[test]
fn test_add_response_headers_batch() {
let waf = WafConfig::new()
.unwrap()
.with_directives("SecRuleEngine DetectionOnly")
.build()
.unwrap();
let mut tx = waf.new_transaction();
tx.process_connection("127.0.0.1", 8080, "localhost", 80)
.unwrap();
tx.process_uri("/test", "GET", "HTTP/1.1").unwrap();
tx.process_request_headers().unwrap();
let headers = vec![("Content-Type", "text/html"), ("X-Custom", "value")];
tx.process_response_headers(200, "HTTP/1.1").unwrap();
tx.add_response_headers(&headers);
tx.process_response_body().unwrap();
tx.process_logging();
assert!(tx.intervention().is_none());
}