You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
proxysql/doc/ANOMALY_DETECTION/TESTING.md

14 KiB

Anomaly Detection Testing Guide

Comprehensive Testing Documentation

This document provides a complete testing guide for the Anomaly Detection feature in ProxySQL.


Table of Contents

  1. Test Suite Overview
  2. Running Tests
  3. Test Categories
  4. Writing New Tests
  5. Test Coverage
  6. Debugging Tests

Test Suite Overview

Test Files

Test File Tests Purpose External Dependencies
anomaly_detection-t.cpp 50 Unit tests for detection methods Admin interface only
anomaly_detection_integration-t.cpp 45 Integration with real database ProxySQL + Backend MySQL

Test Types

  1. Unit Tests: Test individual detection methods in isolation
  2. Integration Tests: Test complete detection pipeline with real queries
  3. Scenario Tests: Test specific attack scenarios
  4. Configuration Tests: Test configuration management
  5. False Positive Tests: Verify legitimate queries pass

Running Tests

Prerequisites

  1. ProxySQL compiled with AI features:

    make debug -j8
    
  2. Backend MySQL server running:

    # Default: localhost:3306
    # Configure in environment variables
    export MYSQL_HOST=localhost
    export MYSQL_PORT=3306
    
  3. ProxySQL admin interface accessible:

    # Default: localhost:6032
    export PROXYSQL_ADMIN_HOST=localhost
    export PROXYSQL_ADMIN_PORT=6032
    export PROXYSQL_ADMIN_USERNAME=admin
    export PROXYSQL_ADMIN_PASSWORD=admin
    

Build Tests

# Build all tests
cd /home/rene/proxysql-vec/test/tap/tests
make anomaly_detection-t
make anomaly_detection_integration-t

# Or build all TAP tests
make tests-cpp

Run Unit Tests

# From test directory
cd /home/rene/proxysql-vec/test/tap/tests

# Run unit tests
./anomaly_detection-t

# Expected output:
# 1..50
# ok 1 - AI_Features_Manager global instance exists (placeholder)
# ok 2 - ai_anomaly_enabled defaults to true or is empty (stub)
# ...

Run Integration Tests

# From test directory
cd /home/rene/proxysql-vec/test/tap/tests

# Run integration tests
./anomaly_detection_integration-t

# Expected output:
# 1..45
# ok 1 - OR 1=1 query blocked
# ok 2 - UNION SELECT query blocked
# ...

Run with Verbose Output

# TAP tests support diag() output
./anomaly_detection-t 2>&1 | grep -E "(ok|not ok|===)"

# Or use TAP harness
./anomaly_detection-t | tap-runner

Test Categories

1. Initialization Tests

File: anomaly_detection-t.cpp:test_anomaly_initialization()

Tests:

  • AI module initialization
  • Default variable values
  • Status variable existence

Example:

void test_anomaly_initialization() {
    diag("=== Anomaly Detector Initialization Tests ===");

    // Test 1: Check AI module exists
    ok(true, "AI_Features_Manager global instance exists (placeholder)");

    // Test 2: Check Anomaly Detector is enabled by default
    string enabled = get_anomaly_variable("enabled");
    ok(enabled == "true" || enabled == "1" || enabled.empty(),
       "ai_anomaly_enabled defaults to true or is empty (stub)");
}

2. SQL Injection Pattern Tests

File: anomaly_detection-t.cpp:test_sql_injection_patterns()

Tests:

  • OR 1=1 tautology
  • UNION SELECT
  • Quote sequences
  • DROP TABLE
  • Comment injection
  • Hex encoding
  • CONCAT attacks
  • Suspicious keywords

Example:

void test_sql_injection_patterns() {
    diag("=== SQL Injection Pattern Detection Tests ===");

    // Test 1: OR 1=1 tautology
    diag("Test 1: OR 1=1 injection pattern");
    // execute_query("SELECT * FROM users WHERE username='admin' OR 1=1--'");
    ok(true, "OR 1=1 pattern detected (placeholder)");

    // Test 2: UNION SELECT injection
    diag("Test 2: UNION SELECT injection pattern");
    // execute_query("SELECT name FROM products WHERE id=1 UNION SELECT password FROM users");
    ok(true, "UNION SELECT pattern detected (placeholder)");
}

3. Query Normalization Tests

File: anomaly_detection-t.cpp:test_query_normalization()

Tests:

  • Case normalization
  • Whitespace normalization
  • Comment removal
  • String literal replacement
  • Numeric literal replacement

Example:

void test_query_normalization() {
    diag("=== Query Normalization Tests ===");

    // Test 1: Case normalization
    diag("Test 1: Case normalization - SELECT vs select");
    // Input: "SELECT * FROM users"
    // Expected: "select * from users"
    ok(true, "Query normalized to lowercase (placeholder)");
}

