Enrolment
Cette page n'existe qu'en anglais pour le moment.
An agent is complete on its own: it reads its logs, decides and
enforces from the files under /etc/shieldlist. Enrolled to a
controller it is the same program with a conversation added — its rules
come from the controller, what it decides goes up, the fleet's
decisions come down — and that is where the product is at its best:
one place to write rules for every machine, the bans one machine
decides applied by the others, the whole fleet on one screen. Standalone
is a mode, not a fork: the evaluator, the enforcement and the store are
the same either way.
Who manages what
| On its own | Enrolled | |
|---|---|---|
| Parsers, rules, policies, report presets, trust | /etc/shieldlist/{parsers,rules,policies,reports}/, trust.yaml |
the controller — assembled for this machine, held under /var/lib/shieldlist/received/; the local ones are not read and the journal says so |
The machine's state (on / test) and level |
agent.yaml |
the controller's declaration of this machine — held in /var/lib/shieldlist/machine.yaml; agent.yaml's keys are commented out |
| Fleet switches (offer, enforce), who may lift, who reports, sync cadence | — | the controller's declaration, in machine.yaml too |
| Log inputs and their ports | agent.yaml |
agent.yaml, unless the declaration names them (logs:) |
| Memory ceiling, log file, secrets, hooks | agent.yaml, secrets.yaml, hooks/ |
the same — a controller can never push a hook |
| The machine's own exemptions | exemptions.yaml, exemptions.d/, exemptions-cli.yaml |
the same, added to the fleet's |
shield status says which side applies, in two lines:
config controller (revision 0d6633e2761c668f, received 2026-09-13 07:47 UTC) — local rules, parsers and policies not read
state on (controller, heard 2026-09-13T07:47:52Z)
or, on its own, config local — agent.yaml and the rules under it decide
and state on (agent.yaml).
Joining
On the controller, mint a join token — at the Web UI (Servers → Add a
server), with shield token, or at the controller's terminal
(shieldlist-controller token). It is a secret, valid for a day and one
machine by default, and it may carry the controller's certificate
fingerprint (secret@fingerprint).
On the machine, one command:
shield enrol -controller https://shield.example.net:17453 <join-token>
What it does to the files, and says as it does:
agent.yamlgetsmode: enrolledandcontroller:— set where they are, commented out included, appended when absent; nothing else in the file moves.state:andlevel:inagent.yamlare commented out, their values kept in the comment (# state: detect-only # decided by the controller since this machine enrolled — shield status says what applies). From now on the controller decides both, and a line that said otherwise would only be a contradiction waiting to be believed. A file that still carries them — written before this command learned to, or edited since — is put right the same way when the agent starts, with a warning in the journal; a livestate:a person writes back is said byshieldlist-agent -tand never obeyed.- The agent is restarted, presents the token, keeps the credential it receives in its state store, pins the fingerprint, and from then on dials the controller itself: a long-lived connection over which the controller pushes what changed the moment it changes, and a sync every thirty seconds that also uploads what this machine decided. Nothing listens on the machine; where a long connection cannot be held, the sync alone does the job.
- The first answer brings the machine's configuration
(
/var/lib/shieldlist/received/) and its declaration (/var/lib/shieldlist/machine.yaml). Until it has arrived the machine watches — every rule records, nothing new is banned: arming is the controller's act, never a default's. The data plane is up meanwhile (the bans already on record stay enforced), so the controller'sonarms the rules at its first sync, with no restart in between.
Enrolment needs the agent that speaks the protocol (0.0.4 and newer)
and a controller 0.0.1 and newer; a pair too far apart is refused with a
message that names both versions. The controller's address must be an
https:// URL — the agent pins the certificate it meets and refuses to
speak in the clear.
Enrolling again (a reinstall, a lost state store) works with a new
join token, with one guard: while an agent under that hostname is still
talking to the controller — seen in the last two minutes — the
enrolment is refused (an agent by that name is active — revoke it first), so a join token alone cannot take a live machine's place. Stop
the old agent and wait, or revoke it first (Servers → the machine →
Revoke, or POST /v1/agents/{host}/revoke with an operator token),
then enrol.
Leaving
shield leave
The machine runs on its own again, and goes on doing exactly what it
did: state: and level: are written back into agent.yaml as the
controller last set them (from machine.yaml; a machine that never
heard from its controller keeps watching), mode: local, and the agent
is restarted on its own files. The controller's bundle stays on disk
under received/ and is no longer read; the rules under
/etc/shieldlist/rules/ are — the command says how many there are, and
says out loud when there are none (shield init writes the set the
package ships with). The credential and machine.yaml are forgotten,
and controller: is kept as a comment (the loader refuses a controller
on a machine in local mode): enrolling again — here or elsewhere — is
the one command above, which revives the line, and the machine watches
until its controller has spoken. Nothing is revoked on the controller:
revoke the machine there so nothing waits for it.
What comes from the controller
Parsers, rules, policies, report presets, trust and the fleet's
exemptions — the tree the controller assembled for this machine at its
level, with its overrides applied. It lands under
/var/lib/shieldlist/received/, a directory the agent replaces whole
on every new revision (a marker file, .shieldlist-received, says it is
the agent's — the agent never replaces a directory it did not write).
shieldlist-agent -t reads it beside your local files, so what you
validate is what runs.
A new revision is applied in place: parsers, rules, policies and the decider are rebuilt between two rounds, without a restart, and read under the controller's word on this machine's state. The rules' windows start over; sanctions, the store and the kernel's sets are untouched. A revision this agent's loader refuses is kept on disk for you to look at, the previous one keeps running, and the refusal is logged and shown to the controller.
The declaration — machine.yaml — is written by the agent at every sync
where something in it changed: the state, the level, whether this
machine offers its bans to the fleet and enforces the fleet's, who
reports its sanctions (the local reporter stands down when the
controller does it), the sync cadence. It is read again at start, so a
restart with the controller away begins the way the controller left
things; it is not yours to edit — the controller's declaration of the
machine is where these are set. The state applies at once, both ways:
on arms the rules, test relaxes them, and the journal says so. The
data plane's own mode — armed, or watching — is decided at start, so a
machine started watching and armed since bans at the next restart; the
journal says that too. shield status prints rules_armed 23/23, and
the console shows Test whenever the rules record, whatever was
declared.
What goes up, what comes down
Every sanction the machine decides is reported to the controller (with
its rule, the threshold that fired, hits, report state), and every
change to one afterwards. The fleet's sanctions come down when the
machine enforces the fleet's: each passes the local exemptions and the
machine's own addresses first, extends rather than duplicates an active
ban, and is recorded with its origin (fleet:web01/ssh-bruteforce) so
shield why says where it came from. Lifts decided on the controller —
Web UI, API, shield unban — arrive within seconds and are applied
like a local shield unban.
Reading the state of the conversation
shield status agent
config controller (revision 5f3038a21eb2de3a, received 2026-09-13 07:47 UTC) — local rules, parsers and policies not read
state on (controller, heard 2026-09-13T07:47:52Z)
controller_version 0.1.0
controller_revision 5f3038a21eb2de3a
controller_sync 9s_ago
controller_error … # only when there is one
The controller's agent list shows the same from its side, plus the machine's active bans and enforcement summary. When the controller is away the agent says so once, keeps enforcing on the last configuration received, and says once more when it is back.
Every sync also compares clocks: past thirty seconds of disagreement the
agent warns in its log (once, and again when it is over) and shield status shows controller_clock; the Web UI warns on its side. Fix NTP.
What travels up
On every sync the agent also sends the offenders it is counting but has not convicted — the nearest to a threshold first, two hundred at most, then the most recently hit among the rest, two hundred at most — so the Web UI shows who is knocking before the ban, the fresh ones included. An offender exempt on this machine travels marked as such (why), past its count too — it is hammering and will never be convicted; the Web UI hides those unless asked. A snapshot: the controller keeps the latest. Beside it, the hits the rules scored since the last sync, by offender — a flow the controller keeps per minute and draws over time; a sync that fails puts them back, nothing is lost. When the controller holds none of this machine's flow yet, the agent sends, once, what its windows still hold, minute by minute — the past as far as the longest window reaches.
Every sanction the machine decides, with the log lines that convicted it (bounded: twenty lines of a kilobyte), and every change to one — a return counted, a report settled, an end brought forward. When the controller holds less than the agent believes — the machine was enrolled again, or an operator asked for its history from the Web UI — the agent sends everything again from what is really held, a thousand a sync, evidence included; nothing doubles, the controller keys each sanction by the machine, its own id and its start.