Network sensor (Zeek, Suricata)¶
A network sensor watches the traffic on your network and writes down what it sees. Signalith reads what two free tools write, Zeek and Suricata, and turns it into searchable events, threat intel matches, findings and a Network dashboard. It works for any network: an office, a school, a clinic or a data centre.
What a network sensor is¶
A network sensor is a small Linux computer (or virtual machine) that gets a copy of your network traffic. The copy comes from a switch port set to mirror (also called SPAN) the port your internet connection or servers are on, or from a network tap. The sensor only listens: it never changes or blocks traffic.
internet ── firewall ── switch ──── your computers and servers
│
│ mirror (SPAN) port: a copy of the traffic
▼
┌───────────────────────────┐
│ sensor (Linux) │
│ Zeek: what happened │ HTTPS ┌───────────┐
│ Suricata: IDS alerts │────────▶│ Signalith │
│ Fluent Bit: sends logs │ └───────────┘
└───────────────────────────┘
Two programs run on the sensor, side by side:
- Zeek writes one log line for every connection, DNS lookup, web request, TLS session, certificate and file it sees. It answers "what happened on the network".
- Suricata checks the same traffic against a set of rules, such as the free Emerging Threats Open rules, and writes an alert when one matches (malware downloads, exploits, command and control traffic). It answers "was any of it known to be bad".
You can run either one alone, or both. Fluent Bit, the same agent Signalith uses everywhere else, sends both programs' logs to Signalith over HTTPS.
What you need¶
- A copy of the traffic: a switch port set to mirror the uplink, or a network tap. Most managed switches can mirror a port, even inexpensive ones.
- A machine with two network ports: one plugged into the mirror port (it only listens and has no IP address), and one for normal network access, to reach Signalith and get updates.
- Linux: Ubuntu Server 24.04 LTS is the easiest choice; Debian 12 or 13 and Rocky, Alma or Red Hat Enterprise Linux 9 work too. Zeek does not run on Windows. The simplest sensor is a spare mini PC with two network ports.
If you only have Windows servers, the sensor can be a small Ubuntu virtual machine in Hyper-V, but Hyper-V's own port mirroring only copies traffic between virtual machines on the same virtual switch: it does not see your office network or internet link. To see the real network, the Hyper-V host needs a spare physical network port cabled to the switch's mirror port, given to the sensor virtual machine through an external virtual switch of its own (with Allow management operating system to share this network adapter turned off), and Hyper-V must be set to pass that port's mirrored traffic on to the virtual machine, which takes extra PowerShell settings. A mini PC is usually simpler. - Outbound HTTPS from the sensor to Signalith (port 8080 by default), and to the package repositories and the rules site for updates. Nothing needs to reach the sensor: it opens no ports.
How big a sensor you need depends on how much traffic it sees:
| Traffic on the mirror port | Sensor | Events to Signalith per day | Signalith storage per day |
|---|---|---|---|
| Up to 100 Mbit/s (an office of about 50 people) | 4 CPU cores, 8 GB memory, 100 GB disk | 1 to 3 million | 1.5 to 4 GB |
| Up to 1 Gbit/s | 8 CPU cores, 16 GB memory, 250 GB SSD, an Intel network card | 10 to 30 million | 15 to 40 GB |
| Several Gbit/s | 16 or more CPU cores, 32 GB or more memory, a network card with RSS; or one sensor per uplink | 50 million and up | 75 GB and up |
These are rough guides, assuming about 1.3 KB stored per event. To send less: keep Suricata's protocol logs off when Zeek runs (the default), leave backup and storage traffic out with a capture filter on the sensor, and keep Network_Traffic for a shorter time on the Data retention page.
Already running Zeek or Suricata¶
If Zeek, Suricata or both already run on a machine, sending their logs takes a few minutes. On Signalith, open Add data and pick Network sensor:
- Tick the tools that run there: Zeek, Suricata, or both.
- If your logs are not in the usual place, change the paths:
- Zeek logs in: the folder with Zeek's current logs.
/opt/zeek/logs/currentfor Zeek's own packages (the default),/usr/local/zeek/logs/currentwhen Zeek was built from source,/nsm/zeek/logs/currenton Security Onion. - Suricata EVE file:
/var/log/suricata/eve.jsonby default. If yours is elsewhere, look forfilenameundereve-loginsuricata.yaml.
- Zeek logs in: the folder with Zeek's current logs.
- Follow the steps on the page, on the sensor: install Fluent Bit, turn on Zeek's JSON logs, save Signalith's CA certificate and the configuration, and restart Fluent Bit.
The page makes a token (purpose sensor) when you copy or download the configuration, named
after the Token for these agents field. Every copy made under the same name uses the same
token, so before copying, change the name to the sensor's own (such as nsm-01): each sensor
then has its own token. To stop a sensor sending, revoke its token on Settings > Agent
tokens (see Agent tokens).
Zeek's JSON logs¶
Zeek writes tab-separated logs unless told otherwise. Signalith needs JSON, one object per
line. Add this line to Zeek's local.zeek (/opt/zeek/share/zeek/site/local.zeek for Zeek's
packages) and apply it:
echo '@load policy/tuning/json-logs' | sudo tee -a /opt/zeek/share/zeek/site/local.zeek
sudo /opt/zeek/bin/zeekctl deploy
zeekctl is in Zeek's own bin folder, which is not on the usual search path: use the full
path (/usr/local/zeek/bin/zeekctl when Zeek was built from source). Security Onion's Zeek
already writes JSON and has Community ID on, so on Security Onion skip this and the next
section.
Suricata's eve.json is always JSON, so it needs nothing.
Community ID (recommended)¶
Zeek and Suricata can both tag each event with the Community ID of its connection, a
short code that is the same in both tools. With it, a search like
community_id="1:LQU9qZlK+B5F3KDmev6m5PMibrg=" shows a Suricata alert next to the Zeek
connection, DNS lookup, web request and files of the same flow.
- Zeek: add
@load policy/protocols/conn/community-id-loggingtolocal.zeek, then runsudo /opt/zeek/bin/zeekctl deploy. - Suricata: in
suricata.yaml, under theeve-logoutput, setcommunity-id: true, then restart Suricata (sudo systemctl restart suricata).
When both tools run¶
Zeek and Suricata can both log connections, DNS, web requests and TLS. To avoid counting them twice, when both run the configuration sends everything from Zeek and only Suricata's alerts, anomalies, files and statistics. Tick Also Suricata protocol logs on the Add data page to send Suricata's flow, DNS, web and TLS events as well.
| Running | Sent from Zeek | Sent from Suricata |
|---|---|---|
| Both | everything | everything except flow, netflow, dns, http and tls (unless you tick the switch) |
| Zeek only | everything | - |
| Suricata only | - | everything |
What the configuration does¶
The configuration reads the logs as they are written, starting with new lines (logs written before you set it up are not sent). It follows Zeek's hourly log rotation, keeps what it has not sent yet on the sensor's disk while Signalith is busy or down, and sends over HTTPS, checking Signalith's certificate with the CA you saved. The part that reads the logs looks like this:
[INPUT]
Name tail
Tag zeek.*
Path /opt/zeek/logs/current/*.log
Exclude_Path *loaded_scripts.log,*packet_filter.log,*stderr.log,*stdout.log,*print.log
DB /var/lib/fluent-bit/signalith-zeek.db
Skip_Long_Lines On
storage.type filesystem
[INPUT]
Name tail
Tag suricata.eve
Path /var/log/suricata/eve.json
DB /var/lib/fluent-bit/signalith-suricata.db
Skip_Long_Lines On
storage.type filesystem
If Fluent Bit already runs on the sensor for something else, add these inputs and the
generated [FILTER] and [OUTPUT] sections to your own configuration. Keep the tags
zeek.* and suricata.eve: they are how Signalith knows what the lines are. (Signalith also
recognises a Zeek or Suricata line by its shape under another tag, but the tag is surer.)
Suricata on pfSense or OPNsense¶
pfSense and OPNsense run Suricata on the firewall itself, and can send its EVE log as syslog. Signalith recognises an EVE JSON object inside a syslog message and maps it exactly like one sent by Fluent Bit.
- In Suricata's settings on the firewall, turn on the EVE JSON log and set its output to syslog (pfSense: the interface's EVE Output Settings, output type SYSLOG; OPNsense: Services > Intrusion Detection > Administration, then the option to send the EVE log to syslog).
- Send the firewall's syslog to Signalith: open Add data, pick Syslog and follow the pfSense and OPNsense steps there. Use TLS (port 6514) when the firewall supports it; plain UDP syslog is unencrypted and can lose messages.
The event time and the sensor name come from the EVE object (timestamp, host), else from
the syslog header. Very long EVE events can be cut short by the firewall's syslog; a cut
event is not valid JSON, so it is kept as a plain syslog event. Fluent Bit on a full sensor
does not have this limit.
Building a new sensor¶
On Signalith, open Add data, pick Network sensor, then the Set up a new sensor tab. It shows what a sensor needs and a few options, and makes one line to run on the new machine:
- Capture port: the port plugged into the mirror port or tap, such as
ens192. Leave it empty to choose on the sensor: the script lists the ports with their link and traffic. - Your networks: the address ranges inside your network, separated by commas, so Zeek
and Suricata know which side of a connection is yours. Empty means the private ranges
(
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fd00::/8). - Zeek, Suricata and Also Suricata protocol logs: which programs to install, as on the other tab.
- Install with: Packages (recommended) installs Zeek, Suricata and Fluent Bit from their publishers' signed package repositories; Docker runs their official images instead (see Docker).
- Token for these agents: the name of the sensor's agent token.
Press Copy: Signalith makes the sensor's token and puts it in the line. The line holds
that token, so anyone with it can send data as this sensor: don't share it. Paste it on the
new machine, as a user who can use sudo:
f=$(mktemp) && curl -fsSk --proto '=https' https://siem.example.com:8080/api/sensor/install.sh -o "$f" && echo "<sha-256> $f" | sha256sum -c --quiet && echo 'sgl_...' | sudo sh "$f" --role sensor --from https://siem.example.com:8080 --ca-sha256 <fingerprint> --version 1.6.0 --token -
It downloads the sensor install script from Signalith, checks it against the SHA-256 in the line, and runs it. The token goes in on the standard input, so it never shows in the list of running programs.
The line itself holds the token, though, and the shell may keep it in its history
(~/.bash_history). Unlike the one-use join token of Add a server, a
sensor's token keeps working until you revoke it. The line starts with a space, which keeps
it out of bash's history on Ubuntu and Debian (they set HISTCONTROL=ignoreboth); on Rocky,
Alma Linux and other shells, clear the history after running it (history -c, or remove the
line from ~/.bash_history). If someone else may have seen it, revoke the token
(Settings > Agent tokens) and make a new line.
The first download skips the certificate check
(the new machine does not trust Signalith's certificate authority yet); the SHA-256 checks
the script, and the script then checks Signalith's certificate authority against
--ca-sha256 before it trusts it. With a certificate from a public authority there is no
--ca-sha256: the machine's own trusted authorities are used.
Signalith must use HTTPS: the tab makes no line while it serves plain HTTP, because a sensor sends what it sees on your network.
While you install, the tab waits for the sensor and says nsm-01 is sending once its first logs arrive (it stops waiting after 30 minutes).
The line runs on Ubuntu 22.04 or 24.04, Debian 12 or 13, Rocky Linux 9 or AlmaLinux 9, on
64-bit x86 or ARM, with systemd. Zeek publishes no packages for Rocky or Alma Linux: there, use
--no-zeek (Suricata only) or --docker. The checklist says so before anything is installed.
What the script checks¶
First it checks the machine and Signalith, and prints a checklist. ✗ stops it, ! is a warning (it goes on), - is for your information.
| Check | When it is not met |
|---|---|
A supported Linux, 64-bit, with systemd, run with sudo |
✗ stops |
| Each program chosen has signed packages for this system, with a signing key the script can check (see Signing keys) | ✗ stops, before anything is installed, and says what works instead (--docker, or --no-zeek) |
Signalith answers, its certificate authority matches --ca-sha256, and it accepts the token |
✗ stops, before anything is installed |
4 or more processor cores, 8 GB or more memory, 50 GB or more free under /var and /opt |
! Zeek and Suricata may drop packets, or run out of room |
| The capture port has a link, receives packets in 10 seconds, and has no IP address | ! check the cable, the mirror port, and remove the address |
| A second network port, to reach Signalith | ! a sensor needs two |
| The clock is synchronised | ! times of what the sensor sees may be off |
| The disk is encrypted (LUKS) | - the logs hold what people do on your network |
Warnings scroll past while it installs, so it counts them again at the end (2 warning(s) above): read the Fix line under each.
Add --check-only to the line to only check, or --dry-run /tmp/sensor to see every file it
would write (under that folder) and every command it would run, changing nothing.
Choosing the capture port¶
With --interface the script uses that port. Without it, it picks the one free port: not the
one that holds the default route (the one this machine reaches the network with), and not one
with an IP address or in a bridge or bond, which carries some other network (storage,
backups). When there are several free ports, it lists them with their link and packet counts
and asks; when none is free, it stops and asks you to name the port with --interface. It
uses the port that holds the default route only when --interface names it, and warns, since
listening there could cut you off.
The script then keeps the capture port up with no address, in promiscuous mode, never
answering (no ARP, no IPv6), and turns off the network card offloads that merge packets and
confuse Zeek and Suricata (GRO, LRO, GSO, TSO): signalith-sensor-capture.service does this
at every start. It keeps what the port was like before, and puts it back when it stops (and
so on --uninstall). It never changes the machine's network settings files.
Options¶
| Option | What it does |
|---|---|
--role sensor |
What to build. The only role for now. |
--from <address> |
Signalith's address, which the sensor sends to. |
--ca-sha256 <hex> |
SHA-256 of Signalith's certificate authority; the script refuses any other. |
--next-ca-sha256 <hex> |
The new certificate authority, while Signalith switches to it. |
--version <v> |
Signalith's version, shown on the Endpoints page. |
--token - |
Read the sensor's agent token from the standard input. |
--name <name> |
The sensor's name in Signalith (default: the host name). |
--interface <port> |
The capture port. |
--networks <cidr,...> |
Your own networks (default: the private ranges). |
--no-zeek, --no-suricata |
Suricata only, or Zeek only. |
--suricata-protocols |
Suricata also logs flows, DNS, HTTP and TLS (on when there is no Zeek). |
--local-rules <file> |
A file of your own Suricata rules, added to the downloaded ones. |
--zeek-workers <n> |
Zeek processes reading the port (default: one Zeek process below 8 cores; from 8 cores, a quarter of them, at most 8). |
--suricata-threads <n> |
Suricata capture threads (default: half the cores). |
--docker |
Run the three programs in containers. |
--check-only |
Check the machine and Signalith, change nothing. |
--dry-run <folder> |
Write every file under the folder and print the commands, changing nothing. |
--uninstall [--purge] |
Remove what the script added (--purge: the logs too). |
What it installs, and from where¶
Only from each publisher's own signed package repository. Each repository's signing key is
downloaded from the publisher and used only when its fingerprint is the one pinned in the
script (see Signing keys); the key is trusted for that repository alone
(signed-by), not for every package on the machine.
| Program | Ubuntu | Debian 12 | Debian 13 | Rocky, Alma Linux 9 |
|---|---|---|---|---|
Zeek (LTS, package zeek-8.0) |
openSUSE Build Service security:zeek, as Zeek documents |
the same | the same | none: use --no-zeek or --docker |
| Suricata, suricata-update | the OISF PPA ppa:oisf/suricata-stable |
Debian's bookworm-backports |
Debian's own trixie |
OISF's COPR @oisf/suricata-8.0 |
| Fluent Bit | packages.fluentbit.io | packages.fluentbit.io | packages.fluentbit.io | packages.fluentbit.io |
Signalith does not include or pass on any of them, nor any rules: your machine downloads them from their publishers.
What it writes:
/etc/signalith-sensor/:sensor.conf(the options),token.env(the token, root only),ca.pem(Signalith's certificate authority),fluent-bit.conf(the agent's configuration, from Signalith),local.rules(your own Suricata rules, see Keeping rules up to date),manifest(everything the script added, for--uninstall)./etc/suricata/signalith-sensor.yaml, which loads the package'ssuricata.yamland changes only what the sensor needs (the capture port, your networks, the EVE log, Community ID), anddisable.confandthreshold.configbeside it, which the script creates once and never overwrites./var/lib/signalith-sensor/suricata/: the rules andsuricata-update's downloads, which only root can change./usr/lib/signalith-sensor/: its helpers, and a copy of the script itself, for--uninstall.- Zeek's
node.cfg,networks.cfg,zeekctl.cfgand asignalith.zeeksite script (JSON logs, Community ID). - The systemd units
signalith-zeek,signalith-suricata,signalith-fluent-bit,signalith-sensor-capture, and the timerssignalith-suricata-update,signalith-sensor-statusandsignalith-zeekctl-cron. The packages' ownsuricataandfluent-bitservices are masked, so only Signalith's run.
Who runs as whom¶
- Zeek runs as its own
zeekuser, with only the right to capture packets (CAP_NET_RAW), never as root. It cannot change the machine's network settings. - Suricata starts as root to open the port, then drops to its
suricatauser. Its rules are root's and it only reads them. - The rules update runs as root, and works only in folders that only root can change, so a Suricata that someone took over through the traffic it reads cannot use it to gain more.
- Fluent Bit runs as
signalith-sensor, with no special rights; it can read Zeek's and Suricata's logs (their groups) and nothing else. Only it gets the token, fromtoken.env. - Every unit is locked down with systemd's sandboxing (read-only system, private
/tmp, no new privileges);systemd-analyze security signalith-zeekshows the details. - Zeek and Suricata keep two days of logs on the sensor; Signalith holds the history.
Rules¶
The script downloads the Emerging Threats Open rules with suricata-update and installs
signalith-suricata-update.timer, which updates them once a day (at a random time, so
sensors don't all ask at once): suricata-update checks that Suricata accepts the new rules
before installing them, then Suricata reloads them without stopping. If the check fails, the
old rules stay and the sensor's status says the update failed. See
Keeping rules up to date.
Status¶
Every 5 minutes the sensor sends a status line (sourcetype=sensor:status): the versions,
whether Zeek and Suricata run, how old the rules are, the capture port's link, the share of
packets dropped, free disk, and what looks wrong. The Endpoints page shows it on the
sensor's row and in its details (each warning with what to run to fix it), and the Health
page counts the sensors that send, went silent, drop more than 5% of packets or run on rules
more than 3 days old.
A sensor the script built is silent after 15 minutes without anything from it (three status lines missed); a sensor you set up yourself, which sends no status line, after an hour. Both pages count the same way.
Running it again, and uninstalling¶
Run the line again to repair, or to upgrade: Zeek (within its long-term support line), Suricata and Fluent Bit get their publishers' newest packages, and the sensor's own files those of the Signalith version that made the line. The script changes only what differs, restarts only what it changed, and says Nothing to change when nothing does. A new line from Add data also gives the sensor a new token. With Docker, the images are the ones the Signalith version pins: update Signalith, then run its line.
To remove the sensor, run the copy of the script it keeps:
It stops and removes the units, puts the capture port back as it was, removes the files it
wrote (putting back any it replaced), the packages and repositories it added, and the zeek,
suricata and signalith-sensor accounts; packages that were there before stay, and their
own suricata or fluent-bit service starts again if it ran before. The logs stay unless you
add --purge. Then revoke the sensor's token on Signalith: Settings > Agent tokens.
Docker¶
With Install with: Docker (--docker), the script runs Zeek, Suricata and Fluent Bit in
containers with Docker Compose, from their publishers' official images, each pinned by
digest (zeek/zeek, jasonish/suricata, fluent/fluent-bit). It needs Docker Engine 24 or
later with Compose v2 on Linux; Docker Desktop cannot capture from a physical port. The
Compose file is /etc/signalith-sensor/docker/docker-compose.yml.
- The containers use the host's network, so they see the capture port. Only Zeek and
Suricata get the rights to capture (Zeek only
NET_RAW, so it cannot change the network settings); every container drops all others, has a read-only file system and cannot gain privileges. Fluent Bit reads their logs read-only. - The token is in
/etc/signalith-sensor/token.env(root only), which Compose hands to the Fluent Bit container as an environment variable: anyone who can rundocker inspecton the sensor can read it, so only administrators should be in thedockergroup. - Zeek runs as one process in its container, which suits up to a few hundred Mbit/s; for more traffic, use the packages.
- The rules update and the status line run from the host's systemd timers, as with packages.
Signing keys¶
The script trusts a repository only when its signing key's fingerprint is the one pinned in
the script (KEY_FPR_* near the top of deploy/sensor/install.sh):
| Repository | Fingerprint | Where the publisher shows it |
|---|---|---|
| Fluent Bit (packages.fluentbit.io) | C3C0 A285 34B9 293E AF51 FABD 9F9D DC08 3888 C1CD |
Fluent Bit's documentation, Linux downloads |
| Suricata, OISF PPA | 1215 04AD E276 E141 AD70 4A75 AC10 378C F205 C960 |
the PPA's page on Launchpad, "Signing key" |
| Zeek, openSUSE Build Service | not pinned yet | the project's Release.key on download.opensuse.org |
| Suricata, OISF COPR | not pinned yet | the COPR project's pubkey.gpg |
While a fingerprint is not pinned, the script will not add that repository: on Ubuntu and
Debian a sensor with Zeek needs the Zeek one, on Rocky and Alma Linux Suricata needs the COPR
one. Its checklist says so before anything is installed, and so does the Add data page. Use
--docker meanwhile, or leave Zeek out with --no-zeek on Ubuntu or Debian.
All the fingerprints are in one place near the top of the script. CI's sensor-script job
checks every one against what its publisher serves (.github/scripts/sensor-keys.sh prints
them), and fails while one is empty (TestSensorKeyFingerprintsSet with
SIGNALITH_SENSOR_KEYS=required). Before filling one in, check it against the publisher's
own page too.
By hand¶
On a system the script does not support, build the sensor by hand, then send its logs with the I already run Zeek or Suricata tab:
- Leave the capture port up with no IP address (for netplan:
dhcp4: false,dhcp6: false,link-local: [],optional: true). - Install Zeek from Zeek's signed packages (https://docs.zeek.org/en/current/install.html);
in
/opt/zeek/etc/node.cfgsetinterface=to the capture port, list your networks innetworks.cfg, add@load policy/tuning/json-logsand@load policy/protocols/conn/community-id-loggingtosite/local.zeek, and runzeekctl deploy. Add*/5 * * * * /opt/zeek/bin/zeekctl cronto root's crontab. - Install Suricata from OISF's packages; in
suricata.yamlset theaf-packetinterface,community-id: trueundereve-log, andrun-asto thesuricatauser. - Run
sudo suricata-update, restart Suricata, and add a dailysuricata-update && suricatasc -c ruleset-reload-nonblockingto root's crontab. Withrun-asset,suricata-update's test of the rules drops to that user and fails; give it--suricata-confwith a copy of the settings withoutrun-as, as the script'ssignalith-sensor-test.yamlis.
Done this way, zeekctl runs Zeek's workers as root: keep the sensor patched and give it no
other job.
What you get¶
Data models¶
Zeek and Suricata events are mapped onto data models with the usual field names, so one search covers both tools (and, for Network_Traffic and Network_Resolution, firewalls and Sysmon too).
| Zeek log | Suricata event | Data model | Main fields |
|---|---|---|---|
conn |
flow, netflow |
Network_Traffic | src, src_port, dest, dest_port, transport, app, bytes, bytes_in, bytes_out, packets_in, packets_out, duration, direction, conn_state, action, vlan, src_mac, dest_mac |
dns |
dns |
Network_Resolution | src (who asked), dest_ip (the DNS server), query, query_type, answer, reply_code, reply_code_id, ttl |
http |
http |
Web | http_method, url, url_domain, uri_path, uri_query, status, http_user_agent, http_referrer, http_content_type, bytes_in, bytes_out, user |
ssl, x509 |
tls |
Certificates | ssl_server_name, ssl_version, ssl_cipher, ssl_subject, ssl_issuer and their common names, ssl_serial, ssl_hash, ssl_start_time, ssl_end_time, ssl_validation_status, ssl_self_signed, ja3, ja3s, ja4 |
files |
fileinfo |
File_Transfers | file_name, file_size, file_hash (SHA-256), file_md5, file_sha1, file_sha256, mime_type, app, src (sender), dest (receiver), url |
notice |
alert |
Intrusion_Detection | signature, signature_id, category, severity, severity_id, action, mitre, dest_host, url, reason, local_host |
Every sensor event also has:
hostanddvc: the sensor's name.vendor_product:ZeekorSuricata;sourcetype:zeek:<log>orsuricata:<event type>, such aszeek:connorsuricata:alert.session_id: Zeek's connectionuidor Suricata'sflow_id;community_idwhen it is turned on.direction:outbound,inbound,internalorexternal. Zeek's own view of which side is local is used when it logs it (local_orig,local_resp); otherwise private addresses (10/8, 172.16/12, 192.168/16, 100.64/10, fc00::/7, loopback and link-local) count as yours._raw: the line as Zeek or Suricata wrote it, without the parts that are never stored (see Security).
Some details worth knowing:
- Severity of Suricata alerts: rule severity 1 is
high, 2medium, 3low, 4 and aboveinformational; an Emerging Threats rule whose metadata sayssignature_severity Criticaliscritical. Zeek notices aremedium. - Action: a connection that was answered is
allowed; one that was refused or got no answer (ZeekS0,REJ, ...; a Suricata TCP flow without packets back) isfailed. A sensor only listens, so it cannot tell a firewall drop from a closed port and never saysblocked. Suricata alerts areblockedonly when Suricata runs inline and dropped the packet. - DNS: on sensor events
srcis the computer that asked. On Sysmon eventsdestis the host that looked up, as before, so searchsrcfor "who looked this up" on network data. Suricata logs each lookup twice, the question and the answer; only the answer (which holds the question too) is in Network_Resolution, so lookups are not counted twice. The question is kept in Other withsignature="DNS request". - IPv6 addresses are stored in their short form (
2001:db8::10), whichever tool saw them, so one search finds a host in both. - Alerts also have
local_host, your network's end of the traffic (see The Network dashboard). - Web status is text (
status=404), as elsewhere in Signalith. - Other Zeek logs (ssh, rdp, smb, kerberos, dhcp, weird and the rest) go to Other with
sourcetype=zeek:<log>,src,src_port,dest,dest_port, and up to 64 of their other values aszeek_<name>fields. Other Suricata events (anomaly, ssh, smb and the rest) go to Other withsourcetype=suricata:<type>and their details as text insuricata_detail. - Sensor health: Zeek's
capture_lossandstatslogs and Suricata'sstatsevents go to Other withcapture_loss_percent,packets_received,packets_dropped,drop_percentanduptime_seconds. Suricata's statistics hold hundreds of counters; only these are kept, and_rawis a one-line summary. - Sent twice: each event's ID comes from its log line, so if Fluent Bit sends a batch again (after a network error or a busy server), lines already stored are not stored twice.
The Network dashboard¶
Dashboards > Network shows, for the last 24 hours by default: IDS alerts and hosts with high alerts, connections, data sent to the internet, alerts over time by severity, the top alert signatures, the hosts with the most alerts, the top talkers and destinations on the internet, the top DNS lookups and the hosts with the most failed lookups, web requests by status, the top user agents, files by type, servers with self-signed certificates, and the sensor's packet loss. See Dashboards.
The alert panels count local_host: your network's end of each alert. Suricata keeps the
direction of the packet that matched, so for most malware and command and control rules your
computer is src and the outside server dest; local_host is src for traffic that went
out, dest for traffic that came in, and both for traffic that stayed inside. The panels
about connections, DNS, web and files count only Zeek and Suricata events, so Sysmon or
firewall data does not mix in.
Threat intel matches¶
Every address, domain, URL and file hash the sensor sees is checked against your threat
intel feeds by the built-in threat-activity detection: src, dest, dest_ip,
dest_host, query, url, url_domain and the file hashes. A lookup of a known-bad
domain, a connection to a known command and control address or the download of a known
malware file becomes a finding. See Threat intel.
The ids-alert-high detection¶
The built-in ids-alert-high detection (on by default) turns every high or critical IDS
alert into a finding, every 5 minutes. The same signature between the same two hosts raises
one finding an hour. It adds risk to both hosts.
Rules in the Emerging Threats INFO and POLICY categories are mostly medium or low and do not
raise findings; search datamodel=Intrusion_Detection to see them. If one high rule is noisy
on your network, add an exception on its signature_id (from one of its findings,
Don't alert on this again; see Exceptions), or turn the
rule off on the sensor (see Keeping rules up to date).
Network detections¶
Besides threat-activity and ids-alert-high, the sensor's data lights up built-in
detections written for it (all on by default except the last):
- DNS: DNS tunnelling (many long encoded names from one host) and bursts of lookups
of names that do not exist (a domain generation algorithm). Your DNS servers are left
out: give them the asset category
dns-server(ordomain-controller) in the asset list (Assets & identities), since a sensor on the internet link sees them ask upstream for every client. - Scanning: an internal host whose connections to many other internal hosts or ports
fail, and Zeek's own
Scan::*notices. - Downloads and credentials: a program or script downloaded over plain HTTP, and passwords sent in clear text (HTTP basic authentication, FTP, Telnet).
- Risk only (they add risk to the host, and raise a risk incident when they add up): TLS to a server with a self-signed or expired certificate, more than 1 GB sent to one outside address, and connections out to the internet on reverse shell ports.
- Sensor health: Zeek missing, or Suricata dropping, more than 5% of the traffic.
- Off by default: a host inside answering the internet on a port it had not answered on in 30 days, and beaconing to one outside address (turn it on after adding exceptions for the services all your hosts talk to).
See Network detections for each one's rule and threshold. The content packs also have rules for this data, off until you turn them on: 45 SigmaHQ rules (Zeek SMB, DCE-RPC, Kerberos, RDP and certificate rules, DNS rules such as mining pools and Cobalt Strike names, and web proxy rules such as malicious user agents and paste sites) and 5 Splunk network rules (Cisco Smart Install, the F5 iControl REST exploit, ngrok, protocol on an unusual port, Tor). The web rules of both packs, written for web server logs, also search the sensor's web requests (see Network rules and Content packs).
The sample data includes a DNS tunnelling story and a clear-text sign-in story seen only by the sensor, so you can see these fire before your own sensor is running.
Keeping rules up to date¶
Suricata only knows what its rules describe, so they are updated every day. On a sensor the
script built, signalith-suricata-update.timer does it; on your own, add the daily
suricata-update of By hand. suricata-update checks that Suricata accepts the new
rules before installing them; if they fail the check, the old rules stay. Run an update now
with sudo systemctl start signalith-suricata-update.
To turn off a rule that is wrong for your network, put its SID in
/etc/suricata/disable.conf (one per line, such as 2014520; with Docker,
/etc/signalith-sensor/docker/suricata/disable.conf) and run the update again.
threshold.config beside it limits how often a rule may alert.
Your own rules go in /etc/signalith-sensor/local.rules, with packages or Docker (or give a
file with --local-rules in the line, which the script copies there). With packages, load
them with sudo systemctl start signalith-suricata-update; with Docker, run the line again.
Running the line again with a changed --local-rules file loads them straight away. Don't
edit the copy in /etc/signalith-sensor/docker/suricata/: the next run replaces it.
Air-gapped networks can load rules from a file: suricata-update --local /path/to/rules.
Security¶
- Only outbound HTTPS. The sensor sends to Signalith over HTTPS and checks Signalith's certificate with the CA saved on it (or your own certificate's root), like every agent. It opens no ports and needs nothing from Signalith except the address.
- One token per sensor. Name the token after the sensor on the Add data page and each
sensor gets its own (purpose
sensor), shown once, stored on Signalith only as a hash, and revocable on its own. On a sensor the script built it is in/etc/signalith-sensor/token.env, which only root can read, and only Fluent Bit's service gets it; set up by hand, it is in/etc/fluent-bit/fluent-bit.conf(mode 600). - A sensor is not a server. It is an agent, not a member of Signalith's servers: it holds no OpenSearch password, no CA key and no settings, only its token and the CA certificate, which is public.
- Never stored. Signalith never stores fields named
password(Zeek logs HTTP and FTP passwords if someone turns that on), the password of an FTPPASScommand in Suricata's ftp events (it becomes(hidden)), the challenge and response of a VNC sign-in in its rfb events (from which a password can be cracked), nor Suricata'spayloadandpacket(copies of the traffic) or HTTP bodies. Suricata'spayload_printable, when turned on, is cut to 2 KB. They are removed from_rawtoo. Better still, keep them off on the sensor: insuricata.yamlleavepayload,packetandhttp-bodyset tono(the default). - The capture port has no IP address. It only listens. A capture port with an address would let traffic on the mirrored network reach the sensor itself.
- Hostile input. The traffic a sensor sees can come from attackers, and so can parts of
its logs. Signalith refuses lines over 1 MB or nested more than 32 levels deep, and every
parser is fuzz tested. It maps known fields; a Zeek log it has no mapping for keeps at most
64
zeek_*fields per event, within the daily budget of new field names. On the sensor, Fluent Bit skips lines over 500 KB, and keeps at most 4 GB waiting on disk while Signalith cannot be reached (the oldest is dropped after that). - At rest. On Signalith, sensor events are stored and encrypted like all other events (see Encryption) and kept as long as the retention settings say. On the sensor, turn on full-disk encryption when you install Linux (in the Ubuntu installer: Encrypt the new Ubuntu installation), and let Zeek and Suricata keep only a few days of logs: Signalith holds the history.
- The token in the line. It keeps working until you revoke it. The line starts with a space, which keeps it out of bash's history on Ubuntu and Debian; elsewhere, clear the history after running it (see Building a new sensor).
- Least rights. With the script, Zeek no longer runs as root: it runs as its own
zeekuser with only the rights to capture packets, Suricata drops to itssuricatauser, and Fluent Bit runs assignalith-sensorwith none (see Who runs as whom). - Updates. Zeek, Suricata and Fluent Bit come only from their signed package
repositories, each key pinned by fingerprint (see Signing keys). Keep the
operating system updated (
sudo apt upgrade, or turn on unattended upgrades, which leave the publishers' repositories alone); running the line again upgrades Zeek, Suricata and Fluent Bit, and the sensor's own files.
Licences¶
Signalith only reads what these programs write. They run on the sensor as separate programs; Signalith does not include, link or ship any of them, and does not copy or pass on any rules.
| Program or content | Licence | How it is used |
|---|---|---|
| Zeek | BSD 3-Clause | Installed on the sensor from Zeek's packages. Signalith reads its JSON logs. |
| Suricata, suricata-update | GPL version 2 | Installed on the sensor from the Open Information Security Foundation's packages. Signalith reads its EVE JSON. |
| Emerging Threats Open rules | Proofpoint's ET Open licence (BSD-style; some older rules GPL version 2); the licence text comes with each download | Downloaded by suricata-update on the sensor, straight from Proofpoint. The sample data uses made-up rules with SIDs from 9100000 up, not Emerging Threats text. |
| Fluent Bit | Apache 2.0 | The Signalith agent, as on every other machine. |
Docker images (zeek/zeek, jasonish/suricata, fluent/fluent-bit) |
as the programs above | With --docker, pulled by your Docker from their publishers, not rebuilt or passed on by Signalith. |
The sensor install script and its Compose file are Signalith's own; they install or pull
these programs from their publishers and download the rules on the sensor. The Emerging
Threats Open rules' licence text is the LICENSE file in each download, in
/var/lib/signalith-sensor/suricata/update/cache on the sensor (with Docker, in the
suricata-rules volume).
Troubleshooting¶
Nothing arrives. Open the Endpoints page: the sensor should be listed as Network sensor, with when it last sent. If it is not there, check Fluent Bit's log on the sensor:
- on a sensor the script built:
sudo journalctl -u signalith-fluent-bit -n 50; - with Docker:
sudo docker compose -f /etc/signalith-sensor/docker/docker-compose.yml logs fluent-bit; - on a sensor you set up yourself:
journalctl -u fluent-bit -n 50.
Common causes are a wrong address under Agents send to, a firewall blocking port 8080, a revoked token, or a wrong log path (Fluent Bit says it found no files). The Health page shows problems with incoming data.
Zeek events are in sourcetype=zeek:unknown. Zeek is writing tab-separated logs: turn
on JSON logs (see Zeek's JSON logs). Lines written before the change stay
as they are.
Zeek and Suricata both show the same connections. You ticked Also Suricata protocol
logs, or changed the generated filter. Untick it and copy the configuration again, or
search one tool: vendor_product=Zeek.
The packet loss panel shows more than a few percent. The sensor misses part of the traffic, so it may miss attacks too. Common causes: a sensor too small for the traffic (see the sizing table), a busy disk, or network card offloading. Check the mirror port too: a mirror port slower than the ports it copies drops packets before they reach the sensor.
The script says "Signalith refused the token". The token was revoked, or is not a sensor token, or the line was cut when it was copied. Make a new line on **Add data > Network sensor
Set up a new sensor** and run it.
The script says "This Signalith's certificate authority is not the one the line names". The machine reached something else at that address (a proxy that inspects HTTPS, or another server), or Signalith's certificate authority changed since the line was made. Copy the line again from Add data; if it still fails, check that the address reaches your Signalith.
The status says no traffic, or the capture link is down. Check the cable from the
switch's mirror (SPAN) port or tap, and that the switch copies the right ports to it:
sudo tcpdump -ni ens192 -c 20 on the sensor should print packets.
The status says the sensor drops packets. It is too small for the traffic (see the sizing
table), or the disk is busy. Run the line again with more Zeek workers or Suricata threads
(--zeek-workers, --suricata-threads) if it has cores to spare, or mirror less traffic.
Offloads are turned off by signalith-sensor-capture.service; check it ran with
systemctl status signalith-sensor-capture.
A service does not start. journalctl -u signalith-zeek -u signalith-suricata -u
signalith-fluent-bit -n 100 says why. Run the line again: it repairs what differs.
No alerts at all. Check that Suricata has rules: the sensor's row on Endpoints shows
how many and how old; sudo systemctl start signalith-suricata-update updates them now, and
journalctl -u signalith-suricata-update says how it went.
To see what arrives: