9 Getting Started
Robin edited this page 2026-08-26 21:01:02 +00:00

Getting started

Install

On Debian and its derivatives, the APT repository:

curl -fsSL https://repo.lrob.net/apt/shieldlist/shieldlist.gpg -o /usr/share/keyrings/shieldlist.gpg
echo "deb [signed-by=/usr/share/keyrings/shieldlist.gpg] https://repo.lrob.net/apt/shieldlist stable main" > /etc/apt/sources.list.d/shieldlist.list
apt update && apt install shieldlist-agent

The repository is signed with the Shieldlist key, fingerprint B528 0C88 7016 8A94 40A9 05C4 3BB5 8113 CFAB 6DC5 (gpg --show-keys shieldlist.gpg); dev in place of stable gets prereleases and development builds. The same .deb files sit on the release page, one per architecture, for apt install ./file.deb.

The package puts the binaries under /usr/sbin, installs the unit, a starter configuration that enforces nothing when there is none, and enables the service — its own and nothing else: it does not start nftables.service (on Debian that is nft -f /etc/nftables.conf, which begins with flush ruleset and would wipe Docker's NAT and every other firewall rule). The agent owns one table, inet shieldlist, and touches nothing outside it. Elsewhere, the installer:

curl -fsSLO https://git.lrob.net/Shieldlist/shieldlist-agent/raw/branch/main/scripts/install.sh
less install.sh          # one file of sh — read it before running it
sh install.sh

It works out this machine's architecture, downloads that build from the latest release, checks it against the release's SHA256SUMS, installs shieldlist-agent and shield into /usr/local/sbin, installs the systemd unit, and validates before starting anything.

Re-run it to upgrade. It replaces the binaries, touches no configuration, and unbans nothing: the sets and their timeouts live in the kernel, so a restart is a gap in detection, not an opening.

Releases are not signed yet. A checksum served from the same host as the binary proves the download was not corrupted and nothing more.

Requirements

  • Linux with nftables, and the nft binary on the path. The installer says so if it is missing; the agent will detect without it and enforce nothing.
  • Root — for programming the kernel's tables, and for reading logs it does not own.
  • journalctl, only if you watch journald units.

Building instead

git clone ssh://git@git.lrob.net:2222/Shieldlist/shieldlist-agent.git
cd shieldlist-agent && make build
install -m 0755 dist/shieldlist-agent dist/shield /usr/local/sbin/

make dist cross-compiles the whole release matrix — amd64, arm64, armv7, riscv64 — and writes SHA256SUMS beside it. No cgo anywhere, so there is no toolchain to install.

It enforces nothing yet

On a machine with no /etc/shieldlist, the installer writes a starter configuration that watches sshd and is in test:

state: test

Every rule decides and records exactly as it would otherwise, and the data plane is never touched — no nftables table, no restore, not one element. That is deliberate. Nobody points a new blocker at a production machine and hopes: run it for a day beside fail2ban, CrowdSec or whatever already guards the box, and read what it would have done.

shield status               # what it would be holding
shield bans                 # which addresses, by which rule
shield why 203.0.113.9      # the log lines that convicted one of them

shield status answers enforcement DETECT-ONLY before any count, and while the agent is in that mode the figures are the records it made rather than the bans it holds.

Exempt yourself before going further, in /etc/shieldlist/exemptions.yaml:

- ip: 203.0.113.7
  note: the office

- range: 2001:db8:1234::/48
  note: home

An exemption removes the sanction, never the observation: the traffic is still parsed, still counted, still visible in shield why.

When the decisions look right, set state: on, validate, restart. Nothing else changes.

What it watches

/etc/shieldlist/agent.yaml lists the log inputs:

logs:
  - unit: ssh.service                  # a journald unit…
    log: ssh
    ports: [22]
  - path: /var/log/nginx/access.log    # …or a file…
    log: http
  - path: /var/www/vhosts/system/*/logs/proxy_access_ssl_log   # …or many
    log: http

A path may be a pattern, and on a machine hosting sites it should be: a panel gives every site its own log directory, and sites are added by people who are not thinking about this agent. The pattern is re-read every fifteen seconds, so a site created this afternoon is watched this afternoon.

category is what ties an input to its parsers. Read a category from one kind of source only — a file and a journald unit means every line counted twice.

Check before running

shieldlist-agent -t

It validates everything and reports every problem at once, each with its file and line, then exits, changing nothing. Run it after every edit. It also answers three questions that only this machine can:

shieldlist-agent: configuration in /etc/shieldlist is valid —
  3 parsers, 1 rule, 1 policy, 0 trust entries, 2 exemptions, 3 log inputs
  test: every rule records what it would do, nothing is enforced
  /var/www/vhosts/system/*/logs/proxy_access_ssl_log matches 23 files
  enforcement: this kernel takes the agent's table
  • what each pattern matches here, now — the moment to find out that a path is wrong is before the agent starts, not in a log nobody reads;
  • whether a category connects nothing to nothing — a log input no parser reads, or a parser nothing feeds, both of which are silent;
  • whether this kernel will take the table. The agent builds its own table under a throwaway name, with no hook so nothing is evaluated against a packet, puts elements in it, and removes it again. A kernel that refuses something says so here rather than during an attack.

Run

systemctl enable --now shieldlist-agent
journalctl -u shieldlist-agent -f
shield status

A starting agent says what it loaded:

2026-08-14 17:08:57 INFO  shieldlist-agent version=v0.0.1 config=/etc/shieldlist
2026-08-14 17:08:57 INFO  loaded parsers=3 rules=1 policies=1 inputs="0 files, 1 journald"
2026-08-14 17:08:57 WARN  test: every rule records what it would do, nothing is enforced
2026-08-14 17:08:57 INFO  memory ceiling limit=956.6MB source="25% of machine memory"

and then very little, on purpose. A healthy agent is quiet: it speaks when it decides something and when something is wrong. The log reflects decisions, not traffic — a flood of a million lines produces a few hundred lines of log, and the live view is shield status.

Before you trust a rule

Do not arm a rule and hope. Ask what it would have done to the logs you already have:

shieldlist-agent -dry-run 24h
shieldlist-agent -dry-run 24h -rule my-new-rule

It reads the last day, runs the rules over it, prints every crossing with the lines that caused it, and enforces nothing — judging by window and ban duration, so a crossing counts only if the sanction it earns would still be in force. See Replay.

Note that a dry run reports crossings, not decisions: it does not apply exemptions or trust, so an address you have exempted can appear in its output and still be spared when the agent runs.

Where next