Skip to content

Commit 49c614a

Browse files
committed
Added IP-based rate limiting for Copi (fixes #1877)
Added Copi.RateLimiter (GenServer) and CopiWeb.Plugs.RateLimiter (Plug). Integrate rate limiting into LiveView for game creation and WebSocket connections. Added test and SECURITY.md. Making limits configurable via environment variables. Addresses #1877.
1 parent 2ee00bb commit 49c614a

File tree

11 files changed

+797
-9
lines changed

11 files changed

+797
-9
lines changed

copi.owasp.org/README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,52 @@ To start your Phoenix server:
7575

7676
Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
7777

78+
## Security Features
79+
80+
### Rate Limiting
81+
82+
Copi implements IP-based rate limiting to protect against abuse and ensure availability for all users. This addresses CAPEC 212 (Functionality Misuse) attacks.
83+
84+
**Features:**
85+
- **Game Creation Limiting**: Limits the number of games that can be created from a single IP address
86+
- **Connection Limiting**: Limits the number of WebSocket connections from a single IP address
87+
- **Configurable Limits**: All limits and time windows are configurable via environment variables
88+
89+
**Configuration:**
90+
91+
Set the following environment variables to customize rate limits:
92+
93+
```bash
94+
# Maximum games per IP (default: 10)
95+
export MAX_GAMES_PER_IP=10
96+
97+
# Time window for game creation in seconds (default: 3600 = 1 hour)
98+
export GAME_CREATION_WINDOW_SECONDS=3600
99+
100+
# Maximum connections per IP (default: 50)
101+
export MAX_CONNECTIONS_PER_IP=50
102+
103+
# Time window for connections in seconds (default: 300 = 5 minutes)
104+
export CONNECTION_WINDOW_SECONDS=300
105+
```
106+
107+
**How it works:**
108+
- The rate limiter tracks requests by IP address
109+
- When a limit is exceeded, users receive a clear error message with a retry time
110+
- Expired entries are automatically cleaned up every 5 minutes
111+
- Rate limits are independent for game creation vs. connections
112+
- The system handles both IPv4 and IPv6 addresses
113+
- X-Forwarded-For headers are respected for reverse proxy deployments
114+
115+
**For Reverse Proxy Deployments:**
116+
117+
If deploying behind a reverse proxy (nginx, Apache, Cloudflare, etc.), ensure the proxy passes the real client IP:
118+
119+
```nginx
120+
# Nginx example
121+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
122+
```
123+
78124
Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html).
79125

80126
## More about Phoenix
@@ -97,6 +143,14 @@ Login to fly and create a PostgreSQL cluster. See: see: https://fly.io/dashboard
97143
fly launch --no-deploy
98144

99145
Make a note of the host and name of the app and the name of the postgresql cluster.
146+
147+
Configure rate limiting (optional, uses defaults if not set):
148+
149+
fly secrets set MAX_GAMES_PER_IP=10 --app <app name>
150+
fly secrets set GAME_CREATION_WINDOW_SECONDS=3600 --app <app name>
151+
fly secrets set MAX_CONNECTIONS_PER_IP=50 --app <app name>
152+
fly secrets set CONNECTION_WINDOW_SECONDS=300 --app <app name>
153+
100154
Then deploy the app from `./copi.owasp.org`
101155

102156
fly mpg attach <cluster name> --app <app name>
@@ -139,6 +193,12 @@ Set your prefered app name instead of `<name>`
139193
heroku config:set SECRET_KEY_BASE=$(mix phx.gen.secret)
140194
heroku config:set POOL_SIZE=18
141195
heroku config:set PROJECT_PATH=copi.owasp.org # points to the subdirectory
196+
197+
# Optional: Configure rate limiting (uses defaults if not set)
198+
heroku config:set MAX_GAMES_PER_IP=10
199+
heroku config:set GAME_CREATION_WINDOW_SECONDS=3600
200+
heroku config:set MAX_CONNECTIONS_PER_IP=50
201+
heroku config:set CONNECTION_WINDOW_SECONDS=300
142202

143203
### Heroku deploy
144204

