Commands
Two binaries. shieldlist-agent is the daemon and its one-shot modes;
shield is what you type at it.
shieldlist-agent
shieldlist-agent [-config /etc/shieldlist] [-state /var/lib/shieldlist/agent.db]
| Flag | |
|---|---|
-config <dir> |
Configuration directory. Default /etc/shieldlist. |
-state <file> |
The durable store. Default /var/lib/shieldlist/agent.db. |
-t |
Validate the configuration and exit. Changes nothing. |
-dry-run <duration> |
Read the logs' past and report what the rules make of it. Enforces nothing. 0 reads everything. |
-rule <name> |
With -dry-run, judge only this rule. |
-version |
Version, commit, build date, Go version and platform. |
-t reports every problem at once, each with its file and line —
run it after every edit.
-dry-run prints a summary and then every crossing with the lines that
convicted it. See Replay.
shield
shield [-state /var/lib/shieldlist/agent.db] <command>
Reading commands open the store read-only and work whether or not the agent is running. Acting commands write a request the agent answers on its next round — see below.
status
shield status # everything
shield status bans rules # just those sections
shield status new_today # one key: its value alone (277526) — for a script, a monitor
One key prints its value and nothing else (exit 1 if the key is not
there); new_today is the sanctions since midnight in the machine's
zone, new_today_utc the same in UTC, active_bans what is in force.
Sections: bans, agent, kernel, store, rules, reports. The
reports section only appears when asked for by name — its counts are
a scan of the store, which the default status must not pay for on a big
one. With a live
agent, every figure is read from its published snapshot — status answers
in milliseconds whatever the store's size, because the agent has already
paid for the counting on its own cadence (counts_age and counts_cost
in the store section say when and how much). Without a live agent the
CLI counts for itself, which on a multi-million-row store takes seconds.
status (fields)
The aggregate summary. Safe on a store holding millions of bans: it counts, it never lists.
shield status
active_bans 5099267
active_addresses 5099267
active_ranges 0
new_1h 7048
new_24h 7048
new_7d 7048
new_today 4211
new_today_utc 4009
bans_loadavg 124 280 469
unban_loadavg 235 254 166
lines_read 5995115
read_lag 0B
lines_loadavg 101k 337k 400k
hits_loadavg 103k 343k 408k
memory_rss 650.3MB
memory_heap 454.2MB
eval_groups 9
eval_events 12
goroutines 6
blocked_packets 1840233
blocked_bytes 212.4MB
state_file /var/lib/shieldlist/agent.db
state_size 3.8GB
history_rows 5104299
counts_age 1m37s
counts_cost 19s
rule_ssh-password-bf 10
Output is key<TAB>value with a real tab, so cut -f2 and awk both
work and a terminal still aligns.
The four loadavgs are rates per minute over 1, 5 and 15 minutes, the
same shape as /proc/loadavg: bans issued against bans expired tells you
whether the active set is growing; lines and hits tell you how fast it is
reading and how much of that it recognises.
counts_age and counts_cost appear because the aggregate figures are
computed by the agent on its own cadence and read from its health
snapshot — scanning millions of rows on every invocation would cost more
than the agent does. They say how old the figures are and what the scan
took.
Some lines only appear when they are not zero — shed, unenforced,
nft_table MISSING — because zero is the only good answer for them.
stats
The running agent's health, in more detail: uptime, throughput, read lag, counters for hits, bans, absorbed triggers and errors, memory, the size of the evaluator's working set, the index of active sanctions and its misses, and the state file.
shield stats
agent stale with a last_beat means nothing has written a health
snapshot recently — the agent is stopped or wedged.
stats also prints what each parser and each rule costs
(parser_cpu_*, rule_cpu_*, dearest first, sampled): which pattern
the cycles go to, and which rule never fires — a rule at 0 hits is
naming itself dead. The agent warns in its log when a parser or rule
runs past its budget; it never switches one off on its own.
bans
shield bans [-rule NAME] [-n 50]
Active bans, soonest to expire first, bounded by an explicit limit because a fleet under attack holds more than anyone wants printed.
ranges
shield ranges [-n 20]
The networks (/24 for IPv4, /64 for IPv6) holding the most active bans — the question behind "should this be a range ban". Every ban is stamped with its network as it is written; bans from before the stamp existed carry none and stay out of the grouping.
why
shield why 203.0.113.9
shield why 198.51.100.0/24
Everything on record against an address or a range — active and past
sanctions, with the log lines that convicted each one, and for an
address, any range ban that covers it without naming it. Detect-only
records appear too, marked detected only, never enforced. This is the
answer to "why is my customer blocked".
tail
shield tail # every decision, live
shield tail -rule ssh-bf
Follow the agent's decisions as they are made, each with the log lines
that convicted it — the real-time view of "what is this thing actually
doing". Reads the store concurrently, costs the agent nothing, works in
test (and says so). Long evidence is cut at ten lines; shield why <address> has the rest. ^C to stop.
An offender RETURNING to a sanction that already answers it shows up
too, as an again line with its running hit count — an address that
keeps coming back must be seen coming back. (Under real enforcement
these are rare by nature: the kernel drops a banned address's traffic,
so returns belong to test and to the moment before a block
lands.) The same count appears in shield bans (HITS and LAST columns)
and shield why.
When reporting is armed, a report's fate follows the ban it belongs
to: reported … score N, already reported by this account, or
report refused with the provider's words. The same status is the
REPORTED column of shield bans (sent / dup / owed / refused / -
when the rule reports nothing) and a line in shield why.
12:33:31 srv00-mail ban 213.176.26.159/32 ports all 25d
| Aug 15 12:33:29 srv00 postfix/smtpd[…]: warning: unknown[213.176.26.159]: SASL LOGIN authentication failed…
12:33:52 srv00-mail reported 213.176.26.159/32 score 100
12:41:07 srv00-mail again 213.176.26.159/32 hits 14 detected, expires 24d23h
| Aug 15 12:41:06 srv00 postfix/smtpd[…]: warning: unknown[213.176.26.159]: SASL LOGIN authentication failed…
last seen and the again timestamps are the LOG's clock — the
newest evidence line — not the moment the verdict landed, which for a
weighted rule can be minutes later (the lateness allowance).
unban
shield unban 203.0.113.9
shield unban 198.51.100.0/24
End a ban early. Unbanning something that was not banned is an answer, not an error — and if the address is covered by a range ban, the answer names the range, because the kernel would keep dropping it either way.
exempt
shield exempt 203.0.113.7
shield exempt 203.0.113.7 -for 2h -note "office, while I fix the client"
Flags go before or after the address, and durations read like the
configuration's: 30s, 10m, 2h, 30d, 4w.
Never sanction this address. It takes effect immediately — the case that matters is someone who has just locked their own office out and cannot edit a file on a server they can no longer reach.
The durable record goes into exemptions-cli.yaml beside your own
exemptions, plainly named so it can be found, read and removed with
ordinary tools.
replay
shield replay # everything the logs hold
shield replay -last 8h
Ask the running agent to read its logs' past and enforce what still applies. The reading runs beside the live watch — the agent keeps detecting while it judges the past — and is bounded by the agent's memory budget: if the past outgrows it, the newest evidence is judged and the answer says how much was dropped. See Replay for what "still applies" means.
report
shield report -last 6h # count, estimate, then ask
shield report -last 2d -n 500 -y
Submit the abuse reports that could not leave at their moment — the
live agent gives each offender one chance, at detection, and never
drains a backlog by itself. You name the window (-last has no
default), the count is shown before anything leaves, and the freshest
go first. Unlike the other acting verbs this one does not go through
the agent: it reads the key from /etc/shieldlist (-config points
elsewhere) and submits directly, settling each outcome in the store. If
it races the live sender over the same address, the provider answers
"duplicate" to one of them, which is recorded and harmless. See
Abuse reporting.
enrol
shield enrol <join-token>
Join the controller named in agent.yaml (mode: enrolled,
controller: https://…). The token comes from the controller
(shieldlist-controller token, or the console); the agent presents it,
keeps the credential it gets back, pins the controller's certificate
when the token carried its fingerprint, and from then on talks to the
controller on its own. See Enrolment.
shield on the fleet
With -controller URL -token T — or SHIELD_CONTROLLER and
SHIELD_TOKEN in the environment — shield is a client of the
controller's API and its verbs act on the whole fleet:
shield agents # every enrolled machine: versions, seen, config, bans
shield agent srv00 # one machine's declaration
shield set srv00 level=strict offer=no rules.ssh-bruteforce.thresholds=3/1m
shield bans -agent srv00 -n 20 # the fleet's bans, newest first
shield why 203.0.113.9 # banned where, by which rule, since when
shield unban 203.0.113.9 -reason "customer call" # -withdraw-report: and withdraw what was reported (AbuseIPDB)
shield exempt 198.51.100.0/24 -note partner # -lift: and lift its bans in force; -lift -withdraw-report
shield rules | shield rule ssh-bruteforce state=off
shield reload | shield validate | shield audit | shield token | shield sources | shield refresh all
The token is an operator's (shieldlist-controller api-token -name you
mints one). Everything set, rule and exempt do is a write to the
controller's configuration files, reloaded whole or reverted.
How acting commands work
unban, exempt and replay do not act directly. They write a request
into the store; the agent picks it up on its next round, does the work,
and writes the answer back, which shield prints.
That indirection is not ceremony. The agent owns the store and the kernel's sets: a second process writing either behind its back would leave the agent's memory wrong and the kernel disagreeing with the record. It also means a request made while the agent is stopped is not lost — it waits, and is carried out when one starts:
203.0.113.9 asked; no agent answered — it will be done when one starts