aiwaf 0.1.9.0.5__py3-none-any.whl → 0.1.9.0.7__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 CHANGED
@@ -1,6 +1,6 @@
1
1
  default_app_config = "aiwaf.apps.AiwafConfig"
2
2
 
3
- __version__ = "0.1.9.0.5"
3
+ __version__ = "0.1.9.0.7"
4
4
 
5
5
  # Note: Middleware classes are available from aiwaf.middleware
6
6
  # Import them only when needed to avoid circular imports during Django app loading
@@ -12,7 +12,7 @@ class BlacklistManager:
12
12
  return # Don't block exempted IPs
13
13
 
14
14
  store = get_blacklist_store()
15
- store.add_ip(ip, reason)
15
+ store.block_ip(ip, reason)
16
16
 
17
17
  @staticmethod
18
18
  def is_blocked(ip):
@@ -29,9 +29,9 @@ class BlacklistManager:
29
29
  @staticmethod
30
30
  def all_blocked():
31
31
  store = get_blacklist_store()
32
- return store.get_all()
32
+ return store.get_all_blocked_ips()
33
33
 
34
34
  @staticmethod
35
35
  def unblock(ip):
36
36
  store = get_blacklist_store()
37
- store.remove_ip(ip)
37
+ store.unblock_ip(ip)
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from django.core.management.base import BaseCommand
4
+ from aiwaf.storage import get_exemption_store
5
+
6
+ class Command(BaseCommand):
7
+ help = 'Add IP to exemption list using Django models'
8
+
9
+ def add_arguments(self, parser):
10
+ parser.add_argument('ip', help='IP address to exempt')
11
+ parser.add_argument('--reason', default='Manual exemption', help='Reason for exemption')
12
+
13
+ def handle(self, *args, **options):
14
+ ip = options['ip']
15
+ reason = options['reason']
16
+
17
+ self.stdout.write(f"Adding IP {ip} to exemption list...")
18
+
19
+ exemption_store = get_exemption_store()
20
+ exemption_store.add_exemption(ip, reason)
21
+
22
+ # Verify it was added
23
+ if exemption_store.is_exempted(ip):
24
+ self.stdout.write(self.style.SUCCESS(f"✅ Successfully exempted IP: {ip}"))
25
+ else:
26
+ self.stdout.write(self.style.ERROR(f"❌ Failed to exempt IP: {ip}"))
27
+
28
+ # Show all exempted IPs
29
+ all_exempted = exemption_store.get_all_exempted_ips()
30
+ self.stdout.write(f"\nAll exempted IPs: {all_exempted}")
@@ -17,7 +17,7 @@ class Command(BaseCommand):
17
17
  if store.is_exempted(ip):
18
18
  self.stdout.write(self.style.WARNING(f'IP {ip} is already exempted.'))
19
19
  else:
20
- store.add_ip(ip, reason)
20
+ store.add_exemption(ip, reason)
21
21
  self.stdout.write(self.style.SUCCESS(f'IP {ip} added to exemption list.'))
22
22
  if reason:
23
23
  self.stdout.write(self.style.SUCCESS(f'Reason: {reason}'))
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from django.core.management.base import BaseCommand
4
+ from django.core.cache import cache
5
+
6
+ class Command(BaseCommand):
7
+ help = 'Clear Django cache'
8
+
9
+ def handle(self, *args, **options):
10
+ cache.clear()
11
+ self.stdout.write(self.style.SUCCESS("✅ Django cache cleared successfully!"))
12
+
13
+ # Also show what was cleared
14
+ self.stdout.write("🧹 Cleared all cached data including:")
15
+ self.stdout.write(" - Rate limiting data")
16
+ self.stdout.write(" - Blacklist cache")
17
+ self.stdout.write(" - AI anomaly data")
18
+ self.stdout.write(" - Honeypot timing data")
@@ -101,7 +101,7 @@ class Command(BaseCommand):
101
101
  self.stdout.write(f"Before: IP {test_ip} exempted = {is_exempted_before}")
102
102
 
103
103
  # Add to exemption
104
- exemption_store.add_ip(test_ip, "Test exemption from debug command")
104
+ exemption_store.add_exemption(test_ip, "Test exemption from debug command")
105
105
  self.stdout.write(f"✅ Added {test_ip} to exemption list")
106
106
 
