Skip to content

Commit

Permalink
ja4: add ja4 report
Browse files Browse the repository at this point in the history
Still a bit crude, but working.
  • Loading branch information
jasonish committed Jul 2, 2024
1 parent 96da2af commit 45cc4ce
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/elastic/eventrepo/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,11 @@ impl ElasticEventRepo {
"ssh.client.software_version" => "ssh.client.software_version.keyword",
"ssh.server.software_version" => "ssh.server.software_version.keyword",
"quic.sni" => "quic.sni.keyword",
"quic.ja4" => "quic.ja4.keyword",
"tls.issuerdn" => "tls.issuerdn.keyword",
"tls.sni" => "tls.sni.keyword",
"tls.subject" => "tls.subject.keyword",
"tls.ja4" => "tls.ja4.keyword",
"traffic.id" => "traffic.id.keyword",
"traffic.label" => "traffic.label.keyword",
_ => name,
Expand Down
2 changes: 2 additions & 0 deletions webapp/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { AlertsReport } from "./reports/AlertsReport";
import { OverviewReport } from "./reports/OverviewReport";
import { DhcpReport } from "./reports/DhcpReport";
import { IS_AUTHENTICATED } from "./global";
import { Ja4Report } from "./pages/ja4";

export function AppRouter() {
return (
Expand Down Expand Up @@ -57,6 +58,7 @@ export function AppRouter() {
<Route path={"reports/alerts"} component={AlertsReport} />
<Route path={"reports/dhcp"} component={DhcpReport} />
<Route path={"reports/address/:address"} component={AddressReport} />
<Route path="ja4/:ja4" component={Ja4Report} />
<Route path={"stats"} component={Stats} />
<Route path="*" component={RedirectToIndex} />
</Route>
Expand Down
13 changes: 13 additions & 0 deletions webapp/src/EventView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,19 @@ export function EventView() {
{e.val}
</SearchLink>
</Match>
<Match
when={
e.key == "ja4"
}
>
<A
href={
"/ja4/" + e.val
}
>
{e.val}
</A>
</Match>
</Switch>
</td>
</tr>
Expand Down
159 changes: 159 additions & 0 deletions webapp/src/pages/ja4.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// SPDX-FileCopyrightText: (C) 2024 Jason Ish <jason@codemonkey.net>
// SPDX-License-Identifier: MIT

import { useParams } from "@solidjs/router";
import { TIME_RANGE, Top } from "../Top";
import * as api from "../api";
import { createResource } from "solid-js";
import { CountValueDataTable } from "../components/CountValueDataTable";
import { SearchLink } from "../common/SearchLink";

function getPrefix(ja4: string) {
if (ja4.startsWith("t")) {
return "tls";
} else if (ja4.startsWith("q")) {
return "quic";
} else {
// TODO: Throw an error?
return "";
}
}

export function Ja4Report() {
const params = useParams<{ ja4: string }>();
const prefix = getPrefix(params.ja4);
const q = `${prefix}.ja4:${params.ja4}`;

const [topSnis] = createResource(TIME_RANGE, async () => {
let snis = await api.fetchAgg({
field: `${prefix}.sni`,
q: q,
time_range: TIME_RANGE(),
});
return snis.rows;
});

const [leastSnis] = createResource(TIME_RANGE, async () => {
let snis = await api.fetchAgg({
field: `${prefix}.sni`,
q: q,
order: "asc",
time_range: TIME_RANGE(),
});
return snis.rows;
});

const [topSourceIps] = createResource(TIME_RANGE, async () => {
let agg = await api.fetchAgg({
field: `src_ip`,
q: q,
time_range: TIME_RANGE(),
});
return agg.rows;
});

const [leastSourceIps] = createResource(TIME_RANGE, async () => {
let agg = await api.fetchAgg({
field: "src_ip",
q: q,
order: "asc",
time_range: TIME_RANGE(),
});
return agg.rows;
});

const [topDestIps] = createResource(TIME_RANGE, async () => {
let agg = await api.fetchAgg({
field: "dest_ip",
q: q,
time_range: TIME_RANGE(),
});
return agg.rows;
});

const [leastDestIps] = createResource(TIME_RANGE, async () => {
let agg = await api.fetchAgg({
field: "dest_ip",
q: q,
order: "asc",
time_range: TIME_RANGE(),
});
return agg.rows;
});

return (
<>
<Top />

<div class="container-fluid mt-2">
<div class="row">
<div class="col">
<h2>
JA4:
<SearchLink field={prefix + ".ja4"} value={params.ja4}>
{params.ja4}
</SearchLink>
</h2>
</div>
</div>

<div class="row">
<div class="col mb-2">
<CountValueDataTable
title="Top SNIs"
label="SNI"
rows={topSnis() || []}
loading={topSnis.loading}
/>
</div>
<div class="col mb-2">
<CountValueDataTable
title="Least SNIs"
label="SNI"
rows={leastSnis() || []}
loading={leastSnis.loading}
/>
</div>
</div>

<div class="row">
<div class="col mb-2">
<CountValueDataTable
title="Top Source IPs"
label="IP"
rows={topSourceIps() || []}
loading={topSourceIps.loading}
/>
</div>
<div class="col mb-2">
<CountValueDataTable
title="Least Source IPs"
label="IP"
rows={leastSourceIps() || []}
loading={leastSourceIps.loading}
/>
</div>
</div>

<div class="row">
<div class="col mb-2">
<CountValueDataTable
title="Top Destination IPs"
label="IP"
rows={topDestIps() || []}
loading={topDestIps.loading}
/>
</div>
<div class="col mb-2">
<CountValueDataTable
title="Least Destination IPs"
label="IP"
rows={leastDestIps() || []}
loading={leastDestIps.loading}
/>
</div>
</div>
</div>
</>
);
}

0 comments on commit 45cc4ce

Please sign in to comment.