Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
"url": "https://github.com/crowdsecurity"
},
"metadata": {
"description": "CrowdSec skills and tooling for Claude Code — operational automation for installing, configuring, and debugging CrowdSec."
"description": "CrowdSec skills and tooling for Claude Code — operational automation for installing, configuring, and debugging CrowdSec, plus a Service API skill for the premium Console cloud API."
},
"plugins": [
{
"name": "crowdsec",
"source": "./",
"description": "Operational skill for installing, configuring, operating, and debugging CrowdSec (cscli, LAPI/CAPI, hub, bouncers, WAF/AppSec) across bare-metal, Docker, and Kubernetes.",
"description": "CrowdSec skills for Claude Code: an operational skill (engine, cscli, hub, bouncers, WAF/AppSec across bare-metal, Docker, Kubernetes) and a Service API skill (premium Console cloud API — blocklists, allowlists, firewall integrations, metrics, decisions).",
"version": "0.2.3",
"author": {
"name": "CrowdSec",
Expand All @@ -30,7 +30,12 @@
"bouncer",
"fail2ban",
"devops",
"sre"
"sre",
"console",
"service-api",
"sapi",
"blocklist",
"integration"
]
}
]
Expand Down
13 changes: 10 additions & 3 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
"name": "crowdsec",
"version": "0.2.3",
"skills": [
"./skills/crowdsec"
"./skills/crowdsec",
"./skills/crowdsec-service-api"
],
"description": "Operational skill for installing, configuring, operating, and debugging CrowdSec (cscli, LAPI/CAPI, hub, bouncers, WAF/AppSec) across bare-metal, Docker, and Kubernetes.",
"description": "CrowdSec skills for Claude Code: an operational skill (install/configure/operate/debug the engine, cscli, bouncers, WAF across bare-metal/Docker/Kubernetes) and a Service API skill (drive the premium Console cloud API — blocklists, allowlists, firewall integrations, metrics, decisions).",
"author": {
"name": "CrowdSec",
"url": "https://github.com/crowdsecurity"
Expand All @@ -18,6 +19,12 @@
"waf",
"appsec",
"bouncer",
"fail2ban"
"fail2ban",
"console",
"service-api",
"sapi",
"blocklist",
"integration",
"cloud"
]
}
32 changes: 32 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ If you are uncertain about any fact, statistic, date, or piece of technical info

## Content structure

The plugin ships **two skills** under `skills/`: `crowdsec` (operational — local
engine/cscli/bouncers/WAF) and `crowdsec-service-api` (the premium Console cloud REST API). Each has
its own router `SKILL.md` and `references/`. The layout below describes the `crowdsec` skill; the
`crowdsec-service-api` layout follows in its own subsection.

### `crowdsec` skill layout

`SKILL.md` is the router — a symptom/intent-indexed table that points into `references/`.
All depth lives in `references/<area>/`, organized by the axis that fits the area:

Expand Down Expand Up @@ -61,6 +68,31 @@ All depth lives in `references/<area>/`, organized by the axis that fits the are
area's organizing axis — update the table above in the *same* change. This section is the
authoritative map of the layout; let it drift and it stops being trustworthy.

### `crowdsec-service-api` skill layout

`SKILL.md` is the router **plus** the "acting on the user's behalf" operating contract (key
resolution, `/info` validation, read-vs-mutate confirm gate). `references/` is organized by **API
resource group** — the axis that fits a REST API — one file each:

| File | Covers |
|---|---|
| `authentication.md` | Key creation, `x-api-key`, key resolution (env → `~/.config/crowdsec/sapi_key`), `/info`, Python SDK pointer. |
| `blocklists.md` | CRUD, add/remove/bulk IPs + expiration, download, subscribers, shares, search. |
| `allowlists.md` | CRUD, items + expiration, subscribers (cross-links the `crowdsec` skill's *local* allowlists). |
| `integrations.md` | Firewall/appliance feeds: create (entity_type/output_format), Basic-auth content pull + pagination, decisions stream, vendor table. |
| `decisions.md` | Org-level decisions + aggregated (short; prefer blocklists). |
| `metrics.md` | `/metrics/remediation` ROI (raw vs computed). |

CTI (the tracker surface — cves/vendors/fingerprints/tags/products/tracker-*) is **deferred to a
future iteration**: it needs a CTI-scoped key (a blocklist/decision key gets `403`). When added,
restore a `cti.md` row here.

Conventions differ from the `crowdsec` skill in two ways: **no platform axis** (the "environment" is
HTTPS, not systemd/docker/k8s — so no per-platform prefixes), and recipes are `curl` the skill runs
on the user's behalf, every mutating one gated behind explicit confirmation. Verification uses
`env: sapi` in the `verified:` block. When you add/rename a resource file here, update this table in
the same change.

## Testing

- **Nothing ships unverified.** Every command and every expected outcome must have been
Expand Down
30 changes: 25 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,22 @@

---

This is an [Agent Skill](https://docs.claude.com/en/docs/claude-code/skills) that turns Claude/Codex/... into a
hands-on CrowdSec operator. Ask it to stand up an engine, wire a bouncer, enable
the WAF, or figure out why nothing's getting blocked — it knows the `cscli`
commands, the config layout, the failure modes, and the safe way through each of
them across **bare-metal/systemd, Docker, OpnSense and Kubernetes/Helm**.
This plugin bundles **two [Agent Skills](https://docs.claude.com/en/docs/claude-code/skills)**:

- **`crowdsec`** — a hands-on CrowdSec operator. Stand up an engine, wire a
bouncer, enable the WAF, or figure out why nothing's getting blocked. It knows
the `cscli` commands, the config layout, the failure modes, and the safe way
through each across **bare-metal/systemd, Docker, OpnSense and Kubernetes/Helm**.
- **`crowdsec-service-api`** — drives the premium **Console Service API** (cloud)
on your behalf with your API key: create and populate blocklists/allowlists,
wire firewall/appliance integrations, pull remediation ROI metrics, and manage
org-level decisions — every state change gated behind an explicit confirmation.


## What it covers

**`crowdsec` (operational):**

| Area | Covered |
|---|---|
| **Install** | bare-metal/systemd · Docker · Kubernetes/Helm · OpnSense · Console enrollment |
Expand All @@ -34,6 +41,16 @@ them across **bare-metal/systemd, Docker, OpnSense and Kubernetes/Helm**.
| **Operate** | health checks & smoke tests · upgrades & rollback · multi-server / remote LAPI / mTLS |
| **Debug** | logs not parsing · no alerts firing · bouncer not blocking · specific errors |

**`crowdsec-service-api` (premium cloud API):**

| Area | Covered |
|---|---|
| **Blocklists** | create · add/remove/bulk IPs (with expiry) · download · share across orgs · subscribe engines/bouncers |
| **Allowlists** | create · items with expiry · subscribe by engine/tag/org |
| **Integrations** | firewall/appliance feeds (Palo Alto, Fortinet, Cisco, F5, Sophos, pfSense/OPNsense…) · paginated Basic-auth content pull |
| **Metrics** | remediation ROI (traffic dropped, bytes/egress saved, attacks prevented) |
| **Decisions** | org-level decisions + aggregated (read/manage) |

## 🚀 Install

The skill loads automatically once installed. Just talk to
Expand Down Expand Up @@ -80,6 +97,9 @@ Once installed, Claude picks the skill up whenever your prompt involves CrowdSec
- _"CrowdSec doesn't detect attacks on my nginx server, why?"_
- _"There's a decision for this IP but it's not being blocked."_
- _"Migrate my fail2ban jails to CrowdSec."_
- _"Create a Console blocklist and push these IPs from my SIEM to it."_ (Service API)
- _"Wire a Palo Alto external dynamic list to my CrowdSec blocklist."_ (Service API)
- _"Show me the remediation ROI metrics for last month."_ (Service API)

## What it does **not** do

Expand Down
124 changes: 124 additions & 0 deletions skills/crowdsec-service-api/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
---
name: crowdsec-service-api
description: Use when the user wants to drive the CrowdSec Console **Service API (SAPI)** — the premium cloud REST API at admin.api.crowdsec.net — to programmatically manage blocklists (add/remove/bulk IPs, share, subscribe engines), allowlists, firewall/appliance integrations (Palo Alto, Fortinet, Cisco, F5, Sophos, pfSense/OPNsense…), remediation ROI metrics, and org-level decisions. Acts on the user's behalf with their API key. This is the cloud/API skill — for the local engine, cscli, and bouncers use the `crowdsec` skill.
verified:
- date: 2026-07-29
version: "1.70.52"
env: sapi
notes: "key resolution + GET /info validation + list recipes against production SAPI"
---

# CrowdSec Service API (SAPI) — cloud blocklist / allowlist / integration automation

SAPI is the **premium** REST API behind the CrowdSec Console. It manages
**cloud-side** objects (private blocklists, allowlists, decisions, firewall integrations)
that then push down to enrolled engines and bouncers. It is **not** the local
engine API — there is no `cscli` here, only HTTPS.

- **Base URL:** `https://admin.api.crowdsec.net/v1`
- **Auth:** `x-api-key: <key>` header on every call. (One exception: the
integration *content* endpoint uses HTTP Basic with credentials minted at
integration creation — see [references/integrations.md](./references/integrations.md).)
- **Interactive API docs:** <https://admin.api.crowdsec.net/v1/docs> · spec
<https://admin.api.crowdsec.net/v1/openapi.json>

## Boundary — this skill vs the `crowdsec` skill

| You want to… | Use |
|---|---|
| Create/manage a **private blocklist** in the cloud, push IPs to it via API | this skill |
| Wire a firewall/appliance (Palo Alto, Fortinet…) to a cloud **integration** | this skill |
| Manage **cloud allowlists**, subscribe engines/tags/orgs to lists | this skill |
| Create/manage **org-level decisions** (targeted or ad-hoc bans, non-IP scopes, per tag/entity) | this skill |
| Pull **remediation ROI metrics** | this skill |
| Install / run / debug the **local engine**, `cscli`, bouncers, WAF | the `crowdsec` skill |
| **Enroll** an engine into the Console (`cscli console enroll`) | the `crowdsec` skill → `references/install/console.md` |
| Configure a **local** allowlist/whitelist on one engine | the `crowdsec` skill → `references/configure/allowlists.md` |

Cloud allowlists/blocklists here only take effect on an engine once that engine
is enrolled **and** subscribed (and `console_management` is enabled locally). The
enrollment half lives in the `crowdsec` skill.

## Acting on the user's behalf — operating contract

This skill runs real cloud mutations. Follow this every session.

**1 — Resolve the key** (never echo it, never write it into the repo):
```bash
KEY="${CROWDSEC_SAPI_KEY:-$(cat ~/.config/crowdsec/sapi_key 2>/dev/null)}"
[ -n "$KEY" ] || echo "No key: export CROWDSEC_SAPI_KEY or write it to ~/.config/crowdsec/sapi_key (chmod 0600)"
```

**2 — Validate before acting** — one read call confirms the key and shows *which
tenant* is about to change:
```bash
curl -s -H "x-api-key: $KEY" https://admin.api.crowdsec.net/v1/info
# → {"organization_id":"…","subscription_type":"…","api_key_name":"…"}
```

**3 — Classify read vs mutate.** `GET` / download / `POST …/search` are safe —
run them directly. Every **`POST` / `PATCH` / `DELETE` that changes state** needs
**explicit user confirmation first**; show the exact URL and JSON body you will
send, then wait.

**4 — Extra-danger operations** — warn in plain words *before* the confirm,
because subscribed engines **enforce** these lists, so a change can block or
unblock real traffic and is hard to undo:

| Operation | Why it's dangerous |
|---|---|
| `POST …/ips/bulk_overwrite` | Replaces the **entire** blocklist content. |
| `DELETE /blocklists/{id}` · `/allowlists/{id}` · `/integrations/{id}` | Removes the object and everyone's subscription/feed to it. |
| `POST …/ips/delete` | Un-blocks IPs fleet-wide. |
| `POST /decisions` with `target.type: org` | Bans fleet-wide across every enrolled engine in the org. |
| `…/shares` / unshare | Grants/revokes another **organization** access. |
| any `…/subscribers` change | Changes which engines/bouncers enforce the list. |

**5 — Clean up** any throwaway objects you created while testing, and remind the
user to rotate a key that was pasted into chat.

## Step — Detect the intent

| Cue from user | Go to |
|---|---|
| "test my key", "what org / plan am I on" | [references/authentication.md](./references/authentication.md) |
| "create a blocklist", "push IPs from my SIEM/SOAR", "expire IPs", "share a blocklist with another org", "subscribe my engine to a list" | [references/blocklists.md](./references/blocklists.md) |
| "cloud allowlist via API", "allow my office/CDN across the fleet" | [references/allowlists.md](./references/allowlists.md) |
| "connect Palo Alto / Fortinet / Cisco / F5 / Sophos / pfSense / OPNsense", "firewall integration", "pull IP list in vendor format", "paginate the feed" | [references/integrations.md](./references/integrations.md) |
| "remediation metrics", "how much did CrowdSec save / block", "ROI dashboard" | [references/metrics.md](./references/metrics.md) |
| "org-level decisions via API", "aggregated decisions" | [references/decisions.md](./references/decisions.md) |

## Step — curl cheat sheet

All assume `KEY` is set (see operating contract). `jq` optional for readability.

| Purpose | Command |
|---|---|
| Who am I / validate key | `curl -s -H "x-api-key: $KEY" $B/info` |
| List blocklists | `curl -s -H "x-api-key: $KEY" "$B/blocklists"` |
| List allowlists | `curl -s -H "x-api-key: $KEY" "$B/allowlists"` |
| List integrations | `curl -s -H "x-api-key: $KEY" "$B/integrations"` |
| List / find decisions | `curl -s -H "x-api-key: $KEY" "$B/decisions"` · `…?ips=1.2.3.4` |
| Remediation metrics | `curl -s -H "x-api-key: $KEY" "$B/metrics/remediation?start_date=$FROM&end_date=$TO"` |
| Add IPs to a blocklist *(mutating — confirm)* | `curl -s -H "x-api-key: $KEY" -H 'Content-Type: application/json' -X POST "$B/blocklists/$ID/ips" -d '{"ips":["1.2.3.4"]}'` |
| Create a decision *(mutating — confirm)* | `curl -s -H "x-api-key: $KEY" -H 'Content-Type: application/json' -X POST "$B/decisions" -d '{"duration":"4h","origin":"cscli","scenario":"manual","scope":"Ip","type":"ban","value":"1.2.3.4","target":{"type":"org","value":"<org>"}}'` |

where `B=https://admin.api.crowdsec.net/v1`.

## Hard don'ts

- Don't run any mutating call without showing the user the URL + body and getting
a yes (see operating contract §3–4).
- Don't use `…/ips/bulk_overwrite` when the user means "add a few IPs" — that's
`…/ips`. `bulk_overwrite` wipes the list first.
- Don't print, log, or commit the API key. Resolve it from the env var or
`~/.config/crowdsec/sapi_key` only.
- Don't assume a cloud allowlist/blocklist is enforced just because the API call
succeeded — the engine must be enrolled, subscribed, and have
`console_management` on (that's the `crowdsec` skill's job to verify).

## Docs

Canonical: <https://docs.crowdsec.net/u/console/service_api/getting_started>. Each
`references/` file cites the specific upstream page and the live OpenAPI operation
it derives from.
84 changes: 84 additions & 0 deletions skills/crowdsec-service-api/references/allowlists.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
verified:
- date: 2026-07-29
version: "1.70.52"
env: sapi
notes: "create, items create + list (scope/value fields); delete needs unsubscribe first (409 otherwise)"
- date: 2026-07-30
version: "1.70.52"
env: sapi
notes: "closed loop: subscribe org -> engine on crowdsec-vm shows it 'Managed by Console: yes', cscli allowlists check confirms the item allowlisted (~40s sync); unsubscribe then delete 204"
---

# SAPI — Allowlists

Canonical docs: <https://docs.crowdsec.net/u/console/service_api/allowlists>
OpenAPI: the `/allowlists` group — <https://admin.api.crowdsec.net/v1/docs>

A cloud **allowlist** is a named set of IPs/ranges (each with optional expiration and reason)
that subscribed engines treat as "never act on this". Same subscription model as
blocklists.

> **Cloud vs local:** this is the *org-wide, Console-pushed* allowlist. A single
> engine's local allowlist/whitelist is different — see the `crowdsec` skill,
> `references/configure/allowlists.md`. A cloud allowlist only reaches an engine
> that is enrolled, **subscribed here**, and has `console_management` enabled
> locally (`crowdsec` skill → `references/install/console.md`).
>
> Mutating calls are marked ⚠. `B=https://admin.api.crowdsec.net/v1`, `KEY` set.

## Lifecycle

### Create ⚠ / list / delete ⚠
`name` is required; `description` optional.
```bash
curl -s -H "x-api-key: $KEY" -H 'Content-Type: application/json' -X POST "$B/allowlists" -d '{
"name": "corp-egress", "description": "office + CDN egress IPs"
}'
curl -s -H "x-api-key: $KEY" "$B/allowlists" # list (paginated)
curl -s -H "x-api-key: $KEY" -X DELETE "$B/allowlists/$ID" # removes list + subscriptions
```
Delete returns **`409 Conflict`** while the list still has subscribers. Either
unsubscribe first (see Subscribers below), or **force it** —
`DELETE "$B/allowlists/$ID?force=true"`. On subscribed engines the allowlist shows
in `cscli allowlists list` as *Managed by Console: yes* and syncs within ~40s.

## Items — the IPs/ranges

`items` and `description` are required; `expiration` optional (RFC3339).
```bash
# add items ⚠
curl -s -H "x-api-key: $KEY" -H 'Content-Type: application/json' -X POST "$B/allowlists/$ID/items" -d '{
"items": ["203.0.113.10", "198.51.100.0/24"],
"description": "office ranges"
}'

curl -s -H "x-api-key: $KEY" "$B/allowlists/$ID/items" # list items
curl -s -H "x-api-key: $KEY" -H 'Content-Type: application/json' \
-X PATCH "$B/allowlists/$ID/items/<item_id>" -d '{"expiration":"2026-12-31T00:00:00Z"}' # ⚠ update
curl -s -H "x-api-key: $KEY" -X DELETE "$B/allowlists/$ID/items/<item_id>" # ⚠ delete item
```
Items accept a single IP or a CIDR range.

## Subscribers — who applies the allowlist ⚠

`entity_type` is required, one of: `org`, `tag`, `engine`, `firewall_integration`,
`remediation_component_integration`, `remediation_component`, `log_processor`. As
with blocklists, `org` takes **no `ids`** (whole org); the other types take `ids`.
```bash
# a specific tag
curl -s -H "x-api-key: $KEY" -H 'Content-Type: application/json' -X POST "$B/allowlists/$ID/subscribers" -d '{
"entity_type": "tag", "ids": ["<tag_id>"]
}'
# the entire org (no ids)
curl -s -H "x-api-key: $KEY" -H 'Content-Type: application/json' -X POST "$B/allowlists/$ID/subscribers" -d '{"entity_type":"org"}'
curl -s -H "x-api-key: $KEY" "$B/allowlists/$ID/subscribers" # list
curl -s -H "x-api-key: $KEY" -X DELETE "$B/allowlists/$ID/subscribers/<entity_id>" # ⚠ unsubscribe
```

## Use case — fleet-wide "don't block us"

Put office/CDN/monitoring ranges in one allowlist, subscribe by `tag` (or `org` for
everything). New engines that inherit the tag pick it up automatically — no
per-engine config. Set `expiration` on temporary items (a vendor's scan window,
a short-lived NAT) so they clean themselves up.
Loading
Loading