aiwaf 0.1.8.7__py3-none-any.whl → 0.1.8.8__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/management/commands/aiwaf_logging.py +166 -0
- aiwaf/middleware_logger.py +160 -0
- aiwaf/trainer.py +22 -7
- {aiwaf-0.1.8.7.dist-info → aiwaf-0.1.8.8.dist-info}/METADATA +86 -3
- {aiwaf-0.1.8.7.dist-info → aiwaf-0.1.8.8.dist-info}/RECORD +8 -6
- {aiwaf-0.1.8.7.dist-info → aiwaf-0.1.8.8.dist-info}/WHEEL +0 -0
- {aiwaf-0.1.8.7.dist-info → aiwaf-0.1.8.8.dist-info}/licenses/LICENSE +0 -0
- {aiwaf-0.1.8.7.dist-info → aiwaf-0.1.8.8.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
from django.core.management.base import BaseCommand
|
|
2
|
+
from django.conf import settings
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
class Command(BaseCommand):
|
|
6
|
+
help = 'Manage AI-WAF middleware logging settings and view log status'
|
|
7
|
+
|
|
8
|
+
def add_arguments(self, parser):
|
|
9
|
+
parser.add_argument(
|
|
10
|
+
'--enable',
|
|
11
|
+
action='store_true',
|
|
12
|
+
help='Enable middleware logging (shows settings to add)'
|
|
13
|
+
)
|
|
14
|
+
parser.add_argument(
|
|
15
|
+
'--disable',
|
|
16
|
+
action='store_true',
|
|
17
|
+
help='Disable middleware logging (shows settings to remove)'
|
|
18
|
+
)
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
'--status',
|
|
21
|
+
action='store_true',
|
|
22
|
+
help='Show current middleware logging status'
|
|
23
|
+
)
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
'--clear',
|
|
26
|
+
action='store_true',
|
|
27
|
+
help='Clear/delete middleware log files'
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def handle(self, *args, **options):
|
|
31
|
+
if options['enable']:
|
|
32
|
+
self._show_enable_instructions()
|
|
33
|
+
elif options['disable']:
|
|
34
|
+
self._show_disable_instructions()
|
|
35
|
+
elif options['clear']:
|
|
36
|
+
self._clear_logs()
|
|
37
|
+
else:
|
|
38
|
+
self._show_status()
|
|
39
|
+
|
|
40
|
+
def _show_status(self):
|
|
41
|
+
"""Show current middleware logging configuration"""
|
|
42
|
+
self.stdout.write(self.style.HTTP_INFO("🔍 AI-WAF Middleware Logging Status"))
|
|
43
|
+
self.stdout.write("")
|
|
44
|
+
|
|
45
|
+
# Check settings
|
|
46
|
+
logging_enabled = getattr(settings, 'AIWAF_MIDDLEWARE_LOGGING', False)
|
|
47
|
+
log_file = getattr(settings, 'AIWAF_MIDDLEWARE_LOG', 'aiwaf_requests.log')
|
|
48
|
+
csv_format = getattr(settings, 'AIWAF_MIDDLEWARE_CSV', True)
|
|
49
|
+
csv_file = log_file.replace('.log', '.csv') if csv_format else None
|
|
50
|
+
|
|
51
|
+
# Status
|
|
52
|
+
status_color = self.style.SUCCESS if logging_enabled else self.style.WARNING
|
|
53
|
+
self.stdout.write(f"Status: {status_color('ENABLED' if logging_enabled else 'DISABLED')}")
|
|
54
|
+
self.stdout.write(f"Log File: {log_file}")
|
|
55
|
+
if csv_format:
|
|
56
|
+
self.stdout.write(f"CSV File: {csv_file}")
|
|
57
|
+
self.stdout.write(f"Format: {'CSV' if csv_format else 'Text'}")
|
|
58
|
+
self.stdout.write("")
|
|
59
|
+
|
|
60
|
+
# File existence and sizes
|
|
61
|
+
if logging_enabled:
|
|
62
|
+
self.stdout.write("📁 Log Files:")
|
|
63
|
+
|
|
64
|
+
if csv_format and csv_file:
|
|
65
|
+
if os.path.exists(csv_file):
|
|
66
|
+
size = os.path.getsize(csv_file)
|
|
67
|
+
lines = self._count_csv_lines(csv_file)
|
|
68
|
+
self.stdout.write(f" ✅ {csv_file} ({size:,} bytes, {lines:,} entries)")
|
|
69
|
+
else:
|
|
70
|
+
self.stdout.write(f" ❌ {csv_file} (not found)")
|
|
71
|
+
|
|
72
|
+
if os.path.exists(log_file):
|
|
73
|
+
size = os.path.getsize(log_file)
|
|
74
|
+
self.stdout.write(f" ✅ {log_file} ({size:,} bytes)")
|
|
75
|
+
else:
|
|
76
|
+
self.stdout.write(f" ❌ {log_file} (not found)")
|
|
77
|
+
|
|
78
|
+
# Middleware check
|
|
79
|
+
middleware_list = getattr(settings, 'MIDDLEWARE', [])
|
|
80
|
+
middleware_installed = 'aiwaf.middleware_logger.AIWAFLoggerMiddleware' in middleware_list
|
|
81
|
+
|
|
82
|
+
self.stdout.write("")
|
|
83
|
+
middleware_color = self.style.SUCCESS if middleware_installed else self.style.ERROR
|
|
84
|
+
self.stdout.write(f"Middleware: {middleware_color('INSTALLED' if middleware_installed else 'NOT INSTALLED')}")
|
|
85
|
+
|
|
86
|
+
if logging_enabled and not middleware_installed:
|
|
87
|
+
self.stdout.write(self.style.WARNING("⚠️ Logging is enabled but middleware is not installed!"))
|
|
88
|
+
|
|
89
|
+
def _show_enable_instructions(self):
|
|
90
|
+
"""Show instructions for enabling middleware logging"""
|
|
91
|
+
self.stdout.write(self.style.SUCCESS("🚀 Enable AI-WAF Middleware Logging"))
|
|
92
|
+
self.stdout.write("")
|
|
93
|
+
self.stdout.write("Add these settings to your Django settings.py:")
|
|
94
|
+
self.stdout.write("")
|
|
95
|
+
self.stdout.write(self.style.HTTP_INFO("# Enable AI-WAF middleware logging"))
|
|
96
|
+
self.stdout.write(self.style.HTTP_INFO("AIWAF_MIDDLEWARE_LOGGING = True"))
|
|
97
|
+
self.stdout.write(self.style.HTTP_INFO("AIWAF_MIDDLEWARE_LOG = 'aiwaf_requests.log' # Optional"))
|
|
98
|
+
self.stdout.write(self.style.HTTP_INFO("AIWAF_MIDDLEWARE_CSV = True # Optional (default: True)"))
|
|
99
|
+
self.stdout.write("")
|
|
100
|
+
self.stdout.write("Add middleware to MIDDLEWARE list (preferably near the end):")
|
|
101
|
+
self.stdout.write("")
|
|
102
|
+
self.stdout.write(self.style.HTTP_INFO("MIDDLEWARE = ["))
|
|
103
|
+
self.stdout.write(self.style.HTTP_INFO(" # ... your existing middleware ..."))
|
|
104
|
+
self.stdout.write(self.style.HTTP_INFO(" 'aiwaf.middleware_logger.AIWAFLoggerMiddleware',"))
|
|
105
|
+
self.stdout.write(self.style.HTTP_INFO("]"))
|
|
106
|
+
self.stdout.write("")
|
|
107
|
+
self.stdout.write("Benefits:")
|
|
108
|
+
self.stdout.write(" ✅ Fallback when main access logs unavailable")
|
|
109
|
+
self.stdout.write(" ✅ CSV format for easy analysis")
|
|
110
|
+
self.stdout.write(" ✅ Automatic integration with AI-WAF trainer")
|
|
111
|
+
self.stdout.write(" ✅ Captures response times for better detection")
|
|
112
|
+
|
|
113
|
+
def _show_disable_instructions(self):
|
|
114
|
+
"""Show instructions for disabling middleware logging"""
|
|
115
|
+
self.stdout.write(self.style.WARNING("⏹️ Disable AI-WAF Middleware Logging"))
|
|
116
|
+
self.stdout.write("")
|
|
117
|
+
self.stdout.write("To disable, update your Django settings.py:")
|
|
118
|
+
self.stdout.write("")
|
|
119
|
+
self.stdout.write(self.style.HTTP_INFO("# Disable AI-WAF middleware logging"))
|
|
120
|
+
self.stdout.write(self.style.HTTP_INFO("AIWAF_MIDDLEWARE_LOGGING = False"))
|
|
121
|
+
self.stdout.write("")
|
|
122
|
+
self.stdout.write("And remove from MIDDLEWARE list:")
|
|
123
|
+
self.stdout.write("")
|
|
124
|
+
self.stdout.write(self.style.HTTP_INFO("MIDDLEWARE = ["))
|
|
125
|
+
self.stdout.write(self.style.HTTP_INFO(" # ... your existing middleware ..."))
|
|
126
|
+
self.stdout.write(self.style.HTTP_INFO(" # 'aiwaf.middleware_logger.AIWAFLoggerMiddleware', # Remove this line"))
|
|
127
|
+
self.stdout.write(self.style.HTTP_INFO("]"))
|
|
128
|
+
|
|
129
|
+
def _clear_logs(self):
|
|
130
|
+
"""Clear/delete middleware log files"""
|
|
131
|
+
log_file = getattr(settings, 'AIWAF_MIDDLEWARE_LOG', 'aiwaf_requests.log')
|
|
132
|
+
csv_format = getattr(settings, 'AIWAF_MIDDLEWARE_CSV', True)
|
|
133
|
+
csv_file = log_file.replace('.log', '.csv') if csv_format else None
|
|
134
|
+
|
|
135
|
+
files_deleted = 0
|
|
136
|
+
|
|
137
|
+
# Delete CSV file
|
|
138
|
+
if csv_file and os.path.exists(csv_file):
|
|
139
|
+
try:
|
|
140
|
+
os.remove(csv_file)
|
|
141
|
+
self.stdout.write(self.style.SUCCESS(f"✅ Deleted {csv_file}"))
|
|
142
|
+
files_deleted += 1
|
|
143
|
+
except Exception as e:
|
|
144
|
+
self.stdout.write(self.style.ERROR(f"❌ Failed to delete {csv_file}: {e}"))
|
|
145
|
+
|
|
146
|
+
# Delete text log file
|
|
147
|
+
if os.path.exists(log_file):
|
|
148
|
+
try:
|
|
149
|
+
os.remove(log_file)
|
|
150
|
+
self.stdout.write(self.style.SUCCESS(f"✅ Deleted {log_file}"))
|
|
151
|
+
files_deleted += 1
|
|
152
|
+
except Exception as e:
|
|
153
|
+
self.stdout.write(self.style.ERROR(f"❌ Failed to delete {log_file}: {e}"))
|
|
154
|
+
|
|
155
|
+
if files_deleted == 0:
|
|
156
|
+
self.stdout.write(self.style.WARNING("ℹ️ No log files found to delete"))
|
|
157
|
+
else:
|
|
158
|
+
self.stdout.write(self.style.SUCCESS(f"🗑️ Deleted {files_deleted} log file(s)"))
|
|
159
|
+
|
|
160
|
+
def _count_csv_lines(self, csv_file):
|
|
161
|
+
"""Count lines in CSV file (excluding header)"""
|
|
162
|
+
try:
|
|
163
|
+
with open(csv_file, 'r', encoding='utf-8') as f:
|
|
164
|
+
return sum(1 for line in f) - 1 # Subtract header
|
|
165
|
+
except:
|
|
166
|
+
return 0
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# aiwaf/middleware_logger.py
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import csv
|
|
5
|
+
import time
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from django.conf import settings
|
|
8
|
+
from django.utils.deprecation import MiddlewareMixin
|
|
9
|
+
from .utils import get_ip
|
|
10
|
+
|
|
11
|
+
class AIWAFLoggerMiddleware(MiddlewareMixin):
|
|
12
|
+
"""
|
|
13
|
+
Middleware that logs requests to a CSV file for AI-WAF training.
|
|
14
|
+
Acts as a fallback when main access logs are unavailable.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, get_response):
|
|
18
|
+
super().__init__(get_response)
|
|
19
|
+
self.log_file = getattr(settings, "AIWAF_MIDDLEWARE_LOG", "aiwaf_requests.log")
|
|
20
|
+
self.csv_format = getattr(settings, "AIWAF_MIDDLEWARE_CSV", True)
|
|
21
|
+
self.log_enabled = getattr(settings, "AIWAF_MIDDLEWARE_LOGGING", False)
|
|
22
|
+
|
|
23
|
+
# CSV file path (if using CSV format)
|
|
24
|
+
if self.csv_format and self.log_enabled:
|
|
25
|
+
self.csv_file = self.log_file.replace('.log', '.csv')
|
|
26
|
+
self._ensure_csv_header()
|
|
27
|
+
|
|
28
|
+
def _ensure_csv_header(self):
|
|
29
|
+
"""Ensure CSV file has proper header row"""
|
|
30
|
+
if not os.path.exists(self.csv_file):
|
|
31
|
+
os.makedirs(os.path.dirname(self.csv_file), exist_ok=True) if os.path.dirname(self.csv_file) else None
|
|
32
|
+
with open(self.csv_file, 'w', newline='', encoding='utf-8') as f:
|
|
33
|
+
writer = csv.writer(f)
|
|
34
|
+
writer.writerow([
|
|
35
|
+
'timestamp', 'ip_address', 'method', 'path', 'status_code',
|
|
36
|
+
'response_time', 'user_agent', 'referer', 'content_length'
|
|
37
|
+
])
|
|
38
|
+
|
|
39
|
+
def process_request(self, request):
|
|
40
|
+
"""Store request start time"""
|
|
41
|
+
request._aiwaf_start_time = time.time()
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
def process_response(self, request, response):
|
|
45
|
+
"""Log the completed request"""
|
|
46
|
+
if not self.log_enabled:
|
|
47
|
+
return response
|
|
48
|
+
|
|
49
|
+
# Calculate response time
|
|
50
|
+
start_time = getattr(request, '_aiwaf_start_time', time.time())
|
|
51
|
+
response_time = time.time() - start_time
|
|
52
|
+
|
|
53
|
+
# Extract request data
|
|
54
|
+
log_data = {
|
|
55
|
+
'timestamp': datetime.now().strftime('%d/%b/%Y:%H:%M:%S +0000'),
|
|
56
|
+
'ip_address': get_ip(request),
|
|
57
|
+
'method': request.method,
|
|
58
|
+
'path': request.path,
|
|
59
|
+
'status_code': response.status_code,
|
|
60
|
+
'response_time': f"{response_time:.3f}",
|
|
61
|
+
'user_agent': request.META.get('HTTP_USER_AGENT', '-'),
|
|
62
|
+
'referer': request.META.get('HTTP_REFERER', '-'),
|
|
63
|
+
'content_length': response.get('Content-Length', '-')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if self.csv_format:
|
|
67
|
+
self._log_to_csv(log_data)
|
|
68
|
+
else:
|
|
69
|
+
self._log_to_text(log_data)
|
|
70
|
+
|
|
71
|
+
return response
|
|
72
|
+
|
|
73
|
+
def _log_to_csv(self, data):
|
|
74
|
+
"""Write log entry to CSV file"""
|
|
75
|
+
try:
|
|
76
|
+
with open(self.csv_file, 'a', newline='', encoding='utf-8') as f:
|
|
77
|
+
writer = csv.writer(f)
|
|
78
|
+
writer.writerow([
|
|
79
|
+
data['timestamp'], data['ip_address'], data['method'],
|
|
80
|
+
data['path'], data['status_code'], data['response_time'],
|
|
81
|
+
data['user_agent'], data['referer'], data['content_length']
|
|
82
|
+
])
|
|
83
|
+
except Exception as e:
|
|
84
|
+
# Fail silently to avoid breaking the application
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
def _log_to_text(self, data):
|
|
88
|
+
"""Write log entry in common log format"""
|
|
89
|
+
try:
|
|
90
|
+
# Common Log Format with response time
|
|
91
|
+
log_line = f'{data["ip_address"]} - - [{data["timestamp"]}] "{data["method"]} {data["path"]} HTTP/1.1" {data["status_code"]} {data["content_length"]} "{data["referer"]}" "{data["user_agent"]}" response-time={data["response_time"]}\n'
|
|
92
|
+
|
|
93
|
+
with open(self.log_file, 'a', encoding='utf-8') as f:
|
|
94
|
+
f.write(log_line)
|
|
95
|
+
except Exception as e:
|
|
96
|
+
# Fail silently to avoid breaking the application
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class AIWAFCSVLogParser:
|
|
101
|
+
"""
|
|
102
|
+
Parser for AI-WAF CSV logs that converts them to the format expected by trainer.py
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def parse_csv_log(csv_file_path):
|
|
107
|
+
"""
|
|
108
|
+
Parse CSV log file and return records in the format expected by trainer.py
|
|
109
|
+
Returns list of dictionaries with keys: ip, timestamp, path, status, referer, user_agent, response_time
|
|
110
|
+
"""
|
|
111
|
+
records = []
|
|
112
|
+
|
|
113
|
+
if not os.path.exists(csv_file_path):
|
|
114
|
+
return records
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
with open(csv_file_path, 'r', newline='', encoding='utf-8') as f:
|
|
118
|
+
reader = csv.DictReader(f)
|
|
119
|
+
for row in reader:
|
|
120
|
+
try:
|
|
121
|
+
# Convert timestamp to datetime object
|
|
122
|
+
timestamp = datetime.strptime(row['timestamp'], '%d/%b/%Y:%H:%M:%S +0000')
|
|
123
|
+
|
|
124
|
+
record = {
|
|
125
|
+
'ip': row['ip_address'],
|
|
126
|
+
'timestamp': timestamp,
|
|
127
|
+
'path': row['path'],
|
|
128
|
+
'status': row['status_code'],
|
|
129
|
+
'referer': row['referer'],
|
|
130
|
+
'user_agent': row['user_agent'],
|
|
131
|
+
'response_time': float(row['response_time'])
|
|
132
|
+
}
|
|
133
|
+
records.append(record)
|
|
134
|
+
except (ValueError, KeyError) as e:
|
|
135
|
+
# Skip malformed rows
|
|
136
|
+
continue
|
|
137
|
+
except Exception as e:
|
|
138
|
+
# Return empty list if file can't be read
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
return records
|
|
142
|
+
|
|
143
|
+
@staticmethod
|
|
144
|
+
def get_log_lines_for_trainer(csv_file_path):
|
|
145
|
+
"""
|
|
146
|
+
Convert CSV log to format compatible with trainer.py's _read_all_logs()
|
|
147
|
+
Returns list of log line strings
|
|
148
|
+
"""
|
|
149
|
+
records = AIWAFCSVLogParser.parse_csv_log(csv_file_path)
|
|
150
|
+
log_lines = []
|
|
151
|
+
|
|
152
|
+
for record in records:
|
|
153
|
+
# Convert back to common log format that trainer.py expects
|
|
154
|
+
timestamp_str = record['timestamp'].strftime('%d/%b/%Y:%H:%M:%S +0000')
|
|
155
|
+
content_length = '-' # We don't track this in our format
|
|
156
|
+
|
|
157
|
+
log_line = f'{record["ip"]} - - [{timestamp_str}] "GET {record["path"]} HTTP/1.1" {record["status"]} {content_length} "{record["referer"]}" "{record["user_agent"]}" response-time={record["response_time"]:.3f}'
|
|
158
|
+
log_lines.append(log_line)
|
|
159
|
+
|
|
160
|
+
return log_lines
|
aiwaf/trainer.py
CHANGED
|
@@ -65,16 +65,31 @@ def remove_exempt_keywords() -> None:
|
|
|
65
65
|
|
|
66
66
|
def _read_all_logs() -> list[str]:
|
|
67
67
|
lines = []
|
|
68
|
+
|
|
69
|
+
# First try to read from main access log
|
|
68
70
|
if LOG_PATH and os.path.exists(LOG_PATH):
|
|
69
71
|
with open(LOG_PATH, "r", errors="ignore") as f:
|
|
70
72
|
lines.extend(f.readlines())
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
for p in sorted(glob.glob(f"{LOG_PATH}.*")):
|
|
74
|
+
opener = gzip.open if p.endswith(".gz") else open
|
|
75
|
+
try:
|
|
76
|
+
with opener(p, "rt", errors="ignore") as f:
|
|
77
|
+
lines.extend(f.readlines())
|
|
78
|
+
except OSError:
|
|
79
|
+
continue
|
|
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
|
+
|
|
78
93
|
return lines
|
|
79
94
|
|
|
80
95
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aiwaf
|
|
3
|
-
Version: 0.1.8.
|
|
3
|
+
Version: 0.1.8.8
|
|
4
4
|
Summary: AI-powered Web Application Firewall
|
|
5
5
|
Home-page: https://github.com/aayushgauba/aiwaf
|
|
6
6
|
Author: Aayush Gauba
|
|
@@ -83,7 +83,28 @@ aiwaf/
|
|
|
83
83
|
- Submit forms faster than `AIWAF_MIN_FORM_TIME` seconds (default: 1 second)
|
|
84
84
|
|
|
85
85
|
- **UUID Tampering Protection**
|
|
86
|
-
Blocks guessed or invalid UUIDs that don
|
|
86
|
+
Blocks guessed or invalid UUIDs that don't resolve to real models.
|
|
87
|
+
|
|
88
|
+
- **Built-in Request Logger**
|
|
89
|
+
Optional middleware logger that captures requests to CSV:
|
|
90
|
+
- **Automatic fallback** when main access logs unavailable
|
|
91
|
+
- **CSV format** for easy analysis and training
|
|
92
|
+
- **Captures response times** for better anomaly detection
|
|
93
|
+
- **Zero configuration** - works out of the box
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
**Exempt Path & IP Awareness**
|
|
97
|
+
|
|
98
|
+
**Exempt Paths:**
|
|
99
|
+
AI‑WAF automatically exempts common login paths (`/admin/`, `/login/`, `/accounts/login/`, etc.) from all blocking mechanisms. You can add additional exempt paths in your Django `settings.py`:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
AIWAF_EXEMPT_PATHS = [
|
|
103
|
+
"/api/webhooks/",
|
|
104
|
+
"/health/",
|
|
105
|
+
"/special-endpoint/",
|
|
106
|
+
]
|
|
107
|
+
```
|
|
87
108
|
|
|
88
109
|
|
|
89
110
|
**Exempt Path & IP Awareness**
|
|
@@ -213,6 +234,42 @@ AIWAF_CSV_DATA_DIR = "aiwaf_data" # Directory for CSV files
|
|
|
213
234
|
|
|
214
235
|
---
|
|
215
236
|
|
|
237
|
+
### Built-in Request Logger (Optional)
|
|
238
|
+
|
|
239
|
+
Enable AI-WAF's built-in request logger as a fallback when main access logs aren't available:
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
# Enable middleware logging
|
|
243
|
+
AIWAF_MIDDLEWARE_LOGGING = True # Enable/disable logging
|
|
244
|
+
AIWAF_MIDDLEWARE_LOG = "aiwaf_requests.log" # Log file path
|
|
245
|
+
AIWAF_MIDDLEWARE_CSV = True # Use CSV format (recommended)
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Then add middleware to MIDDLEWARE list:**
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
MIDDLEWARE = [
|
|
252
|
+
# ... your existing middleware ...
|
|
253
|
+
'aiwaf.middleware_logger.AIWAFLoggerMiddleware', # Add near the end
|
|
254
|
+
]
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**Manage middleware logging:**
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
python manage.py aiwaf_logging --status # Check logging status
|
|
261
|
+
python manage.py aiwaf_logging --enable # Show setup instructions
|
|
262
|
+
python manage.py aiwaf_logging --clear # Clear log files
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**Benefits:**
|
|
266
|
+
- **Automatic fallback** when `AIWAF_ACCESS_LOG` unavailable
|
|
267
|
+
- **CSV format** with precise timestamps and response times
|
|
268
|
+
- **Zero configuration** - trainer automatically detects and uses CSV logs
|
|
269
|
+
- **Lightweight** - fails silently to avoid breaking your application
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
216
273
|
### Optional (defaults shown)
|
|
217
274
|
|
|
218
275
|
```python
|
|
@@ -244,14 +301,40 @@ Add in **this** order to your `MIDDLEWARE` list:
|
|
|
244
301
|
```python
|
|
245
302
|
MIDDLEWARE = [
|
|
246
303
|
"aiwaf.middleware.IPAndKeywordBlockMiddleware",
|
|
247
|
-
"aiwaf.middleware.RateLimitMiddleware",
|
|
304
|
+
"aiwaf.middleware.RateLimitMiddleware",
|
|
248
305
|
"aiwaf.middleware.AIAnomalyMiddleware",
|
|
249
306
|
"aiwaf.middleware.HoneypotTimingMiddleware",
|
|
250
307
|
"aiwaf.middleware.UUIDTamperMiddleware",
|
|
251
308
|
# ... other middleware ...
|
|
309
|
+
"aiwaf.middleware_logger.AIWAFLoggerMiddleware", # Optional: Add if using built-in logger
|
|
252
310
|
]
|
|
253
311
|
```
|
|
254
312
|
|
|
313
|
+
> **⚠️ Order matters!** AI-WAF protection middleware should come early. The logger middleware should come near the end to capture final response data.
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Running Detection & Training
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
python manage.py detect_and_train
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### What happens:
|
|
324
|
+
1. Read access logs (incl. rotated or gzipped) **OR** AI-WAF middleware CSV logs
|
|
325
|
+
2. Auto‑block IPs with ≥ 6 total 404s
|
|
326
|
+
3. Extract features & train IsolationForest
|
|
327
|
+
4. Save `model.pkl`
|
|
328
|
+
5. Extract top 10 dynamic keywords from 4xx/5xx
|
|
329
|
+
6. Remove any keywords associated with newly exempt paths
|
|
330
|
+
|
|
331
|
+
**Note:** If main access log (`AIWAF_ACCESS_LOG`) is unavailable, trainer automatically falls back to AI-WAF middleware CSV logs.
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## 🧠 How It Works
|
|
336
|
+
```
|
|
337
|
+
|
|
255
338
|
---
|
|
256
339
|
|
|
257
340
|
## Running Detection & Training
|
|
@@ -3,20 +3,22 @@ aiwaf/apps.py,sha256=nCez-Ptlv2kaEk5HenA8b1pATz1VfhrHP1344gwcY1A,142
|
|
|
3
3
|
aiwaf/blacklist_manager.py,sha256=92ltIrFfv8WOC4CXwvNVZYfivkRZHGNg3E2QAbHQipQ,550
|
|
4
4
|
aiwaf/decorators.py,sha256=IUKOdM_gdroffImRZep1g1wT6gNqD10zGwcp28hsJCs,825
|
|
5
5
|
aiwaf/middleware.py,sha256=1JPrc0npI_a5bnB-thN0ME1ehfTbWBl1j9wTndZwRdQ,9505
|
|
6
|
+
aiwaf/middleware_logger.py,sha256=uTYTvIc4Mv1pjY50aXaqQ5cWAO9qqquijAyVMs1KWlM,6517
|
|
6
7
|
aiwaf/models.py,sha256=XaG1pd_oZu3y-fw66u4wblGlWcUY9gvsTNKGD0kQk7Y,1672
|
|
7
8
|
aiwaf/storage.py,sha256=Z0KWArfLmOHnvUcL5aVx8W_aHMr-qoEW8FVGrM23BvA,11639
|
|
8
|
-
aiwaf/trainer.py,sha256=
|
|
9
|
+
aiwaf/trainer.py,sha256=bgVoBewnNVMJdgxcNchfhsPOnFXxStoBOqNhFYnpsqs,9244
|
|
9
10
|
aiwaf/utils.py,sha256=BJk5vJCYdGPl_4QQiknjhCbkzv5HZCXgFcBJDMJpHok,3390
|
|
10
11
|
aiwaf/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
12
|
aiwaf/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
13
|
aiwaf/management/commands/add_ipexemption.py,sha256=srgdVPDJtF7G9GGIqaZ7L3qTuNheoS_uwlhlRO4W2bc,945
|
|
14
|
+
aiwaf/management/commands/aiwaf_logging.py,sha256=FCIqULn2tii2vD9VxL7vk3PV4k4vr7kaA00KyaCExYY,7692
|
|
13
15
|
aiwaf/management/commands/aiwaf_reset.py,sha256=0FIBqpZS8xgFFvAKJ-0zAC_-QNQwRkOHpXb8N-OdFr8,3740
|
|
14
16
|
aiwaf/management/commands/detect_and_train.py,sha256=-o-LZ7QZ5GeJPCekryox1DGXKMmFEkwwrcDsiM166K0,269
|
|
15
17
|
aiwaf/resources/model.pkl,sha256=5t6h9BX8yoh2xct85MXOO60jdlWyg1APskUOW0jZE1Y,1288265
|
|
16
18
|
aiwaf/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
19
|
aiwaf/templatetags/aiwaf_tags.py,sha256=XXfb7Tl4DjU3Sc40GbqdaqOEtKTUKELBEk58u83wBNw,357
|
|
18
|
-
aiwaf-0.1.8.
|
|
19
|
-
aiwaf-0.1.8.
|
|
20
|
-
aiwaf-0.1.8.
|
|
21
|
-
aiwaf-0.1.8.
|
|
22
|
-
aiwaf-0.1.8.
|
|
20
|
+
aiwaf-0.1.8.8.dist-info/licenses/LICENSE,sha256=Ir8PX4dxgAcdB0wqNPIkw84fzIIRKE75NoUil9RX0QU,1069
|
|
21
|
+
aiwaf-0.1.8.8.dist-info/METADATA,sha256=851Url25O97G0KGi3gHFF-zYSaI91BHiK7CbKJrLbk0,11261
|
|
22
|
+
aiwaf-0.1.8.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
23
|
+
aiwaf-0.1.8.8.dist-info/top_level.txt,sha256=kU6EyjobT6UPCxuWpI_BvcHDG0I2tMgKaPlWzVxe2xI,6
|
|
24
|
+
aiwaf-0.1.8.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|