aiwaf 0.1.9.0.4__py3-none-any.whl → 0.1.9.0.6__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.
Potentially problematic release.
This version of aiwaf might be problematic. Click here for more details.
- aiwaf/__init__.py +1 -1
- aiwaf/blacklist_manager.py +17 -4
- aiwaf/management/commands/add_exemption.py +30 -0
- aiwaf/management/commands/clear_cache.py +18 -0
- aiwaf/management/commands/diagnose_blocking.py +96 -0
- aiwaf/management/commands/setup_models.py +35 -0
- aiwaf/management/commands/test_exemption.py +120 -0
- aiwaf/management/commands/test_exemption_fix.py +54 -0
- aiwaf/middleware.py +28 -16
- aiwaf/middleware_logger.py +66 -106
- aiwaf/models.py +28 -1
- aiwaf/storage.py +166 -360
- aiwaf/trainer.py +0 -12
- {aiwaf-0.1.9.0.4.dist-info → aiwaf-0.1.9.0.6.dist-info}/METADATA +30 -27
- aiwaf-0.1.9.0.6.dist-info/RECORD +32 -0
- aiwaf/management/commands/debug_csv.py +0 -155
- aiwaf-0.1.9.0.4.dist-info/RECORD +0 -27
- {aiwaf-0.1.9.0.4.dist-info → aiwaf-0.1.9.0.6.dist-info}/WHEEL +0 -0
- {aiwaf-0.1.9.0.4.dist-info → aiwaf-0.1.9.0.6.dist-info}/licenses/LICENSE +0 -0
- {aiwaf-0.1.9.0.4.dist-info → aiwaf-0.1.9.0.6.dist-info}/top_level.txt +0 -0
aiwaf/storage.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import os, csv, gzip, glob
|
|
2
1
|
import numpy as np
|
|
3
2
|
import pandas as pd
|
|
4
3
|
from django.conf import settings
|
|
@@ -22,402 +21,209 @@ def _import_models():
|
|
|
22
21
|
# Keep models as None if can't import
|
|
23
22
|
pass
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
STORAGE_MODE = getattr(settings, "AIWAF_STORAGE_MODE", "models") # "models" or "csv"
|
|
27
|
-
CSV_DATA_DIR = getattr(settings, "AIWAF_CSV_DATA_DIR", "aiwaf_data")
|
|
28
|
-
FEATURE_CSV = getattr(settings, "AIWAF_CSV_PATH", os.path.join(CSV_DATA_DIR, "access_samples.csv"))
|
|
29
|
-
BLACKLIST_CSV = os.path.join(CSV_DATA_DIR, "blacklist.csv")
|
|
30
|
-
EXEMPTION_CSV = os.path.join(CSV_DATA_DIR, "exemptions.csv")
|
|
31
|
-
KEYWORDS_CSV = os.path.join(CSV_DATA_DIR, "keywords.csv")
|
|
32
|
-
|
|
33
|
-
CSV_HEADER = [
|
|
34
|
-
"ip","path_len","kw_hits","resp_time",
|
|
35
|
-
"status_idx","burst_count","total_404","label"
|
|
36
|
-
]
|
|
37
|
-
|
|
38
|
-
def ensure_csv_directory():
|
|
39
|
-
"""Ensure the CSV data directory exists"""
|
|
40
|
-
if STORAGE_MODE == "csv" and not os.path.exists(CSV_DATA_DIR):
|
|
41
|
-
os.makedirs(CSV_DATA_DIR)
|
|
42
|
-
|
|
43
|
-
class CsvFeatureStore:
|
|
44
|
-
@staticmethod
|
|
45
|
-
def persist_rows(rows):
|
|
46
|
-
ensure_csv_directory()
|
|
47
|
-
new_file = not os.path.exists(FEATURE_CSV)
|
|
48
|
-
with open(FEATURE_CSV, "a", newline="", encoding="utf-8") as f:
|
|
49
|
-
w = csv.writer(f)
|
|
50
|
-
if new_file:
|
|
51
|
-
w.writerow(CSV_HEADER)
|
|
52
|
-
w.writerows(rows)
|
|
53
|
-
|
|
54
|
-
@staticmethod
|
|
55
|
-
def load_matrix():
|
|
56
|
-
if not os.path.exists(FEATURE_CSV):
|
|
57
|
-
return np.empty((0,6))
|
|
58
|
-
df = pd.read_csv(
|
|
59
|
-
FEATURE_CSV,
|
|
60
|
-
names=CSV_HEADER,
|
|
61
|
-
skiprows=1,
|
|
62
|
-
engine="python",
|
|
63
|
-
on_bad_lines="skip"
|
|
64
|
-
)
|
|
65
|
-
feature_cols = CSV_HEADER[1:7]
|
|
66
|
-
df[feature_cols] = df[feature_cols].apply(pd.to_numeric, errors="coerce").fillna(0)
|
|
67
|
-
return df[feature_cols].to_numpy()
|
|
68
|
-
|
|
69
|
-
class DbFeatureStore:
|
|
24
|
+
class ModelFeatureStore:
|
|
70
25
|
@staticmethod
|
|
71
26
|
def persist_rows(rows):
|
|
27
|
+
"""Persist feature data to Django models"""
|
|
72
28
|
_import_models()
|
|
73
|
-
if FeatureSample is
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
29
|
+
if FeatureSample is None:
|
|
30
|
+
print("Warning: Django models not available, skipping feature storage")
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
for row in rows:
|
|
34
|
+
try:
|
|
35
|
+
FeatureSample.objects.create(
|
|
36
|
+
ip=row[0],
|
|
37
|
+
path_len=int(row[1]),
|
|
38
|
+
kw_hits=int(row[2]),
|
|
39
|
+
resp_time=float(row[3]),
|
|
40
|
+
status_idx=int(row[4]),
|
|
41
|
+
burst_count=int(row[5]),
|
|
42
|
+
total_404=int(row[6]),
|
|
43
|
+
label=int(row[7]),
|
|
44
|
+
created_at=timezone.now()
|
|
45
|
+
)
|
|
46
|
+
except Exception as e:
|
|
47
|
+
print(f"Error saving feature sample: {e}")
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def get_all_data():
|
|
51
|
+
"""Get all feature data as DataFrame"""
|
|
86
52
|
_import_models()
|
|
87
|
-
if FeatureSample is
|
|
88
|
-
|
|
89
|
-
|
|
53
|
+
if FeatureSample is None:
|
|
54
|
+
return pd.DataFrame()
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
queryset = FeatureSample.objects.all().values(
|
|
58
|
+
'ip', 'path_len', 'kw_hits', 'resp_time',
|
|
59
|
+
'status_idx', 'burst_count', 'total_404', 'label'
|
|
90
60
|
)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
61
|
+
df = pd.DataFrame(list(queryset))
|
|
62
|
+
if df.empty:
|
|
63
|
+
return df
|
|
64
|
+
|
|
65
|
+
# Ensure proper column order and types
|
|
66
|
+
feature_cols = ['path_len', 'kw_hits', 'resp_time', 'status_idx', 'burst_count', 'total_404']
|
|
67
|
+
for col in feature_cols:
|
|
68
|
+
if col in df.columns:
|
|
69
|
+
df[col] = pd.to_numeric(df[col], errors='coerce')
|
|
70
|
+
|
|
71
|
+
return df
|
|
72
|
+
except Exception as e:
|
|
73
|
+
print(f"Error loading feature data: {e}")
|
|
74
|
+
return pd.DataFrame()
|
|
101
75
|
|
|
102
|
-
class
|
|
103
|
-
"""CSV-based storage for IP blacklist entries"""
|
|
104
|
-
|
|
105
|
-
@staticmethod
|
|
106
|
-
def add_ip(ip_address, reason):
|
|
107
|
-
ensure_csv_directory()
|
|
108
|
-
# Check if IP already exists
|
|
109
|
-
if CsvBlacklistStore.is_blocked(ip_address):
|
|
110
|
-
return
|
|
111
|
-
|
|
112
|
-
# Add new entry
|
|
113
|
-
new_file = not os.path.exists(BLACKLIST_CSV)
|
|
114
|
-
with open(BLACKLIST_CSV, "a", newline="", encoding="utf-8") as f:
|
|
115
|
-
writer = csv.writer(f)
|
|
116
|
-
if new_file:
|
|
117
|
-
writer.writerow(["ip_address", "reason", "created_at"])
|
|
118
|
-
writer.writerow([ip_address, reason, timezone.now().isoformat()])
|
|
119
|
-
|
|
76
|
+
class ModelBlacklistStore:
|
|
120
77
|
@staticmethod
|
|
121
|
-
def is_blocked(
|
|
122
|
-
if
|
|
78
|
+
def is_blocked(ip):
|
|
79
|
+
"""Check if IP is in blacklist"""
|
|
80
|
+
_import_models()
|
|
81
|
+
if BlacklistEntry is None:
|
|
82
|
+
return False
|
|
83
|
+
try:
|
|
84
|
+
return BlacklistEntry.objects.filter(ip_address=ip).exists()
|
|
85
|
+
except Exception:
|
|
123
86
|
return False
|
|
124
|
-
|
|
125
|
-
with open(BLACKLIST_CSV, "r", newline="", encoding="utf-8") as f:
|
|
126
|
-
reader = csv.DictReader(f)
|
|
127
|
-
for row in reader:
|
|
128
|
-
if row["ip_address"] == ip_address:
|
|
129
|
-
return True
|
|
130
|
-
return False
|
|
131
|
-
|
|
132
|
-
@staticmethod
|
|
133
|
-
def get_all():
|
|
134
|
-
"""Return list of dictionaries with blacklist entries"""
|
|
135
|
-
if not os.path.exists(BLACKLIST_CSV):
|
|
136
|
-
return []
|
|
137
|
-
|
|
138
|
-
entries = []
|
|
139
|
-
with open(BLACKLIST_CSV, "r", newline="", encoding="utf-8") as f:
|
|
140
|
-
reader = csv.DictReader(f)
|
|
141
|
-
for row in reader:
|
|
142
|
-
entries.append(row)
|
|
143
|
-
return entries
|
|
144
|
-
|
|
145
|
-
@staticmethod
|
|
146
|
-
def remove_ip(ip_address):
|
|
147
|
-
if not os.path.exists(BLACKLIST_CSV):
|
|
148
|
-
return
|
|
149
|
-
|
|
150
|
-
# Read all entries except the one to remove
|
|
151
|
-
entries = []
|
|
152
|
-
with open(BLACKLIST_CSV, "r", newline="", encoding="utf-8") as f:
|
|
153
|
-
reader = csv.DictReader(f)
|
|
154
|
-
entries = [row for row in reader if row["ip_address"] != ip_address]
|
|
155
|
-
|
|
156
|
-
# Write back the filtered entries
|
|
157
|
-
with open(BLACKLIST_CSV, "w", newline="", encoding="utf-8") as f:
|
|
158
|
-
if entries:
|
|
159
|
-
writer = csv.DictWriter(f, fieldnames=["ip_address", "reason", "created_at"])
|
|
160
|
-
writer.writeheader()
|
|
161
|
-
writer.writerows(entries)
|
|
162
|
-
|
|
163
87
|
|
|
164
|
-
class CsvExemptionStore:
|
|
165
|
-
"""CSV-based storage for IP exemption entries"""
|
|
166
|
-
|
|
167
88
|
@staticmethod
|
|
168
|
-
def
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
89
|
+
def block_ip(ip, reason="Automated block"):
|
|
90
|
+
"""Add IP to blacklist"""
|
|
91
|
+
_import_models()
|
|
92
|
+
if BlacklistEntry is None:
|
|
93
|
+
print(f"Warning: Cannot block IP {ip}, models not available")
|
|
173
94
|
return
|
|
174
|
-
|
|
175
|
-
# Add new entry
|
|
176
|
-
new_file = not os.path.exists(EXEMPTION_CSV)
|
|
177
95
|
try:
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
writer.writerow([ip_address, reason, timezone.now().isoformat()])
|
|
96
|
+
BlacklistEntry.objects.get_or_create(
|
|
97
|
+
ip_address=ip,
|
|
98
|
+
defaults={'reason': reason, 'created_at': timezone.now()}
|
|
99
|
+
)
|
|
183
100
|
except Exception as e:
|
|
184
|
-
print(f"Error
|
|
185
|
-
|
|
186
|
-
print(f"Directory exists: {os.path.exists(CSV_DATA_DIR)}")
|
|
187
|
-
raise
|
|
188
|
-
|
|
101
|
+
print(f"Error blocking IP {ip}: {e}")
|
|
102
|
+
|
|
189
103
|
@staticmethod
|
|
190
|
-
def
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
104
|
+
def unblock_ip(ip):
|
|
105
|
+
"""Remove IP from blacklist"""
|
|
106
|
+
_import_models()
|
|
107
|
+
if BlacklistEntry is None:
|
|
108
|
+
return
|
|
194
109
|
try:
|
|
195
|
-
|
|
196
|
-
reader = csv.DictReader(f)
|
|
197
|
-
for row in reader:
|
|
198
|
-
if row["ip_address"] == ip_address:
|
|
199
|
-
return True
|
|
110
|
+
BlacklistEntry.objects.filter(ip_address=ip).delete()
|
|
200
111
|
except Exception as e:
|
|
201
|
-
print(f"Error
|
|
202
|
-
return False
|
|
203
|
-
return False
|
|
204
|
-
|
|
205
|
-
@staticmethod
|
|
206
|
-
def get_all():
|
|
207
|
-
"""Return list of dictionaries with exemption entries"""
|
|
208
|
-
if not os.path.exists(EXEMPTION_CSV):
|
|
209
|
-
return []
|
|
210
|
-
|
|
211
|
-
entries = []
|
|
212
|
-
with open(EXEMPTION_CSV, "r", newline="", encoding="utf-8") as f:
|
|
213
|
-
reader = csv.DictReader(f)
|
|
214
|
-
for row in reader:
|
|
215
|
-
entries.append(row)
|
|
216
|
-
return entries
|
|
217
|
-
|
|
218
|
-
@staticmethod
|
|
219
|
-
def remove_ip(ip_address):
|
|
220
|
-
if not os.path.exists(EXEMPTION_CSV):
|
|
221
|
-
return
|
|
222
|
-
|
|
223
|
-
# Read all entries except the one to remove
|
|
224
|
-
entries = []
|
|
225
|
-
with open(EXEMPTION_CSV, "r", newline="", encoding="utf-8") as f:
|
|
226
|
-
reader = csv.DictReader(f)
|
|
227
|
-
entries = [row for row in reader if row["ip_address"] != ip_address]
|
|
228
|
-
|
|
229
|
-
# Write back the filtered entries
|
|
230
|
-
with open(EXEMPTION_CSV, "w", newline="", encoding="utf-8") as f:
|
|
231
|
-
if entries:
|
|
232
|
-
writer = csv.DictWriter(f, fieldnames=["ip_address", "reason", "created_at"])
|
|
233
|
-
writer.writeheader()
|
|
234
|
-
writer.writerows(entries)
|
|
235
|
-
|
|
112
|
+
print(f"Error unblocking IP {ip}: {e}")
|
|
236
113
|
|
|
237
|
-
class CsvKeywordStore:
|
|
238
|
-
"""CSV-based storage for dynamic keywords"""
|
|
239
|
-
|
|
240
|
-
@staticmethod
|
|
241
|
-
def add_keyword(keyword, count=1):
|
|
242
|
-
ensure_csv_directory()
|
|
243
|
-
|
|
244
|
-
# Read existing keywords
|
|
245
|
-
keywords = CsvKeywordStore._load_keywords()
|
|
246
|
-
|
|
247
|
-
# Update or add keyword
|
|
248
|
-
keywords[keyword] = keywords.get(keyword, 0) + count
|
|
249
|
-
|
|
250
|
-
# Save back to file
|
|
251
|
-
CsvKeywordStore._save_keywords(keywords)
|
|
252
|
-
|
|
253
114
|
@staticmethod
|
|
254
|
-
def
|
|
255
|
-
|
|
256
|
-
# Sort by count in descending order and return top N
|
|
257
|
-
sorted_keywords = sorted(keywords.items(), key=lambda x: x[1], reverse=True)
|
|
258
|
-
return [kw for kw, count in sorted_keywords[:limit]]
|
|
259
|
-
|
|
260
|
-
@staticmethod
|
|
261
|
-
def remove_keyword(keyword):
|
|
262
|
-
keywords = CsvKeywordStore._load_keywords()
|
|
263
|
-
if keyword in keywords:
|
|
264
|
-
del keywords[keyword]
|
|
265
|
-
CsvKeywordStore._save_keywords(keywords)
|
|
266
|
-
|
|
267
|
-
@staticmethod
|
|
268
|
-
def clear_all():
|
|
269
|
-
if os.path.exists(KEYWORDS_CSV):
|
|
270
|
-
os.remove(KEYWORDS_CSV)
|
|
271
|
-
|
|
272
|
-
@staticmethod
|
|
273
|
-
def _load_keywords():
|
|
274
|
-
"""Load keywords from CSV file as a dictionary"""
|
|
275
|
-
if not os.path.exists(KEYWORDS_CSV):
|
|
276
|
-
return {}
|
|
277
|
-
|
|
278
|
-
keywords = {}
|
|
279
|
-
with open(KEYWORDS_CSV, "r", newline="", encoding="utf-8") as f:
|
|
280
|
-
reader = csv.DictReader(f)
|
|
281
|
-
for row in reader:
|
|
282
|
-
keywords[row["keyword"]] = int(row["count"])
|
|
283
|
-
return keywords
|
|
284
|
-
|
|
285
|
-
@staticmethod
|
|
286
|
-
def _save_keywords(keywords):
|
|
287
|
-
"""Save keywords dictionary to CSV file"""
|
|
288
|
-
with open(KEYWORDS_CSV, "w", newline="", encoding="utf-8") as f:
|
|
289
|
-
writer = csv.writer(f)
|
|
290
|
-
writer.writerow(["keyword", "count", "last_updated"])
|
|
291
|
-
for keyword, count in keywords.items():
|
|
292
|
-
writer.writerow([keyword, count, timezone.now().isoformat()])
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
# ============= Storage Factory Functions =============
|
|
296
|
-
|
|
297
|
-
def get_blacklist_store():
|
|
298
|
-
"""Return appropriate blacklist storage class based on settings"""
|
|
299
|
-
if STORAGE_MODE == "csv":
|
|
300
|
-
return CsvBlacklistStore
|
|
301
|
-
else:
|
|
302
|
-
# Return a wrapper for Django models (only if models are available)
|
|
303
|
-
if BlacklistEntry is not None:
|
|
304
|
-
return ModelBlacklistStore
|
|
305
|
-
else:
|
|
306
|
-
# Fallback to CSV if models aren't available
|
|
307
|
-
return CsvBlacklistStore
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
def get_exemption_store():
|
|
311
|
-
"""Return appropriate exemption storage class based on settings"""
|
|
312
|
-
if STORAGE_MODE == "csv":
|
|
313
|
-
return CsvExemptionStore
|
|
314
|
-
else:
|
|
315
|
-
if IPExemption is not None:
|
|
316
|
-
return ModelExemptionStore
|
|
317
|
-
else:
|
|
318
|
-
return CsvExemptionStore
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
def get_keyword_store():
|
|
322
|
-
"""Return appropriate keyword storage class based on settings"""
|
|
323
|
-
if STORAGE_MODE == "csv":
|
|
324
|
-
return CsvKeywordStore
|
|
325
|
-
else:
|
|
326
|
-
if DynamicKeyword is not None:
|
|
327
|
-
return ModelKeywordStore
|
|
328
|
-
else:
|
|
329
|
-
return CsvKeywordStore
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
# ============= Django Model Wrappers =============
|
|
333
|
-
|
|
334
|
-
class ModelBlacklistStore:
|
|
335
|
-
"""Django model-based storage for blacklist entries"""
|
|
336
|
-
|
|
337
|
-
@staticmethod
|
|
338
|
-
def add_ip(ip_address, reason):
|
|
115
|
+
def get_all_blocked_ips():
|
|
116
|
+
"""Get all blocked IPs"""
|
|
339
117
|
_import_models()
|
|
340
|
-
if BlacklistEntry is
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if BlacklistEntry is not None:
|
|
347
|
-
return BlacklistEntry.objects.filter(ip_address=ip_address).exists()
|
|
348
|
-
return False
|
|
349
|
-
|
|
350
|
-
@staticmethod
|
|
351
|
-
def get_all():
|
|
352
|
-
_import_models()
|
|
353
|
-
if BlacklistEntry is not None:
|
|
354
|
-
return list(BlacklistEntry.objects.values("ip_address", "reason", "created_at"))
|
|
355
|
-
return []
|
|
356
|
-
|
|
357
|
-
@staticmethod
|
|
358
|
-
def remove_ip(ip_address):
|
|
359
|
-
_import_models()
|
|
360
|
-
if BlacklistEntry is not None:
|
|
361
|
-
BlacklistEntry.objects.filter(ip_address=ip_address).delete()
|
|
362
|
-
|
|
118
|
+
if BlacklistEntry is None:
|
|
119
|
+
return []
|
|
120
|
+
try:
|
|
121
|
+
return list(BlacklistEntry.objects.values_list('ip_address', flat=True))
|
|
122
|
+
except Exception:
|
|
123
|
+
return []
|
|
363
124
|
|
|
364
125
|
class ModelExemptionStore:
|
|
365
|
-
"""Django model-based storage for exemption entries"""
|
|
366
|
-
|
|
367
126
|
@staticmethod
|
|
368
|
-
def
|
|
127
|
+
def is_exempted(ip):
|
|
128
|
+
"""Check if IP is exempted"""
|
|
369
129
|
_import_models()
|
|
370
|
-
if IPExemption is
|
|
371
|
-
|
|
372
|
-
|
|
130
|
+
if IPExemption is None:
|
|
131
|
+
return False
|
|
132
|
+
try:
|
|
133
|
+
return IPExemption.objects.filter(ip_address=ip).exists()
|
|
134
|
+
except Exception:
|
|
135
|
+
return False
|
|
136
|
+
|
|
373
137
|
@staticmethod
|
|
374
|
-
def
|
|
138
|
+
def add_exemption(ip, reason="Manual exemption"):
|
|
139
|
+
"""Add IP to exemption list"""
|
|
375
140
|
_import_models()
|
|
376
|
-
if IPExemption is
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
141
|
+
if IPExemption is None:
|
|
142
|
+
print(f"Warning: Cannot exempt IP {ip}, models not available")
|
|
143
|
+
return
|
|
144
|
+
try:
|
|
145
|
+
IPExemption.objects.get_or_create(
|
|
146
|
+
ip_address=ip,
|
|
147
|
+
defaults={'reason': reason, 'created_at': timezone.now()}
|
|
148
|
+
)
|
|
149
|
+
except Exception as e:
|
|
150
|
+
print(f"Error exempting IP {ip}: {e}")
|
|
151
|
+
|
|
380
152
|
@staticmethod
|
|
381
|
-
def
|
|
153
|
+
def remove_exemption(ip):
|
|
154
|
+
"""Remove IP from exemption list"""
|
|
382
155
|
_import_models()
|
|
383
|
-
if IPExemption is
|
|
384
|
-
return
|
|
385
|
-
|
|
386
|
-
|
|
156
|
+
if IPExemption is None:
|
|
157
|
+
return
|
|
158
|
+
try:
|
|
159
|
+
IPExemption.objects.filter(ip_address=ip).delete()
|
|
160
|
+
except Exception as e:
|
|
161
|
+
print(f"Error removing exemption for IP {ip}: {e}")
|
|
162
|
+
|
|
387
163
|
@staticmethod
|
|
388
|
-
def
|
|
164
|
+
def get_all_exempted_ips():
|
|
165
|
+
"""Get all exempted IPs"""
|
|
389
166
|
_import_models()
|
|
390
|
-
if IPExemption is
|
|
391
|
-
|
|
392
|
-
|
|
167
|
+
if IPExemption is None:
|
|
168
|
+
return []
|
|
169
|
+
try:
|
|
170
|
+
return list(IPExemption.objects.values_list('ip_address', flat=True))
|
|
171
|
+
except Exception:
|
|
172
|
+
return []
|
|
393
173
|
|
|
394
174
|
class ModelKeywordStore:
|
|
395
|
-
"""Django model-based storage for dynamic keywords"""
|
|
396
|
-
|
|
397
175
|
@staticmethod
|
|
398
|
-
def add_keyword(keyword
|
|
176
|
+
def add_keyword(keyword):
|
|
177
|
+
"""Add a keyword to the dynamic keyword list"""
|
|
399
178
|
_import_models()
|
|
400
|
-
if DynamicKeyword is
|
|
401
|
-
|
|
179
|
+
if DynamicKeyword is None:
|
|
180
|
+
return
|
|
181
|
+
try:
|
|
182
|
+
obj, created = DynamicKeyword.objects.get_or_create(keyword=keyword)
|
|
402
183
|
if not created:
|
|
403
|
-
obj.count +=
|
|
184
|
+
obj.count += 1
|
|
404
185
|
obj.save()
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
_import_models()
|
|
409
|
-
if DynamicKeyword is not None:
|
|
410
|
-
return list(DynamicKeyword.objects.order_by("-count").values_list("keyword", flat=True)[:limit])
|
|
411
|
-
return []
|
|
412
|
-
|
|
186
|
+
except Exception as e:
|
|
187
|
+
print(f"Error adding keyword {keyword}: {e}")
|
|
188
|
+
|
|
413
189
|
@staticmethod
|
|
414
|
-
def
|
|
190
|
+
def get_top_keywords(n=10):
|
|
191
|
+
"""Get top N keywords by count"""
|
|
415
192
|
_import_models()
|
|
416
|
-
if DynamicKeyword is
|
|
417
|
-
|
|
418
|
-
|
|
193
|
+
if DynamicKeyword is None:
|
|
194
|
+
return []
|
|
195
|
+
try:
|
|
196
|
+
return list(
|
|
197
|
+
DynamicKeyword.objects.order_by('-count')[:n]
|
|
198
|
+
.values_list('keyword', flat=True)
|
|
199
|
+
)
|
|
200
|
+
except Exception:
|
|
201
|
+
return []
|
|
202
|
+
|
|
419
203
|
@staticmethod
|
|
420
|
-
def
|
|
204
|
+
def reset_keywords():
|
|
205
|
+
"""Reset all keyword counts"""
|
|
421
206
|
_import_models()
|
|
422
|
-
if DynamicKeyword is
|
|
207
|
+
if DynamicKeyword is None:
|
|
208
|
+
return
|
|
209
|
+
try:
|
|
423
210
|
DynamicKeyword.objects.all().delete()
|
|
211
|
+
except Exception as e:
|
|
212
|
+
print(f"Error resetting keywords: {e}")
|
|
213
|
+
|
|
214
|
+
# Factory functions that only return Django model stores
|
|
215
|
+
def get_feature_store():
|
|
216
|
+
"""Get the feature store (Django models only)"""
|
|
217
|
+
return ModelFeatureStore()
|
|
218
|
+
|
|
219
|
+
def get_blacklist_store():
|
|
220
|
+
"""Get the blacklist store (Django models only)"""
|
|
221
|
+
return ModelBlacklistStore()
|
|
222
|
+
|
|
223
|
+
def get_exemption_store():
|
|
224
|
+
"""Get the exemption store (Django models only)"""
|
|
225
|
+
return ModelExemptionStore()
|
|
226
|
+
|
|
227
|
+
def get_keyword_store():
|
|
228
|
+
"""Get the keyword store (Django models only)"""
|
|
229
|
+
return ModelKeywordStore()
|
aiwaf/trainer.py
CHANGED
|
@@ -78,18 +78,6 @@ def _read_all_logs() -> list[str]:
|
|
|
78
78
|
except OSError:
|
|
79
79
|
continue
|
|
80
80
|
|
|
81
|
-
# If no lines found from main log, try AI-WAF middleware CSV log
|
|
82
|
-
if not lines:
|
|
83
|
-
middleware_csv = getattr(settings, "AIWAF_MIDDLEWARE_LOG", "aiwaf_requests.log").replace('.log', '.csv')
|
|
84
|
-
if os.path.exists(middleware_csv):
|
|
85
|
-
try:
|
|
86
|
-
from .middleware_logger import AIWAFCSVLogParser
|
|
87
|
-
csv_lines = AIWAFCSVLogParser.get_log_lines_for_trainer(middleware_csv)
|
|
88
|
-
lines.extend(csv_lines)
|
|
89
|
-
print(f"📋 Using AI-WAF middleware CSV log: {middleware_csv} ({len(csv_lines)} entries)")
|
|
90
|
-
except Exception as e:
|
|
91
|
-
print(f"⚠️ Failed to read middleware CSV log: {e}")
|
|
92
|
-
|
|
93
81
|
return lines
|
|
94
82
|
|
|
95
83
|
|