feat: ja4-platform monorepo — 5 services unified, tests & RPM builds standardized

Services:
- ja4sentinel: TLS/JA4 fingerprint capture daemon (Go, libpcap)
- logcorrelator: JA4 log correlation engine (Go, ClickHouse)
- mod_reqin_log: Apache module (C, JSON request logging)
- bot_detector: ML bot detection pipeline (Python)
- dashboard: FastAPI/Streamlit analytics UI (Python)

Shared libraries:
- shared/go/ja4common: logger, config, shutdown, ipfilter (Go module)
- shared/python/ja4_common: ClickHouseClient, ClickHouseSettings (Python package)
- shared/clickhouse/: canonical SQL migrations (10 files)

Build & packaging:
- Unified 3-stage Dockerfile.package for Go RPMs (el8/el9/el10)
- go.work workspace linking sentinel, correlator, ja4common
- Makefile with test-all, build-all, rpm-* targets

Fixes applied:
- go.work: 1.21 → 1.24.6 (required by sentinel)
- correlator Dockerfiles: golang:1.21 → golang:1.24
- replace directives in go.mod for ja4common local path
- pyproject.toml: setuptools.backends → setuptools.build_meta
- Removed static libpcap linking (unavailable on Rocky 9)
- Fixed data races in output/writers_test.go (sync.Mutex + atomic.Int32)
- Rewrote corrupted test files (logger_test.go × 2)

Test coverage:
- correlator: 67.1% total (unixsocket 80.5%, config 91.7%, app 83.3%, multi 87.7%, stdout 100%)
- sentinel: all 10 packages pass (api, capture, config, fingerprint, ipfilter, logging, output, tlsparse)

Documentation:
- README.md + docs/ (architecture, development, 5 services, shared libs, DB schema & migrations)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
toto
2026-04-07 16:42:59 +02:00
commit d469e39da7
278 changed files with 1621301 additions and 0 deletions

View File