107
107
  # Check if now exempted
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from django.core.management.base import BaseCommand
4
+ from django.core.cache import cache
5
+ from aiwaf.blacklist_manager import BlacklistManager
6
+ from aiwaf.storage import get_exemption_store, get_blacklist_store
7
+ from aiwaf.utils import get_ip
8
+ from django.test import RequestFactory
9
+
10
+ class Command(BaseCommand):
11
+ help = 'Comprehensive diagnosis of blocking issues'
12
+
13
+ def add_arguments(self, parser):
14
+ parser.add_argument('--ip', default='97.187.30.95', help='IP address to test')
15
+ parser.add_argument('--clear-cache', action='store_true', help='Clear Django cache')
16
+
17
+ def handle(self, *args, **options):
18
+ test_ip = options['ip']
19
+
20
+ self.stdout.write(f"\n🔍 Comprehensive Blocking Diagnosis for IP: {test_ip}")
21
+ self.stdout.write("=" * 60)
22
+
23
+ if options['clear_cache']:
24
+ cache.clear()
25
+ self.stdout.write("🧹 Cleared Django cache")
26
+
27
+ # 1. Check exemption status
28
+ exemption_store = get_exemption_store()
29
+ is_exempted = exemption_store.is_exempted(test_ip)
30
+ self.stdout.write(f"1. ✅ IP exempted in storage: {is_exempted}")
31
+
32
+ # 2. Check blacklist status
33
+ blacklist_store = get_blacklist_store()
34
+ is_in_blacklist = blacklist_store.is_blocked(test_ip)
35
+ self.stdout.write(f"2. 🚫 IP in blacklist storage: {is_in_blacklist}")
36
+
37
+ # 3. Check BlacklistManager final decision
38
+ manager_blocked = BlacklistManager.is_blocked(test_ip)
39
+ self.stdout.write(f"3. 🎯 BlacklistManager says blocked: {manager_blocked}")
40
+
41
+ # 4. Check Django cache for blacklist entries
42
+ cache_key = f"blacklist:{test_ip}"
43
+ cached_value = cache.get(cache_key)
44
+ self.stdout.write(f"4. 💾 Cache value for blacklist:{test_ip}: {cached_value}")
45
+
46
+ # 5. Test what IP would be detected from a request
47
+ factory = RequestFactory()
48
+
49
+ # Test different scenarios
50
+ scenarios = [
51
+ ("Direct IP", {'REMOTE_ADDR': test_ip}),
52
+ ("X-Forwarded-For", {'HTTP_X_FORWARDED_FOR': test_ip}),
53
+ ("X-Real-IP", {'HTTP_X_REAL_IP': test_ip}),
54
+ ("CloudFlare", {'HTTP_CF_CONNECTING_IP': test_ip}),
55
+ ]
56
+
57
+ self.stdout.write(f"\n5. 🌐 IP Detection Tests:")
58
+ for name, meta in scenarios:
59
+ request = factory.get('/', **meta)
60
+ detected_ip = get_ip(request)
61
+ self.stdout.write(f" {name}: {detected_ip}")
62
+ if detected_ip == test_ip:
63
+ self.stdout.write(f" ✅ Match!")
64
+
65
+ # 6. Check rate limiting cache entries
66
+ self.stdout.write(f"\n6. 🚦 Rate Limiting Cache Entries:")
67
+ rate_keys = [
68
+ f"ratelimit:{test_ip}",
69
+ f"aiwaf:{test_ip}",
70
+ f"honeypot_get:{test_ip}"
71
+ ]
72
+
73
+ for key in rate_keys:
74
+ value = cache.get(key)
75
+ if value:
76
+ self.stdout.write(f" {key}: {value}")
77
+ else:
78
+ self.stdout.write(f" {key}: None")
79
+
80
+ # 7. Summary
81
+ self.stdout.write(f"\n📋 SUMMARY:")
82
+ if is_exempted and not manager_blocked:
83
+ self.stdout.write(self.style.SUCCESS("✅ IP should NOT be blocked"))
84
+ if options.get('still_blocked'):
85
+ self.stdout.write(self.style.WARNING("⚠️ If still blocked, check:"))
86
+ self.stdout.write(" - Web server logs (nginx, apache)")
87
+ self.stdout.write(" - Other middleware or security software")
88
+ self.stdout.write(" - Browser cache/cookies")
89
+ elif not is_exempted:
90
+ self.stdout.write(self.style.WARNING(f"⚠️ IP {test_ip} is NOT exempted"))
91
+ elif manager_blocked:
92
+ self.stdout.write(self.style.ERROR(f"❌ IP is being blocked despite exemption"))
93
+
94
+ self.stdout.write(f"\n💡 To clear all caches and reset:")
95
+ self.stdout.write(f" python manage.py shell -c \"from django.core.cache import cache; cache.clear()\"")
96
+ self.stdout.write(f"=" * 60)
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from django.core.management.base import BaseCommand
4
+ from django.db import transaction
5
+
6
+ class Command(BaseCommand):
7
+ help = 'Create Django database migrations for AI-WAF models (after removing CSV support)'
8
+
9
+ def handle(self, *args, **options):
10
+ self.stdout.write("🔄 Creating AI-WAF database migrations...")
11
+
12
+ try:
13
+ # Import the management command functions
14
+ from django.core.management import call_command
15
+
16
+ # Create migrations for aiwaf app
17
+ self.stdout.write("Creating migrations for aiwaf models...")
18
+ call_command('makemigrations', 'aiwaf', verbosity=2)
19
+
20
+ # Apply migrations
21
+ self.stdout.write("Applying migrations...")
22
+ call_command('migrate', 'aiwaf', verbosity=2)
23
+
24
+ self.stdout.write(self.style.SUCCESS("✅ Successfully created and applied AI-WAF migrations!"))
25
+ self.stdout.write("")
26
+ self.stdout.write("🎯 Next steps:")
27
+ self.stdout.write("1. Add your IP to exemptions: python manage.py add_exemption YOUR_IP")
28
+ self.stdout.write("2. Test the system: python manage.py diagnose_blocking --ip YOUR_IP")
29
+ self.stdout.write("3. Clear any old cache: python manage.py clear_cache")
30
+
31
+ except Exception as e:
32
+ self.stdout.write(self.style.ERROR(f"❌ Error during migration: {e}"))
33
+ self.stdout.write("You may need to run these commands manually:")
34
+ self.stdout.write(" python manage.py makemigrations aiwaf")
35
+ self.stdout.write(" python manage.py migrate aiwaf")
@@ -63,7 +63,7 @@ class Command(BaseCommand):
63
63
 
