Skip to content

EzyShield Quick Start ​

Get EzyShield running on your server in under 5 minutes.


1. Requirements ​

RequirementMinimum version
Linuxkernel 4.x+
nftables0.9+

2. Installation ​

bash
curl -sfL https://get.ezyshield.com | sudo sh

The installer is package-first: on apt/dnf hosts it sets up the EzyShield package repository and installs the signed ezyshield package (upgrades then arrive via the package manager). On other hosts it falls back to downloading the latest ezyshield and ezyshield-enforcer binaries, verifying their checksum's cosign signature when cosign is installed (plain SHA-256 otherwise).

To install a specific version:

bash
curl -sfL https://get.ezyshield.com | sudo EZYSHIELD_VERSION=v0.1.2 sh

Build from source ​

Requires Go 1.26+.

bash
git clone https://github.com/evertramos/ezy-shield.git
cd ezy-shield
go build -o ezyshield ./cmd/ezyshield
go build -o ezyshield-enforcer ./cmd/ezyshield-enforcer
sudo mv ezyshield ezyshield-enforcer /usr/local/bin/

Verify ​

bash
ezyshield version

3. Initial setup ​

ezyshield init ​

Runs the interactive setup wizard: detects the environment, writes config files, installs systemd units (only when the OS package doesn't already provide them in /usr/lib/systemd/system/), and starts EzyShield in dry-run mode.

bash
sudo ezyshield init

This creates:

  • /etc/ezyshield/config.yaml
  • /etc/ezyshield/policy.yaml
  • /etc/ezyshield/rules.d/ (drop-in rule customizations; WordPress installs also get a commented tuning template 10-wordpress.yaml)
  • /etc/ezyshield/.env (AI API key, mode 0600)
  • /var/lib/ezyshield/ (runtime data, SQLite)
  • /etc/systemd/system/ezyshield.service and ezyshield-enforcer.service — source/script installs only: on a package install the packaged units are used and nothing is written to /etc/systemd/system/

Tip: If config files already exist, ezyshield init exits immediately listing the conflicting paths. Remove them and re-run to regenerate.

AI provider API key ​

When you enable AI analysis, the wizard prompts for your API key. The key is stored in /etc/ezyshield/.env (mode 0600) — never in config files or logs.

Supported providers:

ProviderEnv var
anthropicANTHROPIC_API_KEY
openaiOPENAI_API_KEY
ollama(no key needed)

Use --yes for non-interactive mode (writes a placeholder you edit later).

ezyshield doctor ​

Validates configuration and checks dependencies:

bash
sudo ezyshield doctor

Expected output:

[PASS] config.yaml: exists
[PASS] config.yaml: parses
[PASS] policy.yaml: exists
[PASS] policy.yaml: parses
[PASS] nft: binary present
[PASS] journald: readable
[PASS] enforcer: socket connectivity

4. Configuration — config.yaml ​

Main file at /etc/ezyshield/config.yaml.

Collectors (log sources) ​

yaml
collectors:
  - kind: journald
    unit: sshd
  - kind: file
    path: /var/log/nginx/access.log

Available types:

  • journald — requires unit field (systemd service name)
  • file — requires path field (log file path)

Enforce (local enforcement) ​

yaml
enforce:
  nftables:
    table: inet ezyshield
    set: blocked

The privileged helper (ezyshield-enforcer) handles all firewall writes via a unix socket. The daemon re-syncs the full ban set to the enforcer whenever the daemon restarts, so blocks survive daemon restarts. Restarting only the ezyshield-enforcer helper does not trigger that re-sync on its own — the ban set catches up on the next periodic ban-expiry tick, or the next daemon restart.

AI (optional) ​

yaml
ai:
  provider: anthropic
  model: claude-haiku-4-5-20251001
  api_key: env:ANTHROPIC_API_KEY
  ambiguous_band: [30, 69]
  token_budget_daily: 500000

Important: Secrets must use env:VAR_NAME references. Inline values are rejected at config load time.


5. Configuration — policy.yaml ​

File at /etc/ezyshield/policy.yaml. Controls blocking behavior.

armed (operating mode) ​

yaml
armed: false   # dry-run (default) — no real blocking
# armed: true  # enable only after validating with 'ezyshield doctor'

Allowlist ​

IPs and CIDRs that are never blocked:

yaml
allowlist:
  - 192.168.1.0/24
  - 10.0.0.1

admin_cidrs:
  - 203.0.113.50/32   # your SSH access IP

Strike table (ban escalation) ​

yaml
strikes:
  - ttl: 5m      # strike 1 — 5 minutes
  - ttl: 1h      # strike 2 — 1 hour
  - ttl: 24h     # strike 3 — 24 hours
  - ttl: 168h    # strike 4 — 7 days
  - ttl: 0       # strike 5 — permanent

Each strike represents an attack episode, not a single request. While an IP is already banned, new detections are suppressed until the ban expires.

Thresholds ​

yaml
ban_threshold: 70       # score ≥ 70 → apply strike
observe_threshold: 40   # score 40–69 → log/notify, no ban
max_bans_per_minute: 30 # safety: pause enforcement if exceeded

6. Custom rules — rules.d drop-ins ​

The detection rules are embedded in the binary and update with it. To tune or add rules, drop a *.yaml file in /etc/ezyshield/rules.d/ — entries merge over the built-in rules by name and survive updates. Full guide: Customizing Detection Rules.

Rule structure ​

yaml
rules:
  - name: ssh_bruteforce
    description: "Repeated SSH authentication failures"
    kinds: [ssh_fail, ssh_invalid_user]
    window: 60s
    threshold: 5
    score: 85
    category: bruteforce

Fields ​

FieldDescription
nameUnique rule identifier
descriptionHuman-readable description
kindsEvent types that activate the rule
windowTime window for counting
thresholdOccurrences to trigger
scoreAssigned score (0–100)
categoryCategory (bruteforce, scanner, etc.)
fieldEvent field to filter (optional)
valueExact field value (optional)
containsSubstring match (optional)
contains_anyAny-of substring match (optional)

field and a matcher (value, contains, or contains_any — mutually exclusive) only work as a pair: a rule that sets one without the other is rejected at load time. Without the pairing check, a matcher alone would count every event of the listed kinds, and a field alone would never fire.

Example: block API scanners ​

yaml
  - name: api_scanner
    description: "Scan of non-existent API endpoints"
    kinds: [http_request]
    field: status
    value: "404"
    window: 30s
    threshold: 15
    score: 75
    category: scanner

Note: A drop-in only touches the rules it names — everything else keeps riding binary updates. An invalid drop-in stops the daemon from starting (fail-closed). The legacy rules_path (whole-file replacement) is deprecated.


7. Notifications ​

Since issue #290, ezyshield init offers this step during setup — both interactively (the Notifications section) and via the non-interactive answers file (see the automation guide). The instructions below remain the post-install path for adding or changing a channel later (ezyshield config notifier <name>, or config.yaml by hand).

Telegram ​

  1. Create a bot via @BotFather and get the token.
  2. Add the bot to your group/channel and get the chat_id.
  3. Configure in config.yaml:
yaml
notify:
  rate_limit_per_minute: 5
  dedup_window_sec: 600

  telegram:
    bot_token: env:EZYSHIELD_TELEGRAM_BOT_TOKEN
    chat_ids:
      - "-1001234567890"
    severity: []   # empty = all; or: [warn, critical]

Email (SMTP) ​

yaml
  email:
    from: ezyshield@yourdomain.com
    to:
      - admin@yourdomain.com
    host: smtp.yourdomain.com
    port: 587
    username: ezyshield@yourdomain.com
    password: env:EZYSHIELD_SMTP_PASSWORD
    tls: starttls   # starttls | tls | none
    severity: []

8. Test notifications ​

Validate delivery without waiting for a real event:

bash
sudo ezyshield test notifier telegram
sudo ezyshield test notifier email

9. Run the daemon ​

bash
sudo ezyshield run

While armed: false, EzyShield runs in dry-run: it processes everything, records strikes and simulated bans so escalation mirrors production exactly (ADR-0009), and logs what would be blocked — without ever touching the firewall.

As a systemd service ​

bash
sudo systemctl enable --now ezyshield-enforcer
sudo systemctl enable --now ezyshield

Checklist before arming ​

  1. ✅ ezyshield doctor — no errors
  2. ✅ allowlist includes your access IPs
  3. ✅ admin_cidrs includes your SSH IP
  4. ✅ Notifications tested with test notifier
  5. ✅ Ran in dry-run, reviewed the logs
  6. ⬜ Run sudo ezyshield arm --for 1h (pre-flight + auto-revert window), then sudo ezyshield arm --keep once you're confident

Learn more ​

  • Anatomy of a Ban — one SSH brute force walked through every pipeline stage (with the safety-gate diagram), in dry-run and armed.
  • Troubleshooting — "nothing is detected", "ban recorded but not blocked", socket permissions, journald matching — each with the doctor check that diagnoses it.