Skip to content
This repository was archived by the owner on Oct 13, 2025. It is now read-only.

Commit 1511b42

Browse files
committed
devel/websocket-api: Explain WebSocket bridge approach
1 parent ef05afe commit 1511b42

File tree

5 files changed

+150
-1
lines changed

5 files changed

+150
-1
lines changed

devel/websocket-api.rst

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
.. _websocket-api:
22

3+
.. _websocat: https://github.com/vi/websocat
4+
35

46
======================================
57
Interacting with Zeek using WebSockets
@@ -75,7 +77,7 @@ on the Zeek manager:
7577
7678
7779
To verify that the WebSocket API is functional in your deployment use, for example,
78-
`websocat <https://github.com/vi/websocat>`_ as a quick check.
80+
`websocat`_ as a quick check.
7981

8082
.. code-block:: shell
8183
@@ -206,3 +208,110 @@ or the `small helper library <https://raw.githubusercontent.com/zeek/zeek/refs/h
206208
WebSocket API.
207209
Zeekctl similarly ships a `light implementation <https://github.com/zeek/zeekctl/blob/93459b37c3deab4bec9e886211672024fa3e4759/ZeekControl/events.py#L159>`_
208210
using the ``websockets`` library to implement its ``netstats`` and ``print`` commands.
211+
212+
213+
Outgoing Connections
214+
====================
215+
216+
For some deployment scenarios, Zeek only offering a WebSocket server can be cumbersome.
217+
Concretely, when multiple independent Zeek clusters interact with
218+
a single instance of a remote API. For instance, this could be needed for
219+
configuring a central firewall.
220+
In such scenarios, it is more natural for Zeek to connect out to the
221+
remote API, rather than the remote API connecting to the Zeek cluster.
222+
223+
For these use-cases, the current suggestion is to run a WebSocket bridge between
224+
a Zeek cluster and the remote API. One concrete tool that can be used
225+
for this purpose is `websocat`_.
226+
227+
.. note::
228+
229+
This topic has previously been discussed elsewhere. The following
230+
`GitHub issue <https://github.com/zeek/zeek/issues/3597>`_ and
231+
`discussion <https://github.com/zeek/zeek/discussions/4768>`_
232+
provide more background and details.
233+
234+
235+
Example Architecture
236+
--------------------
237+
238+
.. figure:: ../images/websocket-api/one-api-many-zeek.svg
239+
:width: 300
240+
241+
Multiple Zeek instances and a single remote API
242+
243+
The following proposal decouples the components using a WebSocket
244+
bridge for every Zeek cluster. This ensures that the depicted remote API
245+
does not need knowledge about an arbitrary number of Zeek clusters.
246+
247+
248+
.. figure:: ../images/websocket-api/one-api-many-zeek-ws-bridge.svg
249+
:width: 300
250+
251+
Multiple Zeek instances and a single remote API with WebSocket bridges.
252+
253+
Example Implementation
254+
----------------------
255+
256+
Assuming the depicted remote API provides a WebSocket server as well,
257+
it is possible to use ``websocat`` as the bridge directly.
258+
The crux for the remote API is that upon a new WebSocket client connection,
259+
the first message is the topic array that the remote API wishes to subscribe
260+
to on a Zeek cluster.
261+
262+
263+
Putting these pieces together, the following JavaScript script presents the
264+
remote API, implemented using the `ws library <https://github.com/websockets/ws?tab=readme-ov-file>`_.
265+
It accepts WebSocket clients on port 8080 and sends the topic array as the first message
266+
containing just ``zeek.bridge.test``. Thereafter, it simply echos all incoming
267+
WebSocket messages.
268+
269+
.. literalinclude:: websocket-api/server.js
270+
:caption: server.js
271+
:language: javascript
272+
273+
The Zeek side starts a WebSocket server on port 8000 and regularly publishes
274+
a ``hello`` event to the ``zeek.bridge.test`` topic.
275+
276+
.. literalinclude:: websocket-api/server.zeek
277+
:caption: server.zeek
278+
:language: zeek
279+
280+
These two servers can now be connected by running ``websocat`` as follows:
281+
282+
.. code-block:: shell
283+
284+
# In terminal 1 (use node if your Zeek has no JavaScript support)
285+
$ zeek server.js
286+
287+
# In terminal 2
288+
$ zeek server.zeek
289+
290+
# In terminal 3
291+
$ while true; do websocat --text -H='X-Application-Name: client1' ws://localhost:8000/v1/messages/json ws://localhost:8080 || sleep 0.1 ; done
292+
293+
294+
The first few lines of output in terminal 1 should then look as follows:
295+
296+
.. code-block:: shell
297+
298+
# zeek server.js
299+
client1: connected, sending topics array ["zeek.bridge.test"]
300+
client1: received: {"type":"ack","endpoint":"9089e06b-8d33-5585-ad79-4f7f6348754e-websocket-135","version":"8.1.0-dev.91"}
301+
client1: received: {"type":"data-message","topic":"zeek.bridge.test","@data-type":"vector","data":[{"@data-type":"count","data":1},{"@data-type":"count","data":1},{"@data-type":"vector","data":[{"@data-type":"string","data":"hello"},{"@data-type":"vector","data":[{"@data-type":"count","data":1792}]},{"@data-type":"vector","data":[]}]}]}
302+
...
303+
304+
If you require synchronization between the Zeek instance and the remote API, this
305+
is best achieved with events once the connection between the remote API and the
306+
Zeek cluster is established.
307+
308+
Alternative Approaches
309+
----------------------
310+
311+
Since v21, Node.js contains a built-in `WebSocket client <https://nodejs.org/en/learn/getting-started/websocket>`_,
312+
making it possible to use vanilla :ref:`javascript` within
313+
Zeek to establish outgoing WebSocket connections, too.
314+
315+
The ``websocat`` tool provides more flexibility, potentially allowing
316+
to forward WebSocket messages to external commands which in turn could
317+
use HTTP POST requests to an external API.

devel/websocket-api/server.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// server.js
2+
import WebSocket, { WebSocketServer } from 'ws';
3+
4+
const wss = new WebSocketServer({ port: 8080 });
5+
6+
wss.on('connection', (ws, req) => {
7+
ws.on('error', console.error);
8+
ws.on('close', () => { console.log('%s: gone', ws.zeek.app); });
9+
10+
ws.on('message', function message(data) {
11+
console.log('%s: received: %s', ws.zeek.app, data);
12+
});
13+
14+
let topics = ['zeek.bridge.test'];
15+
let app = req.headers['x-application-name'] || '<unknown application>'
16+
ws.zeek = {
17+
app: app,
18+
topics: topics,
19+
};
20+
21+
console.log(`${app}: connected, sending topics array ${JSON.stringify(topics)}`);
22+
ws.send(JSON.stringify(topics));
23+
});

devel/websocket-api/server.zeek

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
global hello: event(c : count);
2+
3+
global c = 0;
4+
5+
event tick()
6+
{
7+
Cluster::publish("zeek.bridge.test", hello, ++c);
8+
schedule 1.0sec { tick() };
9+
}
10+
11+
event zeek_init()
12+
{
13+
Cluster::listen_websocket([$listen_addr=127.0.0.1, $listen_port=8000/tcp]);
14+
event tick();
15+
}
Lines changed: 1 addition & 0 deletions
Loading

0 commit comments

Comments
 (0)