fix: split LAPI auth — bouncer key for read, machine JWT for delete
All checks were successful
Deploy / deploy (push) Successful in 19s
All checks were successful
Deploy / deploy (push) Successful in 19s
LAPI does not let machine JWTs hit GET /v1/decisions even after the machine is validated (returns 403 access forbidden). Conversely, bouncer X-Api-Key does not satisfy DELETE /v1/decisions (returns 401 "cookie token is empty"). The webapp now holds both credentials and routes each call to the right authority. Adds LAPI_BOUNCER_KEY env var + Gitea secret. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
28
README.md
28
README.md
@@ -16,26 +16,40 @@ Built so the admin can unban an IP without SSH when their own network routes cha
|
||||
|
||||
LAPI lives on the host at `0.0.0.0:8080`. Container reaches it via `host.docker.internal:host-gateway`.
|
||||
|
||||
LAPI `DELETE /v1/decisions` requires **machine JWT** (bouncers are read-only). App registers as machine via `cscli machines add`, logs in to `/v1/watchers/login`, caches the JWT ~13 minutes, and refreshes on 401.
|
||||
**LAPI splits read vs write auth:**
|
||||
|
||||
| Endpoint | Auth | Notes |
|
||||
|-------------------------------|-----------------------|------------------------------------------------|
|
||||
| `GET /v1/decisions` | bouncer `X-Api-Key` | Machine JWT returns 403 even when validated. |
|
||||
| `DELETE /v1/decisions[/{id}]` | machine `Bearer` JWT | Bouncer key returns 401 "cookie token is empty". |
|
||||
|
||||
So the app holds both credentials. JWT is acquired via `POST /v1/watchers/login`, cached ~13 min, auto-refreshed on 401. Bouncer key is used as-is.
|
||||
|
||||
## One-time setup
|
||||
|
||||
1. **Register the machine on the host** (needs sudo, choose a strong password and save it in `secrets.yml`):
|
||||
1. **Register the LAPI machine** on the host (for DELETE). Choose a strong password and save it in `secrets.yml`. `-f -` dumps creds to stdout instead of overwriting the local agent's credentials file.
|
||||
|
||||
```bash
|
||||
sudo cscli machines add crowdsec-admin --password '<PW>'
|
||||
sudo cscli machines add crowdsec-admin --password '<PW>' -f -
|
||||
```
|
||||
|
||||
2. **Gitea repo secrets** (Settings → Secrets and variables → Actions):
|
||||
2. **Register a bouncer** on the host (for GET). Save the key in `secrets.yml`.
|
||||
|
||||
```bash
|
||||
sudo cscli bouncers add crowdsec-admin
|
||||
```
|
||||
|
||||
3. **Gitea repo secrets** (Settings → Secrets and variables → Actions):
|
||||
|
||||
- `LAPI_MACHINE_ID=crowdsec-admin`
|
||||
- `LAPI_MACHINE_PASSWORD=<PW from step 1>`
|
||||
- `LAPI_BOUNCER_KEY=<key from step 2>`
|
||||
|
||||
3. **DNS**: `crowdsec.domverse-berlin.eu` → host IP.
|
||||
4. **DNS**: `crowdsec.domverse-berlin.eu` → host IP.
|
||||
|
||||
4. **Authentik**: nothing — the existing wildcard `forward_domain` provider covers `*.domverse-berlin.eu`, and the `authentik@docker` label in `docker-compose.yml` gates the router.
|
||||
5. **Authentik**: nothing — the existing wildcard `forward_domain` provider covers `*.domverse-berlin.eu`, and the `authentik@docker` label in `docker-compose.yml` gates the router.
|
||||
|
||||
5. **First deploy**: push to `main` or trigger `workflow_dispatch`. Runner builds the image, brings the stack up, and reports health.
|
||||
6. **First deploy**: push to `main` or trigger `workflow_dispatch`. Runner builds the image, brings the stack up, and reports health.
|
||||
|
||||
## Layout
|
||||
|
||||
|
||||
Reference in New Issue
Block a user