copi.owasp.org/SECURITY.md

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# Security Implementation for Copi
2+
3+
This document describes the security measures implemented in Copi to protect against abuse and ensure service availability.
4+
5+
## Overview
6+
7+
Copi has implemented IP-based rate limiting to protect against **CAPEC 212 (Functionality Misuse)** attacks. These protections help ensure that the service remains available for all legitimate users by preventing a single source from overwhelming the system.
8+
9+
## Implemented Protections
10+
11+
### 1. Game Creation Rate Limiting
12+
13+
**Purpose**: Prevents a single IP address from creating an excessive number of games, which could lead to database exhaustion or denial of service.
14+
15+
**Default Configuration**:
16+
- Maximum games per IP: 10
17+
- Time window: 3600 seconds (1 hour)
18+
19+
**Behavior**:
20+
- Tracks game creation attempts by IP address
21+
- When the limit is exceeded, users receive a clear error message
22+
- The limit resets after the time window expires
23+
- Different IP addresses have independent limits
24+
25+
**Configuration**:
26+
```bash
27+
export MAX_GAMES_PER_IP=10
28+
export GAME_CREATION_WINDOW_SECONDS=3600
29+
```
30+
31+
### 2. WebSocket Connection Rate Limiting
32+
33+
**Purpose**: Prevents a single IP address from opening excessive WebSocket connections, which could exhaust server resources.
34+
35+
**Default Configuration**:
36+
- Maximum connections per IP: 50
37+
- Time window: 300 seconds (5 minutes)
38+
39+
**Behavior**:
40+
- Tracks connection attempts by IP address
41+
- When the limit is exceeded, connections are rejected with an error message
42+
- The limit resets after the time window expires
43+
- Does not affect existing active connections
44+
45+
**Configuration**:
46+
```bash
47+
export MAX_CONNECTIONS_PER_IP=50
48+
export CONNECTION_WINDOW_SECONDS=300
49+
```
50+
51+
## Technical Implementation
52+
53+
### Architecture
54+
55+
The rate limiting system consists of several components:
56+
57+
1. **Copi.RateLimiter** (`lib/copi/rate_limiter.ex`)
58+
- GenServer that maintains rate limit state
59+
- Tracks requests per IP address and action type
60+
- Automatically cleans up expired entries every 5 minutes
61+
- Provides a simple API for checking and recording actions
62+
63+
2. **CopiWeb.Plugs.RateLimiter** (`lib/copi_web/plugs/rate_limiter.ex`)
64+
- Plug for HTTP request rate limiting
65+
- Extracts IP addresses from connections
66+
- Handles X-Forwarded-For headers for reverse proxies
67+
- Returns proper HTTP 429 status codes when limits are exceeded
68+
69+
3. **LiveView Integration**
70+
- Game creation rate limiting in `CopiWeb.GameLive.CreateGameForm`
71+
- Connection rate limiting in `CopiWeb.GameLive.Index`
72+
- Provides user-friendly error messages
73+
74+
### IP Address Handling
75+
76+
The system correctly handles:
77+
- IPv4 addresses (e.g., 192.168.1.1)
78+
- IPv6 addresses (e.g., 2001:db8::1)
79+
- X-Forwarded-For headers (for reverse proxy deployments)
80+
- Multiple IP addresses in X-Forwarded-For (uses the first one)
81+
82+
### Rate Limit Response Headers
83+
84+
When rate limiting is active, the following headers are included in responses:
85+
86+
- `X-RateLimit-Remaining`: Number of requests remaining in the current window
87+
- `Retry-After`: Seconds until the rate limit resets (only included when rate limited)
88+
89+
### Error Messages
90+
91+
Users who exceed rate limits receive clear, informative error messages:
92+
93+
```
94+
Rate limit exceeded. Too many games created from your IP address.
95+
Please try again in 3456 seconds.
96+
This limit helps ensure service availability for all users.
97+
```
98+
99+
## Deployment Considerations
100+
101+
### Reverse Proxy Configuration
102+
103+
If deploying behind a reverse proxy (nginx, HAProxy, Cloudflare, etc.), ensure the real client IP is passed through:
104+
105+
**Nginx**:
106+
```nginx
107+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
108+
```
109+
110+
**Apache**:
111+
```apache
112+
RequestHeader set X-Forwarded-For "%{REMOTE_ADDR}s"
113+
```
114+
115+
**Cloudflare**:
116+
Cloudflare automatically sets the CF-Connecting-IP header. The X-Forwarded-For header should also be present.
117+
118+
### Monitoring
119+
120+
Monitor the following metrics to detect abuse or adjust rate limits:
121+
122+
- Rate limit rejections (logged as warnings)
123+
- Rate limit state size (number of tracked IPs)
124+
- 429 HTTP responses
125+
- Flash messages about rate limiting
126+
127+
### Adjusting Rate Limits
128+
129+
Rate limits can be adjusted based on your deployment needs:
130+
131+
**For Development/Testing**:
132+
```bash
133+
export MAX_GAMES_PER_IP=100
134+
export MAX_CONNECTIONS_PER_IP=200
135+
```
136+
137+
**For High-Traffic Production**:
138+
```bash
139+
export MAX_GAMES_PER_IP=5
140+
export GAME_CREATION_WINDOW_SECONDS=7200 # 2 hours
141+
export MAX_CONNECTIONS_PER_IP=30
142+
export CONNECTION_WINDOW_SECONDS=600 # 10 minutes
143+
```
144+
145+
**For Low-Traffic/Internal Use**:
146+
```bash
147+
export MAX_GAMES_PER_IP=50
148+
export MAX_CONNECTIONS_PER_IP=100
149+
```
150+
151+
## Testing
152+
153+
The implementation includes comprehensive tests:
154+
155+
- **Unit tests** for the RateLimiter GenServer (`test/copi/rate_limiter_test.exs`)
156+
- **Integration tests** for the RateLimiter Plug (`test/copi_web/plugs/rate_limiter_test.exs`)
157+
158+
Run tests:
159+
```bash
160+
mix test
161+
```
162+
163+
Run specific rate limiter tests:
164+
```bash
165+
mix test test/copi/rate_limiter_test.exs
166+
mix test test/copi_web/plugs/rate_limiter_test.exs
167+
```
168+
169+
## Future Enhancements
170+
171+
Potential future security enhancements (not currently implemented):
172+
173+
1. **Authentication Integration**
174+
- Associate rate limits with authenticated users
175+
- Different limits for authenticated vs. anonymous users
176+
- Per-user rate limiting instead of just per-IP
177+
178+
2. **Geographic Rate Limiting**
179+
- Different limits based on geographic location
180+
- Blocking or stricter limits for high-risk regions
181+
182+
3. **Adaptive Rate Limiting**
183+
- Automatically adjust limits based on system load
184+
- Stricter limits during high traffic periods
185+
186+
4. **CAPTCHA Integration**
187+
- Require CAPTCHA after repeated rate limit violations
188+
- Optional CAPTCHA for game creation
189+
190+
5. **Rate Limit Dashboard**
191+
- Admin interface to view current rate limit state
192+
- Ability to manually block/unblock IPs
193+
- Real-time monitoring of rate limit violations
194+
195+
## Reporting Security Issues
196+
197+
If you discover a security vulnerability in Copi, please report it to the OWASP Cornucopia team through the [GitHub Security Advisories](https://github.com/OWASP/cornucopia/security/advisories) page.
198+
199+
## References
200+
201+
- [CAPEC-212: Functionality Misuse](https://capec.mitre.org/data/definitions/212.html)
202+
- [OWASP API Security Top 10 - API4:2023 Unrestricted Resource Consumption](https://owasp.org/API-Security/editions/2023/en/0xa4-unrestricted-resource-consumption/)
203+
- [Phoenix Framework Security](https://hexdocs.pm/phoenix/security.html)

copi.owasp.org/config/config.exs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,17 @@ config :copi, CopiWeb.Endpoint,
2727

2828
config :copi, env: Mix.env()
2929

30+
# Configure rate limiting to prevent abuse (CAPEC 212 - Functionality Misuse)
31+
config :copi, Copi.RateLimiter,
32+
# Maximum number of games that can be created from a single IP in the time window
33+
max_games_per_ip: System.get_env("MAX_GAMES_PER_IP", "10") |> String.to_integer(),
34+
# Time window in seconds for game creation rate limiting (default: 1 hour)
35+
game_creation_window_seconds: System.get_env("GAME_CREATION_WINDOW_SECONDS", "3600") |> String.to_integer(),
36+
# Maximum number of WebSocket connections from a single IP in the time window
37+
max_connections_per_ip: System.get_env("MAX_CONNECTIONS_PER_IP", "50") |> String.to_integer(),
38+
# Time window in seconds for connection rate limiting (default: 5 minutes)
39+
connection_window_seconds: System.get_env("CONNECTION_WINDOW_SECONDS", "300") |> String.to_integer()
40+
3041
# Configure tailwind (the version is required)
3142
config :tailwind,
3243
version: "3.4.0",

copi.owasp.org/lib/copi/application.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ defmodule Copi.Application do
1515
{Phoenix.PubSub, name: Copi.PubSub},
1616
# Start the DNS clustering
1717
{DNSCluster, query: Application.get_env(:copi, :dns_cluster_query) || :ignore},
18+
# Start the Rate Limiter for security
19+
Copi.RateLimiter,
1820
# Start the Endpoint (http/https)
1921
CopiWeb.Endpoint
2022
# Start a worker by calling: Copi.Worker.start_link(arg)

0 commit comments

Comments
 (0)