Security fixes: #1 Buffer overflow: Validate socket path length against sun_path limit - Add MAX_SOCKET_PATH_LEN constant - Reject paths >= 108 bytes before snprintf #2,#3 NULL pointer dereference: Add NULL checks - r->connection->local_ip: use conditional append - r->protocol: fallback to "UNKNOWN" if NULL #4 Sensitive headers blacklist: Prevent credential leakage - Add DEFAULT_SENSITIVE_HEADERS[] blacklist - Block: Authorization, Cookie, Set-Cookie, X-Api-Key, etc. - Log skipped headers at DEBUG level only #5 Memory exhaustion DoS: Add MAX_JSON_SIZE limit (64KB) - Check buffer size before adding headers - Truncate header list if limit reached #6 Socket permissions: Change 0o666 → 0o660 - Owner and group only (not world-writable) - Apache user must be in socket's group #7 Race condition: Add mutex for FD access in worker/event MPMs - apr_thread_mutex_t protects socket_fd - FD_MUTEX_LOCK/UNLOCK macros - Created in reqin_log_create_server_conf() #8 Timestamp overflow: Document 2262 limitation - Add comment explaining apr_time_t limits - Safe until ~2262 (uint64 nanoseconds) #9 Error logging verbosity: Reduce information disclosure - APLOG_ERR: Generic messages only - APLOG_DEBUG: Detailed error information #10 Socket path security: Move from /tmp to /var/run - Update socket_consumer.py, test scripts - Use environment variable MOD_REQIN_LOG_SOCKET - More secure default location Files modified: - src/mod_reqin_log.c: All security fixes - scripts/socket_consumer.py: Permissions, path - scripts/run_integration_tests.sh: Path security - scripts/test_unix_socket.sh: Path security - tests/integration/test_integration.py: Path security Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
264 lines
6.8 KiB
Bash
Executable File
264 lines
6.8 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# run_integration_tests.sh - Integration test script for mod_reqin_log
|
|
#
|
|
# This script runs integration tests for the mod_reqin_log module.
|
|
# It requires:
|
|
# - Apache HTTPD with the module loaded
|
|
# - A running socket consumer
|
|
#
|
|
|
|
set -e
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
# Use /var/run for production (more secure than /tmp)
|
|
SOCKET_PATH="${SOCKET_PATH:-/var/run/mod_reqin_log.sock}"
|
|
LOG_FILE="${LOG_FILE:-/var/log/mod_reqin_log_test.log}"
|
|
APACHE_URL="${APACHE_URL:-http://localhost:8080}"
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Counters
|
|
TESTS_RUN=0
|
|
TESTS_PASSED=0
|
|
TESTS_FAILED=0
|
|
|
|
log_info() {
|
|
echo -e "${YELLOW}[INFO]${NC} $1" >&2
|
|
}
|
|
|
|
log_pass() {
|
|
echo -e "${GREEN}[PASS]${NC} $1" >&2
|
|
((TESTS_PASSED++)) || true
|
|
}
|
|
|
|
log_fail() {
|
|
echo -e "${RED}[FAIL]${NC} $1" >&2
|
|
((TESTS_FAILED++)) || true
|
|
}
|
|
|
|
cleanup() {
|
|
log_info "Cleaning up..."
|
|
rm -f "$LOG_FILE"
|
|
rm -f "$SOCKET_PATH"
|
|
}
|
|
|
|
trap cleanup EXIT
|
|
|
|
# Check prerequisites
|
|
check_prerequisites() {
|
|
log_info "Checking prerequisites..."
|
|
|
|
if ! command -v curl &> /dev/null; then
|
|
echo "Error: curl is required but not installed."
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v python3 &> /dev/null; then
|
|
echo "Error: python3 is required but not installed."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Strip timestamp prefix from log line
|
|
strip_log_prefix() {
|
|
# Remove [YYYY-MM-DD HH:MM:SS] prefix from log lines
|
|
sed 's/^\[[0-9-]* [0-9:]*\] //'
|
|
}
|
|
|
|
# Test: Basic request logging
|
|
test_basic_logging() {
|
|
((TESTS_RUN++)) || true
|
|
log_info "Test: Basic request logging"
|
|
|
|
curl -s "$APACHE_URL/" > /dev/null
|
|
sleep 1
|
|
|
|
# Strip prefix and check for method
|
|
if strip_log_prefix < "$LOG_FILE" 2>/dev/null | grep -q '"method":"GET"'; then
|
|
log_pass "Basic logging test"
|
|
return 0
|
|
else
|
|
log_fail "Basic logging test - No GET method found in logs"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Test: Custom header logging
|
|
test_custom_headers() {
|
|
((TESTS_RUN++)) || true
|
|
log_info "Test: Custom header logging"
|
|
|
|
curl -s -H "X-Request-Id: test-12345" "$APACHE_URL/" > /dev/null
|
|
sleep 1
|
|
|
|
if strip_log_prefix < "$LOG_FILE" 2>/dev/null | grep -q '"header_X-Request-Id":"test-12345"'; then
|
|
log_pass "Custom header logging test"
|
|
return 0
|
|
else
|
|
log_fail "Custom header logging test - X-Request-Id not found in logs"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Test: Multiple headers
|
|
test_multiple_headers() {
|
|
((TESTS_RUN++)) || true
|
|
log_info "Test: Multiple headers"
|
|
|
|
curl -s \
|
|
-H "X-Request-Id: req-abc" \
|
|
-H "X-Trace-Id: trace-xyz" \
|
|
"$APACHE_URL/" > /dev/null
|
|
sleep 1
|
|
|
|
local stripped_logs=$(strip_log_prefix < "$LOG_FILE" 2>/dev/null)
|
|
local found_request_id=$(echo "$stripped_logs" | grep -c '"header_X-Request-Id":"req-abc"' || echo 0)
|
|
local found_trace_id=$(echo "$stripped_logs" | grep -c '"header_X-Trace-Id":"trace-xyz"' || echo 0)
|
|
|
|
if [ "$found_request_id" -gt 0 ] && [ "$found_trace_id" -gt 0 ]; then
|
|
log_pass "Multiple headers test"
|
|
return 0
|
|
else
|
|
log_fail "Multiple headers test - Not all headers found"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Test: JSON format validation
|
|
test_json_format() {
|
|
((TESTS_RUN++)) || true
|
|
log_info "Test: JSON format validation"
|
|
|
|
curl -s "$APACHE_URL/" > /dev/null
|
|
sleep 1
|
|
|
|
# Get last line, strip prefix, and validate JSON
|
|
local last_line=$(tail -1 "$LOG_FILE" 2>/dev/null | strip_log_prefix)
|
|
|
|
if echo "$last_line" | python3 -m json.tool > /dev/null 2>&1; then
|
|
log_pass "JSON format validation test"
|
|
return 0
|
|
else
|
|
log_fail "JSON format validation test - Invalid JSON format"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Test: Required fields presence
|
|
test_required_fields() {
|
|
((TESTS_RUN++)) || true
|
|
log_info "Test: Required fields presence"
|
|
|
|
curl -s "$APACHE_URL/" > /dev/null
|
|
sleep 1
|
|
|
|
local last_line=$(tail -1 "$LOG_FILE" 2>/dev/null | strip_log_prefix)
|
|
|
|
local required_fields=("time" "timestamp" "src_ip" "dst_ip" "method" "path" "host")
|
|
local all_present=true
|
|
|
|
for field in "${required_fields[@]}"; do
|
|
if ! echo "$last_line" | grep -q "\"$field\":"; then
|
|
all_present=false
|
|
break
|
|
fi
|
|
done
|
|
|
|
if $all_present; then
|
|
log_pass "Required fields presence test"
|
|
return 0
|
|
else
|
|
log_fail "Required fields presence test - Missing required fields"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Test: HTTP method variations
|
|
test_method_variations() {
|
|
((TESTS_RUN++)) || true
|
|
log_info "Test: HTTP method variations"
|
|
|
|
curl -s -X POST "$APACHE_URL/" > /dev/null
|
|
curl -s -X PUT "$APACHE_URL/" > /dev/null
|
|
curl -s -X DELETE "$APACHE_URL/" > /dev/null
|
|
sleep 1
|
|
|
|
local stripped_logs=$(strip_log_prefix < "$LOG_FILE" 2>/dev/null)
|
|
local found_post=$(echo "$stripped_logs" | grep -c '"method":"POST"' || echo 0)
|
|
local found_put=$(echo "$stripped_logs" | grep -c '"method":"PUT"' || echo 0)
|
|
local found_delete=$(echo "$stripped_logs" | grep -c '"method":"DELETE"' || echo 0)
|
|
|
|
if [ "$found_post" -gt 0 ] && [ "$found_put" -gt 0 ] && [ "$found_delete" -gt 0 ]; then
|
|
log_pass "HTTP method variations test"
|
|
return 0
|
|
else
|
|
log_fail "HTTP method variations test - Not all methods found"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Test: Path logging
|
|
test_path_logging() {
|
|
((TESTS_RUN++)) || true
|
|
log_info "Test: Path logging"
|
|
|
|
curl -s "$APACHE_URL/api/users" > /dev/null
|
|
curl -s "$APACHE_URL/foo/bar/baz" > /dev/null
|
|
sleep 1
|
|
|
|
local stripped_logs=$(strip_log_prefix < "$LOG_FILE" 2>/dev/null)
|
|
local found_api=$(echo "$stripped_logs" | grep -c '"path":"/api/users"' || echo 0)
|
|
local found_foo=$(echo "$stripped_logs" | grep -c '"path":"/foo/bar/baz"' || echo 0)
|
|
|
|
if [ "$found_api" -gt 0 ] && [ "$found_foo" -gt 0 ]; then
|
|
log_pass "Path logging test"
|
|
return 0
|
|
else
|
|
log_fail "Path logging test - Not all paths found"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Main test runner
|
|
main() {
|
|
echo "========================================"
|
|
echo "mod_reqin_log Integration Tests"
|
|
echo "========================================"
|
|
echo ""
|
|
|
|
check_prerequisites
|
|
|
|
# Run individual tests
|
|
test_basic_logging || true
|
|
test_custom_headers || true
|
|
test_multiple_headers || true
|
|
test_json_format || true
|
|
test_required_fields || true
|
|
test_method_variations || true
|
|
test_path_logging || true
|
|
|
|
# Summary
|
|
echo ""
|
|
echo "========================================"
|
|
echo "Test Summary"
|
|
echo "========================================"
|
|
echo "Tests run: $TESTS_RUN"
|
|
echo -e "Tests passed: ${GREEN}$TESTS_PASSED${NC}"
|
|
echo -e "Tests failed: ${RED}$TESTS_FAILED${NC}"
|
|
echo ""
|
|
|
|
if [ $TESTS_FAILED -gt 0 ]; then
|
|
exit 1
|
|
fi
|
|
|
|
echo -e "${GREEN}All tests passed!${NC}"
|
|
exit 0
|
|
}
|
|
|
|
main "$@"
|