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>
319 lines
8.1 KiB
Bash
Executable File
319 lines
8.1 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# test_unix_socket.sh - Integration test for mod_reqin_log Unix socket logging
|
|
#
|
|
# This test verifies that:
|
|
# 1. The module connects to a Unix socket
|
|
# 2. HTTP requests generate JSON log entries
|
|
# 3. Log entries are properly formatted and sent to the socket
|
|
#
|
|
|
|
set -e
|
|
|
|
# Use /var/run for production (more secure than /tmp)
|
|
SOCKET_PATH="${SOCKET_PATH:-/var/run/mod_reqin_log_test.sock}"
|
|
LOG_OUTPUT="${LOG_OUTPUT:-/var/log/mod_reqin_log_output.jsonl}"
|
|
APACHE_PORT="${APACHE_PORT:-8080}"
|
|
TIMEOUT=30
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m'
|
|
|
|
log_info() {
|
|
echo -e "${YELLOW}[INFO]${NC} $1"
|
|
}
|
|
|
|
log_pass() {
|
|
echo -e "${GREEN}[PASS]${NC} $1"
|
|
}
|
|
|
|
log_fail() {
|
|
echo -e "${RED}[FAIL]${NC} $1"
|
|
}
|
|
|
|
cleanup() {
|
|
log_info "Cleaning up..."
|
|
rm -f "$SOCKET_PATH" "$LOG_OUTPUT"
|
|
pkill -f "socket_listener.py" 2>/dev/null || true
|
|
pkill -f "apache.*test" 2>/dev/null || true
|
|
}
|
|
|
|
trap cleanup EXIT
|
|
|
|
# Check dependencies
|
|
check_dependencies() {
|
|
log_info "Checking dependencies..."
|
|
if ! command -v curl &> /dev/null; then
|
|
log_fail "curl is required but not installed"
|
|
exit 1
|
|
fi
|
|
if ! command -v python3 &> /dev/null; then
|
|
log_fail "python3 is required but not installed"
|
|
exit 1
|
|
fi
|
|
log_pass "Dependencies OK"
|
|
}
|
|
|
|
# Start Unix socket listener that logs received data
|
|
start_socket_listener() {
|
|
log_info "Starting Unix socket listener on $SOCKET_PATH..."
|
|
rm -f "$SOCKET_PATH"
|
|
|
|
# Use Python script to listen on Unix socket
|
|
python3 /build/scripts/socket_listener.py "$SOCKET_PATH" -o "$LOG_OUTPUT" &
|
|
LISTENER_PID=$!
|
|
|
|
sleep 2
|
|
|
|
if ! kill -0 $LISTENER_PID 2>/dev/null; then
|
|
log_fail "Failed to start socket listener"
|
|
exit 1
|
|
fi
|
|
|
|
log_pass "Socket listener started (PID: $LISTENER_PID)"
|
|
}
|
|
|
|
# Create Apache test configuration
|
|
create_apache_config() {
|
|
log_info "Creating Apache test configuration..."
|
|
|
|
cat > /tmp/httpd_test.conf << EOF
|
|
ServerRoot "/etc/httpd"
|
|
Listen $APACHE_PORT
|
|
ServerName localhost
|
|
|
|
LoadModule reqin_log_module /build/.libs/mod_reqin_log.so
|
|
LoadModule mpm_event_module modules/mod_mpm_event.so
|
|
LoadModule authz_core_module modules/mod_authz_core.so
|
|
LoadModule dir_module modules/mod_dir.so
|
|
LoadModule mime_module modules/mod_mime.so
|
|
LoadModule unixd_module modules/mod_unixd.so
|
|
LoadModule log_config_module modules/mod_log_config.so
|
|
|
|
User apache
|
|
Group apache
|
|
|
|
TypesConfig /etc/mime.types
|
|
DirectoryIndex index.html
|
|
|
|
JsonSockLogEnabled On
|
|
JsonSockLogSocket "$SOCKET_PATH"
|
|
JsonSockLogHeaders X-Request-Id User-Agent X-Test-Header
|
|
JsonSockLogMaxHeaders 10
|
|
JsonSockLogMaxHeaderValueLen 256
|
|
JsonSockLogReconnectInterval 5
|
|
JsonSockLogErrorReportInterval 5
|
|
|
|
<VirtualHost *:$APACHE_PORT>
|
|
DocumentRoot /var/www/html
|
|
<Directory /var/www/html>
|
|
Require all granted
|
|
</Directory>
|
|
</VirtualHost>
|
|
|
|
ErrorLog /dev/stderr
|
|
LogLevel warn
|
|
EOF
|
|
|
|
log_pass "Apache configuration created"
|
|
}
|
|
|
|
# Start Apache with test configuration
|
|
start_apache() {
|
|
log_info "Starting Apache with mod_reqin_log..."
|
|
|
|
# Create document root if needed
|
|
mkdir -p /var/www/html
|
|
echo "<html><body><h1>Test</h1></body></html>" > /var/www/html/index.html
|
|
|
|
# Check socket exists
|
|
if [ ! -S "$SOCKET_PATH" ]; then
|
|
log_fail "Socket file does not exist: $SOCKET_PATH"
|
|
ls -la /tmp/*.sock 2>&1 || true
|
|
exit 1
|
|
fi
|
|
log_info "Socket file exists: $SOCKET_PATH"
|
|
ls -la "$SOCKET_PATH"
|
|
|
|
# Start Apache and capture stderr
|
|
httpd -f /tmp/httpd_test.conf -DFOREGROUND 2>&1 &
|
|
APACHE_PID=$!
|
|
|
|
# Wait for Apache to start
|
|
sleep 2
|
|
|
|
if ! kill -0 $APACHE_PID 2>/dev/null; then
|
|
log_fail "Failed to start Apache"
|
|
exit 1
|
|
fi
|
|
|
|
log_pass "Apache started (PID: $APACHE_PID)"
|
|
|
|
# Wait for Apache workers to initialize and connect to socket
|
|
log_info "Waiting for Apache workers to initialize..."
|
|
sleep 3
|
|
}
|
|
|
|
# Send test HTTP requests
|
|
send_test_requests() {
|
|
log_info "Sending test HTTP requests..."
|
|
|
|
# Health check first
|
|
local retries=5
|
|
while [ $retries -gt 0 ]; do
|
|
if curl -s -o /dev/null -w "%{http_code}" "http://localhost:$APACHE_PORT/" | grep -q "200"; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
retries=$((retries - 1))
|
|
done
|
|
|
|
if [ $retries -eq 0 ]; then
|
|
log_fail "Apache health check failed"
|
|
return 1
|
|
fi
|
|
log_info "Apache health check passed"
|
|
|
|
# Request 1: Basic GET
|
|
curl -s "http://localhost:$APACHE_PORT/" > /dev/null
|
|
sleep 0.5
|
|
|
|
# Request 2: With custom headers
|
|
curl -s -H "X-Request-Id: test-12345" "http://localhost:$APACHE_PORT/" > /dev/null
|
|
sleep 0.5
|
|
|
|
# Request 3: POST request
|
|
curl -s -X POST -d "test=data" "http://localhost:$APACHE_PORT/" > /dev/null
|
|
sleep 0.5
|
|
|
|
# Request 4: With User-Agent
|
|
curl -s -A "TestAgent/1.0" "http://localhost:$APACHE_PORT/api/test" > /dev/null
|
|
sleep 0.5
|
|
|
|
# Request 5: Multiple headers
|
|
curl -s \
|
|
-H "X-Request-Id: req-abc" \
|
|
-H "X-Test-Header: header-value" \
|
|
-H "User-Agent: Mozilla/5.0" \
|
|
"http://localhost:$APACHE_PORT/page" > /dev/null
|
|
sleep 1
|
|
|
|
log_pass "Test requests sent"
|
|
}
|
|
|
|
# Verify log output
|
|
verify_logs() {
|
|
log_info "Verifying log output..."
|
|
|
|
if [ ! -f "$LOG_OUTPUT" ]; then
|
|
log_fail "Log file not created"
|
|
return 1
|
|
fi
|
|
|
|
local log_count=$(wc -l < "$LOG_OUTPUT")
|
|
if [ "$log_count" -lt 1 ]; then
|
|
log_fail "Expected at least 1 log entry, got $log_count"
|
|
return 1
|
|
fi
|
|
|
|
log_pass "Received $log_count log entries"
|
|
|
|
# Show all log entries
|
|
log_info "Log file contents:"
|
|
cat "$LOG_OUTPUT"
|
|
|
|
# Verify JSON format
|
|
log_info "Validating JSON format..."
|
|
local invalid_json=0
|
|
while IFS= read -r line; do
|
|
if [ -n "$line" ]; then
|
|
if ! echo "$line" | python3 -m json.tool > /dev/null 2>&1; then
|
|
log_fail "Invalid JSON: $line"
|
|
invalid_json=1
|
|
fi
|
|
fi
|
|
done < "$LOG_OUTPUT"
|
|
|
|
if [ $invalid_json -eq 0 ]; then
|
|
log_pass "All JSON entries are valid"
|
|
fi
|
|
|
|
# Verify required fields
|
|
log_info "Checking required fields..."
|
|
local first_line=$(head -1 "$LOG_OUTPUT")
|
|
|
|
local required_fields=("time" "timestamp" "src_ip" "dst_ip" "method" "path" "host")
|
|
local missing_fields=0
|
|
|
|
for field in "${required_fields[@]}"; do
|
|
if ! echo "$first_line" | grep -q "\"$field\":"; then
|
|
log_fail "Missing required field: $field"
|
|
missing_fields=1
|
|
fi
|
|
done
|
|
|
|
if [ $missing_fields -eq 0 ]; then
|
|
log_pass "All required fields present"
|
|
fi
|
|
|
|
# Verify header logging
|
|
log_info "Checking header logging..."
|
|
if grep -q '"header_X-Request-Id"' "$LOG_OUTPUT"; then
|
|
log_pass "Custom headers are logged"
|
|
else
|
|
log_info "Custom headers test skipped (no X-Request-Id in requests)"
|
|
fi
|
|
|
|
# Verify HTTP methods
|
|
log_info "Checking HTTP methods..."
|
|
if grep -q '"method":"GET"' "$LOG_OUTPUT"; then
|
|
log_pass "GET method logged"
|
|
fi
|
|
|
|
if grep -q '"method":"POST"' "$LOG_OUTPUT"; then
|
|
log_pass "POST method logged"
|
|
fi
|
|
|
|
# Show sample log entry
|
|
log_info "Sample log entry (formatted):"
|
|
head -1 "$LOG_OUTPUT" | python3 -m json.tool 2>/dev/null || head -1 "$LOG_OUTPUT"
|
|
|
|
return 0
|
|
}
|
|
|
|
# Main test runner
|
|
main() {
|
|
echo "========================================"
|
|
echo "mod_reqin_log Unix Socket Integration Test"
|
|
echo "========================================"
|
|
echo ""
|
|
|
|
check_dependencies
|
|
start_socket_listener
|
|
create_apache_config
|
|
start_apache
|
|
send_test_requests
|
|
|
|
log_info "Waiting for logs to be written..."
|
|
sleep 2
|
|
|
|
verify_logs
|
|
local result=$?
|
|
|
|
echo ""
|
|
echo "========================================"
|
|
if [ $result -eq 0 ]; then
|
|
echo -e "${GREEN}All tests passed!${NC}"
|
|
else
|
|
echo -e "${RED}Some tests failed!${NC}"
|
|
fi
|
|
echo "========================================"
|
|
|
|
return $result
|
|
}
|
|
|
|
main "$@"
|