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 console 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, writes a starter tree
under /etc/shieldlist/controller when there is none (local listeners,
no rules yet), 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 console, if anyone opens it from
elsewhere: the API and console listen on
127.0.0.1:17454and 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: 0.0.0.0:17453 # the agents; TLS the controller signs itself
api: 127.0.0.1:17454 # the API and the console; put nginx in front
With nginx and Let's Encrypt on the same machine:
server {
server_name console.example.net;
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;
}
}
certbot --nginx -d console.example.net --redirect does the rest. The
controller believes X-Forwarded-* from a loopback peer only.
The first person
sudo -u shieldlist shieldlist-controller account -name you # asks for a password twice
Sign in at the console with that name and password. Later, under
Settings → Accounts, add people; a one-time code can be enrolled per
account. For scripts and for shield from your desk:
sudo -u shieldlist shieldlist-controller api-token -name deploy
Console commands open the store directly, hence sudo -u shieldlist.
Give it rules
The tree is an agent's tree — parsers/, rules/, policies/,
reports/, exemptions.yaml at the root (what every machine gets), the
same four directories under packs/<name>/ for each pack (a set
given to machines by name) — plus controller.yaml and one
agents/<hostname>.yaml per machine: its packs, what makes it differ.
The starter tree has no rules: create packs from the console (Rules →
New pack…, then rules and patterns in it), copy a working machine's
/etc/shieldlist language into packs/<something>/ (minus agent.yaml
and secrets.yaml, which stay on the machine), or start from the
agent's example pack (/usr/share/shieldlist-agent/examples/ssh/ where
the agent is installed). Then:
sudo -u shieldlist 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 root and the packs its
declaration names — give it packs in the console (Servers → the server →
Rules) or packs: [...] in its file. See Configuration
for packs, levels, overrides and per-machine switches.
Enrol the first machine
On the controller:
sudo -u shieldlist shieldlist-controller token # a join token: one use, valid a day
On the machine, running the agent (0.0.4 or newer):
# /etc/shieldlist/agent.yaml
mode: enrolled
controller: https://console.example.net:17453 # or the address, port 17453 either way
shieldlist-agent -t && systemctl restart shieldlist-agent
shield enrol <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. The machine keeps its own
agent.yaml, secrets and local exemptions; the rule language now comes
from the controller. Fallback at any time: mode: local, restart — the
local files were never touched.
Then
- The Console: 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 Tfor the fleet from any machine.
Report offenders to AbuseIPDB
Reporting is one switch and one key: Settings → Reporting in the console
(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
autoand 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: falsethere).