64
64
  # Create the exemption
65
65
  try:
66
- exemption_store.add_ip(test_ip, "Test exemption from debug")
66
+ exemption_store.add_exemption(test_ip, "Test exemption from debug")
67
67
  self.stdout.write(self.style.SUCCESS("✅ Created test exemption"))
68
68
  except Exception as e:
69
69
  self.stdout.write(self.style.ERROR(f"❌ Failed to create exemption: {e}"))
@@ -1,44 +1,39 @@
1
1
  # aiwaf/middleware_logger.py
2
2
 
3
- import os
4
- import csv
5
3
  import time
6
4
  from datetime import datetime
7
5
  from django.conf import settings
8
6
  from django.utils.deprecation import MiddlewareMixin
7
+ from django.utils import timezone
9
8
  from .utils import get_ip
10
9
 
10
+ # Defer model imports to avoid AppRegistryNotReady during Django app loading
11
+ RequestLog = None
12
+
13
+ def _import_models():
14
+ """Import Django models only when needed and apps are ready."""
15
+ global RequestLog
16
+
17
+ if RequestLog is not None:
18
+ return # Already imported
19
+
20
+ try:
21
+ from django.apps import apps
22
+ if apps.ready and apps.is_installed('aiwaf'):
23
+ from .models import RequestLog
24
+ except (ImportError, RuntimeError, Exception):
25
+ # Keep models as None if can't import
26
+ pass
27
+
11
28
  class AIWAFLoggerMiddleware(MiddlewareMixin):
12
29
  """
13
- Middleware that logs requests to a CSV file for AI-WAF training.
30
+ Middleware that logs requests to Django models for AI-WAF training.
14
31
  Acts as a fallback when main access logs are unavailable.
15
32
  """
16
33
 
17
34
  def __init__(self, get_response):
18
35
  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
36
  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
- # Create directory if it doesn't exist
32
- csv_dir = os.path.dirname(self.csv_file)
33
- if csv_dir and not os.path.exists(csv_dir):
34
- os.makedirs(csv_dir, exist_ok=True)
35
-
36
- with open(self.csv_file, 'w', newline='', encoding='utf-8') as f:
37
- writer = csv.writer(f)
38
- writer.writerow([
39
- 'timestamp', 'ip_address', 'method', 'path', 'status_code',
40
- 'response_time', 'user_agent', 'referer', 'content_length'
41
- ])
42
37
 