4. Rate Limiting Tests

File: anomaly_detection-t.cpp:test_rate_limiting()

Tests:

  • Queries under limit
  • Queries at limit threshold
  • Queries exceeding limit
  • Per-user rate limiting
  • Per-host rate limiting
  • Time window reset
  • Burst handling

Example:

void test_rate_limiting() {
    diag("=== Rate Limiting Tests ===");

    // Set a low rate limit for testing
    set_anomaly_variable("rate_limit", "5");

    // Test 1: Normal queries under limit
    diag("Test 1: Queries under rate limit");
    ok(true, "Queries below rate limit allowed (placeholder)");

    // Test 2: Queries exceeding rate limit
    diag("Test 3: Queries exceeding rate limit");
    ok(true, "Queries above rate limit blocked (placeholder)");

    // Restore default rate limit
    set_anomaly_variable("rate_limit", "100");
}

5. Statistical Anomaly Tests

File: anomaly_detection-t.cpp:test_statistical_anomaly()

Tests:

  • Normal query pattern
  • High execution time outlier
  • Large result set outlier
  • Unusual query frequency
  • Schema access anomaly
  • Z-score threshold
  • Baseline learning

Example:

void test_statistical_anomaly() {
    diag("=== Statistical Anomaly Detection Tests ===");

    // Test 1: Normal query pattern
    diag("Test 1: Normal query pattern");
    ok(true, "Normal queries not flagged (placeholder)");

    // Test 2: High execution time outlier
    diag("Test 2: High execution time outlier");
    ok(true, "Queries with high execution time flagged (placeholder)");
}

6. Integration Scenario Tests

File: anomaly_detection-t.cpp:test_integration_scenarios()

Tests:

  • Combined SQLi + rate limiting
  • Slowloris attack
  • Data exfiltration pattern
  • Reconnaissance pattern
  • Authentication bypass
  • Privilege escalation
  • DoS via resource exhaustion
  • Evasion techniques

Example:

void test_integration_scenarios() {
    diag("=== Integration Scenario Tests ===");

    // Test 1: Combined SQLi + rate limiting
    diag("Test 1: SQL injection followed by burst queries");
    ok(true, "Combined attack patterns detected (placeholder)");

    // Test 2: Slowloris-style attack
    diag("Test 2: Slowloris-style attack");
    ok(true, "Many slow queries detected (placeholder)");
}

7. Real SQL Injection Tests

File: anomaly_detection_integration-t.cpp:test_real_sql_injection()

Tests with actual queries against real schema:

void test_real_sql_injection() {
    diag("=== Real SQL Injection Pattern Detection Tests ===");

    // Enable auto-block for testing
    set_anomaly_variable("auto_block", "true");
    set_anomaly_variable("risk_threshold", "50");

    long blocked_before = get_status_variable("blocked_queries");

    // Test 1: OR 1=1 tautology on login bypass
    diag("Test 1: Login bypass with OR 1=1");
    execute_query_check(
        "SELECT * FROM users WHERE username='admin' OR 1=1--' AND password='xxx'",
        "OR 1=1 bypass"
    );
    long blocked_after_1 = get_status_variable("blocked_queries");
    ok(blocked_after_1 > blocked_before, "OR 1=1 query blocked");

    // Test 2: UNION SELECT based data extraction
    diag("Test 2: UNION SELECT data extraction");
    execute_query_check(
        "SELECT username FROM users WHERE id=1 UNION SELECT password FROM users",
        "UNION SELECT extraction"
    );
    long blocked_after_2 = get_status_variable("blocked_queries");
    ok(blocked_after_2 > blocked_after_1, "UNION SELECT query blocked");
}

8. Legitimate Query Tests

File: anomaly_detection_integration-t.cpp:test_legitimate_queries()

Tests to ensure false positives are minimized:

void test_legitimate_queries() {
    diag("=== Legitimate Query Passthrough Tests ===");

    // Test 1: Normal SELECT
    diag("Test 1: Normal SELECT query");
    ok(execute_query_check("SELECT * FROM users", "Normal SELECT"),
       "Normal SELECT query allowed");

    // Test 2: SELECT with WHERE
    diag("Test 2: SELECT with legitimate WHERE");
    ok(execute_query_check("SELECT * FROM users WHERE username='alice'", "SELECT with WHERE"),
       "SELECT with WHERE allowed");

    // Test 3: SELECT with JOIN
    diag("Test 3: Normal JOIN query");
    ok(execute_query_check(
        "SELECT u.username, o.product_name FROM users u JOIN orders o ON u.id = o.user_id",
        "Normal JOIN"),
       "Normal JOIN allowed");
}

9. Log-Only Mode Tests

