For bouncers
This page explains how to interact with the local API exposed by crowdsec.
It's meant to be useful for system administrators, or users that want to create their own bouncers.
Introduction
This documentation only covers the API usage from the bouncer POV :
- Authentication via API token (rather than JWT as crowdsec/cscli)
- Reading decisions
This guide will assume that you already have crowdsec running locally.
Authentication
Existing tokens can be viewed with cscli bouncers list
:
# cscli bouncers list
-------------------------------------------------------------------------------------------
NAME IP ADDRESS VALID LAST API PULL TYPE VERSION
-------------------------------------------------------------------------------------------
cs-firewall-bouncer-hPrueCas ✔️ 2021-02-25T19:54:46+01:00
-------------------------------------------------------------------------------------------
Let's create a new token with cscli bouncers add MyTestClient
:
# cscli bouncers add MyTestClient
Api key for 'MyTestClient':
837be58e22a28738066de1be8f53636b
Please keep this key since you will not be able to retrieve it!
This is the token that we will use to authenticate with the API :
▶ curl -H "X-Api-Key: 837be58e22a28738066de1be8f53636b" -I localhost:8080/v1/decisions
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Fri, 26 Feb 2021 12:35:37 GMT
Note: if the token is missing or incorrect, you will get a 403 answer.
API Usage
As stated in the swagger documentation, bouncer's method are restricted to the /decisions
path. They allow to query the local decisions in two modes :
- stream mode : Intended for bouncers that will - on a regular basis - query the local api for new and expired/decisions
- query mode : Intended for bouncers that want to query the local api about a specific ip/range/username etc.
Query Mode
To have some data to query for, let's add two decisions to our local API
▶ sudo cscli decisions add -i 1.2.3.4
INFO[0000] Decision successfully added
▶ sudo cscli decisions add -r 2.2.3.0/24
INFO[0000] Decision successfully added
▶ sudo cscli decisions list
+------+--------+------------------+----------------------------------------------------+--------+---------+----+--------+--------------------+----------+
| ID | SOURCE | SCOPE:VALUE | REASON | ACTION | COUNTRY | AS | EVENTS | EXPIRATION | ALERT ID |
+------+--------+------------------+----------------------------------------------------+--------+---------+----+--------+--------------------+----------+
| 2337 | cscli | Range:2.2.3.0/24 | manual 'ban' from | ban | | | 1 | 3h59m18.079301785s | 1164 |
| | | | '939972095cf1459c8b22cc608eff85daEb4yoi2wiTD7Y3fA' | | | | | | |
| 2336 | cscli | Ip:1.2.3.4 | manual 'ban' from | ban | | | 1 | 3h59m11.079297437s | 1163 |
| | | | '939972095cf1459c8b22cc608eff85daEb4yoi2wiTD7Y3fA' | | | | | | |
+------+--------+------------------+----------------------------------------------------+--------+---------+----+--------+--------------------+----------+
Query mode : IP
▶ curl -H "X-Api-Key: 837be58e22a28738066de1be8f53636b" http://localhost:8080/v1/decisions\?ip=1.2.3.4
[{"duration":"3h51m57.363171728s","id":2336,"origin":"cscli","scenario":"manual 'ban' from '939972095cf1459c8b22cc608eff85daEb4yoi2wiTD7Y3fA'","scope":"Ip","type":"ban","value":"1.2.3.4"}]
▶ curl -H "X-Api-Key: 837be58e22a28738066de1be8f53636b" http://localhost:8080/v1/decisions\?ip=1.2.3.5
null
▶ curl -H "X-Api-Key: 837be58e22a28738066de1be8f53636b" http://localhost:8080/v1/decisions\?ip\=2.2.3.42
[{"duration":"3h38m32.349736035s","id":2337,"origin":"cscli","scenario":"manual 'ban' from '939972095cf1459c8b22cc608eff85daEb4yoi2wiTD7Y3fA'","scope":"Range","type":"ban","value":"2.2.3.0/24"}]
note: notice that the decision returned is the range that we banned earlier and that contains query ip
Query mode : Range
▶ curl -H "X-Api-Key: 837be58e22a28738066de1be8f53636b" http://localhost:8080/v1/decisions\?range=1.2.3.0/24\&contains\=false
[{"duration":"3h48m7.676653651s","id":2336,"origin":"cscli","scenario":"manual 'ban' from '939972095cf1459c8b22cc608eff85daEb4yoi2wiTD7Y3fA'","scope":"Ip","type":"ban","value":"1.2.3.4"}]
note: notice the contains
flag that is set to false
▶ curl -H "X-Api-Key: 837be58e22a28738066de1be8f53636b" http://localhost:8080/v1/decisions\?range=1.2.3.0/24\&contains\=true
null
▶ curl -H "X-Api-Key: 837be58e22a28738066de1be8f53636b" http://localhost:8080/v1/decisions\?range\=2.2.3.1/25
[{"duration":"3h30m24.773063133s","id":2337,"origin":"cscli","scenario":"manual 'ban' from '939972095cf1459c8b22cc608eff85daEb4yoi2wiTD7Y3fA'","scope":"Range","type":"ban","value":"2.2.3.0/24"}]
Query mode : non IP centric decisions
While most people will use crowdsec to ban IPs or ranges, decisions can target other scopes and other decisions :
▶ sudo cscli decisions add --scope username --value myuser --type enforce_mfa
INFO[0000] Decision successfully added
▶ sudo cscli decisions list
+------+--------+------------------+----------------------------------------------------+-------------+---------+----+--------+--------------------+----------+
| ID | SOURCE | SCOPE:VALUE | REASON | ACTION | COUNTRY | AS | EVENTS | EXPIRATION | ALERT ID |
+------+--------+------------------+----------------------------------------------------+-------------+---------+----+--------+--------------------+----------+
| 2338 | cscli | username:myuser | manual 'enforce_mfa' from | enforce_mfa | | | 1 | 3h59m55.384975175s | 1165 |
| | | | '939972095cf1459c8b22cc608eff85daEb4yoi2wiTD7Y3fA' | | | | | | |
| 2337 | cscli | Range:2.2.3.0/24 | manual 'ban' from | ban | | | 1 | 3h27m1.384972861s | 1164 |
| | | | '939972095cf1459c8b22cc608eff85daEb4yoi2wiTD7Y3fA' | | | | | | |
| 2336 | cscli | Ip:1.2.3.4 | manual 'ban' from | ban | | | 1 | 3h26m54.384971268s | 1163 |
| | | | '939972095cf1459c8b22cc608eff85daEb4yoi2wiTD7Y3fA' | | | | | | |
+------+--------+------------------+----------------------------------------------------+-------------+---------+----+--------+--------------------+----------+
▶ curl -H "X-Api-Key: 837be58e22a28738066de1be8f53636b" http://localhost:8080/v1/decisions\?scope\=username\&value\=myuser
[{"duration":"3h57m59.021170481s","id":2338,"origin":"cscli","scenario":"manual 'enforce_mfa' from '939972095cf1459c8b22cc608eff85daEb4yoi2wiTD7Y3fA'","scope":"username","type":"enforce_mfa","value":"myuser"}]
▶ curl -H "X-Api-Key: 837be58e22a28738066de1be8f53636b" http://localhost:8080/v1/decisions\?type\=enforce_mfa
[{"duration":"3h57m21.050290118s","id":2338,"origin":"cscli","scenario":"manual 'enforce_mfa' from '939972095cf1459c8b22cc608eff85daEb4yoi2wiTD7Y3fA'","scope":"username","type":"enforce_mfa","value":"myuser"}]
Stream mode
The "streaming mode" of the API (which is actually more like polling) allows for bouncers that are going to fetch on a regular basis an update of the existing decisions. The endpoint is /decisions/stream
with a single startup
(boolean) argument. The argument allows to indicate if the bouncer wants the full state of decisions, or only an update since it last pulled.
Given the our state looks like :
▶ sudo cscli decisions list
+------+--------+------------------+----------------------------------------------------+--------+---------+----+--------+--------------------+----------+
| ID | SOURCE | SCOPE:VALUE | REASON | ACTION | COUNTRY | AS | EVENTS | EXPIRATION | ALERT ID |
+------+--------+------------------+----------------------------------------------------+--------+---------+----+--------+--------------------+----------+
| 2337 | cscli | Range:2.2.3.0/24 | manual 'ban' from | ban | | | 1 | 2h55m26.05271136s | 1164 |
| | | | '939972095cf1459c8b22cc608eff85daEb4yoi2wiTD7Y3fA' | | | | | | |
| 2336 | cscli | Ip:1.2.3.4 | manual 'ban' from | ban | | | 1 | 2h55m19.052706441s | 1163 |
| | | | '939972095cf1459c8b22cc608eff85daEb4yoi2wiTD7Y3fA' | | | | | | |
+------+--------+------------------+----------------------------------------------------+--------+---------+----+--------+--------------------+----------+
The first call to /decisions/stream
will look like :
▶ curl -s -H "X-Api-Key: 837be58e22a28738066de1be8f53636b" http://localhost:8080/v1/decisions/stream\?startup\=true | jq .
{
"deleted": [
{
"duration": "-18897h25m52.809576151s",
"id": 1,
"origin": "crowdsec",
"scenario": "crowdsecurity/http-probing",
"scope": "Ip",
"type": "ban",
"value": "123.206.50.249"
},
...
],
"new": [
{
"duration": "22h20m11.909761348s",
"id": 2266,
"origin": "CAPI",
"scenario": "crowdsecurity/http-sensitive-files",
"scope": "ip",
"type": "ban",
"value": "91.241.19.122/32"
},
...
]
}
note: the initial state will contained passed deleted events (to account for crashes/services restart for example), and the current decisions, both local and those fed from the central API
You might notice that even you are requesting for the initial state, you receive a lot of "deleted" decisions. This is intended to allow you to easily restart the local API without having a desynchronized state with the bouncers.
▶ curl -s -H "X-Api-Key: 837be58e22a28738066de1be8f53636b" http://localhost:8080/v1/decisions/stream\?startup\=false | jq .
{
"deleted": null,
"new": null
}
note: Calling the decisions/stream just after will lead to empty results, as no decisions have been added or deleted
Let's now add a new decision :
▶ sudo cscli decisions add -i 3.3.3.4
INFO[0000] Decision successfully added
And call our endpoint again :
▶ curl -s -H "X-Api-Key: 837be58e22a28738066de1be8f53636b" http://localhost:8080/v1/decisions/stream\?startup\=false | jq .
{
"deleted": null,
"new": [
{
"duration": "3h59m57.641708614s",
"id": 2410,
"origin": "cscli",
"scenario": "manual 'ban' from '939972095cf1459c8b22cc608eff85daEb4yoi2wiTD7Y3fA'",
"scope": "Ip",
"type": "ban",
"value": "3.3.3.4"
}
]
}