mirror of
https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
synced 2026-10-05 15:08:24 +00:00
248 lines
8.8 KiB
Markdown
248 lines
8.8 KiB
Markdown
# Git Endpoint IP-Based Throttling
|
|
|
|
**ID:** ff38
|
|
|
|
## Problem
|
|
|
|
Git HTTP endpoints currently have **no IP-based rate limiting**, making the relay vulnerable to resource exhaustion attacks:
|
|
|
|
- **Bandwidth exhaustion:** Repeated large clone operations can saturate network and incur high egress costs
|
|
- **CPU exhaustion:** Pack generation for complex histories can saturate server CPU
|
|
- **Connection exhaustion:** Many concurrent git operations can exhaust file descriptors and memory
|
|
- **Slowloris-style attacks:** Slow pack downloads holding connections open
|
|
|
|
### Current State
|
|
|
|
**Git HTTP endpoints** (implemented in `src/git/handlers.rs`, routed via `src/http/mod.rs`):
|
|
- `/<npub>/<id>.git/info/refs?service=git-upload-pack` (clone/fetch ref advertisement)
|
|
- `/<npub>/<id>.git/info/refs?service=git-receive-pack` (push ref advertisement)
|
|
- `/<npub>/<id>.git/git-upload-pack` (clone/fetch pack transfer)
|
|
- `/<npub>/<id>.git/git-receive-pack` (push pack transfer)
|
|
|
|
**Current protection level:** NONE
|
|
- No rate limiting middleware
|
|
- No concurrent connection limits per IP
|
|
- No bandwidth throttling
|
|
- Only GRASP authorization for push operations
|
|
|
|
### Threat Model
|
|
|
|
Git operations are fundamentally different from Nostr relay operations:
|
|
|
|
| Aspect | Git Endpoints | Nostr Relay |
|
|
|--------|---------------|-------------|
|
|
| **Resource intensity** | Very high (pack generation, large transfers) | Low (small JSON events) |
|
|
| **Connection duration** | Long (minutes for large repos) | Variable (seconds to hours) |
|
|
| **Bandwidth** | Potentially GB per operation | KB per event |
|
|
| **CPU** | High (delta compression, pack generation) | Low (signature verification) |
|
|
|
|
This is why git endpoint throttling must be **separate** from relay rate limiting (issue d6ee).
|
|
|
|
## Plan
|
|
|
|
### Phase 1: Implement GitThrottleManager (9-12 hours)
|
|
|
|
**Scope:**
|
|
- Create `src/git/throttle.rs` with `GitThrottleManager`
|
|
- Per-IP concurrent operation limit: **5 concurrent git operations**
|
|
- Per-IP rate limit: **30 requests per minute**
|
|
- Hardcoded sensible defaults (no configuration initially)
|
|
|
|
**Files to create:**
|
|
- `src/git/throttle.rs` - NEW: GitThrottleManager implementation
|
|
|
|
**Files to modify:**
|
|
- `src/git/mod.rs` - Export throttle module
|
|
- `src/http/mod.rs` - Integrate throttle check before git handler dispatch
|
|
- `src/main.rs` - Initialize GitThrottleManager, spawn cleanup task
|
|
|
|
**Architecture:**
|
|
```rust
|
|
// src/git/throttle.rs
|
|
|
|
use dashmap::DashMap;
|
|
use std::collections::VecDeque;
|
|
use std::net::IpAddr;
|
|
use std::time::{Duration, Instant};
|
|
|
|
/// Per-IP throttle state for git operations
|
|
struct IpThrottleState {
|
|
/// Current in-flight git operations
|
|
in_flight: u32,
|
|
/// Request timestamps (sliding window for rate limiting)
|
|
request_times: VecDeque<Instant>,
|
|
}
|
|
|
|
/// Manages IP-based rate limiting for git HTTP endpoints
|
|
pub struct GitThrottleManager {
|
|
states: DashMap<IpAddr, IpThrottleState>,
|
|
max_concurrent_per_ip: u32, // Hardcoded: 5
|
|
max_per_minute_per_ip: u32, // Hardcoded: 30
|
|
}
|
|
|
|
impl GitThrottleManager {
|
|
pub fn with_defaults() -> Self {
|
|
Self::new(5, 30)
|
|
}
|
|
|
|
/// Check if IP can proceed with git operation
|
|
/// Returns Ok(guard) if allowed, Err(reason) if throttled
|
|
pub fn try_acquire(&self, ip: IpAddr) -> Result<ThrottleGuard, ThrottleError>;
|
|
|
|
/// Cleanup expired entries (called periodically)
|
|
pub fn cleanup(&self);
|
|
}
|
|
|
|
/// RAII guard that releases throttle on drop
|
|
pub struct ThrottleGuard { /* ... */ }
|
|
|
|
pub enum ThrottleError {
|
|
TooManyConcurrent { current: u32, max: u32 },
|
|
RateLimitExceeded { requests_in_window: u32, max: u32 },
|
|
}
|
|
```
|
|
|
|
**Integration in `src/http/mod.rs`:**
|
|
```rust
|
|
// In HttpService::call(), before git handler dispatch
|
|
if let Some((npub, identifier, subpath)) = git::parse_git_url(&path) {
|
|
let ip = self.remote.ip();
|
|
let throttle_guard = match self.git_throttle.try_acquire(ip) {
|
|
Ok(guard) => guard,
|
|
Err(e) => {
|
|
tracing::warn!(ip = %ip, error = %e, "Git request throttled");
|
|
return Box::pin(async move {
|
|
Ok(add_cors_headers(Response::builder())
|
|
.status(hyper::StatusCode::TOO_MANY_REQUESTS)
|
|
.header("Retry-After", "60")
|
|
.body(Full::new(Bytes::from(format!("Rate limited: {}", e))))
|
|
.unwrap())
|
|
});
|
|
}
|
|
};
|
|
|
|
// ... existing git handling ...
|
|
// throttle_guard dropped automatically when response completes
|
|
}
|
|
```
|
|
|
|
**Cleanup strategy:**
|
|
- RAII: `ThrottleGuard` decrements `in_flight` on drop
|
|
- Background task: Every 60 seconds, remove IPs with no activity in last 5 minutes
|
|
- Sliding window: Prune request timestamps older than 60 seconds on each access
|
|
|
|
**Acceptance Criteria:**
|
|
- [ ] GitThrottleManager implements concurrent limit (5 per IP)
|
|
- [ ] GitThrottleManager implements rate limit (30/min per IP)
|
|
- [ ] 6th concurrent git operation from same IP returns 429
|
|
- [ ] 31st request in 60 seconds from same IP returns 429
|
|
- [ ] Different IPs can each have 5 concurrent operations
|
|
- [ ] ThrottleGuard properly releases on drop (RAII)
|
|
- [ ] Cleanup task removes stale IP entries (no memory leak)
|
|
- [ ] Unit tests: Concurrent limit enforcement
|
|
- [ ] Unit tests: Rate limit sliding window
|
|
- [ ] Unit tests: Cleanup logic
|
|
- [ ] Integration test: Multiple concurrent git clones from same IP
|
|
- [ ] Integration test: Rapid git requests hitting rate limit
|
|
- [ ] All tests passing
|
|
- [ ] Changes committed with message: "Add IP-based throttling for git HTTP endpoints"
|
|
|
|
### Phase 2: Documentation & Metrics (2-3 hours)
|
|
|
|
**Scope:**
|
|
- Document git throttling in defensive-measures.md
|
|
- Add Prometheus metrics for git throttling
|
|
- Update README with git endpoint protection
|
|
|
|
**Files to modify:**
|
|
- `docs/explanation/defensive-measures.md` - Add git throttling section
|
|
- `README.md` - Update defensive measures section
|
|
- `src/git/throttle.rs` - Add Prometheus metrics (optional)
|
|
|
|
**Metrics to add (optional):**
|
|
- `ngit_git_requests_throttled_total{reason="concurrent|rate_limit"}`
|
|
- `ngit_git_concurrent_operations{ip_count}` (gauge of active IPs)
|
|
|
|
**Acceptance Criteria:**
|
|
- [ ] defensive-measures.md documents git throttling
|
|
- [ ] README updated with git endpoint protection
|
|
- [ ] Metrics added (if implemented)
|
|
- [ ] Changes committed with message: "docs: Document git endpoint throttling"
|
|
|
|
### Phase 3: Configuration Support (Future - If Needed)
|
|
|
|
**Status:** Deferred until operators request tuning
|
|
|
|
**Scope:**
|
|
- Add environment variables for git throttle limits
|
|
- Update all 4 config locations (src/config.rs, docs, nix/module.nix, .env.example)
|
|
|
|
**Proposed config:**
|
|
```bash
|
|
NGIT_GIT_MAX_CONCURRENT_PER_IP=5
|
|
NGIT_GIT_MAX_REQUESTS_PER_MINUTE_PER_IP=30
|
|
```
|
|
|
|
## Progress
|
|
|
|
### 2026-01-14 [Session 14:45]
|
|
- Created: Issue ff38 for git endpoint IP-based throttling
|
|
- Separated from d6ee (relay rate limiting) due to different threat model
|
|
- Completed: Comprehensive threat analysis via architect subagent
|
|
- Completed: Architecture design with GitThrottleManager
|
|
- Decision: Hardcoded defaults (5 concurrent, 30/min) initially
|
|
- Decision: No interaction with relay code (HTTP layer only)
|
|
- Next: Implement Phase 1 after d6ee Phase 1 completes
|
|
|
|
**Key Design Decisions:**
|
|
- Reuse patterns from ThrottleManager (outbound git) and ConnectionTracker (relay)
|
|
- RAII guard pattern for automatic cleanup
|
|
- DashMap for concurrent access
|
|
- Sliding window for rate limiting
|
|
- Privacy-preserving (IPs not exposed in metrics)
|
|
|
|
## Notes
|
|
|
|
### Relationship to Issue d6ee
|
|
|
|
**d6ee (Defensive Relay Features):**
|
|
- Focuses on Nostr relay WebSocket connections
|
|
- Per-connection limits (500 subs, 60 events/min)
|
|
- Total connection limit (500)
|
|
- Deferred: Per-IP relay enforcement (Phase 2/3)
|
|
|
|
**ff38 (Git Endpoint Throttling):**
|
|
- Focuses on git HTTP endpoints
|
|
- Per-IP concurrent operations (5)
|
|
- Per-IP request rate (30/min)
|
|
- Independent implementation (no relay code interaction)
|
|
|
|
### Legitimate Use Cases to Preserve
|
|
|
|
- **CI/CD pipelines:** Multiple concurrent clones from same IP (5 allows this)
|
|
- **Corporate proxies/NAT:** Many developers behind single IP (30/min allows burst activity)
|
|
- **Shallow clones:** Partial fetches for large repos
|
|
- **Incremental fetches:** Small delta updates
|
|
- **Mirror operations:** Periodic full syncs
|
|
|
|
### Comparison with Existing Throttling
|
|
|
|
**ThrottleManager** (`src/purgatory/sync/throttle.rs`):
|
|
- Purpose: Rate limit **outbound** git fetches to remote servers
|
|
- Key: Domain (remote server)
|
|
- Limits: 5 concurrent, 30/min per domain
|
|
- Queue: Yes (identifier queue for processing)
|
|
|
|
**GitThrottleManager** (this issue):
|
|
- Purpose: Rate limit **inbound** git requests from clients
|
|
- Key: IP address (client)
|
|
- Limits: 5 concurrent, 30/min per IP
|
|
- Queue: No (immediate reject with 429)
|
|
|
|
### References
|
|
|
|
- Threat analysis: Session 2026-01-14 architect subagent analysis
|
|
- Related issue: d6ee (relay rate limiting)
|
|
- Existing patterns: `src/purgatory/sync/throttle.rs`, `src/metrics/connection.rs`
|
|
- Git HTTP protocol: https://git-scm.com/docs/http-protocol
|