mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-10-28 23:57:58 +09:00
Each was referenced only by its own definition across the panel, its tests, scripts, builtin extensions and every sibling extension repo (~350 lines). Imports they alone used go with them.
152 lines
3.8 KiB
Python
152 lines
3.8 KiB
Python
"""
|
|
IP address utilities for ServerKit.
|
|
|
|
Provides IP matching with support for single IPs, CIDR notation, and wildcards.
|
|
"""
|
|
|
|
import ipaddress
|
|
import re
|
|
from typing import List
|
|
|
|
|
|
def is_ip_allowed(client_ip: str, allowed_list: List[str]) -> bool:
|
|
"""
|
|
Check if a client IP is allowed based on an allowlist.
|
|
|
|
Supports:
|
|
- Single IP addresses: "192.168.1.100"
|
|
- CIDR notation: "192.168.1.0/24"
|
|
- Wildcards: "192.168.1.*" or "192.168.*.*"
|
|
- IPv6 addresses and CIDR: "2001:db8::/32"
|
|
|
|
Args:
|
|
client_ip: The client IP address to check
|
|
allowed_list: List of allowed IP patterns
|
|
|
|
Returns:
|
|
bool: True if the IP is allowed, False otherwise
|
|
"""
|
|
if not allowed_list:
|
|
# Empty list means all IPs are allowed
|
|
return True
|
|
|
|
try:
|
|
# Parse the client IP
|
|
client_addr = ipaddress.ip_address(client_ip)
|
|
except ValueError:
|
|
# Invalid IP address format
|
|
return False
|
|
|
|
for pattern in allowed_list:
|
|
if not pattern:
|
|
continue
|
|
|
|
try:
|
|
# Check for wildcard pattern (e.g., "192.168.1.*")
|
|
if '*' in pattern:
|
|
if _match_wildcard(client_ip, pattern):
|
|
return True
|
|
continue
|
|
|
|
# Check for CIDR notation (e.g., "192.168.1.0/24")
|
|
if '/' in pattern:
|
|
network = ipaddress.ip_network(pattern, strict=False)
|
|
if client_addr in network:
|
|
return True
|
|
continue
|
|
|
|
# Check for exact match
|
|
allowed_addr = ipaddress.ip_address(pattern)
|
|
if client_addr == allowed_addr:
|
|
return True
|
|
|
|
except ValueError:
|
|
# Invalid pattern, skip it
|
|
continue
|
|
|
|
return False
|
|
|
|
|
|
def _match_wildcard(ip: str, pattern: str) -> bool:
|
|
"""
|
|
Match an IP address against a wildcard pattern.
|
|
|
|
Args:
|
|
ip: The IP address to check
|
|
pattern: Wildcard pattern like "192.168.1.*" or "192.168.*.*"
|
|
|
|
Returns:
|
|
bool: True if the IP matches the pattern
|
|
"""
|
|
# Convert wildcard pattern to regex
|
|
# Escape dots and replace * with digit pattern
|
|
regex_pattern = pattern.replace('.', r'\.')
|
|
regex_pattern = regex_pattern.replace('*', r'\d{1,3}')
|
|
regex_pattern = f'^{regex_pattern}$'
|
|
|
|
return bool(re.match(regex_pattern, ip))
|
|
|
|
|
|
def validate_ip_pattern(pattern: str) -> tuple:
|
|
"""
|
|
Validate an IP pattern.
|
|
|
|
Args:
|
|
pattern: The IP pattern to validate
|
|
|
|
Returns:
|
|
tuple: (is_valid: bool, error_message: str or None)
|
|
"""
|
|
if not pattern:
|
|
return False, "Pattern cannot be empty"
|
|
|
|
# Check for wildcard pattern
|
|
if '*' in pattern:
|
|
# Validate wildcard format
|
|
parts = pattern.split('.')
|
|
if len(parts) != 4:
|
|
return False, "Invalid wildcard pattern format"
|
|
|
|
for part in parts:
|
|
if part == '*':
|
|
continue
|
|
try:
|
|
num = int(part)
|
|
if num < 0 or num > 255:
|
|
return False, f"Invalid octet value: {part}"
|
|
except ValueError:
|
|
return False, f"Invalid octet: {part}"
|
|
|
|
return True, None
|
|
|
|
# Check for CIDR notation
|
|
if '/' in pattern:
|
|
try:
|
|
ipaddress.ip_network(pattern, strict=False)
|
|
return True, None
|
|
except ValueError as e:
|
|
return False, str(e)
|
|
|
|
# Check for single IP
|
|
try:
|
|
ipaddress.ip_address(pattern)
|
|
return True, None
|
|
except ValueError as e:
|
|
return False, str(e)
|
|
|
|
|
|
def normalize_ip(ip: str) -> str:
|
|
"""
|
|
Normalize an IP address to a standard format.
|
|
|
|
Args:
|
|
ip: The IP address to normalize
|
|
|
|
Returns:
|
|
str: Normalized IP address
|
|
"""
|
|
try:
|
|
return str(ipaddress.ip_address(ip))
|
|
except ValueError:
|
|
return ip
|