mirror of
https://github.com/zeek/zeek.git
synced 2025-10-05 16:18:19 +00:00
Copy docs into Zeek repo directly
This is based on commit 2731def9159247e6da8a3191783c89683363689c from the zeek-docs repo.
This commit is contained in:
parent
83f1e74643
commit
ded98cd373
1074 changed files with 169319 additions and 0 deletions
317
doc/devel/websocket-api.rst
Normal file
317
doc/devel/websocket-api.rst
Normal file
|
@ -0,0 +1,317 @@
|
|||
.. _websocket-api:
|
||||
|
||||
.. _websocat: https://github.com/vi/websocat
|
||||
|
||||
|
||||
======================================
|
||||
Interacting with Zeek using WebSockets
|
||||
======================================
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
Usually, Zeek produces protocol logs consumed by external applications. These
|
||||
external applications might be SIEMs, real-time streaming analysis platforms
|
||||
or basic archival processes compressing logs for long term storage.
|
||||
|
||||
Certain use-cases require interacting and influencing Zeek's runtime behavior
|
||||
outside of static configuration via ``local.zeek``.
|
||||
|
||||
The classic :ref:`framework-input` and :ref:`framework-configuration` can be
|
||||
leveraged for runtime configuration of Zeek as well as triggering arbitrary
|
||||
events or script execution via option handlers. These frameworks are mostly
|
||||
file- or process-based and may feel a bit unusual in environments where creation
|
||||
of files is uncommon or even impossible due to separation of concerns. In many
|
||||
of today's environments, interacting using HTTP-based APIs or other remote
|
||||
interfaces is more common.
|
||||
|
||||
.. note::
|
||||
|
||||
As an aside, if you need more flexibility than the WebSocket API offers today,
|
||||
an alternative could be to use :ref:`javascript` within Zeek. This opens the
|
||||
possibility to run a separate HTTP or a totally different Node.js based server
|
||||
within a Zeek process for quick experimentation and evaluation of other
|
||||
approaches.
|
||||
|
||||
Background and Setup
|
||||
====================
|
||||
|
||||
Since Zeek 5.0, Zeek allows connections from external clients over WebSocket.
|
||||
This allows these clients to interact with Zeek's publish-subscribe layer and
|
||||
exchange Zeek events with other Zeek nodes.
|
||||
Initially, this implementation resided in the Broker subsystem.
|
||||
With Zeek 8.0, most of the implementation has been moved into core Zeek
|
||||
itself with the v1 serialization format remaining in Broker.
|
||||
|
||||
WebSocket clients may subscribe to a fixed set of topics and will receive
|
||||
Zeek events matching these topics that Zeek cluster nodes, but also other
|
||||
WebSocket clients, publish.
|
||||
|
||||
With Zeek 8.0, Zeekctl has received support to interact with Zeek cluster nodes
|
||||
using the WebSocket protocol. If you're running a Zeekctl based cluster and
|
||||
want to experiment with WebSocket functionality, add ``UseWebSocket = 1`` to
|
||||
your ``zeekctl.cfg``:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
# zeekctl.cfg
|
||||
...
|
||||
UseWebSocket = 1
|
||||
|
||||
This will essentially add the following snippet, enabling a WebSocket server
|
||||
on the Zeek manager:
|
||||
|
||||
.. code-block:: zeek
|
||||
:caption: websocket.zeek
|
||||
|
||||
event zeek_init()
|
||||
{
|
||||
if ( Cluster::local_node_type() == Cluster::MANAGER )
|
||||
{
|
||||
Cluster::listen_websocket([
|
||||
$listen_addr=127.0.0.1,
|
||||
$listen_port=27759/tcp,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
To verify that the WebSocket API is functional in your deployment use, for example,
|
||||
`websocat`_ as a quick check.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ echo '[]' | websocat ws://127.0.0.1:27759/v1/messages/json
|
||||
{"type":"ack","endpoint":"3eece35d-9f94-568d-861c-6a16c433e090-websocket-2","version":"8.0.0-dev.684"}
|
||||
|
||||
Zeek's ``cluster.log`` file will also have an entry for the WebSocket client connection.
|
||||
The empty array in the command specifies the client's subscriptions, in this case none.
|
||||
|
||||
Version 1
|
||||
=========
|
||||
|
||||
The currently implemented protocol is accessible at ``/v1/messages/json``.
|
||||
The `data representation <https://docs.zeek.org/projects/broker/en/current/web-socket.html#data-representation>`_
|
||||
is documented in detail within the Broker project. Note that this format is a
|
||||
direct translation of Broker's binary format into JSON, resulting in a fairly
|
||||
tight coupling between WebSocket clients and the corresponding Zeek scripts.
|
||||
Most prominently is the representation of record values as vectors instead
|
||||
of objects, making the protocol sensitive against reordering or introduction
|
||||
of optional fields to records.
|
||||
|
||||
.. note::
|
||||
|
||||
We're looking into an iteration of the format. If you have feedback or
|
||||
would like to contribute, please reach out on the usual community channels.
|
||||
|
||||
|
||||
Handshake and Acknowledgement
|
||||
-----------------------------
|
||||
|
||||
The first message after a WebSocket connection has been established originates
|
||||
from the client. This message is a JSON array of strings that represent the
|
||||
topics the WebSocket client wishes to subscribe to.
|
||||
|
||||
Zeek replies with an acknowledgement message that's a JSON object or an error.
|
||||
|
||||
Events
|
||||
------
|
||||
|
||||
After the acknowledgement, WebSocket clients receive all events arriving on
|
||||
topics they have subscribed to.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ websocat ws://127.0.0.1:27759/v1/messages/json
|
||||
["zeek.test"]
|
||||
{"type":"ack","endpoint":"d955d990-ad8a-5ed4-8bc5-bee252d4a2e6-websocket-0","version":"8.0.0-dev.684"}
|
||||
{"type":"data-message","topic":"zeek.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":3}]},{"@data-type":"vector","data":[]}]}]}
|
||||
|
||||
The received messages, again, are encoded in Broker's JSON format. Above ``data-message``
|
||||
represents an event received on topic ``zeek.test``. The event's name is ``hello``.
|
||||
This event has a single argument of type :zeek:type:`count`. In the example above
|
||||
its value is ``3``.
|
||||
|
||||
To send events, WebSocket clients similarly encode their event representation
|
||||
to Broker's JSON format and send them as `text data frames <https://datatracker.ietf.org/doc/html/rfc6455#section-5.6>`_.
|
||||
|
||||
|
||||
X-Application-Name Header
|
||||
-------------------------
|
||||
|
||||
When a WebSocket client includes an ``X-Application-Name`` HTTP header in
|
||||
the initial WebSocket Handshake's GET request, that header's value is available
|
||||
in the :zeek:see:`Cluster::websocket_client_added` event's ``endpoint`` argument (see :zeek:see:`Cluster::EndpointInfo`).
|
||||
|
||||
The header's value will also be included in ``cluster.log`` messages.
|
||||
|
||||
Additionally, if the cluster telemetry for WebSocket clients is set to
|
||||
:zeek:see:`Cluster::Telemetry::VERBOSE` or :zeek:see:`Cluster::Telemetry::DEBUG`
|
||||
via :zeek:see:`Cluster::Telemetry::websocket_metrics`, the header's value is
|
||||
included as ``app`` label in metrics exposed by the :ref:`framework-telemetry`.
|
||||
|
||||
As of Zeek 8.0, a WebSocket client will be rejected if the header is set, but
|
||||
its value doesn't match ``[-/_.=:*@a-zA-Z0-9]+``.
|
||||
|
||||
|
||||
Language Bindings
|
||||
-----------------
|
||||
|
||||
Note that it's possible to use any language that offers WebSocket bindings.
|
||||
The ones listed below mostly add a bit of convenience features around the
|
||||
initial Handshake message, error handling and serializing Zeek events and
|
||||
values into the Broker-specific serialization format.
|
||||
|
||||
For example, using the Node.js `builtin WebSocket functionality <https://nodejs.org/en/learn/getting-started/websocket>`_,
|
||||
the ``websocat`` example from above can be reproduced as follows:
|
||||
|
||||
.. code-block:: javascript
|
||||
:caption: client.js
|
||||
|
||||
// client.js
|
||||
const socket = new WebSocket('ws://192.168.122.107:27759/v1/messages/json');
|
||||
|
||||
socket.addEventListener('open', event => {
|
||||
socket.send('["zeek.test"]');
|
||||
});
|
||||
|
||||
socket.addEventListener('message', event => {
|
||||
console.log('Message from server: ', event.data);
|
||||
});
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ node ./client.js
|
||||
Message from server: {"type":"ack","endpoint":"2e951b0c-3ca4-504c-ae8a-5d3750fec588-websocket-10","version":"8.0.0-dev.684"}
|
||||
Message from server: {"type":"data-message","topic":"zeek.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":374}]},{"@data-type":"vector","data":[]}]}]}
|
||||
|
||||
|
||||
Golang
|
||||
^^^^^^
|
||||
|
||||
* `Zeek Broker websocket interface library for Golang <https://github.com/corelight/go-zeek-broker-ws>`_ (not an official Zeek project)
|
||||
|
||||
|
||||
Rust
|
||||
^^^^
|
||||
|
||||
* `Rust types for interacting with Zeek over WebSocket <https://github.com/bbannier/zeek-websocket-rs>`_ (not an official Zeek project)
|
||||
|
||||
Python
|
||||
^^^^^^
|
||||
|
||||
There are no ready to use Python libraries available, but the third-party
|
||||
`websockets <https://github.com/python-websockets/websockets>`_ package
|
||||
allows to get started quickly.
|
||||
You may take inspiration from `zeek-client's implementation <https://github.com/zeek/zeek-client>`_
|
||||
or the `small helper library <https://raw.githubusercontent.com/zeek/zeek/refs/heads/master/testing/btest/Files/ws/wstest.py>`_ used by various of Zeek's own tests for the
|
||||
WebSocket API.
|
||||
Zeekctl similarly ships a `light implementation <https://github.com/zeek/zeekctl/blob/93459b37c3deab4bec9e886211672024fa3e4759/ZeekControl/events.py#L159>`_
|
||||
using the ``websockets`` library to implement its ``netstats`` and ``print`` commands.
|
||||
|
||||
|
||||
Outgoing Connections
|
||||
====================
|
||||
|
||||
For some deployment scenarios, Zeek only offering a WebSocket server can be cumbersome.
|
||||
Concretely, when multiple independent Zeek clusters interact with
|
||||
a single instance of a remote API. For instance, this could be needed for
|
||||
configuring a central firewall.
|
||||
In such scenarios, it is more natural for Zeek to connect out to the
|
||||
remote API, rather than the remote API connecting to the Zeek cluster.
|
||||
|
||||
For these use-cases, the current suggestion is to run a WebSocket bridge between
|
||||
a Zeek cluster and the remote API. One concrete tool that can be used
|
||||
for this purpose is `websocat`_.
|
||||
|
||||
.. note::
|
||||
|
||||
This topic has previously been discussed elsewhere. The following
|
||||
`GitHub issue <https://github.com/zeek/zeek/issues/3597>`_ and
|
||||
`discussion <https://github.com/zeek/zeek/discussions/4768>`_
|
||||
provide more background and details.
|
||||
|
||||
|
||||
Example Architecture
|
||||
--------------------
|
||||
|
||||
.. figure:: ../images/websocket-api/one-api-many-zeek.svg
|
||||
:width: 300
|
||||
|
||||
Multiple Zeek instances and a single remote API
|
||||
|
||||
The following proposal decouples the components using a WebSocket
|
||||
bridge for every Zeek cluster. This ensures that the depicted remote API
|
||||
does not need knowledge about an arbitrary number of Zeek clusters.
|
||||
|
||||
|
||||
.. figure:: ../images/websocket-api/one-api-many-zeek-ws-bridge.svg
|
||||
:width: 300
|
||||
|
||||
Multiple Zeek instances and a single remote API with WebSocket bridges.
|
||||
|
||||
Example Implementation
|
||||
----------------------
|
||||
|
||||
Assuming the depicted remote API provides a WebSocket server as well,
|
||||
it is possible to use ``websocat`` as the bridge directly.
|
||||
The crux for the remote API is that upon a new WebSocket client connection,
|
||||
the first message is the topic array that the remote API wishes to subscribe
|
||||
to on a Zeek cluster.
|
||||
|
||||
|
||||
Putting these pieces together, the following JavaScript script presents the
|
||||
remote API, implemented using the `ws library <https://github.com/websockets/ws?tab=readme-ov-file>`_.
|
||||
It accepts WebSocket clients on port 8080 and sends the topic array as the first message
|
||||
containing just ``zeek.bridge.test``. Thereafter, it simply echos all incoming
|
||||
WebSocket messages.
|
||||
|
||||
.. literalinclude:: websocket-api/server.js
|
||||
:caption: server.js
|
||||
:language: javascript
|
||||
|
||||
The Zeek side starts a WebSocket server on port 8000 and regularly publishes
|
||||
a ``hello`` event to the ``zeek.bridge.test`` topic.
|
||||
|
||||
.. literalinclude:: websocket-api/server.zeek
|
||||
:caption: server.zeek
|
||||
:language: zeek
|
||||
|
||||
These two servers can now be connected by running ``websocat`` as follows:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
# In terminal 1 (use node if your Zeek has no JavaScript support)
|
||||
$ zeek server.js
|
||||
|
||||
# In terminal 2
|
||||
$ zeek server.zeek
|
||||
|
||||
# In terminal 3
|
||||
$ while true; do websocat --text -H='X-Application-Name: client1' ws://localhost:8000/v1/messages/json ws://localhost:8080 || sleep 0.1 ; done
|
||||
|
||||
|
||||
The first few lines of output in terminal 1 should then look as follows:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
# zeek server.js
|
||||
client1: connected, sending topics array ["zeek.bridge.test"]
|
||||
client1: received: {"type":"ack","endpoint":"9089e06b-8d33-5585-ad79-4f7f6348754e-websocket-135","version":"8.1.0-dev.91"}
|
||||
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":[]}]}]}
|
||||
...
|
||||
|
||||
If you require synchronization between the Zeek instance and the remote API, this
|
||||
is best achieved with events once the connection between the remote API and the
|
||||
Zeek cluster is established.
|
||||
|
||||
Alternative Approaches
|
||||
----------------------
|
||||
|
||||
Since v21, Node.js contains a built-in `WebSocket client <https://nodejs.org/en/learn/getting-started/websocket>`_,
|
||||
making it possible to use vanilla :ref:`javascript` within
|
||||
Zeek to establish outgoing WebSocket connections, too.
|
||||
|
||||
The ``websocat`` tool provides more flexibility, potentially allowing
|
||||
to forward WebSocket messages to external commands which in turn could
|
||||
use HTTP POST requests to an external API.
|
Loading…
Add table
Add a link
Reference in a new issue