Skip to content

Commit e6f81b9

Browse files
khashashinsabban
andauthored
Add supabase-supavisor Collection (#1606)
* feat: add Supabase Supavisor collection with parser and brute-force scenario. * refactor: clean up parser and scenario assertions for Supavisor logs by removing comments * feat: add Supavisor Docker logs parser and configuration * refactor: streamline Supavisor log parsing patterns and remove redundant comments * refactor: simplify Supavisor scenario documentation by removing redundant sections and improving clarity * refactor: update Supabase Supavisor documentation for clarity and accuracy * refactor: add timestamp extraction to Supavisor log parsing patterns * refactor: update Supavisor Docker logs parser to prepare logs for parsing * refactor: simplify scenario assertions by removing redundant structure * refactor: remove redundant Supavisor Docker logs parser from documentation * refactor: update Supavisor Docker logs parser path in configuration * docs: add detection details for Supavisor log patterns * docs: add supabase-supavisor container name to regex patterns --------- Co-authored-by: Manuel Sabban <github@sabban.eu>
1 parent 48cdb8d commit e6f81b9

File tree

10 files changed

+284
-0
lines changed

10 files changed

+284
-0
lines changed

.tests/supavisor-logs/config.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
parsers:
2+
- crowdsecurity/syslog-logs
3+
- crowdsecurity/dateparse-enrich
4+
- ./parsers/s01-parse/crowdsecurity/supavisor-logs.yaml
5+
scenarios:
6+
- ./scenarios/crowdsecurity/supavisor-bf.yaml
7+
postoverflows:
8+
- ""
9+
log_file: supavisor-logs.log
10+
log_type: supavisor
11+
ignore_parsers: false
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
results["s01-parse"]["crowdsecurity/supavisor-logs"][0].Success == true
2+
results["s01-parse"]["crowdsecurity/supavisor-logs"][0].Evt.Meta.source_ip == "192.168.1.100"
3+
results["s01-parse"]["crowdsecurity/supavisor-logs"][0].Evt.Meta.log_type == "supavisor_auth_fail"
4+
results["s01-parse"]["crowdsecurity/supavisor-logs"][0].Evt.Meta.service == "supavisor"
5+
results["s01-parse"]["crowdsecurity/supavisor-logs"][0].Evt.Parsed.project == "dev_tenant"
6+
results["s01-parse"]["crowdsecurity/supavisor-logs"][0].Evt.Parsed.db_user == "postgres"
7+
results["s01-parse"]["crowdsecurity/supavisor-logs"][6].Success == true
8+
results["s01-parse"]["crowdsecurity/supavisor-logs"][6].Evt.Meta.source_ip == "10.0.0.50"
9+
results["s01-parse"]["crowdsecurity/supavisor-logs"][6].Evt.Meta.log_type == "supavisor_auth_fail"
10+
results["s01-parse"]["crowdsecurity/supavisor-logs"][6].Evt.Parsed.db_user == "admin"
11+
results["s01-parse"]["crowdsecurity/supavisor-logs"][12].Success == true
12+
results["s01-parse"]["crowdsecurity/supavisor-logs"][12].Evt.Meta.source_ip == "172.16.0.25"
13+
results["s01-parse"]["crowdsecurity/supavisor-logs"][12].Evt.Meta.log_type == "supavisor_ssl_required"
14+
results["s01-parse"]["crowdsecurity/supavisor-logs"][13].Success == true
15+
results["s01-parse"]["crowdsecurity/supavisor-logs"][13].Evt.Meta.log_type == "supavisor_bad_startup"
16+
results["s01-parse"]["crowdsecurity/supavisor-logs"][16].Success == true
17+
results["s01-parse"]["crowdsecurity/supavisor-logs"][16].Evt.Meta.log_type == "supavisor_user_not_found"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
results[0].Overflow.Alert.Source.IP == "192.168.1.100"
2+
results[1].Overflow.Alert.Source.IP == "10.0.0.50"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
18:37:23.568 project=dev_tenant user=postgres region=local mode=transaction type=single app_name=psql peer_ip=192.168.1.100 [error] ClientHandler: Exchange error: "Wrong password" when method :auth_query
2+
18:38:08.977 project=dev_tenant user=postgres region=local mode=transaction type=single app_name=psql peer_ip=192.168.1.100 [error] ClientHandler: Exchange error: "Wrong password" when method :auth_query
3+
18:38:11.207 project=dev_tenant user=postgres region=local mode=transaction type=single app_name=psql peer_ip=192.168.1.100 [error] ClientHandler: Exchange error: "Wrong password" when method :auth_query
4+
18:38:13.394 project=dev_tenant user=postgres region=local mode=transaction type=single app_name=psql peer_ip=192.168.1.100 [error] ClientHandler: Exchange error: "Wrong password" when method :auth_query
5+
18:38:15.581 project=dev_tenant user=postgres region=local mode=transaction type=single app_name=psql peer_ip=192.168.1.100 [error] ClientHandler: Exchange error: "Wrong password" when method :auth_query
6+
18:38:17.778 project=dev_tenant user=postgres region=local mode=transaction type=single app_name=psql peer_ip=192.168.1.100 [error] ClientHandler: Exchange error: "Wrong password" when method :auth_query
7+
19:33:35.083 project=dev_tenant user=admin region=local mode=transaction type=single app_name=psql peer_ip=10.0.0.50 [error] ClientHandler: Exchange error: "Wrong password" when method :auth_query
8+
19:33:37.329 project=dev_tenant user=admin region=local mode=transaction type=single app_name=psql peer_ip=10.0.0.50 [error] ClientHandler: Exchange error: "Wrong password" when method :auth_query
9+
19:33:39.530 project=dev_tenant user=admin region=local mode=transaction type=single app_name=psql peer_ip=10.0.0.50 [error] ClientHandler: Exchange error: "Wrong password" when method :auth_query
10+
19:33:41.717 project=dev_tenant user=admin region=local mode=transaction type=single app_name=psql peer_ip=10.0.0.50 [error] ClientHandler: Exchange error: "Wrong password" when method :auth_query
11+
19:33:43.903 project=dev_tenant user=admin region=local mode=transaction type=single app_name=psql peer_ip=10.0.0.50 [error] ClientHandler: Exchange error: "Wrong password" when method :auth_query
12+
19:33:45.100 project=dev_tenant user=admin region=local mode=transaction type=single app_name=psql peer_ip=10.0.0.50 [error] ClientHandler: Exchange error: "Wrong password" when method :auth_query
13+
05:44:32.395 project=dev_tenant user=postgres region=local mode=transaction type=single app_name=psql peer_ip=172.16.0.25 [error] ClientHandler: Tenant is not allowed to connect without SSL, user postgres
14+
08:31:53.782 region=local [error] ClientHandler: Client startup message error: :bad_startup_payload
15+
08:31:54.123 region=local [error] ClientHandler: Client startup message error: :bad_startup_payload
16+
08:31:54.293 region=local [error] ClientHandler: Client startup message error: :bad_startup_payload
17+
06:06:31.740 region=local [error] ClientHandler: User not found: "Either external_id or sni_hostname must be provided" {:single, "postgres", nil}
18+
06:06:31.767 region=local [error] ClientHandler: User not found: "Either external_id or sni_hostname must be provided" {:single, "postgres", nil}
19+
18:32:01.852 request_id=GICLZXgj-0m5cLcAAUTh region=local [info] GET /api/health
20+
18:32:01.853 request_id=GICLZXgj-0m5cLcAAUTh region=local [info] Sent 204 in 413µs
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Supabase Supavisor Collection
2+
3+
Detect brute force attacks against self-hosted [Supabase](https://supabase.com/) deployments using the [Supavisor](https://github.com/supabase/supavisor) connection pooler.
4+
5+
## Acquisition
6+
7+
```yaml
8+
source: docker
9+
container_name:
10+
- supabase-supavisor
11+
labels:
12+
type: supavisor
13+
```
14+
15+
For dynamic container names (Coolify, etc.):
16+
17+
```yaml
18+
source: docker
19+
container_name_regexp:
20+
- "supabase-supavisor"
21+
- "supabase-supavisor-.*"
22+
labels:
23+
type: supavisor
24+
```
25+
26+
## What Gets Detected
27+
28+
### ✅ Detectable (has peer_ip)
29+
| Attack Type | Log Pattern | Action |
30+
|-------------|-------------|--------|
31+
| Wrong password | `Exchange error: "Wrong password"` | Block after 5 attempts |
32+
| SSL required bypass | `Tenant is not allowed to connect without SSL` | Block after 5 attempts |
33+
34+
### ❌ Not Detectable (no peer_ip in logs)
35+
36+
| Log Type | Reason |
37+
|----------|--------|
38+
| Bad startup payload | Supavisor doesn't log client IP |
39+
| User not found | Supavisor doesn't log client IP |
40+
41+
This is a Supavisor logging limitation, not a CrowdSec limitation.
42+
43+
## Included Components
44+
45+
| Type | Name | Description |
46+
|------|------|-------------|
47+
| Parser | `crowdsecurity/supavisor-logs` | Parses Supavisor logs |
48+
| Scenario | `crowdsecurity/supavisor-bf` | Brute force detection |
49+
50+
## Related
51+
52+
- [Supabase Self-Hosting Guide](https://supabase.com/docs/guides/self-hosting/docker)
53+
- [Supavisor Repository](https://github.com/supabase/supavisor)
54+
- [CrowdSec Documentation](https://docs.crowdsec.net/)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
name: crowdsecurity/supabase-supavisor
2+
description: "Detect attacks against Supabase PostgreSQL via Supavisor connection pooler"
3+
parsers:
4+
- crowdsecurity/supavisor-logs
5+
scenarios:
6+
- crowdsecurity/supavisor-bf
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Supavisor Logs Parser
2+
3+
Parses [Supavisor](https://github.com/supabase/supavisor) connection pooler logs to detect authentication failures.
4+
5+
## Log Format
6+
7+
```
8+
18:38:17.778 project=dev_tenant user=postgres region=local mode=transaction type=single app_name=psql peer_ip=123.123.123.123 [error] ClientHandler: Exchange error: "Wrong password" when method :auth_query
9+
```
10+
11+
## Parsed Fields
12+
13+
| Field | Description |
14+
|-------|-------------|
15+
| `source_ip` | Client IP address |
16+
| `project` | Tenant/project identifier |
17+
| `db_user` | Database user |
18+
| `log_type` | Event classification |
19+
20+
## Acquisition
21+
22+
```yaml
23+
source: docker
24+
container_name:
25+
- supabase-supavisor
26+
labels:
27+
type: supavisor
28+
```
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
name: crowdsecurity/supavisor-logs
2+
description: "Parse Supavisor connection pooler logs for authentication failures"
3+
filter: "evt.Parsed.program == 'supavisor'"
4+
onsuccess: next_stage
5+
debug: false
6+
7+
# Supavisor uses Elixir Logger format with metadata
8+
# Real log example:
9+
# 18:38:17.778 project=dev_tenant user=postgres region=local mode=transaction type=single app_name=psql peer_ip=123.123.123.123 [error] ClientHandler: Exchange error: "Wrong password" when method :auth_query
10+
pattern_syntax:
11+
SUPAVISOR_TS: '%{TIME:timestamp}\.%{INT:timestamp_ms}'
12+
SUPAVISOR_LEVEL: '\[%{WORD:log_level}\]'
13+
SUPAVISOR_META_FULL: 'project=%{DATA:project}\s+user=%{DATA:db_user}\s+region=%{DATA:region}\s+mode=%{DATA:pool_mode}\s+type=%{DATA:pool_type}\s+app_name=%{DATA:app_name}\s+peer_ip=%{IP:source_ip}'
14+
SUPAVISOR_META_PARTIAL: 'region=%{DATA:region}'
15+
16+
nodes:
17+
- grok:
18+
pattern: '%{SUPAVISOR_TS}\s+%{SUPAVISOR_META_FULL}\s+%{SUPAVISOR_LEVEL}\s+ClientHandler:\s+Exchange error:\s+"Wrong password"%{GREEDYDATA}'
19+
apply_on: message
20+
statics:
21+
- meta: log_type
22+
value: supavisor_auth_fail
23+
- meta: service
24+
value: supavisor
25+
- meta: source_ip
26+
expression: evt.Parsed.source_ip
27+
- target: evt.StrTime
28+
expression: evt.Parsed.timestamp
29+
30+
- grok:
31+
pattern: '%{SUPAVISOR_TS}\s+%{SUPAVISOR_META_FULL}\s+%{SUPAVISOR_LEVEL}\s+ClientHandler:\s+Tenant is not allowed to connect without SSL%{GREEDYDATA}'
32+
apply_on: message
33+
statics:
34+
- meta: log_type
35+
value: supavisor_ssl_required
36+
- meta: service
37+
value: supavisor
38+
- meta: source_ip
39+
expression: evt.Parsed.source_ip
40+
- target: evt.StrTime
41+
expression: evt.Parsed.timestamp
42+
43+
- grok:
44+
pattern: '%{SUPAVISOR_TS}\s+%{SUPAVISOR_META_FULL}\s+%{SUPAVISOR_LEVEL}\s+ClientHandler:\s+Exchange error:%{GREEDYDATA:error_detail}'
45+
apply_on: message
46+
statics:
47+
- meta: log_type
48+
value: supavisor_auth_fail
49+
- meta: service
50+
value: supavisor
51+
- meta: source_ip
52+
expression: evt.Parsed.source_ip
53+
- target: evt.StrTime
54+
expression: evt.Parsed.timestamp
55+
56+
- grok:
57+
pattern: '%{SUPAVISOR_TS}\s+%{SUPAVISOR_META_FULL}\s+%{SUPAVISOR_LEVEL}\s+%{GREEDYDATA:error_message}'
58+
apply_on: message
59+
filter: "evt.Parsed.log_level == 'error'"
60+
statics:
61+
- meta: log_type
62+
value: supavisor_error_with_ip
63+
- meta: service
64+
value: supavisor
65+
- meta: source_ip
66+
expression: evt.Parsed.source_ip
67+
- target: evt.StrTime
68+
expression: evt.Parsed.timestamp
69+
70+
- grok:
71+
pattern: '%{SUPAVISOR_TS}\s+%{SUPAVISOR_META_PARTIAL}\s+%{SUPAVISOR_LEVEL}\s+ClientHandler:\s+Client startup message error:\s+:bad_startup_payload'
72+
apply_on: message
73+
statics:
74+
- meta: log_type
75+
value: supavisor_bad_startup
76+
- meta: service
77+
value: supavisor
78+
- target: evt.StrTime
79+
expression: evt.Parsed.timestamp
80+
81+
- grok:
82+
pattern: '%{SUPAVISOR_TS}\s+%{SUPAVISOR_META_PARTIAL}\s+%{SUPAVISOR_LEVEL}\s+ClientHandler:\s+User not found:%{GREEDYDATA:enum_detail}'
83+
apply_on: message
84+
statics:
85+
- meta: log_type
86+
value: supavisor_user_not_found
87+
- meta: service
88+
value: supavisor
89+
- target: evt.StrTime
90+
expression: evt.Parsed.timestamp
91+
92+
statics:
93+
- meta: service
94+
value: supavisor
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Supavisor Brute Force Detection
2+
3+
Detects brute force attacks against PostgreSQL databases through the Supavisor connection pooler.
4+
5+
## Description
6+
7+
This scenario triggers when multiple authentication failures are detected from the same IP address. It detects wrong password attempts via Supavisor's `auth_query` authentication method.
8+
9+
## Behavior
10+
11+
| Parameter | Value | Description |
12+
|-----------|-------|-------------|
13+
| `capacity` | 5 | Failed attempts before triggering |
14+
| `leakspeed` | 30s | Time window for counting |
15+
| `blackhole` | 5m | Cooldown after trigger |
16+
17+
## Labels
18+
19+
| Label | Value |
20+
|-------|-------|
21+
| `confidence` | 3 |
22+
| `spoofable` | 0 |
23+
| `classification` | attack.T1110 |
24+
| `remediation` | true |
25+
26+
## Acquisition
27+
28+
```yaml
29+
source: docker
30+
container_name_regexp:
31+
- "supabase-supavisor"
32+
- "supabase-supavisor-.*"
33+
labels:
34+
type: supavisor
35+
```
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
type: leaky
2+
name: crowdsecurity/supavisor-bf
3+
description: "Detect brute force attacks against PostgreSQL via Supavisor connection pooler"
4+
filter: evt.Meta.log_type == 'supavisor_auth_fail'
5+
groupby: evt.Meta.source_ip
6+
capacity: 5
7+
leakspeed: 30s
8+
blackhole: 5m
9+
labels:
10+
service: supavisor
11+
confidence: 3
12+
spoofable: 0
13+
classification:
14+
- attack.T1110
15+
behavior: "database:bruteforce"
16+
label: "Supavisor bruteforce"
17+
remediation: true

0 commit comments

Comments
 (0)