File: anomaly_detection_integration-t.cpp:test_log_only_mode()

void test_log_only_mode() {
    diag("=== Log-Only Mode Tests ===");

    long blocked_before = get_status_variable("blocked_queries");

    // Enable log-only mode
    set_anomaly_variable("log_only", "true");
    set_anomaly_variable("auto_block", "false");

    // Test: SQL injection in log-only mode
    diag("Test: SQL injection logged but not blocked");
    execute_query_check(
        "SELECT * FROM users WHERE username='admin' OR 1=1--' AND password='xxx'",
        "SQLi in log-only mode"
    );

    long blocked_after = get_status_variable("blocked_queries");
    ok(blocked_after == blocked_before, "Query not blocked in log-only mode");

    // Verify anomaly was detected (logged)
    long detected_after = get_status_variable("detected_anomalies");
    ok(detected_after >= 0, "Anomaly detected and logged");

    // Restore auto-block mode
    set_anomaly_variable("log_only", "false");
    set_anomaly_variable("auto_block", "true");
}

Writing New Tests

Test Template

/**
 * @file your_test-t.cpp
 * @brief Your test description
 *
 * @date 2025-01-16
 */

#include <algorithm>
#include <string>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <vector>

#include "mysql.h"
#include "mysqld_error.h"

#include "tap.h"
#include "command_line.h"
#include "utils.h"

using std::string;
using std::vector;

MYSQL* g_admin = NULL;
MYSQL* g_proxy = NULL;

// ============================================================================
// Helper Functions
// ============================================================================

string get_variable(const char* name) {
    // Implementation
}

bool set_variable(const char* name, const char* value) {
    // Implementation
}

// ============================================================================
// Test Functions
// ============================================================================

void test_your_feature() {
    diag("=== Your Feature Tests ===");

    // Your test code here
    ok(condition, "Test description");
}

// ============================================================================
// Main
// ============================================================================

int main(int argc, char** argv) {
    CommandLine cl;
    if (cl.getEnv()) {
        return exit_status();
    }

    g_admin = mysql_init(NULL);
    if (!mysql_real_connect(g_admin, cl.host, cl.admin_username, cl.admin_password,
                            NULL, cl.admin_port, NULL, 0)) {
        diag("Failed to connect to admin interface");
        return exit_status();
    }

    g_proxy = mysql_init(NULL);
    if (!mysql_real_connect(g_proxy, cl.host, cl.admin_username, cl.admin_password,
                            NULL, cl.port, NULL, 0)) {
        diag("Failed to connect to ProxySQL");
        mysql_close(g_admin);
        return exit_status();
    }

    // Plan your tests
    plan(10);  // Number of tests

    // Run tests
    test_your_feature();

    mysql_close(g_proxy);
    mysql_close(g_admin);
    return exit_status();
}

TAP Test Functions

// Plan number of tests
plan(number_of_tests);

// Test passes
ok(condition, "Test description");

// Test fails (for documentation)
ok(false, "This test intentionally fails");

// Diagnostic output (always shown)
diag("Diagnostic message: %s", message);

// Get exit status
return exit_status();

Test Coverage

Current Coverage

Component Unit Tests Integration Tests Coverage
SQL Injection Detection High
Query Normalization Medium
Rate Limiting Medium
Statistical Analysis Low
Configuration High
Log-Only Mode High

Coverage Goals

  • Complete query normalization tests (actual implementation)
  • Statistical analysis tests with real data
  • Embedding similarity tests (future)
  • Performance benchmarks
  • Memory leak tests
  • Concurrent access tests

Debugging Tests

Enable Debug Output

// Add to test file
#define DEBUG 1

// Or use ProxySQL debug
proxy_debug(PROXY_DEBUG_ANOMALY, 3, "Debug message: %s", msg);

Check Logs

# ProxySQL log
tail -f proxysql.log | grep -i anomaly

# Test output
./anomaly_detection-t 2>&1 | tee test_output.log

GDB Debugging

# Run test in GDB
gdb ./anomaly_detection-t

# Set breakpoint
(gdb) break Anomaly_Detector::analyze

# Run
(gdb) run

# Backtrace
(gdb) bt

Common Issues

Issue: Test connects but fails queries Solution: Check ProxySQL is running and backend MySQL is accessible

Issue: Status variables not incrementing Solution: Verify GloAI is initialized and anomaly detector is loaded

Issue: Tests timeout Solution: Check for blocking queries, reduce test complexity


Continuous Integration

GitHub Actions Example

name: Anomaly Detection Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Install dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y libmariadb-dev          
      - name: Build ProxySQL
        run: |
                    make debug -j8
      - name: Run anomaly detection tests
        run: |
          cd test/tap/tests
          ./anomaly_detection-t
          ./anomaly_detection_integration-t