Initial Commit

This commit is contained in:
Christian Risi 2025-06-02 17:41:15 +02:00
commit c3bc1d993e
15 changed files with 2354 additions and 0 deletions

View File

@ -0,0 +1,54 @@
{
// Displayed name
"name": "suricata-eve-logger",
"build": {
"dockerfile": "../DOCKERFILE"
},
// Env in container
"containerEnv": {
},
// Customization
"customizations": {
"vscode": {
"extensions": [
"denoland.vscode-deno",
"fabiospampinato.vscode-highlight",
"fabiospampinato.vscode-todo-plus"
]
}
},
// Mounts in container
"mounts": [
{
"source": "${localWorkspaceFolder}",
"target": "/workspace",
"type": "bind"
},
{
"source": "${localWorkspaceFolder}/suricata/suricata.rules",
"target": "/var/lib/suricata/rules/suricata.rules",
"type": "bind"
},
{
"source": "${localWorkspaceFolder}/suricata/suricata.yaml",
"target": "/etc/suricata/suricata.yaml",
"type": "bind"
}
],
// The WorkspaceFolder inside container
"workspaceFolder": "/workspace",
// RunArgs
"runArgs": [
"--name",
"suricata-logger"
]
}

22
.gitignore vendored Normal file
View File

@ -0,0 +1,22 @@
deno.lock
http/testdata/%25A.txt
http/testdata/file#2.txt
http/testdata/test file.txt
docs/
# rust build output
crypto/_wasm/target
cli/testdata/unicode_width_crate/target
# coverage
coverage/
# misc files
.DS_Store
.idea
.vim
_tmp/
!_tmp/.keep
# tsconfig for bun
/tsconfig.json

27
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,27 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"request": "launch",
"name": "Launch Program",
"type": "node",
"program": "${workspaceFolder}/src/main.ts",
"cwd": "${workspaceFolder}",
"env": {},
"runtimeExecutable": "/usr/bin/deno",
"runtimeArgs": [
"run",
"--unstable",
"--inspect-wait",
"--allow-all"
],
"attachSimplePort": 9229,
"preLaunchTask": "cleanup",
"console": "integratedTerminal"
// "postDebugTask": "cleanup"
}
]
}

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"deno.enable": true
}

64
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,64 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "stop-suricata",
"type": "shell",
"command": "systemctl",
"args": [
"stop",
"suricata"
]
},
{
"label": "start-suricata",
"type": "shell",
"command": "systemctl",
"args": [
"start",
"suricata"
]
},
{
"label": "remove-socket",
"type": "shell",
"command": "rm",
"args": [
"-rf",
"/var/lib/suricata/eve.sock"
]
},
{
"label": "clean-output",
"type": "shell",
"command": "rm",
"args": [
"-rf",
"${workspaceFolder}/output/parsed.log",
"${workspaceFolder}/output/raw.log",
"${workspaceFolder}/output/semi-parsed.log"
]
},
{
"label": "touch-output",
"type": "shell",
"command": "touch",
"args": [
"${workspaceFolder}/output/parsed.log",
"${workspaceFolder}/output/raw.log",
"${workspaceFolder}/output/semi-parsed.log"
]
},
{
"label": "cleanup",
"type": "shell",
"command": "echo cleanup done",
"dependsOrder": "sequence",
"dependsOn": [
"remove-socket",
]
}
]
}

5
DOCKERFILE Normal file
View File

@ -0,0 +1,5 @@
FROM denoland/deno
RUN apt update && apt upgrade -y
RUN apt install -y git suricata nano
RUN apt install -y systemctl curl iproute2 ethtool

8
deno.json Normal file
View File

@ -0,0 +1,8 @@
{
"tasks": {
"dev": "deno run --watch main.ts"
},
"imports": {
"@std/assert": "jsr:@std/assert@1"
}
}

12
src/main.ts Normal file
View File

@ -0,0 +1,12 @@
import { SuricataSocketStream } from "./socket-server/server.ts";
// UGLY: take them from
const UNIXPATH = "/var/lib/suricata/eve.sock"
const WEB_SOCKET_URI = ""
const app = new SuricataSocketStream(
UNIXPATH,
WEB_SOCKET_URI
)
app.serve()

View File

