pywebguard 0.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pywebguard/__init__.py +87 -0
- pywebguard/cli.py +1022 -0
- pywebguard/core/__init__.py +0 -0
- pywebguard/core/base.py +792 -0
- pywebguard/core/config.py +455 -0
- pywebguard/filters/__init__.py +0 -0
- pywebguard/filters/base.py +44 -0
- pywebguard/filters/ip_filter.py +233 -0
- pywebguard/filters/user_agent.py +102 -0
- pywebguard/frameworks/__init__.py +0 -0
- pywebguard/frameworks/_fastapi.py +191 -0
- pywebguard/frameworks/_flask.py +182 -0
- pywebguard/limiters/__init__.py +0 -0
- pywebguard/limiters/base.py +46 -0
- pywebguard/limiters/rate_limit.py +415 -0
- pywebguard/logging/__init__.py +0 -0
- pywebguard/logging/backends/__init__.py +17 -0
- pywebguard/logging/backends/_elasticsearch.py +307 -0
- pywebguard/logging/backends/_meilisearch.py +310 -0
- pywebguard/logging/backends/_mongodb.py +303 -0
- pywebguard/logging/base.py +117 -0
- pywebguard/logging/logger.py +408 -0
- pywebguard/security/__init__.py +0 -0
- pywebguard/security/base.py +44 -0
- pywebguard/security/cors.py +204 -0
- pywebguard/security/penetration.py +265 -0
- pywebguard/storage/__init__.py +0 -0
- pywebguard/storage/_mongodb.py +396 -0
- pywebguard/storage/_postgresql.py +539 -0
- pywebguard/storage/_redis.py +314 -0
- pywebguard/storage/_sqlite.py +374 -0
- pywebguard/storage/_tinydb.py +271 -0
- pywebguard/storage/base.py +327 -0
- pywebguard/storage/memory.py +245 -0
- pywebguard/utils/__init__.py +0 -0
- pywebguard/utils/ip.py +84 -0
- pywebguard/utils/request.py +65 -0
- pywebguard-0.0.0.dist-info/LICENSE +21 -0
- pywebguard-0.0.0.dist-info/METADATA +368 -0
- pywebguard-0.0.0.dist-info/RECORD +43 -0
- pywebguard-0.0.0.dist-info/WHEEL +5 -0
- pywebguard-0.0.0.dist-info/entry_points.txt +2 -0
- pywebguard-0.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration management for PyWebGuard.
|
|
3
|
+
|
|
4
|
+
This module provides configuration classes for various security features
|
|
5
|
+
of PyWebGuard, including IP filtering, rate limiting, user agent filtering,
|
|
6
|
+
penetration detection, CORS, logging, and storage.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from typing import List, Dict, Any, Optional, Union, Set
|
|
11
|
+
from pydantic import BaseModel, Field, field_validator
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class IPFilterConfig(BaseModel):
|
|
15
|
+
"""Configuration for IP filtering.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
enabled: Whether IP filtering is enabled
|
|
19
|
+
whitelist: List of allowed IP addresses
|
|
20
|
+
blacklist: List of blocked IP addresses
|
|
21
|
+
block_cloud_providers: Whether to block known cloud provider IPs
|
|
22
|
+
geo_restrictions: Dictionary mapping country codes to allow/block status
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
enabled: bool = True
|
|
26
|
+
whitelist: List[str] = Field(default_factory=list)
|
|
27
|
+
blacklist: List[str] = Field(default_factory=list)
|
|
28
|
+
block_cloud_providers: bool = False
|
|
29
|
+
geo_restrictions: Dict[str, bool] = Field(default_factory=dict)
|
|
30
|
+
|
|
31
|
+
@field_validator("whitelist", "blacklist")
|
|
32
|
+
def validate_ip_addresses(cls, v: List[str]) -> List[str]:
|
|
33
|
+
"""Validate IP addresses in whitelist and blacklist.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
v: List of IP addresses to validate
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
List of validated IP addresses
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
ValueError: If any IP address is invalid
|
|
43
|
+
"""
|
|
44
|
+
import ipaddress
|
|
45
|
+
|
|
46
|
+
for ip in v:
|
|
47
|
+
try:
|
|
48
|
+
# Handle CIDR notation
|
|
49
|
+
if "/" in ip:
|
|
50
|
+
ipaddress.ip_network(ip, strict=False)
|
|
51
|
+
else:
|
|
52
|
+
ipaddress.ip_address(ip)
|
|
53
|
+
except ValueError as e:
|
|
54
|
+
raise ValueError(f"Invalid IP address: {ip}") from e
|
|
55
|
+
return v
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class RateLimitConfig(BaseModel):
|
|
59
|
+
"""Configuration for rate limiting.
|
|
60
|
+
|
|
61
|
+
Attributes:
|
|
62
|
+
enabled: Whether rate limiting is enabled
|
|
63
|
+
requests_per_minute: Maximum number of requests allowed per minute
|
|
64
|
+
burst_size: Maximum number of requests allowed in burst
|
|
65
|
+
auto_ban_threshold: Number of violations before auto-ban
|
|
66
|
+
auto_ban_duration_minutes: Duration of auto-ban in minutes
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
enabled: bool = True
|
|
70
|
+
requests_per_minute: int = Field(default=60, ge=1)
|
|
71
|
+
burst_size: int = Field(default=10, ge=0)
|
|
72
|
+
auto_ban_threshold: int = Field(default=100, ge=1)
|
|
73
|
+
auto_ban_duration_minutes: int = Field(default=60, ge=1)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class UserAgentConfig(BaseModel):
|
|
77
|
+
"""Configuration for user agent filtering.
|
|
78
|
+
|
|
79
|
+
Attributes:
|
|
80
|
+
enabled: Whether user agent filtering is enabled
|
|
81
|
+
blocked_agents: List of blocked user agent strings
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
enabled: bool = True
|
|
85
|
+
blocked_agents: List[str] = Field(default_factory=list)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class PenetrationDetectionConfig(BaseModel):
|
|
89
|
+
"""Configuration for penetration attempt detection.
|
|
90
|
+
|
|
91
|
+
Attributes:
|
|
92
|
+
enabled: Whether penetration detection is enabled
|
|
93
|
+
log_suspicious: Whether to log suspicious activities
|
|
94
|
+
suspicious_patterns: List of patterns to detect suspicious activities
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
enabled: bool = True
|
|
98
|
+
log_suspicious: bool = True
|
|
99
|
+
suspicious_patterns: List[str] = Field(default_factory=list)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class CORSConfig(BaseModel):
|
|
103
|
+
"""Configuration for CORS (Cross-Origin Resource Sharing).
|
|
104
|
+
|
|
105
|
+
Attributes:
|
|
106
|
+
enabled: Whether CORS is enabled
|
|
107
|
+
allow_origins: List of allowed origins
|
|
108
|
+
allow_methods: List of allowed HTTP methods
|
|
109
|
+
allow_headers: List of allowed headers
|
|
110
|
+
allow_credentials: Whether to allow credentials
|
|
111
|
+
max_age: Maximum age of preflight requests in seconds
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
enabled: bool = True
|
|
115
|
+
allow_origins: List[str] = Field(default=["*"])
|
|
116
|
+
allow_methods: List[str] = Field(
|
|
117
|
+
default=["GET", "POST", "PUT", "DELETE", "OPTIONS"]
|
|
118
|
+
)
|
|
119
|
+
allow_headers: List[str] = Field(default=["*"])
|
|
120
|
+
allow_credentials: bool = False
|
|
121
|
+
max_age: int = Field(default=600, ge=0)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class LoggingConfig(BaseModel):
|
|
125
|
+
"""Configuration for logging.
|
|
126
|
+
|
|
127
|
+
Attributes:
|
|
128
|
+
enabled: Whether logging is enabled
|
|
129
|
+
log_file: Path to log file (None for stdout)
|
|
130
|
+
log_level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
|
131
|
+
stream: Whether to log to stdout
|
|
132
|
+
stream_levels: List of levels to log to stdout
|
|
133
|
+
max_log_size: Maximum log file size in bytes
|
|
134
|
+
max_log_files: Maximum number of log files to keep
|
|
135
|
+
log_format: Log format string
|
|
136
|
+
log_date_format: Log date format string
|
|
137
|
+
log_rotation: Log rotation interval
|
|
138
|
+
log_backup_count: Number of backup log files to keep
|
|
139
|
+
log_encoding: Log file encoding
|
|
140
|
+
meilisearch: Meilisearch backend configuration
|
|
141
|
+
elasticsearch: Elasticsearch backend configuration
|
|
142
|
+
mongodb: MongoDB backend configuration
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
enabled: bool = True
|
|
146
|
+
log_file: Optional[str] = None
|
|
147
|
+
log_level: str = Field(default="INFO")
|
|
148
|
+
stream: bool = False
|
|
149
|
+
stream_levels: List[str] = Field(default_factory=list)
|
|
150
|
+
max_log_size: int = Field(default=10 * 1024 * 1024) # 10MB
|
|
151
|
+
max_log_files: int = Field(default=2)
|
|
152
|
+
log_format: str = Field(default="{asctime} {levelname} {message}")
|
|
153
|
+
log_date_format: str = Field(default="%Y-%m-%d %H:%M:%S")
|
|
154
|
+
log_rotation: str = Field(default="midnight")
|
|
155
|
+
log_backup_count: int = Field(default=3)
|
|
156
|
+
log_encoding: str = Field(default="utf-8")
|
|
157
|
+
|
|
158
|
+
# Backend configurations
|
|
159
|
+
meilisearch: Optional[Dict[str, Any]] = None
|
|
160
|
+
elasticsearch: Optional[Dict[str, Any]] = None
|
|
161
|
+
mongodb: Optional[Dict[str, Any]] = None
|
|
162
|
+
|
|
163
|
+
@field_validator("log_level")
|
|
164
|
+
def validate_log_level(cls, v: str) -> str:
|
|
165
|
+
"""Validate logging level.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
v: Logging level to validate
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Validated logging level
|
|
172
|
+
|
|
173
|
+
Raises:
|
|
174
|
+
ValueError: If logging level is invalid
|
|
175
|
+
"""
|
|
176
|
+
valid_levels = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}
|
|
177
|
+
if v.upper() not in valid_levels:
|
|
178
|
+
raise ValueError(f"Invalid log level: {v}. Must be one of {valid_levels}")
|
|
179
|
+
return v.upper()
|
|
180
|
+
|
|
181
|
+
@field_validator("meilisearch")
|
|
182
|
+
def validate_meilisearch(
|
|
183
|
+
cls, v: Optional[Dict[str, Any]]
|
|
184
|
+
) -> Optional[Dict[str, Any]]:
|
|
185
|
+
"""Validate Meilisearch configuration.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
v: Meilisearch configuration to validate
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Validated Meilisearch configuration
|
|
192
|
+
|
|
193
|
+
Raises:
|
|
194
|
+
ValueError: If configuration is invalid
|
|
195
|
+
"""
|
|
196
|
+
if v is None:
|
|
197
|
+
return None
|
|
198
|
+
|
|
199
|
+
required_fields = {"url", "api_key", "index_name"}
|
|
200
|
+
missing_fields = required_fields - set(v.keys())
|
|
201
|
+
if missing_fields:
|
|
202
|
+
raise ValueError(f"Missing required Meilisearch fields: {missing_fields}")
|
|
203
|
+
return v
|
|
204
|
+
|
|
205
|
+
@field_validator("elasticsearch")
|
|
206
|
+
def validate_elasticsearch(
|
|
207
|
+
cls, v: Optional[Dict[str, Any]]
|
|
208
|
+
) -> Optional[Dict[str, Any]]:
|
|
209
|
+
"""Validate Elasticsearch configuration.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
v: Elasticsearch configuration to validate
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Validated Elasticsearch configuration
|
|
216
|
+
|
|
217
|
+
Raises:
|
|
218
|
+
ValueError: If configuration is invalid
|
|
219
|
+
"""
|
|
220
|
+
if v is None:
|
|
221
|
+
return None
|
|
222
|
+
|
|
223
|
+
required_fields = {"hosts"}
|
|
224
|
+
missing_fields = required_fields - set(v.keys())
|
|
225
|
+
if missing_fields:
|
|
226
|
+
raise ValueError(f"Missing required Elasticsearch fields: {missing_fields}")
|
|
227
|
+
return v
|
|
228
|
+
|
|
229
|
+
@field_validator("mongodb")
|
|
230
|
+
def validate_mongodb(cls, v: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
|
231
|
+
"""Validate MongoDB configuration.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
v: MongoDB configuration to validate
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Validated MongoDB configuration
|
|
238
|
+
|
|
239
|
+
Raises:
|
|
240
|
+
ValueError: If configuration is invalid
|
|
241
|
+
"""
|
|
242
|
+
if v is None:
|
|
243
|
+
return None
|
|
244
|
+
|
|
245
|
+
# Check if URI is provided or if host/port are provided
|
|
246
|
+
if "uri" not in v and not all(
|
|
247
|
+
k in v for k in ["host", "port", "database", "collection"]
|
|
248
|
+
):
|
|
249
|
+
raise ValueError(
|
|
250
|
+
"MongoDB configuration must include either 'uri' or all of 'host', 'port', 'database', and 'collection'"
|
|
251
|
+
)
|
|
252
|
+
return v
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class StorageConfig(BaseModel):
|
|
256
|
+
"""Configuration for storage.
|
|
257
|
+
|
|
258
|
+
Attributes:
|
|
259
|
+
type: Storage type ("memory", "redis", "sqlite", "tinydb", "mongodb", "postgresql")
|
|
260
|
+
url: Connection URL for the storage backend
|
|
261
|
+
prefix: Prefix for storage keys
|
|
262
|
+
ttl: Time-to-live for stored data in seconds
|
|
263
|
+
table_name: Table or collection name (for SQL/MongoDB)
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
type: str = Field(default="memory")
|
|
267
|
+
url: Optional[str] = None
|
|
268
|
+
prefix: str = Field(default="pywebguard:")
|
|
269
|
+
ttl: int = Field(default=3600, ge=0)
|
|
270
|
+
table_name: str = Field(default="pywebguard")
|
|
271
|
+
|
|
272
|
+
@field_validator("type")
|
|
273
|
+
def validate_storage_type(cls, v: str) -> str:
|
|
274
|
+
"""Validate storage type.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
v: Storage type to validate
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
Validated storage type
|
|
281
|
+
|
|
282
|
+
Raises:
|
|
283
|
+
ValueError: If storage type is invalid
|
|
284
|
+
"""
|
|
285
|
+
valid_types = {"memory", "redis", "sqlite", "tinydb", "mongodb", "postgresql"}
|
|
286
|
+
if v.lower() not in valid_types:
|
|
287
|
+
raise ValueError(f"Invalid storage type: {v}. Must be one of {valid_types}")
|
|
288
|
+
return v.lower()
|
|
289
|
+
|
|
290
|
+
@field_validator("url")
|
|
291
|
+
def validate_url(cls, v: Optional[str], values: Dict[str, Any]) -> Optional[str]:
|
|
292
|
+
"""Validate storage URL.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
v: Storage URL to validate
|
|
296
|
+
values: Other field values
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
Validated storage URL
|
|
300
|
+
|
|
301
|
+
Raises:
|
|
302
|
+
ValueError: If URL is required but not provided
|
|
303
|
+
"""
|
|
304
|
+
storage_type = values.get("type", "memory")
|
|
305
|
+
|
|
306
|
+
# Memory storage doesn't need a URL
|
|
307
|
+
if storage_type == "memory":
|
|
308
|
+
return None
|
|
309
|
+
|
|
310
|
+
# For other storage types, URL is required
|
|
311
|
+
if not v and storage_type != "memory":
|
|
312
|
+
if storage_type == "redis":
|
|
313
|
+
return "redis://localhost:6379/0"
|
|
314
|
+
elif storage_type == "sqlite":
|
|
315
|
+
return "pywebguard.db"
|
|
316
|
+
elif storage_type == "tinydb":
|
|
317
|
+
return "pywebguard.json"
|
|
318
|
+
elif storage_type == "mongodb":
|
|
319
|
+
return "mongodb://localhost:27017/pywebguard"
|
|
320
|
+
elif storage_type == "postgresql":
|
|
321
|
+
return "postgresql://postgres:postgres@localhost:5432/pywebguard"
|
|
322
|
+
|
|
323
|
+
return v
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class GuardConfig(BaseModel):
|
|
327
|
+
"""Main configuration for PyWebGuard.
|
|
328
|
+
|
|
329
|
+
This class combines all configuration components into a single configuration
|
|
330
|
+
object that can be used to initialize the WebGuard instance.
|
|
331
|
+
|
|
332
|
+
Attributes:
|
|
333
|
+
ip_filter: IP filtering configuration
|
|
334
|
+
rate_limit: Rate limiting configuration
|
|
335
|
+
user_agent: User agent filtering configuration
|
|
336
|
+
penetration: Penetration detection configuration
|
|
337
|
+
cors: CORS configuration
|
|
338
|
+
logging: Logging configuration
|
|
339
|
+
storage: Storage configuration
|
|
340
|
+
"""
|
|
341
|
+
|
|
342
|
+
ip_filter: IPFilterConfig = Field(default_factory=IPFilterConfig)
|
|
343
|
+
rate_limit: RateLimitConfig = Field(default_factory=RateLimitConfig)
|
|
344
|
+
user_agent: UserAgentConfig = Field(default_factory=UserAgentConfig)
|
|
345
|
+
penetration: PenetrationDetectionConfig = Field(
|
|
346
|
+
default_factory=PenetrationDetectionConfig
|
|
347
|
+
)
|
|
348
|
+
cors: CORSConfig = Field(default_factory=CORSConfig)
|
|
349
|
+
logging: LoggingConfig = Field(default_factory=LoggingConfig)
|
|
350
|
+
storage: StorageConfig = Field(default_factory=StorageConfig)
|
|
351
|
+
|
|
352
|
+
@classmethod
|
|
353
|
+
def from_env(cls) -> "GuardConfig":
|
|
354
|
+
"""Create a GuardConfig instance from environment variables.
|
|
355
|
+
|
|
356
|
+
Environment variables:
|
|
357
|
+
PYWEBGUARD_STORAGE_TYPE: Storage type (memory, redis, sqlite, tinydb, mongodb, postgresql)
|
|
358
|
+
PYWEBGUARD_STORAGE_URL: Connection URL for the storage backend
|
|
359
|
+
PYWEBGUARD_STORAGE_PREFIX: Prefix for storage keys
|
|
360
|
+
PYWEBGUARD_STORAGE_TTL: Time-to-live for stored data in seconds
|
|
361
|
+
PYWEBGUARD_STORAGE_TABLE: Table or collection name
|
|
362
|
+
|
|
363
|
+
PYWEBGUARD_RATE_LIMIT_ENABLED: Whether rate limiting is enabled (true/false)
|
|
364
|
+
PYWEBGUARD_RATE_LIMIT_RPM: Requests per minute
|
|
365
|
+
PYWEBGUARD_RATE_LIMIT_BURST: Burst size
|
|
366
|
+
PYWEBGUARD_RATE_LIMIT_BAN_THRESHOLD: Auto-ban threshold
|
|
367
|
+
PYWEBGUARD_RATE_LIMIT_BAN_DURATION: Auto-ban duration in minutes
|
|
368
|
+
|
|
369
|
+
PYWEBGUARD_IP_FILTER_ENABLED: Whether IP filtering is enabled (true/false)
|
|
370
|
+
PYWEBGUARD_IP_WHITELIST: Comma-separated list of whitelisted IPs
|
|
371
|
+
PYWEBGUARD_IP_BLACKLIST: Comma-separated list of blacklisted IPs
|
|
372
|
+
|
|
373
|
+
PYWEBGUARD_LOG_ENABLED: Whether logging is enabled (true/false)
|
|
374
|
+
PYWEBGUARD_LOG_FILE: Path to log file
|
|
375
|
+
PYWEBGUARD_LOG_LEVEL: Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
GuardConfig: Configuration instance with values from environment variables
|
|
379
|
+
"""
|
|
380
|
+
# Create default config
|
|
381
|
+
config = cls()
|
|
382
|
+
|
|
383
|
+
# Storage configuration
|
|
384
|
+
storage_type = os.environ.get("PYWEBGUARD_STORAGE_TYPE")
|
|
385
|
+
if storage_type:
|
|
386
|
+
config.storage.type = storage_type.lower()
|
|
387
|
+
|
|
388
|
+
storage_url = os.environ.get("PYWEBGUARD_STORAGE_URL")
|
|
389
|
+
if storage_url:
|
|
390
|
+
config.storage.url = storage_url
|
|
391
|
+
|
|
392
|
+
storage_prefix = os.environ.get("PYWEBGUARD_STORAGE_PREFIX")
|
|
393
|
+
if storage_prefix:
|
|
394
|
+
config.storage.prefix = storage_prefix
|
|
395
|
+
|
|
396
|
+
storage_ttl = os.environ.get("PYWEBGUARD_STORAGE_TTL")
|
|
397
|
+
if storage_ttl and storage_ttl.isdigit():
|
|
398
|
+
config.storage.ttl = int(storage_ttl)
|
|
399
|
+
|
|
400
|
+
storage_table = os.environ.get("PYWEBGUARD_STORAGE_TABLE")
|
|
401
|
+
if storage_table:
|
|
402
|
+
config.storage.table_name = storage_table
|
|
403
|
+
|
|
404
|
+
# Rate limit configuration
|
|
405
|
+
rate_limit_enabled = os.environ.get("PYWEBGUARD_RATE_LIMIT_ENABLED")
|
|
406
|
+
if rate_limit_enabled is not None:
|
|
407
|
+
config.rate_limit.enabled = rate_limit_enabled.lower() in (
|
|
408
|
+
"true",
|
|
409
|
+
"1",
|
|
410
|
+
"yes",
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
rate_limit_rpm = os.environ.get("PYWEBGUARD_RATE_LIMIT_RPM")
|
|
414
|
+
if rate_limit_rpm and rate_limit_rpm.isdigit():
|
|
415
|
+
config.rate_limit.requests_per_minute = int(rate_limit_rpm)
|
|
416
|
+
|
|
417
|
+
rate_limit_burst = os.environ.get("PYWEBGUARD_RATE_LIMIT_BURST")
|
|
418
|
+
if rate_limit_burst and rate_limit_burst.isdigit():
|
|
419
|
+
config.rate_limit.burst_size = int(rate_limit_burst)
|
|
420
|
+
|
|
421
|
+
rate_limit_ban_threshold = os.environ.get("PYWEBGUARD_RATE_LIMIT_BAN_THRESHOLD")
|
|
422
|
+
if rate_limit_ban_threshold and rate_limit_ban_threshold.isdigit():
|
|
423
|
+
config.rate_limit.auto_ban_threshold = int(rate_limit_ban_threshold)
|
|
424
|
+
|
|
425
|
+
rate_limit_ban_duration = os.environ.get("PYWEBGUARD_RATE_LIMIT_BAN_DURATION")
|
|
426
|
+
if rate_limit_ban_duration and rate_limit_ban_duration.isdigit():
|
|
427
|
+
config.rate_limit.auto_ban_duration_minutes = int(rate_limit_ban_duration)
|
|
428
|
+
|
|
429
|
+
# IP filter configuration
|
|
430
|
+
ip_filter_enabled = os.environ.get("PYWEBGUARD_IP_FILTER_ENABLED")
|
|
431
|
+
if ip_filter_enabled is not None:
|
|
432
|
+
config.ip_filter.enabled = ip_filter_enabled.lower() in ("true", "1", "yes")
|
|
433
|
+
|
|
434
|
+
ip_whitelist = os.environ.get("PYWEBGUARD_IP_WHITELIST")
|
|
435
|
+
if ip_whitelist:
|
|
436
|
+
config.ip_filter.whitelist = [ip.strip() for ip in ip_whitelist.split(",")]
|
|
437
|
+
|
|
438
|
+
ip_blacklist = os.environ.get("PYWEBGUARD_IP_BLACKLIST")
|
|
439
|
+
if ip_blacklist:
|
|
440
|
+
config.ip_filter.blacklist = [ip.strip() for ip in ip_blacklist.split(",")]
|
|
441
|
+
|
|
442
|
+
# Logging configuration
|
|
443
|
+
log_enabled = os.environ.get("PYWEBGUARD_LOG_ENABLED")
|
|
444
|
+
if log_enabled is not None:
|
|
445
|
+
config.logging.enabled = log_enabled.lower() in ("true", "1", "yes")
|
|
446
|
+
|
|
447
|
+
log_file = os.environ.get("PYWEBGUARD_LOG_FILE")
|
|
448
|
+
if log_file:
|
|
449
|
+
config.logging.log_file = log_file
|
|
450
|
+
|
|
451
|
+
log_level = os.environ.get("PYWEBGUARD_LOG_LEVEL")
|
|
452
|
+
if log_level:
|
|
453
|
+
config.logging.log_level = log_level.upper()
|
|
454
|
+
|
|
455
|
+
return config
|
|
File without changes
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base filter interfaces for PyWebGuard with both sync and async support.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from typing import Dict, Any, Union
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseFilter(ABC):
|
|
10
|
+
"""
|
|
11
|
+
Abstract base class for synchronous filters.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def is_allowed(self, value: Any) -> Dict[str, Union[bool, str]]:
|
|
16
|
+
"""
|
|
17
|
+
Check if a value is allowed by the filter.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
value: The value to check
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Dict with allowed status and reason
|
|
24
|
+
"""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AsyncBaseFilter(ABC):
|
|
29
|
+
"""
|
|
30
|
+
Abstract base class for asynchronous filters.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
async def is_allowed(self, value: Any) -> Dict[str, Union[bool, str]]:
|
|
35
|
+
"""
|
|
36
|
+
Check if a value is allowed by the filter asynchronously.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
value: The value to check
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Dict with allowed status and reason
|
|
43
|
+
"""
|
|
44
|
+
pass
|