rewrite
This commit is contained in:
27
database/__init__.py
Normal file
27
database/__init__.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""Database initialization"""
|
||||
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_migrate import Migrate
|
||||
|
||||
db = SQLAlchemy()
|
||||
migrate = Migrate()
|
||||
|
||||
def init_db(app):
|
||||
"""Initialize database with app"""
|
||||
db.init_app(app)
|
||||
migrate.init_app(app, db)
|
||||
|
||||
# Create tables
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
# Create default admin user if not exists
|
||||
from database.models import User
|
||||
admin = User.query.filter_by(username='admin').first()
|
||||
if not admin:
|
||||
from config.settings import DEFAULT_ADMIN_USERNAME, DEFAULT_ADMIN_PASSWORD
|
||||
admin = User(username=DEFAULT_ADMIN_USERNAME)
|
||||
admin.set_password(DEFAULT_ADMIN_PASSWORD)
|
||||
db.session.add(admin)
|
||||
db.session.commit()
|
||||
print(f"[DB] Created default admin user: {DEFAULT_ADMIN_USERNAME}", flush=True)
|
||||
83
database/migration.py
Normal file
83
database/migration.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""Migration: Import existing HAProxy config to database"""
|
||||
|
||||
import re
|
||||
from database import db
|
||||
from database.models import VirtualHost, BackendServer
|
||||
|
||||
def parse_existing_haproxy_config(config_path):
|
||||
"""Parse existing haproxy.cfg and import to DB"""
|
||||
try:
|
||||
with open(config_path, 'r') as f:
|
||||
config_content = f.read()
|
||||
|
||||
print(f"[MIGRATION] Parsing {config_path}...")
|
||||
|
||||
# Pattern: frontend FNAME ...
|
||||
frontend_pattern = r'frontend\s+(\S+)\s*\n(.*?)(?=\n\nbackend|\Z)'
|
||||
frontends = re.findall(frontend_pattern, config_content, re.DOTALL)
|
||||
|
||||
vhost_count = 0
|
||||
for fe_name, fe_content in frontends:
|
||||
# Skip stats frontend
|
||||
if 'stats' in fe_name.lower():
|
||||
continue
|
||||
|
||||
# Extract bind
|
||||
bind_match = re.search(r'bind\s+([^\s:]+):(\d+)', fe_content)
|
||||
if not bind_match:
|
||||
continue
|
||||
|
||||
ip, port = bind_match.groups()
|
||||
|
||||
# Extract backend name
|
||||
backend_match = re.search(r'default_backend\s+(\S+)', fe_content)
|
||||
backend_name = backend_match.group(1) if backend_match else f'be_{fe_name}'
|
||||
|
||||
# Check if already exists
|
||||
vhost = VirtualHost.query.filter_by(name=fe_name).first()
|
||||
if vhost:
|
||||
print(f"[MIGRATION] Vhost '{fe_name}' already exists, skipping...")
|
||||
continue
|
||||
|
||||
# Create vhost
|
||||
vhost = VirtualHost(
|
||||
name=fe_name,
|
||||
hostname=f"{fe_name}.local",
|
||||
frontend_ip=ip,
|
||||
frontend_port=int(port),
|
||||
protocol='http',
|
||||
use_ssl='ssl' in fe_content,
|
||||
enabled=True
|
||||
)
|
||||
|
||||
db.session.add(vhost)
|
||||
db.session.flush() # Get vhost.id
|
||||
|
||||
# Parse backend servers
|
||||
be_pattern = rf'backend\s+{re.escape(backend_name)}\s*\n(.*?)(?=\nbackend|\Z)'
|
||||
be_match = re.search(be_pattern, config_content, re.DOTALL)
|
||||
|
||||
if be_match:
|
||||
servers_pattern = r'server\s+(\S+)\s+([^\s:]+):(\d+)'
|
||||
servers = re.findall(servers_pattern, be_match.group(1))
|
||||
|
||||
for srv_name, srv_ip, srv_port in servers:
|
||||
server = BackendServer(
|
||||
vhost_id=vhost.id,
|
||||
name=srv_name,
|
||||
ip_address=srv_ip,
|
||||
port=int(srv_port),
|
||||
enabled=True
|
||||
)
|
||||
db.session.add(server)
|
||||
|
||||
vhost_count += 1
|
||||
|
||||
db.session.commit()
|
||||
print(f"[MIGRATION] Successfully imported {vhost_count} vhosts!", flush=True)
|
||||
return vhost_count
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
print(f"[MIGRATION] Error: {e}", flush=True)
|
||||
return 0
|
||||
170
database/modelspy
Normal file
170
database/modelspy
Normal file
@@ -0,0 +1,170 @@
|
||||
"""Database Models"""
|
||||
|
||||
from datetime import datetime
|
||||
from database import db
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
import json
|
||||
|
||||
|
||||
class User(db.Model):
|
||||
"""User model for authentication"""
|
||||
__tablename__ = 'users'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(80), unique=True, nullable=False, index=True)
|
||||
password_hash = db.Column(db.String(255), nullable=False)
|
||||
is_admin = db.Column(db.Boolean, default=False)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
last_login = db.Column(db.DateTime)
|
||||
|
||||
def set_password(self, password):
|
||||
"""Hash and set password"""
|
||||
self.password_hash = generate_password_hash(password, method='pbkdf2:sha256')
|
||||
|
||||
def check_password(self, password):
|
||||
"""Verify password"""
|
||||
return check_password_hash(self.password_hash, password)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<User {self.username}>'
|
||||
|
||||
|
||||
class Certificate(db.Model):
|
||||
"""SSL Certificate storage"""
|
||||
__tablename__ = 'certificates'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(200), nullable=False, unique=True, index=True)
|
||||
cert_content = db.Column(db.Text, nullable=False) # Full PEM (cert + key combined)
|
||||
cert_only = db.Column(db.Text) # Separate cert (for info)
|
||||
key_only = db.Column(db.Text) # Separate key (for backup)
|
||||
|
||||
# Metadata
|
||||
common_name = db.Column(db.String(255))
|
||||
subject_alt_names = db.Column(db.Text) # JSON array
|
||||
issued_at = db.Column(db.DateTime)
|
||||
expires_at = db.Column(db.DateTime)
|
||||
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow, index=True)
|
||||
updated_at = db.Column(db.DateTime, onupdate=datetime.utcnow)
|
||||
|
||||
# Relationships
|
||||
vhosts = db.relationship('VirtualHost', backref='certificate', lazy=True)
|
||||
|
||||
def get_san_list(self):
|
||||
"""Get Subject Alternative Names as list"""
|
||||
if self.subject_alt_names:
|
||||
try:
|
||||
return json.loads(self.subject_alt_names)
|
||||
except:
|
||||
return []
|
||||
return []
|
||||
|
||||
def set_san_list(self, san_list):
|
||||
"""Set Subject Alternative Names from list"""
|
||||
self.subject_alt_names = json.dumps(san_list)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Certificate {self.name}>'
|
||||
|
||||
|
||||
class VirtualHost(db.Model):
|
||||
"""Virtual Host / Proxy configuration"""
|
||||
__tablename__ = 'virtual_hosts'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(200), nullable=False, unique=True, index=True)
|
||||
hostname = db.Column(db.String(255), nullable=False)
|
||||
description = db.Column(db.Text)
|
||||
|
||||
# ===== FRONTEND SETTINGS =====
|
||||
frontend_ip = db.Column(db.String(50), default='0.0.0.0')
|
||||
frontend_port = db.Column(db.Integer, default=443)
|
||||
protocol = db.Column(db.String(10), default='http') # http or tcp
|
||||
|
||||
# ===== SSL SETTINGS =====
|
||||
use_ssl = db.Column(db.Boolean, default=False)
|
||||
certificate_id = db.Column(db.Integer, db.ForeignKey('certificates.id'))
|
||||
ssl_redirect = db.Column(db.Boolean, default=False)
|
||||
ssl_redirect_port = db.Column(db.Integer, default=80)
|
||||
|
||||
# ===== LOAD BALANCING =====
|
||||
lb_method = db.Column(db.String(50), default='roundrobin') # roundrobin, leastconn, source, uri
|
||||
|
||||
# ===== SECURITY OPTIONS =====
|
||||
dos_protection = db.Column(db.Boolean, default=False)
|
||||
dos_ban_duration = db.Column(db.String(20), default='30m')
|
||||
dos_limit_requests = db.Column(db.Integer, default=100)
|
||||
|
||||
sql_injection_check = db.Column(db.Boolean, default=False)
|
||||
xss_check = db.Column(db.Boolean, default=False)
|
||||
webshell_check = db.Column(db.Boolean, default=False)
|
||||
|
||||
# ===== HEADERS =====
|
||||
add_custom_header = db.Column(db.Boolean, default=False)
|
||||
custom_header_name = db.Column(db.String(200))
|
||||
custom_header_value = db.Column(db.String(500))
|
||||
del_server_header = db.Column(db.Boolean, default=False)
|
||||
forward_for = db.Column(db.Boolean, default=True)
|
||||
|
||||
# ===== STATE =====
|
||||
enabled = db.Column(db.Boolean, default=True, index=True)
|
||||
|
||||
# ===== TIMESTAMPS =====
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow, index=True)
|
||||
updated_at = db.Column(db.DateTime, onupdate=datetime.utcnow)
|
||||
|
||||
# Relationships
|
||||
backend_servers = db.relationship('BackendServer', backref='vhost',
|
||||
lazy=True, cascade='all, delete-orphan')
|
||||
|
||||
def __repr__(self):
|
||||
return f'<VirtualHost {self.name}>'
|
||||
|
||||
|
||||
class BackendServer(db.Model):
|
||||
"""Backend server for virtual host"""
|
||||
__tablename__ = 'backend_servers'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
vhost_id = db.Column(db.Integer, db.ForeignKey('virtual_hosts.id'), nullable=False)
|
||||
|
||||
# Server info
|
||||
name = db.Column(db.String(100), nullable=False)
|
||||
ip_address = db.Column(db.String(50), nullable=False)
|
||||
port = db.Column(db.Integer, nullable=False)
|
||||
maxconn = db.Column(db.Integer)
|
||||
weight = db.Column(db.Integer, default=1)
|
||||
|
||||
# Health check
|
||||
health_check = db.Column(db.Boolean, default=False)
|
||||
health_check_path = db.Column(db.String(200), default='/')
|
||||
|
||||
# State
|
||||
enabled = db.Column(db.Boolean, default=True)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
updated_at = db.Column(db.DateTime, onupdate=datetime.utcnow)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<BackendServer {self.name}:{self.port}>'
|
||||
|
||||
|
||||
class ConfigHistory(db.Model):
|
||||
"""History of HAProxy configuration changes"""
|
||||
__tablename__ = 'config_history'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
config_content = db.Column(db.Text, nullable=False)
|
||||
change_type = db.Column(db.String(50)) # vhost_create, vhost_edit, vhost_delete, manual_edit
|
||||
vhost_id = db.Column(db.Integer, db.ForeignKey('virtual_hosts.id'))
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
|
||||
description = db.Column(db.Text)
|
||||
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow, index=True)
|
||||
|
||||
# Relationships
|
||||
vhost = db.relationship('VirtualHost')
|
||||
user = db.relationship('User')
|
||||
|
||||
def __repr__(self):
|
||||
return f'<ConfigHistory {self.change_type} at {self.created_at}>'
|
||||
Reference in New Issue
Block a user