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,153 @@
import { useParams, useNavigate, Link } from 'react-router-dom';
import { useVariability } from '../hooks/useVariability';
import { VariabilityPanel } from './VariabilityPanel';
import { formatDateShort } from '../utils/dateUtils';
export function DetailsView() {
const { type, value } = useParams<{ type: string; value: string }>();
const navigate = useNavigate();
const { data, loading, error } = useVariability(type || '', value || '');
if (loading) {
return (
<div className="flex items-center justify-center h-64 text-text-secondary">
Chargement
</div>
);
}
if (error) {
return (
<div className="bg-threat-critical_bg border border-threat-critical rounded-xl p-6">
<p className="text-threat-critical font-semibold mb-4">Erreur : {error.message}</p>
<button
onClick={() => navigate('/detections')}
className="bg-accent-primary hover:bg-accent-primary/80 text-white px-4 py-2 rounded-lg text-sm"
>
Retour
</button>
</div>
);
}
if (!data) return null;
const typeLabels: Record<string, string> = {
ip: 'IP',
ja4: 'JA4',
country: 'Pays',
asn: 'ASN',
host: 'Host',
user_agent: 'User-Agent',
};
const typeLabel = typeLabels[type || ''] || type;
const isIP = type === 'ip';
const isJA4 = type === 'ja4';
const first = data.date_range.first_seen ? new Date(data.date_range.first_seen) : null;
const last = data.date_range.last_seen ? new Date(data.date_range.last_seen) : null;
const sameDate = first && last && first.getTime() === last.getTime();
const fmtDate = (d: Date) => formatDateShort(d.toISOString());
return (
<div className="space-y-5 animate-fade-in">
{/* Breadcrumb */}
<nav className="flex items-center gap-2 text-xs text-text-secondary">
<Link to="/" className="hover:text-text-primary">Dashboard</Link>
<span>/</span>
<Link to="/detections" className="hover:text-text-primary">Détections</Link>
<span>/</span>
<span className="text-text-primary">{typeLabel}: {value}</span>
</nav>
{/* Header card */}
<div className="bg-background-secondary rounded-xl p-5">
<div className="flex flex-wrap items-start justify-between gap-4">
{/* Identité */}
<div>
<p className="text-xs font-semibold text-text-secondary uppercase tracking-wider mb-1">{typeLabel}</p>
<p className="text-lg font-mono font-bold text-text-primary break-all">{value}</p>
</div>
{/* Actions */}
<div className="flex flex-wrap gap-2">
{isIP && (
<button
onClick={() => navigate(`/investigation/${encodeURIComponent(value!)}`)}
className="bg-accent-primary hover:bg-accent-primary/80 text-white px-4 py-2 rounded-lg text-sm font-medium"
>
🔍 Investigation complète
</button>
)}
{isJA4 && (
<button
onClick={() => navigate(`/investigation/ja4/${encodeURIComponent(value!)}`)}
className="bg-accent-primary hover:bg-accent-primary/80 text-white px-4 py-2 rounded-lg text-sm font-medium"
>
🔍 Investigation JA4
</button>
)}
<button
onClick={() => navigate('/detections')}
className="bg-background-card hover:bg-background-card/70 text-text-primary px-4 py-2 rounded-lg text-sm"
>
Retour
</button>
</div>
</div>
{/* Métriques clés */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 mt-5">
<Metric label="Détections (24h)" value={data.total_detections.toLocaleString()} accent />
{!isIP && (
<Metric label="IPs uniques" value={data.unique_ips.toLocaleString()} />
)}
<Metric label="User-Agents" value={(data.attributes.user_agents?.length ?? 0).toString()} />
{first && last && (
sameDate ? (
<Metric label="Détecté le" value={fmtDate(last)} />
) : (
<div className="bg-background-card rounded-xl p-3">
<p className="text-[10px] font-semibold text-text-secondary uppercase tracking-wider mb-1">Période</p>
<p className="text-xs text-text-primary font-medium">{fmtDate(first)}</p>
<p className="text-[10px] text-text-secondary"> {fmtDate(last)}</p>
</div>
)
)}
</div>
</div>
{/* Insights */}
{data.insights.length > 0 && (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
{data.insights.map((ins, i) => {
const s: Record<string, string> = {
warning: 'bg-yellow-500/10 border-yellow-500/40 text-yellow-400',
info: 'bg-blue-500/10 border-blue-500/40 text-blue-400',
success: 'bg-green-500/10 border-green-500/40 text-green-400',
};
return (
<div key={i} className={`${s[ins.type] ?? s.info} border rounded-xl p-3 text-sm`}>
{ins.message}
</div>
);
})}
</div>
)}
{/* Attributs */}
<VariabilityPanel attributes={data.attributes} hideAssociatedIPs={isIP} />
</div>
);
}
function Metric({ label, value, accent }: { label: string; value: string; accent?: boolean }) {
return (
<div className="bg-background-card rounded-xl p-3">
<p className="text-[10px] font-semibold text-text-secondary uppercase tracking-wider mb-1">{label}</p>
<p className={`text-xl font-bold ${accent ? 'text-accent-primary' : 'text-text-primary'}`}>{value}</p>
</div>
);
}