15 Getting Started
Robin edited this page 2026-10-03 09:48:24 +00:00

Getting started

The controller runs on one machine — usually not one it protects, though it may share a machine with an agent — and the agents connect to it. This page takes an empty machine to a Web UI with its first enrolled server.

Install

On Debian and its derivatives, the APT repository (the same one as the agent's; add it on this machine too, it is not there by itself):

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-controller

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 sits on the release page, one per architecture, for apt install ./file.deb.

The package creates the shieldlist system user, puts the free rule library under /usr/share/shieldlist-controller, writes a starter tree under /etc/shieldlist/controller when there is none (local listeners), installs the unit and starts the service. Elsewhere, or without packages, see Installing — the binary, the user, the tree, the unit, by hand.

Requirements

  • Linux with systemd; one static binary, no database daemon (the store is SQLite in /var/lib/shieldlist/controller).
  • A name and a certificate for the Web UI, if anyone opens it from elsewhere: the API and Web UI listen on 127.0.0.1:17454 and expect a reverse proxy with TLS in front (below).
  • Port 17453 reachable by the agents — the controller mints its own certificate for it, agents pin its fingerprint; nothing else to set up.

Where it listens

/etc/shieldlist/controller/controller.yaml:

listen:
  agents: ":17453"      # the agents; TLS the controller signs itself
  api: 127.0.0.1:17454       # the API and the Web UI; put nginx in front

nginx and certbot's nginx plugin first, if they are not there — apt install nginx certbot python3-certbot-nginx on Debian and Ubuntu, dnf install nginx certbot python3-certbot-nginx on Red Hat and its family. Without the plugin, --nginx is not a flag certbot knows.

Then, on the same machine — in /etc/nginx/sites-available/shield.example.net.conf on Debian and Ubuntu, /etc/nginx/conf.d/shield.example.net.conf on Red Hat and its family:

server {
    server_name shield.example.net;
    listen 80;
    listen [::]:80;
    location / {
        proxy_pass http://127.0.0.1:17454;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 300s;
    }
}
# Debian and Ubuntu: the symlink is what enables the site
ln -s /etc/nginx/sites-available/shield.example.net.conf /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
certbot --nginx -d shield.example.net --redirect

certbot adds the certificate, the listen 443 ssl lines and the redirection from 80 in place, and renews on its own; the name must already resolve here, since it proves you own it over port 80.

The controller believes X-Forwarded-* from a loopback peer only.

The first person

shieldlist-controller account -name you    # asks for a password twice

Twelve characters at least, and there is a reason it is strict: an account is never locked out, however many times its password is refused. Locking accounts would hand an attacker a way to lock you out; the refusals are logged instead, and a rule of your own can ban whoever collects them. A short password with no lockout behind it is the one combination that does not work.

Sign in at the Web UI with that name and password. Later, under Settings → Access, add people; a one-time code can be enrolled per account. For scripts and for shield from your desk:

shieldlist-controller api-token -name deploy

Run them as root: they open the controller's store directly, and the binary drops to the service user itself, so what it creates belongs to the service and not to root.

The rules it has to give

The tree is an agent's tree — parsers/, rules/, policies/, reports/ (the library: every parser, rule, policy and preset, once), exemptions.yaml at the root (what every machine gets), packs/<name>.yaml for each pack (a named list of rules — what a machine is given) — plus controller.yaml and one agents/<hostname>.yaml per machine: its packs, what makes it differ.

The package provides the free library under /usr/share/shieldlist-controller, read with the tree: openssh, postfix-dovecot, bind, mariadb, web-essentials (the web rules that convict on the request itself) and shieldlist-console (the console's own). Rules → Packs shows them; every rule says there what it blocks, what it applies to, what it needs and what to watch out for. An upgrade replaces the library — that is how a corrected rule reaches you — and touches nothing in the tree; a file under the same name in the tree replaces the package's. Add your own from the Web UI (Rules → New pack…, then rules and parsers in it), or copy a working machine's /etc/shieldlist language into the tree (minus agent.yaml and secrets.yaml, which stay on the machine). Then:

shieldlist-controller -t     # validates the tree AND every machine's assembly
systemctl reload shieldlist-controller          # SIGHUP: loaded whole or refused whole, pushed to the agents

-t says, per declared machine, what it would receive and where it differs from the defaults. A machine runs the packs its declaration names — give it packs in the Web UI (Servers → the server → Rules) or packs: [...] in its file; a machine with no declaration runs the packs controller.yaml names, the shipped ones to begin with, in test. See Configuration for packs, levels, overrides and per-machine switches.

shieldlist-controller init writes the library from the binary where no package put it, and a controller.yaml when there is none; it never replaces a file.

Enrol the first machine

On the controller — or in the Web UI, Servers → Add a server:

shieldlist-controller token      # a join token: one use, valid a day

On the machine, running the agent (0.0.4 or newer; 0.1.0 for the command):

shield enrol -controller https://shield.example.net:17453 <join-token>

Within seconds the machine appears under Servers with the revision it holds, its bans and detections start arriving, and shield status on the machine shows controller_sync. It runs the packs its declaration names — or the shipped ones — in test until you arm it (Servers → the server, two clicks): every rule records, nothing new is banned. The machine keeps its own agent.yaml, secrets and local exemptions; the rule language, the state and the level now come from the controller. Back on its own at any time: shield leave — the local files were never touched.

Then

  • The Web UI: the feed, the map, the panel, bans by hand.
  • Configuration: what each machine runs (levels, overrides, offer/enforce, who may lift), published exemption sources, geolocation, retention, updates.
  • The API, and shield -controller URL -token T for the fleet from any machine.

Report offenders to AbuseIPDB

Reporting is one switch and one key: Settings → Reporting → AbuseIPDB in the Web UI (or abuseipdb: in the controller's secrets.yaml — key, daily_limit, enabled). The controller reports for every server whose declaration says reporting: controller (the default); a server saying reporting: agent reports with the key in its own secrets.yaml.

  • One report per ban, composed from the rule's preset — categories and named fields only, never the raw log line. Recidivism is reported once per episode. Test bans are never reported.
  • The daily limit is your plan's: leave it on auto and the first answer of the day teaches it, or set a number of your own — deliberately below the plan when several machines share one account.
  • Two counts on the page, kept apart on purpose: what this controller sent today, and what AbuseIPDB says is left on the whole account. On a shared account the second moves faster than the first — every machine and tool reporting with the key draws on it.
  • Off keeps the key and sends nothing until it is turned back on. A server reporting with its own key follows its own secrets.yaml (enabled: false there).