@ -0,0 +1,125 @@
import { JSON_BytesToken } from "../utils/json_byte_repr.ts";
// TODO: make this an iterator
export class JSON_Chunker {
private jsons: Array<string> = [];
private chunks: Array<Uint8Array> = [];
private totalBytes = 0;
private _openBrackets = 0;
private _openArray = 0;
private decoder = new TextDecoder("utf-8");
private debug = true;
private get delimiterToken() {
return this.openArray + this.openBrackets
}
private get openBrackets() {
return this._openBrackets;
}
private set openBrackets(value) {
this._openBrackets = value;
if (this._openBrackets < 0) {
this._openBrackets = 0;
}
}
private get openArray() {
return this._openArray;
}
private set openArray(value) {
this._openArray = value;
if (this._openArray < 0) {
this._openArray = 0;
}
}
public get messageReady() {
return (this.jsons.length !== 0);
}
public get message() {
const msg = this.jsons.shift();
return msg;
}
public pushChunk(chunk: Uint8Array) {
const chunkLen = chunk.length;
let lastByteRead = 0;
for (let i = 0; i < chunkLen; i++) {
const byte = chunk[i];
switch (byte) {
case JSON_BytesToken.OPEN_ARR:
this.openArray = this.openArray + 1;
break;
case JSON_BytesToken.CLOSE_ARR:
this.openArray = this.openArray - 1;
break;
case JSON_BytesToken.OPEN_BRACKET:
this.openBrackets = this.openBrackets + 1;
break;
case JSON_BytesToken.CLOSE_BRACKET:
this.openBrackets = this.openBrackets - 1;
break;
default:
continue;
}
// If we are in this situation, then we have a JSON
if (this.delimiterToken === 0) {
const _chunk = chunk.slice(lastByteRead, i + 1);
// console.log(this.decoder.decode(_chunk));
this.insertChunk(_chunk);
this.flush();
lastByteRead = i + 1;
}
}
// Otherwise insert the remaining chunk
this.insertChunk(
chunk.slice(lastByteRead),
);
}
private insertChunk(chunk: Uint8Array) {
this.totalBytes += chunk.length;
this.chunks.push(chunk);
}
private flush() {
let remainingBytes = this.totalBytes;
let writtenBytes = 0;
const msg = new Uint8Array(this.totalBytes);
for (let chunk of this.chunks) {
const validBytes = Math.min(
remainingBytes,
chunk.length,
);
chunk = chunk.slice(0, validBytes);
msg.set(chunk, writtenBytes);
writtenBytes += validBytes;
remainingBytes -= validBytes;
}
this.totalBytes = 0;
this.chunks = []
const message = this.decoder.decode(msg);
this.jsons.push(message);
}
}

View File

@ -0,0 +1,75 @@
import { delay } from "../utils/delay.ts";
import { JSON_Chunker } from "./json_chunker.ts";
export class SuricataSocketStream {
private listener: Deno.UnixListener;
private maxBlobSize: number = 1024;
private decoder: TextDecoder = new TextDecoder("utf-8");
private webSocket: WebSocket;
constructor(
unixSocketPath: string,
webSocketURI: string,
) {
this.listener = Deno.listen({
path: unixSocketPath,
transport: "unix",
});
this.webSocket = new WebSocket(webSocketURI);
}
public async serve() {
// Await for connections
for await (const conn of this.listener) {
while (this.webSocket.readyState !== this.webSocket.OPEN) {
await delay(20);
}
const jsonParser = new JSON_Chunker()
do {
let bytesRead: number | null = 0;
const chunk: Uint8Array = new Uint8Array(this.maxBlobSize);
bytesRead = await conn.read(chunk);
// Connection has ended,
// break the cycle
if (!bytesRead) {
break;
}
jsonParser.pushChunk(chunk.slice(0, bytesRead))
while (jsonParser.messageReady) {
this.messageProcess(jsonParser.message!)
}
} while (true);
}
}
// UGLY: change this to a more flexible method
private messageProcess(msg: string | Uint8Array) {
// this.webSocket.send(msg);
console.log(
`
Debug, found this message:
${msg}
END OF MESSAGE
`
)
}
}

9
src/utils/delay.ts Normal file
View File

@ -0,0 +1,9 @@
export function delay(ms: number) {
return new Promise(
(resolve) => {
setTimeout(() => {
resolve
}, ms)
}
)
}

View File

@ -0,0 +1,6 @@
export enum JSON_BytesToken{
OPEN_ARR = 0x5B,
CLOSE_ARR = 0x5D,
OPEN_BRACKET = 0x7B,
CLOSE_BRACKET = 0x7D
}

1
suricata/suricata.rules Normal file
View File

@ -0,0 +1 @@
alert ip any any -> any any (msg:"GPL ATTACK_RESPONSE id check returned root"; content:"uid=0|28|root|29|"; classtype:bad-unknown; sid:2100498; rev:7; metadata:created_at 2010_09_23, updated_at 2010_09_23;)

1943
suricata/suricata.yaml Normal file

File diff suppressed because it is too large Load Diff

0
tests/main_test.ts Normal file
View File