- Replace Caddy reverse proxy with Nginx (nginx:alpine) - Add nginx.conf with HTTP/HTTPS, gzip, and proxy settings - Add nginx-custom-domains.conf template for custom domains - Update docker-compose.yml to use Nginx service - Add ProxyFix middleware to Flask app for proper header handling - Create nginx_config_reader.py utility to read Nginx configuration - Update admin blueprint to display Nginx status in https_config page - Add Nginx configuration display to https_config.html template - Generate self-signed SSL certificates for localhost - Add utility scripts: generate_nginx_certs.sh - Add documentation: NGINX_SETUP_QUICK.md, PROXY_FIX_SETUP.md - All containers now running, HTTPS working, HTTP redirects to HTTPS - Session cookies marked as Secure - Security headers properly configured
121 lines
4.3 KiB
Python
121 lines
4.3 KiB
Python
"""Nginx configuration reader utility."""
|
|
import os
|
|
import re
|
|
from typing import Dict, List, Optional, Any
|
|
|
|
|
|
class NginxConfigReader:
|
|
"""Read and parse Nginx configuration files."""
|
|
|
|
def __init__(self, config_path: str = '/etc/nginx/nginx.conf'):
|
|
"""Initialize Nginx config reader."""
|
|
self.config_path = config_path
|
|
self.config_content = None
|
|
self.is_available = os.path.exists(config_path)
|
|
|
|
if self.is_available:
|
|
try:
|
|
with open(config_path, 'r') as f:
|
|
self.config_content = f.read()
|
|
except Exception as e:
|
|
self.is_available = False
|
|
self.error = str(e)
|
|
|
|
def get_status(self) -> Dict[str, Any]:
|
|
"""Get Nginx configuration status."""
|
|
if not self.is_available:
|
|
return {
|
|
'available': False,
|
|
'error': 'Nginx configuration not found',
|
|
'path': self.config_path
|
|
}
|
|
|
|
return {
|
|
'available': True,
|
|
'path': self.config_path,
|
|
'file_exists': os.path.exists(self.config_path),
|
|
'ssl_enabled': self._check_ssl_enabled(),
|
|
'http_ports': self._extract_http_ports(),
|
|
'https_ports': self._extract_https_ports(),
|
|
'upstream_servers': self._extract_upstream_servers(),
|
|
'server_names': self._extract_server_names(),
|
|
'ssl_protocols': self._extract_ssl_protocols(),
|
|
'client_max_body_size': self._extract_client_max_body_size(),
|
|
'gzip_enabled': self._check_gzip_enabled(),
|
|
}
|
|
|
|
def _check_ssl_enabled(self) -> bool:
|
|
"""Check if SSL is enabled."""
|
|
if not self.config_content:
|
|
return False
|
|
return 'ssl_certificate' in self.config_content
|
|
|
|
def _extract_http_ports(self) -> List[int]:
|
|
"""Extract HTTP listening ports."""
|
|
if not self.config_content:
|
|
return []
|
|
pattern = r'listen\s+(\d+)'
|
|
matches = re.findall(pattern, self.config_content)
|
|
return sorted(list(set(int(p) for p in matches if int(p) < 1000)))
|
|
|
|
def _extract_https_ports(self) -> List[int]:
|
|
"""Extract HTTPS listening ports."""
|
|
if not self.config_content:
|
|
return []
|
|
pattern = r'listen\s+(\d+).*ssl'
|
|
matches = re.findall(pattern, self.config_content)
|
|
return sorted(list(set(int(p) for p in matches)))
|
|
|
|
def _extract_upstream_servers(self) -> List[str]:
|
|
"""Extract upstream servers."""
|
|
if not self.config_content:
|
|
return []
|
|
upstream_match = re.search(r'upstream\s+\w+\s*{([^}]+)}', self.config_content)
|
|
if upstream_match:
|
|
upstream_content = upstream_match.group(1)
|
|
servers = re.findall(r'server\s+([^\s;]+)', upstream_content)
|
|
return servers
|
|
return []
|
|
|
|
def _extract_server_names(self) -> List[str]:
|
|
"""Extract server names."""
|
|
if not self.config_content:
|
|
return []
|
|
pattern = r'server_name\s+([^;]+);'
|
|
matches = re.findall(pattern, self.config_content)
|
|
result = []
|
|
for match in matches:
|
|
names = match.strip().split()
|
|
result.extend(names)
|
|
return result
|
|
|
|
def _extract_ssl_protocols(self) -> List[str]:
|
|
"""Extract SSL protocols."""
|
|
if not self.config_content:
|
|
return []
|
|
pattern = r'ssl_protocols\s+([^;]+);'
|
|
match = re.search(pattern, self.config_content)
|
|
if match:
|
|
return match.group(1).strip().split()
|
|
return []
|
|
|
|
def _extract_client_max_body_size(self) -> Optional[str]:
|
|
"""Extract client max body size."""
|
|
if not self.config_content:
|
|
return None
|
|
pattern = r'client_max_body_size\s+([^;]+);'
|
|
match = re.search(pattern, self.config_content)
|
|
return match.group(1).strip() if match else None
|
|
|
|
def _check_gzip_enabled(self) -> bool:
|
|
"""Check if gzip is enabled."""
|
|
if not self.config_content:
|
|
return False
|
|
return bool(re.search(r'gzip\s+on\s*;', self.config_content))
|
|
|
|
|
|
def get_nginx_status() -> Dict[str, Any]:
|
|
"""Get Nginx configuration status."""
|
|
reader = NginxConfigReader()
|
|
return reader.get_status()
|