Files
secrettext/security.py
T

191 lines
5.6 KiB
Python

import time
import hashlib
import json
from collections import defaultdict
from flask import request, render_template
import redis
import os
import database
redis_client = redis.Redis(
host=os.environ.get('REDIS_HOST', 'localhost'),
port=int(os.environ.get('REDIS_PORT', 6379)),
db=int(os.environ.get('REDIS_DB', 0)),
password=os.environ.get('REDIS_PASSWORD', None),
decode_responses=True
)
BLACKLIST_PREFIX = 'blacklist:'
ATTEMPTS_PREFIX = 'attempts:'
BLOCKED_PREFIX = 'blocked:'
SECURITY_CONFIG = {
'max_attempts': 5,
'block_duration': 300,
'block_duration_increment': 60,
'max_block_duration': 3600,
'attempt_window': 60,
'permanent_blacklist': False,
'permanent_after_blocks': 3,
}
admin_attempts = defaultdict(list)
def get_client_identifier():
ip = request.remote_addr
user_agent = request.headers.get('User-Agent', 'unknown')[:100]
identifier = hashlib.sha256(f"{ip}:{user_agent}".encode()).hexdigest()
return identifier, ip
def get_ip_block_info(ip: str) -> dict:
if redis_client.exists(f"{BLACKLIST_PREFIX}{ip}"):
return {'blocked': True, 'permanent': True, 'reason': 'Постоянная блокировка', 'until': None}
block_key = f"{BLOCKED_PREFIX}{ip}"
ttl = redis_client.ttl(block_key)
if ttl > 0:
return {
'blocked': True,
'permanent': False,
'reason': 'Слишком много попыток',
'until': time.time() + ttl,
'remaining_seconds': ttl,
'remaining_minutes': ttl // 60
}
return {'blocked': False}
def get_attempts_count(ip: str) -> int:
attempts_key = f"{ATTEMPTS_PREFIX}{ip}"
attempts_data = redis_client.get(attempts_key)
if not attempts_data:
return 0
try:
attempts = json.loads(attempts_data)
current_time = time.time()
valid_attempts = [t for t in attempts if current_time - t < SECURITY_CONFIG['attempt_window']]
return len(valid_attempts)
except:
return 0
def block_ip(ip: str):
block_count_key = f"{BLOCKED_PREFIX}{ip}:count"
block_count = int(redis_client.get(block_count_key) or 0)
block_duration = min(
SECURITY_CONFIG['block_duration'] + (block_count * SECURITY_CONFIG['block_duration_increment']),
SECURITY_CONFIG['max_block_duration']
)
redis_client.setex(f"{BLOCKED_PREFIX}{ip}", block_duration, '1')
redis_client.setex(block_count_key, 86400 * 30, str(block_count + 1))
redis_client.delete(f"{ATTEMPTS_PREFIX}{ip}")
database.log_security_event(
ip=ip,
event_type='temporary_block',
details=f'Блокировка на {block_duration} секунд (попытка #{block_count + 1})'
)
def record_failed_attempt(identifier: str):
_, ip = get_client_identifier()
attempts_key = f"{ATTEMPTS_PREFIX}{ip}"
current_time = time.time()
attempts_data = redis_client.get(attempts_key)
attempts = []
if attempts_data:
try:
attempts = json.loads(attempts_data)
attempts = [t for t in attempts if current_time - t < SECURITY_CONFIG['attempt_window']]
except:
attempts = []
attempts.append(current_time)
redis_client.setex(attempts_key, SECURITY_CONFIG['attempt_window'] * 2, json.dumps(attempts))
if len(attempts) >= SECURITY_CONFIG['max_attempts']:
block_ip(ip)
def clear_failed_attempts(identifier: str):
_, ip = get_client_identifier()
redis_client.delete(f"{ATTEMPTS_PREFIX}{ip}")
def get_blocked_ips() -> list:
result = []
cursor = 0
try:
while True:
cursor, keys = redis_client.scan(
cursor,
match=f"{BLOCKED_PREFIX}*",
count=100
)
for key in keys:
if key.endswith(':count'):
continue
ip = key.replace(BLOCKED_PREFIX, '')
ttl = redis_client.ttl(key)
if ttl > 0:
count_key = f"{BLOCKED_PREFIX}{ip}:count"
count = int(redis_client.get(count_key) or 1)
result.append({
'ip': ip,
'permanent': False,
'remaining_seconds': ttl,
'remaining_minutes': ttl // 60,
'block_count': count
})
if cursor == 0:
break
except Exception as e:
print(f"Redis scan error in get_blocked_ips: {e}")
return result
cursor = 0
try:
while True:
cursor, keys = redis_client.scan(
cursor,
match=f"{BLACKLIST_PREFIX}*",
count=100
)
for key in keys:
ip = key.replace(BLACKLIST_PREFIX, '')
result.append({'ip': ip, 'permanent': True, 'block_count': '∞'})
if cursor == 0:
break
except Exception as e:
print(f"Redis scan error in get_blocked_ips (blacklist): {e}")
return result
return result
def unblock_ip(ip: str):
redis_client.delete(f"{BLACKLIST_PREFIX}{ip}")
redis_client.delete(f"{BLOCKED_PREFIX}{ip}")
redis_client.delete(f"{ATTEMPTS_PREFIX}{ip}")
redis_client.delete(f"{BLOCKED_PREFIX}{ip}:count")
return True
def check_admin_rate_limit(ip: str) -> bool:
current_time = time.time()
admin_attempts[ip] = [t for t in admin_attempts[ip] if current_time - t < 300]
if len(admin_attempts[ip]) >= 5:
return True
admin_attempts[ip].append(current_time)
return False