mirror of
https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
synced 2026-10-05 15:08:24 +00:00
Add explicit rate limits and total connection limit
- Make RateLimit explicit in relay builder (500 subs, 60 events/min) - Add NGIT_MAX_CONNECTIONS config option (default: 500) - Update all 4 config locations (src, nix, docs, .env.example) - Fix documentation error: filter limit 5000→500 - Document Phase 2 deferral decision (per-IP enforcement) Addresses primary DoS vector (connection exhaustion) with minimal code. Per-IP rate limiting deferred until abuse detected in production. Related: issue ff38 (git endpoint throttling - separate concern)
This commit is contained in:
+11
-1
@@ -277,4 +277,14 @@
|
|||||||
# Examples:
|
# Examples:
|
||||||
# NGIT_EVENT_BLACKLIST=npub1spam...
|
# NGIT_EVENT_BLACKLIST=npub1spam...
|
||||||
# NGIT_EVENT_BLACKLIST=npub1spam...,npub1abuser...
|
# NGIT_EVENT_BLACKLIST=npub1spam...,npub1abuser...
|
||||||
# NGIT_EVENT_BLACKLIST=
|
# NGIT_EVENT_BLACKLIST=
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# RATE LIMITING & DOS PROTECTION
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Maximum total connections to the relay
|
||||||
|
# Prevents connection exhaustion DoS attacks
|
||||||
|
# CLI: --max-connections <count>
|
||||||
|
# Default: 500
|
||||||
|
# NGIT_MAX_CONNECTIONS=500
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
# Defensive Measures & Rate Limiting
|
||||||
|
|
||||||
|
This document describes the defensive measures implemented in ngit-grasp to protect against abuse, spam, and denial-of-service attacks.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
ngit-grasp employs multiple layers of defense:
|
||||||
|
|
||||||
|
1. **Connection & Subscription Limits** - Per-connection limits on subscriptions and event publishing
|
||||||
|
2. **Content Filtering** - Blacklist/whitelist system for repositories and event authors
|
||||||
|
3. **Event Validation** - Strict GRASP-01 protocol validation
|
||||||
|
4. **Relay Health Management** - Intelligent handling of problematic remote relays
|
||||||
|
|
||||||
|
## What's Implemented
|
||||||
|
|
||||||
|
### Per-Connection Rate Limits
|
||||||
|
|
||||||
|
**Source:** Built-in to rust-nostr relay-builder
|
||||||
|
|
||||||
|
- **Subscription limit:** Max 500 concurrent subscriptions per connection
|
||||||
|
- **Event publishing limit:** Max 60 events per minute per connection
|
||||||
|
- **Subscription ID length:** Max 250 characters
|
||||||
|
- **Filter limit:** Max 500 results per query (default)
|
||||||
|
|
||||||
|
These limits prevent individual connections from overwhelming the relay.
|
||||||
|
|
||||||
|
### Per-IP Connection Monitoring
|
||||||
|
|
||||||
|
**Source:** Custom ngit-grasp implementation
|
||||||
|
**Location:** `src/metrics/connection.rs`
|
||||||
|
|
||||||
|
- **Status:** Monitoring only (does NOT enforce limits)
|
||||||
|
- Tracks connections per IP address internally
|
||||||
|
- Flags IPs exceeding threshold (default: 10 connections)
|
||||||
|
- **Privacy:** IP addresses never exposed in Prometheus metrics, only aggregate counts
|
||||||
|
- Logs warnings when threshold exceeded
|
||||||
|
|
||||||
|
**Future:** Could be extended to enforce per-IP connection limits.
|
||||||
|
|
||||||
|
### Content Filtering (Blacklists/Whitelists)
|
||||||
|
|
||||||
|
**Source:** Custom ngit-grasp implementation
|
||||||
|
**Location:** `src/config.rs`, `src/nostr/builder.rs`
|
||||||
|
|
||||||
|
**Event Blacklist:**
|
||||||
|
- Block ALL events from specific authors (npubs)
|
||||||
|
- Takes precedence over all other validation
|
||||||
|
- Events never reach storage or purgatory
|
||||||
|
|
||||||
|
**Repository Blacklist:**
|
||||||
|
- Block specific repositories, developers, or identifiers
|
||||||
|
- Takes precedence over whitelists
|
||||||
|
- Three formats: `npub`, `npub/identifier`, `identifier`
|
||||||
|
|
||||||
|
**Repository Whitelist:**
|
||||||
|
- Curate which repositories are accepted (GRASP-01 mode)
|
||||||
|
- Only accept announcements that both list your service AND match whitelist
|
||||||
|
- Same three formats as blacklist
|
||||||
|
|
||||||
|
**Archive Whitelist (GRASP-05):**
|
||||||
|
- Mirror specific repositories even if they don't list your service
|
||||||
|
- Same three formats as blacklist
|
||||||
|
- Default: read-only mode when enabled
|
||||||
|
|
||||||
|
**Privacy:** Blacklists not advertised in NIP-11 metadata.
|
||||||
|
|
||||||
|
### Event Validation Plugin System
|
||||||
|
|
||||||
|
**Source:** Built-in to rust-nostr relay-builder
|
||||||
|
**Implementation:** Custom GRASP-01 validation in `src/nostr/builder.rs`
|
||||||
|
|
||||||
|
- **WritePolicy trait:** Controls which events are accepted
|
||||||
|
- **QueryPolicy trait:** Controls which queries are allowed (not currently used)
|
||||||
|
- Access to client IP address for future per-IP rate limiting
|
||||||
|
- Modular sub-policies for different event types (announcements, state events, PRs)
|
||||||
|
|
||||||
|
### Relay Health Management (GRASP-02 Sync)
|
||||||
|
|
||||||
|
**Source:** Custom ngit-grasp implementation
|
||||||
|
**Location:** `src/sync/health.rs`
|
||||||
|
|
||||||
|
**Exponential Backoff:**
|
||||||
|
- Failed connections trigger increasing delays: 5s → 10s → 20s → ... → 1 hour max
|
||||||
|
- Prevents hammering dead or slow relays
|
||||||
|
|
||||||
|
**Naughty List:**
|
||||||
|
- Tracks relays with persistent infrastructure issues (DNS, TLS, protocol errors)
|
||||||
|
- Separate from normal connection failures
|
||||||
|
- 12-hour expiration (configurable)
|
||||||
|
- Reduces retry frequency for broken relays
|
||||||
|
|
||||||
|
**Rate Limit Detection:**
|
||||||
|
- Detects when remote relay rate limits us
|
||||||
|
- Automatic 65-second cooldown
|
||||||
|
- Prevents hammering relays that tell us to slow down
|
||||||
|
|
||||||
|
**Domain Throttling (Git Data Fetching):**
|
||||||
|
- Max 5 concurrent requests per domain
|
||||||
|
- Max 30 requests per minute per domain
|
||||||
|
- Respectful rate limiting when fetching missing git data
|
||||||
|
|
||||||
|
## What's NOT Implemented
|
||||||
|
|
||||||
|
### Per-IP Rate Limiting
|
||||||
|
|
||||||
|
- **Per-IP connection limits:** Not enforced (only monitored)
|
||||||
|
- **Per-IP subscription limits:** Not supported
|
||||||
|
- **Per-IP event publishing limits:** Not supported
|
||||||
|
|
||||||
|
**Why:** rust-nostr relay-builder tracks limits per WebSocket connection, not per IP address.
|
||||||
|
|
||||||
|
**To implement:** Would require custom middleware/WritePolicy to aggregate across connections from the same IP.
|
||||||
|
|
||||||
|
### Total Connection Limit
|
||||||
|
|
||||||
|
**Status:** Supported by relay-builder but not currently configured in ngit-grasp.
|
||||||
|
|
||||||
|
**To implement:** Add `max_connections(n)` to relay builder configuration.
|
||||||
|
|
||||||
|
### Query Filtering
|
||||||
|
|
||||||
|
**Status:** QueryPolicy trait available but not currently used.
|
||||||
|
|
||||||
|
**Potential uses:** Rate limit queries per IP, block expensive queries, restrict access to certain event kinds.
|
||||||
|
|
||||||
|
## Future Enhancements: Per-IP Rate Limiting (Deferred)
|
||||||
|
|
||||||
|
### Decision: Defer Until Abuse Detected
|
||||||
|
|
||||||
|
After comprehensive review (2026-01-14), we decided to defer per-IP rate limiting (Phase 2 & 3) until abuse patterns are detected in production.
|
||||||
|
|
||||||
|
**Current protection (Phase 1):**
|
||||||
|
- Per-connection limits: 500 subscriptions, 60 events/min
|
||||||
|
- Total connection limit: 500 (configurable via `NGIT_MAX_CONNECTIONS`)
|
||||||
|
- Connection monitoring: Tracks IPs, flags abuse at 10 connections
|
||||||
|
- Content filtering: Event blacklist, repository blacklist/whitelist
|
||||||
|
|
||||||
|
**Deferred features (Phase 2 & 3):**
|
||||||
|
- Per-IP connection enforcement (reject after 10 connections)
|
||||||
|
- Per-IP event rate limiting (reject after 100 events/min)
|
||||||
|
|
||||||
|
### Rationale for Deferral
|
||||||
|
|
||||||
|
1. **Config-only approach sufficient** - Total connection limit addresses primary DoS vector
|
||||||
|
2. **Git relay context** - Developer users less likely to abuse than general public
|
||||||
|
3. **Existing protections strong** - Per-connection limits + content filtering already robust
|
||||||
|
4. **Data-driven approach** - Monitor ConnectionTracker metrics, implement if needed
|
||||||
|
5. **Minimal maintenance** - Avoid custom rate limiting code until proven necessary
|
||||||
|
|
||||||
|
### Implementation Path if Needed
|
||||||
|
|
||||||
|
**Preferred approach:** Contribute to rust-nostr/relay-builder as PR
|
||||||
|
- Propose IP-based rate limiting as optional feature
|
||||||
|
- Let upstream maintain the code
|
||||||
|
- Benefits entire Nostr ecosystem
|
||||||
|
|
||||||
|
**Fallback:** Implement in ngit-grasp
|
||||||
|
- Per-IP connection enforcement via actix middleware
|
||||||
|
- Per-IP event rate limiting via token bucket in WritePolicy
|
||||||
|
- See issue d6ee for detailed implementation plan
|
||||||
|
|
||||||
|
### Monitoring for Abuse
|
||||||
|
|
||||||
|
Watch these metrics to determine if Phase 2 is needed:
|
||||||
|
- `ngit_connections_per_ip` - IPs exceeding 10 connections
|
||||||
|
- `ngit_flagged_abusers` - IPs flagged by ConnectionTracker
|
||||||
|
- Event publishing patterns from single IPs
|
||||||
|
|
||||||
|
**Trigger for Phase 2:** If abuse detected for 2-4 weeks after Phase 1 deployment
|
||||||
|
|
||||||
|
### Related Work
|
||||||
|
|
||||||
|
**Git endpoint throttling:** Separate concern, tracked in issue ff38
|
||||||
|
- Git HTTP endpoints have different threat model (bandwidth/CPU intensive)
|
||||||
|
- Requires separate IP-based throttling (5 concurrent, 30/min per IP)
|
||||||
|
- No interaction with relay code
|
||||||
|
|
||||||
|
## Summary Table
|
||||||
|
|
||||||
|
| Feature | Status | Enforced? | Configurable? |
|
||||||
|
|---------|--------|-----------|---------------|
|
||||||
|
| **Per-Connection Limits** |
|
||||||
|
| Max subscriptions (500) | ✅ Active | Yes | No (relay-builder default) |
|
||||||
|
| Event rate limit (60/min) | ✅ Active | Yes | No (relay-builder default) |
|
||||||
|
| **Total Connection Limit** |
|
||||||
|
| Max connections (500) | ✅ Active | Yes | Yes (`NGIT_MAX_CONNECTIONS`) |
|
||||||
|
| **Per-IP Monitoring** |
|
||||||
|
| Connection tracking | ✅ Active | No (monitor only) | Threshold only |
|
||||||
|
| **Content Filtering** |
|
||||||
|
| Event blacklist | ✅ Active | Yes | Yes |
|
||||||
|
| Repository blacklist | ✅ Active | Yes | Yes |
|
||||||
|
| Repository whitelist | ✅ Active | Yes (if set) | Yes |
|
||||||
|
| Archive whitelist | ✅ Active | Yes (if set) | Yes |
|
||||||
|
| **Event Validation** |
|
||||||
|
| GRASP-01 validation | ✅ Active | Yes | Via WritePolicy |
|
||||||
|
| **Relay Sync Protection** |
|
||||||
|
| Exponential backoff | ✅ Active | Yes | Yes |
|
||||||
|
| Naughty list | ✅ Active | Yes | Yes (12h default) |
|
||||||
|
| Rate limit detection | ✅ Active | Yes | Automatic |
|
||||||
|
| Domain throttling | ✅ Active | Yes | Hardcoded (5/30) |
|
||||||
|
| **Deferred (Phase 2)** |
|
||||||
|
| Per-IP connection limit | ⚠️ Deferred | No | - |
|
||||||
|
| Per-IP rate limiting | ⚠️ Deferred | No | - |
|
||||||
|
| Query filtering | ⚠️ Available | No | Not implemented |
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- [Configuration Reference](../reference/configuration.md) - All config options for defensive features
|
||||||
|
- [Monitoring Overview](monitoring.md) - Prometheus metrics for tracking abuse
|
||||||
|
- [GRASP-05 Archive](grasp-05-archive.md) - Archive whitelist details
|
||||||
|
- [Architecture](architecture.md) - Overall system design
|
||||||
@@ -925,6 +925,46 @@ Event blacklist does **not** affect NIP-11 metadata:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### Rate Limiting & DoS Protection
|
||||||
|
|
||||||
|
#### `NGIT_MAX_CONNECTIONS`
|
||||||
|
|
||||||
|
**Description:** Maximum total connections to the relay. Prevents connection exhaustion DoS attacks.
|
||||||
|
**Type:** Integer
|
||||||
|
**Default:** `500`
|
||||||
|
**Required:** No
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Default: 500 connections
|
||||||
|
NGIT_MAX_CONNECTIONS=500
|
||||||
|
|
||||||
|
# Higher limit for large public relay
|
||||||
|
NGIT_MAX_CONNECTIONS=1000
|
||||||
|
|
||||||
|
# Lower limit for private relay
|
||||||
|
NGIT_MAX_CONNECTIONS=100
|
||||||
|
```
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
|
||||||
|
- Limits total concurrent WebSocket connections to the relay
|
||||||
|
- Prevents connection exhaustion attacks
|
||||||
|
- Works in conjunction with per-connection limits (500 subscriptions, 60 events/min)
|
||||||
|
- When limit is reached, new connections are rejected
|
||||||
|
- Existing connections continue to work normally
|
||||||
|
|
||||||
|
**Related Limits:**
|
||||||
|
|
||||||
|
Per-connection limits (built-in to relay-builder, not configurable):
|
||||||
|
- Max subscriptions per connection: 500
|
||||||
|
- Max events per minute per connection: 60
|
||||||
|
- Max subscription ID length: 250 characters
|
||||||
|
- Max results per filter: 500
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Logging Configuration
|
### Logging Configuration
|
||||||
|
|
||||||
#### `RUST_LOG`
|
#### `RUST_LOG`
|
||||||
|
|||||||
@@ -250,6 +250,12 @@ let
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
maxConnections = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 500;
|
||||||
|
description = "Maximum total connections to the relay";
|
||||||
|
};
|
||||||
|
|
||||||
user = mkOption {
|
user = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
default = "ngit-grasp-${name}";
|
default = "ngit-grasp-${name}";
|
||||||
@@ -295,6 +301,7 @@ let
|
|||||||
NGIT_REPOSITORY_WHITELIST = concatStringsSep "," cfg.repositoryWhitelist;
|
NGIT_REPOSITORY_WHITELIST = concatStringsSep "," cfg.repositoryWhitelist;
|
||||||
NGIT_REPOSITORY_BLACKLIST = concatStringsSep "," cfg.repositoryBlacklist;
|
NGIT_REPOSITORY_BLACKLIST = concatStringsSep "," cfg.repositoryBlacklist;
|
||||||
NGIT_EVENT_BLACKLIST = concatStringsSep "," cfg.eventBlacklist;
|
NGIT_EVENT_BLACKLIST = concatStringsSep "," cfg.eventBlacklist;
|
||||||
|
NGIT_MAX_CONNECTIONS = toString cfg.maxConnections;
|
||||||
RUST_LOG = cfg.logLevel;
|
RUST_LOG = cfg.logLevel;
|
||||||
} // optionalAttrs (cfg.relayName != null) {
|
} // optionalAttrs (cfg.relayName != null) {
|
||||||
NGIT_RELAY_NAME = cfg.relayName;
|
NGIT_RELAY_NAME = cfg.relayName;
|
||||||
|
|||||||
@@ -469,6 +469,11 @@ pub struct Config {
|
|||||||
/// All events from these authors are blocked from both relay storage and purgatory
|
/// All events from these authors are blocked from both relay storage and purgatory
|
||||||
#[arg(long, env = "NGIT_EVENT_BLACKLIST", default_value = "")]
|
#[arg(long, env = "NGIT_EVENT_BLACKLIST", default_value = "")]
|
||||||
pub event_blacklist: String,
|
pub event_blacklist: String,
|
||||||
|
|
||||||
|
/// Maximum total connections to the relay (default: 500)
|
||||||
|
/// Prevents connection exhaustion DoS attacks
|
||||||
|
#[arg(long, env = "NGIT_MAX_CONNECTIONS", default_value_t = 500)]
|
||||||
|
pub max_connections: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
@@ -703,6 +708,7 @@ impl Config {
|
|||||||
repository_whitelist: String::new(),
|
repository_whitelist: String::new(),
|
||||||
repository_blacklist: String::new(),
|
repository_blacklist: String::new(),
|
||||||
event_blacklist: String::new(),
|
event_blacklist: String::new(),
|
||||||
|
max_connections: 500,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -624,6 +624,14 @@ pub async fn create_relay(
|
|||||||
let relay = LocalRelayBuilder::default()
|
let relay = LocalRelayBuilder::default()
|
||||||
.database(database.clone())
|
.database(database.clone())
|
||||||
.write_policy(write_policy.clone())
|
.write_policy(write_policy.clone())
|
||||||
|
// Explicitly set rate limits (make defaults visible in code)
|
||||||
|
// Per-connection limits: 500 max subscriptions, 60 events/min
|
||||||
|
.rate_limit(RateLimit {
|
||||||
|
max_reqs: 500, // Max concurrent subscriptions per connection
|
||||||
|
notes_per_minute: 60, // Max events per minute per connection
|
||||||
|
})
|
||||||
|
// Total connection limit to prevent DoS attacks
|
||||||
|
.max_connections(config.max_connections)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
|
|||||||
Reference in New Issue
Block a user