43
38
  def process_request(self, request):
44
39
  """Store request start time"""
@@ -46,7 +41,7 @@ class AIWAFLoggerMiddleware(MiddlewareMixin):
46
41
  return None
47
42
 
48
43
  def process_response(self, request, response):
49
- """Log the completed request"""
44
+ """Log the completed request to Django model"""
50
45
  if not self.log_enabled:
51
46
  return response
52
47
 
@@ -54,116 +49,81 @@ class AIWAFLoggerMiddleware(MiddlewareMixin):
54
49
  start_time = getattr(request, '_aiwaf_start_time', time.time())
55
50
  response_time = time.time() - start_time
56
51
 
57
- # Extract request data
58
- log_data = {
59
- 'timestamp': datetime.now().strftime('%d/%b/%Y:%H:%M:%S +0000'),
60
- 'ip_address': get_ip(request),
61
- 'method': request.method,
62
- 'path': request.path,
63
- 'status_code': response.status_code,
64
- 'response_time': f"{response_time:.3f}",
65
- 'user_agent': request.META.get('HTTP_USER_AGENT', '-'),
66
- 'referer': request.META.get('HTTP_REFERER', '-'),
67
- 'content_length': response.get('Content-Length', '-')
68
- }
69
-
70
- if self.csv_format:
71
- self._log_to_csv(log_data)
72
- else:
73
- self._log_to_text(log_data)
52
+ # Import models and log to database
53
+ _import_models()
54
+ if RequestLog is not None:
55
+ try:
56
+ RequestLog.objects.create(
57
+ ip_address=get_ip(request),
58
+ method=request.method,
59
+ path=request.path[:500], # Truncate long paths
60
+ status_code=response.status_code,
61
+ response_time=response_time,
62
+ user_agent=request.META.get('HTTP_USER_AGENT', '')[:2000], # Truncate long user agents
63
+ referer=request.META.get('HTTP_REFERER', '')[:500], # Truncate long referers
64
+ content_length=response.get('Content-Length', '-'),
65
+ timestamp=timezone.now()
66
+ )
67
+ except Exception as e:
68
+ # Fail silently to avoid breaking the application
69
+ pass
74
70
 
75
71
  return response
76
-
77
- def _log_to_csv(self, data):
78
- """Write log entry to CSV file"""
79
- try:
80
- # Ensure directory exists before writing
81
- csv_dir = os.path.dirname(self.csv_file)
82
- if csv_dir and not os.path.exists(csv_dir):
83
- os.makedirs(csv_dir, exist_ok=True)
84
-
85
- with open(self.csv_file, 'a', newline='', encoding='utf-8') as f:
86
- writer = csv.writer(f)
87
- writer.writerow([
88
- data['timestamp'], data['ip_address'], data['method'],
89
- data['path'], data['status_code'], data['response_time'],
90
- data['user_agent'], data['referer'], data['content_length']
91
- ])
92
- except Exception as e:
93
- # Fail silently to avoid breaking the application
94
- pass
95
-
96
- def _log_to_text(self, data):
97
- """Write log entry in common log format"""
98
- try:
99
- # Common Log Format with response time
100
- 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'
101
-
102
- with open(self.log_file, 'a', encoding='utf-8') as f:
103
- f.write(log_line)
104
- except Exception as e:
105
- # Fail silently to avoid breaking the application
106
- pass
107
72
 
108
73
 
109
- class AIWAFCSVLogParser:
74
+ class AIWAFModelLogParser:
110
75
  """
111
- Parser for AI-WAF CSV logs that converts them to the format expected by trainer.py
76
+ Parser for AI-WAF Django model logs that converts them to the format expected by trainer.py
112
77
  """
113
78
 
114
79
  @staticmethod
115
- def parse_csv_log(csv_file_path):
80
+ def parse_model_logs():
116
81
  """
117
- Parse CSV log file and return records in the format expected by trainer.py
82
+ Parse Django model logs and return records in the format expected by trainer.py
118
83
  Returns list of dictionaries with keys: ip, timestamp, path, status, referer, user_agent, response_time
119
84
  """
120
85
  records = []
121
86
 
122
- if not os.path.exists(csv_file_path):
87
+ _import_models()
88
+ if RequestLog is None:
123
89
  return records
124
90
 
125
91
  try:
126
- with open(csv_file_path, 'r', newline='', encoding='utf-8') as f:
127
- reader = csv.DictReader(f)
128
- for row in reader:
129
- try:
130
- # Convert timestamp to datetime object
131
- timestamp = datetime.strptime(row['timestamp'], '%d/%b/%Y:%H:%M:%S +0000')
132
-
133
- record = {
134
- 'ip': row['ip_address'],
135
- 'timestamp': timestamp,
136
- 'path': row['path'],
137
- 'status': row['status_code'],
138
- 'referer': row['referer'],
139
- 'user_agent': row['user_agent'],
140
- 'response_time': float(row['response_time'])
141
- }
142
- records.append(record)
143
- except (ValueError, KeyError) as e:
144
- # Skip malformed rows
145
- continue
92
+ # Get all request logs
93
+ logs = RequestLog.objects.all().order_by('-timestamp')
94
+
95
+ for log in logs:
96
+ record = {
97
+ 'ip': str(log.ip_address),
98
+ 'timestamp': log.timestamp,
99
+ 'path': log.path,
100
+ 'status': str(log.status_code),
101
+ 'referer': log.referer if log.referer else '-',
102
+ 'user_agent': log.user_agent if log.user_agent else '-',
103
+ 'response_time': log.response_time
104
+ }
105
+ records.append(record)
146
106
  except Exception as e:
147
- # Return empty list if file can't be read
107
+ # Return empty list if models can't be accessed
148
108
  pass
149
109
 
150
110
  return records
151
111
 
152
112
  @staticmethod
153
- def get_log_lines_for_trainer(csv_file_path):
113
+ def get_log_lines_for_trainer():
154
114
  """
155
- Convert CSV log to format compatible with trainer.py's _read_all_logs()
115
+ Convert Django model logs to format compatible with trainer.py's _read_all_logs()
156
116
  Returns list of log line strings
157
117
  """
158
- records = AIWAFCSVLogParser.parse_csv_log(csv_file_path)
118
+ records = AIWAFModelLogParser.parse_model_logs()
159
119
  log_lines = []
160
120
 
161
121
  for record in records:
162
- # Convert back to common log format that trainer.py expects
122
+ # Convert to common log format that trainer.py expects
163
123
  timestamp_str = record['timestamp'].strftime('%d/%b/%Y:%H:%M:%S +0000')
164
- content_length = '-' # We don't track this in our format
124
+ content_length = '-' # We don't track this in detail
165
125
 
166
- 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}'
126
+ log_line = f'{record["ip"]} - - [{timestamp_str}] "{record.get("method", "GET")} {record["path"]} HTTP/1.1" {record["status"]} {content_length} "{record["referer"]}" "{record["user_agent"]}" response-time={record["response_time"]:.3f}'
167
127
  log_lines.append(log_line)
168
128
 
169
129
  return log_lines
aiwaf/models.py CHANGED
@@ -43,4 +43,31 @@ class IPExemption(models.Model):
43
43
  created_at = models.DateTimeField(auto_now_add=True)
44
44
 
45
45
  def __str__(self):
46
- return f"{self.ip_address} (Exempted: {self.reason})"
46
+ return f"{self.ip_address} (Exempted: {self.reason})"
47
+
48
+
49
+ # Model to store request logs for AI-WAF training
50
+ class RequestLog(models.Model):
51
+ ip_address = models.GenericIPAddressField(db_index=True)
52
+ method = models.CharField(max_length=10)
53
+ path = models.CharField(max_length=500)
54
+ status_code = models.IntegerField()
55
+ response_time = models.FloatField()
56
+ user_agent = models.TextField(blank=True, default="")
57
+ referer = models.CharField(max_length=500, blank=True, default="")
58
+ content_length = models.CharField(max_length=20, blank=True, default="-")
59
+ timestamp = models.DateTimeField(auto_now_add=True)
60
+
61
+ class Meta:
62
+ verbose_name = "Request Log"
63
+ verbose_name_plural = "Request Logs"
64
+ indexes = [
65
+ models.Index(fields=["ip_address"]),
66
+ models.Index(fields=["timestamp"]),
67
+ models.Index(fields=["status_code"]),
68
+ models.Index(fields=["method"]),
69
+ ]
70
+ ordering = ['-timestamp']
71
+
72
+ def __str__(self):
73
+ return f"{self.ip_address} {self.method} {self.path} - {self.status_code}"