@ -0,0 +1,266 @@
/*
* test_json_serialization.c - Unit tests for JSON serialization
*/
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>
#include <string.h>
#include <stdio.h>
#include <apr_pools.h>
#include <apr_strings.h>
#include <apr_time.h>
#include <apr_lib.h>
#include <apr_general.h>
typedef struct {
char *data;
size_t len;
size_t cap;
apr_pool_t *pool;
} testbuf_t;
static void testbuf_init(testbuf_t *buf, apr_pool_t *pool, size_t initial_capacity)
{
buf->pool = pool;
buf->cap = initial_capacity;
buf->len = 0;
buf->data = apr_palloc(pool, initial_capacity);
buf->data[0] = '\0';
}
static void testbuf_append(testbuf_t *buf, const char *str, size_t len)
{
if (str == NULL) {
return;
}
if (len == (size_t)-1) {
len = strlen(str);
}
if (buf->len + len + 1 > buf->cap) {
size_t new_cap = (buf->len + len + 1) * 2;
char *new_data = apr_palloc(buf->pool, new_cap);
memcpy(new_data, buf->data, buf->len + 1); /* Copy including null terminator */
buf->data = new_data;
buf->cap = new_cap;
}
memcpy(buf->data + buf->len, str, len);
buf->len += len;
buf->data[buf->len] = '\0';
}
static void testbuf_append_char(testbuf_t *buf, char c)
{
if (buf->len + 2 > buf->cap) {
size_t new_cap = (buf->cap * 2);
char *new_data = apr_palloc(buf->pool, new_cap);
memcpy(new_data, buf->data, buf->len + 1);
buf->data = new_data;
buf->cap = new_cap;
}
buf->data[buf->len++] = c;
buf->data[buf->len] = '\0';
}
/* Mock JSON string escaping function for testing */
static void append_json_string(testbuf_t *buf, const char *str)
{
if (str == NULL) {
return;
}
for (const char *p = str; *p; p++) {
char c = *p;
switch (c) {
case '"': testbuf_append(buf, "\\\"", 2); break;
case '\\': testbuf_append(buf, "\\\\", 2); break;
case '\b': testbuf_append(buf, "\\b", 2); break;
case '\f': testbuf_append(buf, "\\f", 2); break;
case '\n': testbuf_append(buf, "\\n", 2); break;
case '\r': testbuf_append(buf, "\\r", 2); break;
case '\t': testbuf_append(buf, "\\t", 2); break;
default:
if ((unsigned char)c < 0x20) {
char unicode[8];
apr_snprintf(unicode, sizeof(unicode), "\\u%04x", (unsigned char)c);
testbuf_append(buf, unicode, (size_t)-1);
} else {
testbuf_append_char(buf, c);
}
break;
}
}
}
/* Test: Empty string */
static void test_json_escape_empty_string(void **state)
{
apr_pool_t *pool;
testbuf_t buf;
(void)state;
apr_pool_create(&pool, NULL);
testbuf_init(&buf, pool, 256);
append_json_string(&buf, "");
assert_string_equal(buf.data, "");
apr_pool_destroy(pool);
}
/* Test: Simple string without special characters */
static void test_json_escape_simple_string(void **state)
{
apr_pool_t *pool;
testbuf_t buf;
(void)state;
apr_pool_create(&pool, NULL);
testbuf_init(&buf, pool, 256);
append_json_string(&buf, "hello world");
assert_string_equal(buf.data, "hello world");
apr_pool_destroy(pool);
}
/* Test: String with double quotes */
static void test_json_escape_quotes(void **state)
{
apr_pool_t *pool;
testbuf_t buf;
(void)state;
apr_pool_create(&pool, NULL);
testbuf_init(&buf, pool, 256);
append_json_string(&buf, "hello \"world\"");
assert_string_equal(buf.data, "hello \\\"world\\\"");
apr_pool_destroy(pool);
}
/* Test: String with backslashes */
static void test_json_escape_backslashes(void **state)
{
apr_pool_t *pool;
testbuf_t buf;
(void)state;
apr_pool_create(&pool, NULL);
testbuf_init(&buf, pool, 256);
append_json_string(&buf, "path\\to\\file");
assert_string_equal(buf.data, "path\\\\to\\\\file");
apr_pool_destroy(pool);
}
/* Test: String with newlines and tabs */
static void test_json_escape_newlines_tabs(void **state)
{
apr_pool_t *pool;
testbuf_t buf;
(void)state;
apr_pool_create(&pool, NULL);
testbuf_init(&buf, pool, 256);
append_json_string(&buf, "line1\nline2\ttab");
assert_string_equal(buf.data, "line1\\nline2\\ttab");
apr_pool_destroy(pool);
}
/* Test: String with control characters */
static void test_json_escape_control_chars(void **state)
{
apr_pool_t *pool;
testbuf_t buf;
(void)state;
apr_pool_create(&pool, NULL);
testbuf_init(&buf, pool, 256);
/* Test with bell character (0x07) - use octal literal */
append_json_string(&buf, "test\007bell");
/* Should contain unicode escape for bell (0x07) */
assert_true(strstr(buf.data, "\\u0007") != NULL);
apr_pool_destroy(pool);
}
/* Test: NULL string */
static void test_json_escape_null_string(void **state)
{
apr_pool_t *pool;
testbuf_t buf;
(void)state;
apr_pool_create(&pool, NULL);
testbuf_init(&buf, pool, 256);
append_json_string(&buf, NULL);
assert_string_equal(buf.data, "");
apr_pool_destroy(pool);
}
/* Test: Complex user agent string */
static void test_json_escape_user_agent(void **state)
{
apr_pool_t *pool;
testbuf_t buf;
const char *ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) \"Test\"";
(void)state;
apr_pool_create(&pool, NULL);
testbuf_init(&buf, pool, 512);
append_json_string(&buf, ua);
assert_true(strstr(buf.data, "\\\"Test\\\"") != NULL);
apr_pool_destroy(pool);
}
static int group_setup(void **state)
{
(void)state;
return apr_initialize();
}
static int group_teardown(void **state)
{
(void)state;
apr_terminate();
return 0;
}
int main(void)
{
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_json_escape_empty_string),
cmocka_unit_test(test_json_escape_simple_string),
cmocka_unit_test(test_json_escape_quotes),
cmocka_unit_test(test_json_escape_backslashes),
cmocka_unit_test(test_json_escape_newlines_tabs),
cmocka_unit_test(test_json_escape_control_chars),
cmocka_unit_test(test_json_escape_null_string),
cmocka_unit_test(test_json_escape_user_agent),
};
return cmocka_run_group_tests(tests, group_setup, group_teardown);
}