|
1 | 1 | .. _websocket-api: |
2 | 2 |
|
| 3 | +.. _websocat: https://github.com/vi/websocat |
| 4 | + |
3 | 5 |
|
4 | 6 | ====================================== |
5 | 7 | Interacting with Zeek using WebSockets |
@@ -75,7 +77,7 @@ on the Zeek manager: |
75 | 77 |
|
76 | 78 |
|
77 | 79 | 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. |
79 | 81 |
|
80 | 82 | .. code-block:: shell |
81 | 83 |
|
@@ -206,3 +208,110 @@ or the `small helper library <https://raw.githubusercontent.com/zeek/zeek/refs/h |
206 | 208 | WebSocket API. |
207 | 209 | Zeekctl similarly ships a `light implementation <https://github.com/zeek/zeekctl/blob/93459b37c3deab4bec9e886211672024fa3e4759/ZeekControl/events.py#L159>`_ |
208 | 210 | 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. |
0 commit comments