-
Notifications
You must be signed in to change notification settings - Fork 198
Add supabase-supavisor Collection #1606
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
khashashin
wants to merge
14
commits into
crowdsecurity:master
Choose a base branch
from
khashashin:master
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
5d1eb93
feat: add Supabase Supavisor collection with parser and brute-force s…
khashashin b872470
refactor: clean up parser and scenario assertions for Supavisor logs …
khashashin f94c169
feat: add Supavisor Docker logs parser and configuration
khashashin 7e4fa6b
refactor: streamline Supavisor log parsing patterns and remove redund…
khashashin 61558b3
refactor: simplify Supavisor scenario documentation by removing redun…
khashashin d23abb0
refactor: update Supabase Supavisor documentation for clarity and acc…
khashashin 375a199
refactor: add timestamp extraction to Supavisor log parsing patterns
khashashin 13c923a
refactor: update Supavisor Docker logs parser to prepare logs for par…
khashashin 4aa1e32
refactor: simplify scenario assertions by removing redundant structure
khashashin e635d2b
refactor: remove redundant Supavisor Docker logs parser from document…
khashashin fbce5d4
refactor: update Supavisor Docker logs parser path in configuration
khashashin e40204d
docs: add detection details for Supavisor log patterns
khashashin 3865ae3
docs: add supabase-supavisor container name to regex patterns
khashashin 7d03bb2
Merge branch 'master' into master
sabban File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| parsers: | ||
| - crowdsecurity/dateparse-enrich | ||
| - ./parsers/s01-parse/crowdsecurity/supavisor-logs.yaml | ||
| scenarios: | ||
| - ./scenarios/crowdsecurity/supavisor-bf.yaml | ||
| postoverflows: | ||
| - "" | ||
| log_file: supavisor-logs.log | ||
| log_type: supavisor | ||
| ignore_parsers: false | ||
khashashin marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| # Wrong password auth failure - first entry (192.168.1.100) | ||
| results["s01-parse"]["crowdsecurity/supavisor-logs"][0].Success == true | ||
| results["s01-parse"]["crowdsecurity/supavisor-logs"][0].Evt.Meta.source_ip == "192.168.1.100" | ||
| results["s01-parse"]["crowdsecurity/supavisor-logs"][0].Evt.Meta.log_type == "supavisor_auth_fail" | ||
| results["s01-parse"]["crowdsecurity/supavisor-logs"][0].Evt.Meta.service == "supavisor" | ||
| results["s01-parse"]["crowdsecurity/supavisor-logs"][0].Evt.Parsed.project == "dev_tenant" | ||
| results["s01-parse"]["crowdsecurity/supavisor-logs"][0].Evt.Parsed.db_user == "postgres" | ||
|
|
||
| # Wrong password auth failure - second attacker (10.0.0.50) - entry index 6 | ||
| results["s01-parse"]["crowdsecurity/supavisor-logs"][6].Success == true | ||
| results["s01-parse"]["crowdsecurity/supavisor-logs"][6].Evt.Meta.source_ip == "10.0.0.50" | ||
| results["s01-parse"]["crowdsecurity/supavisor-logs"][6].Evt.Meta.log_type == "supavisor_auth_fail" | ||
| results["s01-parse"]["crowdsecurity/supavisor-logs"][6].Evt.Parsed.db_user == "admin" | ||
|
|
||
| # SSL required error - entry index 12 | ||
| results["s01-parse"]["crowdsecurity/supavisor-logs"][12].Success == true | ||
| results["s01-parse"]["crowdsecurity/supavisor-logs"][12].Evt.Meta.source_ip == "172.16.0.25" | ||
| results["s01-parse"]["crowdsecurity/supavisor-logs"][12].Evt.Meta.log_type == "supavisor_ssl_required" | ||
|
|
||
| # Bad startup payload (no source_ip) - entry index 13 | ||
| results["s01-parse"]["crowdsecurity/supavisor-logs"][13].Success == true | ||
| results["s01-parse"]["crowdsecurity/supavisor-logs"][13].Evt.Meta.log_type == "supavisor_bad_startup" | ||
|
|
||
| # User not found (no source_ip) - entry index 16 | ||
| results["s01-parse"]["crowdsecurity/supavisor-logs"][16].Success == true | ||
| results["s01-parse"]["crowdsecurity/supavisor-logs"][16].Evt.Meta.log_type == "supavisor_user_not_found" |
khashashin marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # Brute force scenario should trigger for IP 192.168.1.100 (6 auth failures) | ||
| results["scenarios"]["crowdsecurity/supavisor-bf"][0].Success == true | ||
| results["scenarios"]["crowdsecurity/supavisor-bf"][0].Evt.Overflow.Alert.Source.IP == "192.168.1.100" | ||
|
|
||
| # Brute force scenario should trigger for IP 10.0.0.50 (6 auth failures) | ||
| results["scenarios"]["crowdsecurity/supavisor-bf"][1].Success == true | ||
| results["scenarios"]["crowdsecurity/supavisor-bf"][1].Evt.Overflow.Alert.Source.IP == "10.0.0.50" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| 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 | ||
| 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 | ||
| 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 | ||
| 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 | ||
| 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 | ||
| 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 | ||
| 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 | ||
| 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 | ||
| 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 | ||
| 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 | ||
| 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 | ||
| 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 | ||
| 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 | ||
| 08:31:53.782 region=local [error] ClientHandler: Client startup message error: :bad_startup_payload | ||
| 08:31:54.123 region=local [error] ClientHandler: Client startup message error: :bad_startup_payload | ||
| 08:31:54.293 region=local [error] ClientHandler: Client startup message error: :bad_startup_payload | ||
| 06:06:31.740 region=local [error] ClientHandler: User not found: "Either external_id or sni_hostname must be provided" {:single, "postgres", nil} | ||
| 06:06:31.767 region=local [error] ClientHandler: User not found: "Either external_id or sni_hostname must be provided" {:single, "postgres", nil} | ||
| 18:32:01.852 request_id=GICLZXgj-0m5cLcAAUTh region=local [info] GET /api/health | ||
| 18:32:01.853 request_id=GICLZXgj-0m5cLcAAUTh region=local [info] Sent 204 in 413µs |
khashashin marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,220 @@ | ||
| # Supabase Supavisor Collection | ||
|
|
||
| Detect and block attacks against self-hosted [Supabase](https://supabase.com/) deployments using the [Supavisor](https://github.com/supabase/supavisor) connection pooler. | ||
|
|
||
| ## Why This Collection? | ||
|
|
||
| Modern Supabase deployments use Supavisor (an Elixir-based connection pooler) instead of PgBouncer. This changes where you need to monitor for attacks: | ||
|
|
||
| ``` | ||
| ┌─────────────────────────────────────────────────────────────┐ | ||
| │ Attack Flow │ | ||
| ├─────────────────────────────────────────────────────────────┤ | ||
| │ Attacker ──→ Supavisor ──→ PostgreSQL │ | ||
| │ │ │ │ │ | ||
| │ Real IP Sees real IP Only sees Supavisor's IP │ | ||
| │ ✅ Monitor here ❌ Useless for blocking │ | ||
| └─────────────────────────────────────────────────────────────┘ | ||
| ``` | ||
|
|
||
| **PostgreSQL logs are useless** in this architecture - they only show Supavisor's internal container IP, not the attacker's IP. This collection monitors Supavisor directly where the real client IPs are visible. | ||
|
|
||
| ## Installation | ||
|
|
||
| ```bash | ||
| cscli collections install crowdsecurity/supabase-supavisor | ||
| ``` | ||
|
|
||
| ## Acquisition Configuration | ||
|
|
||
| Create `/etc/crowdsec/acquis.d/supavisor.yaml`: | ||
|
|
||
| ### Standard Supabase docker-compose | ||
|
|
||
| ```yaml | ||
| source: docker | ||
| container_name: | ||
| - supabase-supavisor | ||
| labels: | ||
| type: supavisor | ||
| ``` | ||
|
|
||
| ### Coolify / Dynamic Container Names | ||
|
|
||
| Coolify and similar platforms add random suffixes to container names. Use regex matching: | ||
|
|
||
| ```yaml | ||
| source: docker | ||
| container_name_regexp: | ||
| - "supabase-supavisor-.*" | ||
| labels: | ||
| type: supavisor | ||
| ``` | ||
|
|
||
| ### Multiple Patterns (Recommended) | ||
|
|
||
| For maximum compatibility: | ||
|
|
||
| ```yaml | ||
| source: docker | ||
| container_name_regexp: | ||
| - "supabase-supavisor-.*" | ||
| - ".*supavisor.*" | ||
| labels: | ||
| type: supavisor | ||
| ``` | ||
|
|
||
| ## Requirements | ||
|
|
||
| ### Docker Socket Access | ||
|
|
||
| CrowdSec needs access to the Docker socket to read container logs: | ||
|
|
||
| ```yaml | ||
| # In your CrowdSec docker-compose.yml | ||
| volumes: | ||
| - /var/run/docker.sock:/var/run/docker.sock:ro | ||
| ``` | ||
|
|
||
| ### Network Access | ||
khashashin marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| CrowdSec must be able to reach the Supavisor container. If using Docker networks: | ||
|
|
||
| ```yaml | ||
| networks: | ||
| - your-supabase-network | ||
| ``` | ||
|
|
||
| ## What Gets Detected | ||
|
|
||
| ### ✅ Detectable (has peer_ip) | ||
|
|
||
| | Attack Type | Log Pattern | Action | | ||
| |-------------|-------------|--------| | ||
| | Wrong password | `Exchange error: "Wrong password"` | Block after 5 attempts | | ||
| | SSL required bypass | `Tenant is not allowed to connect without SSL` | Block after 5 attempts | | ||
|
|
||
| ### ❌ Not Detectable (no peer_ip in logs) | ||
|
|
||
| | Log Type | Reason | | ||
| |----------|--------| | ||
| | Bad startup payload | Supavisor doesn't log client IP | | ||
| | User not found | Supavisor doesn't log client IP | | ||
|
|
||
| This is a Supavisor logging limitation, not a CrowdSec limitation. | ||
|
|
||
| ## Included Components | ||
|
|
||
| | Type | Name | Description | | ||
| |------|------|-------------| | ||
| | Parser | `crowdsecurity/supavisor-logs` | Extracts fields from Supavisor logs | | ||
| | Scenario | `crowdsecurity/supavisor-bf` | Brute force detection (5 failures/30s) | | ||
|
|
||
| ## Testing | ||
khashashin marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ### Generate Test Alerts | ||
|
|
||
| ```bash | ||
| # Run 6+ failed login attempts (exceeds threshold of 5) | ||
| for i in {1..7}; do | ||
| psql 'postgresql://postgres.your_tenant:wrongpassword@your-host:5432/postgres' -c '\q' 2>/dev/null | ||
| echo "Attempt $i" | ||
| sleep 2 | ||
| done | ||
| ``` | ||
|
|
||
| ### Verify Detection | ||
|
|
||
| ```bash | ||
| # Check alerts | ||
| cscli alerts list | ||
|
|
||
| # Check decisions (bans) | ||
| cscli decisions list | ||
|
|
||
| # View metrics | ||
| cscli metrics | ||
| ``` | ||
|
|
||
| ### Unban Yourself (or add your IP to the whitelist) | ||
|
|
||
| If you accidentally ban your own IP during testing: | ||
|
|
||
| ```bash | ||
| cscli decisions delete --ip YOUR_IP | ||
| ``` | ||
|
|
||
| whitelist config for your IP: | ||
|
|
||
| ```yaml | ||
| # /opt/crowdsec/config/parsers/s02-enrich/my-whitelist.yaml | ||
| name: custom/my-whitelist | ||
| description: "Whitelist my IPs" | ||
| whitelist: | ||
| reason: "My personal/office IPs" | ||
| ip: | ||
| - "123.123.123.123" | ||
| ``` | ||
|
|
||
| ## Remediation | ||
|
|
||
| After detection, you need a bouncer to actually block the IPs: | ||
|
|
||
| ### Firewall Bouncer (iptables/nftables) | ||
|
|
||
| ```bash | ||
| apt install crowdsec-firewall-bouncer-iptables | ||
| # or | ||
| apt install crowdsec-firewall-bouncer-nftables | ||
| ``` | ||
|
|
||
| ### Traefik Bouncer | ||
|
|
||
| If using Traefik as reverse proxy: | ||
|
|
||
| ```bash | ||
| # See: https://github.com/fbonalair/traefik-crowdsec-bouncer | ||
| ``` | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| ### No Logs Being Read | ||
|
|
||
| ```bash | ||
| # Check if CrowdSec sees the container | ||
| docker exec crowdsec cscli metrics | grep docker | ||
|
|
||
| # Check acquisition config | ||
| docker exec crowdsec cat /etc/crowdsec/acquis.yaml | ||
|
|
||
| # Verify container name matches regex | ||
| docker ps --format '{{.Names}}' | grep -i supavisor | ||
| ``` | ||
|
|
||
| ### Parser Not Matching | ||
|
|
||
| ```bash | ||
| # Enable debug mode in parser | ||
| # Edit: /etc/crowdsec/parsers/s01-parse/crowdsecurity/supavisor-logs.yaml | ||
| # Set: debug: true | ||
|
|
||
| # Restart and check logs | ||
| docker restart crowdsec | ||
| docker logs crowdsec 2>&1 | grep -i supavisor | ||
| ``` | ||
|
|
||
| ### Scenario Not Triggering | ||
|
|
||
| ```bash | ||
| # Check if parser is setting log_type | ||
| docker logs crowdsec 2>&1 | grep "log_type" | ||
|
|
||
| # Check scenario is loaded | ||
| cscli scenarios list | grep supavisor | ||
| ``` | ||
|
|
||
| ## Related | ||
|
|
||
| - [Supabase Self-Hosting Guide](https://supabase.com/docs/guides/self-hosting/docker) | ||
| - [Supavisor Repository](https://github.com/supabase/supavisor) | ||
| - [CrowdSec Documentation](https://docs.crowdsec.net/) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| name: crowdsecurity/supabase-supavisor | ||
| description: "Detect attacks against Supabase PostgreSQL via Supavisor connection pooler" | ||
| parsers: | ||
| - crowdsecurity/supavisor-logs | ||
| scenarios: | ||
| - crowdsecurity/supavisor-bf |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| # Supavisor Logs Parser | ||
|
|
||
| Parses [Supavisor](https://github.com/supabase/supavisor) connection pooler logs to detect authentication failures. | ||
|
|
||
| ## Overview | ||
|
|
||
| Supavisor is Supabase's cloud-native, multi-tenant PostgreSQL connection pooler written in Elixir. It replaced PgBouncer in the Supabase stack and is now the default connection pooler. | ||
|
|
||
| **Important**: In modern Supabase deployments using Supavisor, the PostgreSQL container only sees connections from Supavisor's internal IP - not the real client IPs. This makes monitoring at the Supavisor level essential for detecting and blocking attacks. | ||
|
|
||
| ## Supported Log Formats | ||
|
|
||
| ### Authentication Failures (with peer_ip - can be blocked) | ||
|
|
||
| ``` | ||
| 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 | ||
| ``` | ||
|
|
||
| ### SSL Required Errors (with peer_ip) | ||
|
|
||
| ``` | ||
| 05:44:32.395 project=dev_tenant user=postgres region=local mode=transaction type=single app_name=psql peer_ip=123.123.123.123 [error] ClientHandler: Tenant is not allowed to connect without SSL, user postgres | ||
| ``` | ||
|
|
||
| ### Bad Startup Payload (NO peer_ip - monitoring only) | ||
|
|
||
| ``` | ||
| 08:31:53.782 region=local [error] ClientHandler: Client startup message error: :bad_startup_payload | ||
| ``` | ||
|
|
||
| ### User Not Found (NO peer_ip - monitoring only) | ||
|
|
||
| ``` | ||
| 06:06:31.740 region=local [error] ClientHandler: User not found: "Either external_id or sni_hostname must be provided" {:single, "postgres", nil} | ||
| ``` | ||
|
|
||
| ## Limitation | ||
|
|
||
| Some Supavisor error types do NOT include `peer_ip`, making blocking impossible: | ||
|
|
||
| | Log Type | Has peer_ip | Can Block | | ||
| |----------|-------------|-----------| | ||
| | Wrong password | ✅ Yes | ✅ Yes | | ||
| | SSL required | ✅ Yes | ✅ Yes | | ||
| | Bad startup payload | ❌ No | ❌ No | | ||
| | User not found | ❌ No | ❌ No | | ||
|
|
||
| ## Parsed Fields | ||
|
|
||
| | Field | Description | | ||
| |-------|-------------| | ||
| | `source_ip` | Client IP address (when `peer_ip` present) | | ||
| | `project` | Supavisor tenant/project identifier | | ||
| | `db_user` | Database user attempting connection | | ||
| | `pool_mode` | Connection pool mode (transaction/session) | | ||
| | `pool_type` | Pool type (single/pooled) | | ||
| | `app_name` | Client application name | | ||
| | `log_type` | Event classification | | ||
|
|
||
| ## Acquisition | ||
|
|
||
| This parser requires Docker socket acquisition: | ||
|
|
||
| ```yaml | ||
| source: docker | ||
| container_name: | ||
| - supabase-supavisor | ||
| labels: | ||
| type: supavisor | ||
| ``` | ||
|
|
||
| For dynamic container names (Coolify, etc.): | ||
|
|
||
| ```yaml | ||
| source: docker | ||
| container_name_regexp: | ||
| - "supabase-supavisor-.*" | ||
| - ".*supavisor.*" | ||
| labels: | ||
| type: supavisor | ||
| ``` | ||
|
|
||
| ## Related | ||
|
|
||
| - Scenario: `crowdsecurity/supavisor-bf` | ||
| - Collection: `crowdsecurity/supabase-supavisor` |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.