feat: full-stack Docker Compose integration tests
- 4-container stack: ClickHouse, platform (Rocky 9), bot-detector, dashboard - Platform builds sentinel on Rocky (CGO+libpcap native), correlator static - mod-reqin-log compiled with apxs on Rocky (matching RPM build target) - ClickHouse init script patches credentials for test env (sed-based) - 8-phase test runner: schema, traffic gen, pipeline, dashboard API, bot-detector, sentinel - All 13 checks pass, 3 non-blocking warnings (empty dicts, log paths) SQL schema fixes discovered during integration: - 02_dictionaries: IPv6CIDR → String (not a valid ClickHouse type) - 03_anubis_tables: dict_anubis_ua missing has_ip/rule_id/category attrs - 03_anubis_tables: dict_anubis_country FLAT() → COMPLEX_KEY_HASHED() (String key) - 09_audit_table: CODEC before DEFAULT → DEFAULT before CODEC Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
97
tests/integration/platform/Dockerfile
Normal file
97
tests/integration/platform/Dockerfile
Normal file
@ -0,0 +1,97 @@
|
||||
# =============================================================================
|
||||
# Platform container — Rocky Linux 9
|
||||
# Runs: Apache (HTTPS) + mod-reqin-log + sentinel + correlator
|
||||
#
|
||||
# Multi-stage:
|
||||
# 1. go-builder — compile correlator (static, no CGO) on golang image
|
||||
# 2. platform — Rocky Linux 9: builds sentinel (CGO+libpcap), mod-reqin-log,
|
||||
# installs Apache, runs everything
|
||||
#
|
||||
# sentinel is compiled on Rocky so it links against the same libpcap as runtime.
|
||||
# This mirrors RPM packaging where build and target are the same distro.
|
||||
# =============================================================================
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Stage 1: Build correlator (static binary, no CGO — distro-independent)
|
||||
# ---------------------------------------------------------------------------
|
||||
FROM golang:1.24 AS go-builder
|
||||
|
||||
WORKDIR /src
|
||||
COPY go.work go.work.sum* ./
|
||||
COPY shared/go/ja4common/ shared/go/ja4common/
|
||||
COPY services/correlator/ services/correlator/
|
||||
COPY services/sentinel/ services/sentinel/
|
||||
|
||||
RUN cd services/correlator && \
|
||||
CGO_ENABLED=0 go build -ldflags="-s -w" -o /out/correlator ./cmd/logcorrelator
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Stage 2: Rocky Linux 9 — build sentinel + mod-reqin-log, then run everything
|
||||
# ---------------------------------------------------------------------------
|
||||
FROM rockylinux:9
|
||||
|
||||
# Install build deps + runtime deps
|
||||
RUN dnf install -y --allowerasing \
|
||||
httpd httpd-devel mod_ssl \
|
||||
apr-devel apr-util-devel \
|
||||
gcc make redhat-rpm-config \
|
||||
libpcap \
|
||||
golang \
|
||||
procps-ng curl \
|
||||
&& dnf install -y --enablerepo=crb libpcap-devel \
|
||||
&& dnf clean all
|
||||
|
||||
# -- Build sentinel on Rocky (CGO + libpcap from Rocky repos) ---------------
|
||||
COPY go.work go.work.sum* /tmp/sentinel-build/
|
||||
COPY shared/go/ja4common/ /tmp/sentinel-build/shared/go/ja4common/
|
||||
COPY services/sentinel/ /tmp/sentinel-build/services/sentinel/
|
||||
COPY services/correlator/ /tmp/sentinel-build/services/correlator/
|
||||
RUN cd /tmp/sentinel-build/services/sentinel && \
|
||||
CGO_ENABLED=1 go build -ldflags="-s -w" -o /usr/local/bin/sentinel ./cmd/ja4sentinel && \
|
||||
rm -rf /tmp/sentinel-build /root/go
|
||||
|
||||
# -- Build mod-reqin-log from source -----------------------------------------
|
||||
COPY services/mod-reqin-log/src/ /tmp/mod-reqin-log/src/
|
||||
COPY services/mod-reqin-log/Makefile /tmp/mod-reqin-log/Makefile
|
||||
RUN cd /tmp/mod-reqin-log && make all && \
|
||||
cp modules/mod_reqin_log.so /usr/lib64/httpd/modules/ 2>/dev/null || \
|
||||
cp build/.libs/mod_reqin_log.so /usr/lib64/httpd/modules/ && \
|
||||
rm -rf /tmp/mod-reqin-log
|
||||
|
||||
# -- Copy correlator from builder (static binary, no deps) -------------------
|
||||
COPY --from=go-builder /out/correlator /usr/local/bin/correlator
|
||||
|
||||
# -- Create runtime directories ----------------------------------------------
|
||||
RUN mkdir -p /var/run/logcorrelator \
|
||||
/var/log/logcorrelator \
|
||||
/var/log/ja4sentinel \
|
||||
/etc/logcorrelator \
|
||||
/etc/ja4sentinel
|
||||
|
||||
# -- Correlator config -------------------------------------------------------
|
||||
COPY tests/integration/platform/correlator.yml /etc/logcorrelator/correlator.yml
|
||||
|
||||
# -- Sentinel config ----------------------------------------------------------
|
||||
COPY tests/integration/platform/sentinel.yml /etc/ja4sentinel/config.yml
|
||||
|
||||
# -- Apache config (HTTPS + mod-reqin-log) ------------------------------------
|
||||
COPY tests/integration/platform/httpd-integration.conf /etc/httpd/conf.d/integration.conf
|
||||
|
||||
# -- Generate self-signed TLS certificate -------------------------------------
|
||||
RUN openssl req -x509 -nodes -days 365 \
|
||||
-subj "/CN=platform.test" \
|
||||
-newkey rsa:2048 \
|
||||
-keyout /etc/pki/tls/private/localhost.key \
|
||||
-out /etc/pki/tls/certs/localhost.crt
|
||||
|
||||
# -- Simple health endpoint for Apache ---------------------------------------
|
||||
RUN mkdir -p /var/www/html && \
|
||||
echo '{"status":"ok"}' > /var/www/html/health
|
||||
|
||||
# -- Entrypoint (manages all processes) --------------------------------------
|
||||
COPY tests/integration/platform/entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
EXPOSE 80 443
|
||||
|
||||
CMD ["/entrypoint.sh"]
|
||||
30
tests/integration/platform/clickhouse-init.sh
Executable file
30
tests/integration/platform/clickhouse-init.sh
Executable file
@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# clickhouse-init.sh — Pre-process shared SQL files for integration testing
|
||||
#
|
||||
# Copies SQL from /initdb-src/ to /tmp, patches credentials, then executes.
|
||||
# =============================================================================
|
||||
set -e
|
||||
|
||||
SRC_DIR="/initdb-src"
|
||||
TMP_DIR="/tmp/initdb-patched"
|
||||
mkdir -p "$TMP_DIR"
|
||||
|
||||
for f in "$SRC_DIR"/*.sql; do
|
||||
[ -f "$f" ] || continue
|
||||
base=$(basename "$f")
|
||||
echo "[init] Patching $base"
|
||||
sed \
|
||||
-e "s/USER 'admin'/USER 'default'/g" \
|
||||
-e "s/PASSWORD 'CHANGE_ME'/PASSWORD ''/g" \
|
||||
-e "s/PASSWORD 'ChangeMe'/PASSWORD ''/g" \
|
||||
"$f" > "$TMP_DIR/$base"
|
||||
done
|
||||
|
||||
for f in "$TMP_DIR"/*.sql; do
|
||||
[ -f "$f" ] || continue
|
||||
echo "[init] Executing $(basename "$f")"
|
||||
clickhouse-client --multiquery < "$f"
|
||||
done
|
||||
|
||||
echo "[init] All SQL files executed successfully"
|
||||
51
tests/integration/platform/correlator.yml
Normal file
51
tests/integration/platform/correlator.yml
Normal file
@ -0,0 +1,51 @@
|
||||
# Correlator config for integration tests
|
||||
log:
|
||||
level: DEBUG
|
||||
|
||||
inputs:
|
||||
unix_sockets:
|
||||
- name: http
|
||||
source_type: A
|
||||
path: /var/run/logcorrelator/http.socket
|
||||
format: json
|
||||
socket_permissions: "0666"
|
||||
- name: network
|
||||
source_type: B
|
||||
path: /var/run/logcorrelator/network.socket
|
||||
format: json
|
||||
socket_permissions: "0666"
|
||||
|
||||
outputs:
|
||||
clickhouse:
|
||||
enabled: true
|
||||
dsn: clickhouse://default:@clickhouse:9000/ja4_logs
|
||||
table: http_logs_raw
|
||||
batch_size: 10
|
||||
flush_interval_ms: 500
|
||||
max_buffer_size: 5000
|
||||
drop_on_overflow: false
|
||||
async_insert: true
|
||||
timeout_ms: 5000
|
||||
|
||||
file:
|
||||
enabled: true
|
||||
path: /var/log/logcorrelator/correlated.log
|
||||
|
||||
stdout:
|
||||
enabled: true
|
||||
|
||||
correlation:
|
||||
time_window:
|
||||
value: 10
|
||||
unit: s
|
||||
orphan_policy:
|
||||
apache_always_emit: true
|
||||
apache_emit_delay_ms: 1000
|
||||
network_emit: false
|
||||
matching:
|
||||
mode: one_to_many
|
||||
buffers:
|
||||
max_http_items: 10000
|
||||
max_network_items: 20000
|
||||
ttl:
|
||||
network_ttl_s: 120
|
||||
0
tests/integration/platform/csv-stubs/bot_ip.csv
Normal file
0
tests/integration/platform/csv-stubs/bot_ip.csv
Normal file
|
|
0
tests/integration/platform/csv-stubs/bot_ja4.csv
Normal file
0
tests/integration/platform/csv-stubs/bot_ja4.csv
Normal file
|
|
@ -0,0 +1 @@
|
||||
network,asn,country_code,name,org,domain
|
||||
|
59
tests/integration/platform/entrypoint.sh
Executable file
59
tests/integration/platform/entrypoint.sh
Executable file
@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# Platform entrypoint — starts correlator, Apache, sentinel in order
|
||||
# =============================================================================
|
||||
set -eo pipefail
|
||||
|
||||
log() { echo "[entrypoint] $(date +%H:%M:%S) $*"; }
|
||||
|
||||
CORRELATOR_PID=""
|
||||
HTTPD_PID=""
|
||||
SENTINEL_PID=""
|
||||
|
||||
cleanup() {
|
||||
log "Shutting down..."
|
||||
[ -n "$SENTINEL_PID" ] && kill "$SENTINEL_PID" 2>/dev/null || true
|
||||
[ -n "$CORRELATOR_PID" ] && kill "$CORRELATOR_PID" 2>/dev/null || true
|
||||
httpd -k stop 2>/dev/null || true
|
||||
wait 2>/dev/null || true
|
||||
log "All processes stopped."
|
||||
}
|
||||
trap cleanup EXIT SIGTERM SIGINT
|
||||
|
||||
# -- 1. Start correlator (creates Unix sockets) ------------------------------
|
||||
log "Starting correlator..."
|
||||
correlator -config /etc/logcorrelator/correlator.yml &
|
||||
CORRELATOR_PID=$!
|
||||
|
||||
# Wait for correlator to create its sockets
|
||||
for i in $(seq 1 30); do
|
||||
if [ -S /var/run/logcorrelator/http.socket ] && [ -S /var/run/logcorrelator/network.socket ]; then
|
||||
log "Correlator sockets ready."
|
||||
break
|
||||
fi
|
||||
sleep 0.5
|
||||
done
|
||||
|
||||
if [ ! -S /var/run/logcorrelator/http.socket ]; then
|
||||
log "ERROR: correlator sockets not created after 15s"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# -- 2. Start Apache (with mod-reqin-log writing to http.socket) -------------
|
||||
log "Starting Apache..."
|
||||
httpd -DFOREGROUND &
|
||||
HTTPD_PID=$!
|
||||
sleep 2
|
||||
|
||||
# -- 3. Start sentinel (captures network traffic) ----------------------------
|
||||
log "Starting sentinel..."
|
||||
sentinel -config /etc/ja4sentinel/config.yml &
|
||||
SENTINEL_PID=$!
|
||||
|
||||
log "All services started. PIDs: correlator=$CORRELATOR_PID httpd=$HTTPD_PID sentinel=$SENTINEL_PID"
|
||||
|
||||
# -- Wait for any process to exit (indicates failure) -------------------------
|
||||
wait -n "$CORRELATOR_PID" "$HTTPD_PID" "$SENTINEL_PID" 2>/dev/null || true
|
||||
EXIT_CODE=$?
|
||||
log "A process exited with code $EXIT_CODE — triggering shutdown."
|
||||
exit $EXIT_CODE
|
||||
28
tests/integration/platform/httpd-integration.conf
Normal file
28
tests/integration/platform/httpd-integration.conf
Normal file
@ -0,0 +1,28 @@
|
||||
# Integration test Apache config — HTTPS + mod-reqin-log
|
||||
|
||||
# Load mod-reqin-log
|
||||
LoadModule reqin_log_module modules/mod_reqin_log.so
|
||||
|
||||
# Enable mod-reqin-log with correlator socket
|
||||
JsonSockLogEnabled On
|
||||
JsonSockLogSocket "/var/run/logcorrelator/http.socket"
|
||||
JsonSockLogHeaders X-Request-Id User-Agent Referer X-Forwarded-For \
|
||||
Sec-CH-UA Sec-CH-UA-Mobile Sec-CH-UA-Platform \
|
||||
Sec-Fetch-Dest Sec-Fetch-Mode Sec-Fetch-Site \
|
||||
Accept Accept-Language Accept-Encoding Content-Type
|
||||
JsonSockLogMaxHeaders 25
|
||||
JsonSockLogMaxHeaderValueLen 256
|
||||
JsonSockLogReconnectInterval 5
|
||||
JsonSockLogErrorReportInterval 5
|
||||
JsonSockLogLevel DEBUG
|
||||
|
||||
# HTTPS virtual host (port 443 already configured by mod_ssl)
|
||||
<VirtualHost *:80>
|
||||
ServerName platform.test
|
||||
DocumentRoot /var/www/html
|
||||
|
||||
# Simple test pages
|
||||
<Location /health>
|
||||
Require all granted
|
||||
</Location>
|
||||
</VirtualHost>
|
||||
18
tests/integration/platform/sentinel.yml
Normal file
18
tests/integration/platform/sentinel.yml
Normal file
@ -0,0 +1,18 @@
|
||||
# Sentinel config for integration tests
|
||||
core:
|
||||
interface: eth0
|
||||
listen_ports:
|
||||
- 443
|
||||
flow_timeout_sec: 30
|
||||
packet_buffer_size: 1000
|
||||
log_level: debug
|
||||
|
||||
outputs:
|
||||
- type: unix_socket
|
||||
enabled: true
|
||||
async_buffer: 5000
|
||||
params:
|
||||
socket_path: /var/run/logcorrelator/network.socket
|
||||
|
||||
- type: stdout
|
||||
enabled: true
|
||||
Reference in New Issue
Block a user