first commit

This commit is contained in:
Mateusz Gruszczyński
2025-11-01 19:15:49 +01:00
commit 1111d59c2b
23 changed files with 2562 additions and 0 deletions

9
.dockerignore Normal file
View File

@@ -0,0 +1,9 @@
__pycache__
*.pyc
.git
.gitignore
.env
*.log
.pytest_cache
venv/
.vscode

49
Dockerfile Normal file
View File

@@ -0,0 +1,49 @@
FROM python:3.14-rc-trixie
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1
# Install dependencies
RUN apt-get update && apt-get install -y \
python3 \
python3-pip \
haproxy \
supervisor \
openssl \
curl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Copy requirements and install
COPY requirements.txt .
RUN pip3 install --no-cache-dir -r requirements.txt
# Copy application
COPY app.py .
COPY log_parser.py .
COPY routes/ routes/
COPY utils/ utils/
COPY auth/ auth/
COPY templates/ templates/
# Create directories
RUN mkdir -p /app/config/auth \
&& mkdir -p /app/config/ssl \
&& mkdir -p /etc/supervisor/conf.d \
&& mkdir -p /var/log/supervisor \
&& mkdir -p /etc/haproxy
# Copy configs
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD curl -f https://localhost:5000 --insecure 2>/dev/null || exit 1
EXPOSE 5000 80 443 8404
ENTRYPOINT ["/entrypoint.sh"]

4
Makefile Normal file
View File

@@ -0,0 +1,4 @@
.PHONY: install
install:
@pip install -r requirements.txt

201
README.md Normal file
View File

@@ -0,0 +1,201 @@
![Contributors](https://img.shields.io/github/contributors/alonz22/HAProxy-Configurator.svg)
![GitHub Stars](https://img.shields.io/github/stars/alonz22/HAProxy-Configurator.svg?style=social)
![GitHub Forks](https://img.shields.io/github/forks/alonz22/HAProxy-Configurator.svg?style=social)
![Downloads](https://img.shields.io/github/downloads/alonz22/HAProxy-Configurator/total.svg)
![Last Commit](https://img.shields.io/github/last-commit/alonz22/HAProxy-Configurator.svg)
![Issues](https://img.shields.io/github/issues/alonz22/HAProxy-Configurator.svg)
![Pull Requests](https://img.shields.io/github/issues-pr/alonz22/HAProxy-Configurator.svg)
[![Version](https://img.shields.io/badge/version-1.3.4-brightgreen.svg)](CHANGELOG.md)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
# HAProxy Configurator - v1.3.4
### ⚠️ Disclaimer
This HAProxy Configurator is an independent tool designed to simplify HAProxy configuration through a web interface. It is not affiliated with or endorsed by the official HAProxy project.
---
## 🚀 Whats New in 1.3.4
-**Refactored codebase** cleaner, modular structure.
- 🌑 **Dark mode UI** toggle-friendly interface for low-light environments.
- 🔐 **Basic HTTP Authentication** secured access via `.cfg`-based credentials.
- 🔧 **Dynamic Backend Server Addition** - no more static backend servers adding in the "add Frontend & Backend" page.
- 🌐 **IPv6 Frontend Support** configure IPv6 listeners.
---
## 📋 Description
HAProxy Configurator offers a rich GUI to create, validate, and manage HAProxy configurations. Features include frontend/backend setup, health checks, ACLs, SSL, WAF rules, and more — all without manually editing `haproxy.cfg`.
---
## ✅ Requirements
1. **Root or sudo privileges**
2. **HAProxy** installed on your system
3. **Python 3**
4. Port `5000` must be available (for the web interface)
## Install:
1. clone the repository:
```git clone https://github.com/alonz22/HAProxy-Configurator```
2. Install pip (if not installed already):
- for ubuntu:
```apt install python3-pip```
- RedHat:
```yum install python3-pip```
3. enter the root folder of the app:
```cd haproxy-configurator```
4. Install flask and the app dependencies by simply run the Makefile in the cli:
```pip install -r requirements.txt```
5. run the installation script:
```chmod +x install.sh```
if the script failes to run with "bad interpreter" error, run the following:
- ```sed -i 's/\r//' install.sh```
- ```./install.sh```
6. the path of the root directory of the app should be located now at ```/etc/haproxy-configurator```
7. run ```service haproxy-configurator status``` to see if the service is running.
8.browse the app:
```browse https://your-haproxy-server-ip:5000```
## SSL Certificate
The application comes with a self signed certificate related to tthe domain "haproxy-configurator.local". The path to the PEM file can be changed inside ssl.ini configuration file.
## Usage
Launch the HAProxy Configurator by navigating to https://your-haproxy-server-ip:5000.
Fill in the required parameters and options for your HAProxy configuration.
### Home Page:
![home page](https://github.com/user-attachments/assets/8ef82eb4-063c-41fb-9796-2ba97b1ae7fa)
### Frontend Section:
![frontend_section](https://github.com/user-attachments/assets/3f32f221-ffcd-4bca-8fa0-4396845e77f8)
### backend section:
![backend_section](https://github.com/user-attachments/assets/d17d9be9-869b-467a-98af-695420403c90)
### You may as well edit the config file itself via "Edit HAProxy Config":
![image](https://github.com/alonz22/HAProxy-Configurator/assets/72250573/c61ed725-37a4-4ad5-908f-6164311c7fd4)
Click the "Save & Check" button to validate the configuration without reloading HAProxy.
Click the "Save & Reload" button to save the configuration and trigger HAProxy's reload.
Review the generated configuration output and verify its accuracy.
## Review And Analyze Security Events Related To Triggered ACL's Activated:
![image](https://github.com/alonz22/HAProxy-Configurator/assets/72250573/ce0fc97e-0622-4fab-92f1-dd71fdb3e1ba)
## Features
Frontend and Backend Configuration
Easily add and manage frontend and backend configurations through our intuitive GUI. Define your load balancing, SSL termination, and ACL rules with just a few clicks.
Direct Edit of haproxy.cfg
Edit your HAProxy configuration file directly from the GUI. Make quick modifications, add custom settings, and see changes in real-time.
Configuration Validation
Ensure the accuracy of your HAProxy configuration by validating it for errors. Receive clear feedback and suggestions for improvements before applying changes.
Save and Restart HAProxy Service
With the click of a button, save your configuration changes and smoothly restart the HAProxy service, ensuring uninterrupted traffic flow.
Traffic Statistics
Monitor traffic distribution across your frontends and backends. Gain insights into usage patterns and identify performance bottlenecks.
WAF and Security
Enhance security with integrated Web Application Firewall (WAF) features:
* Defend against DOS attacks
* Mitigate SQL injection
* Prevent Cross-Site Scripting (XSS)
* Block access to sensitive paths
* Stop remote file uploads
* Add custom response headers
* Security Event Logs Analysis
* Analyze security event logs directly from the app. Gain visibility into potential threats and anomalies, empowering you to take proactive measures.
* Add path based redirects on Layer7 LoadBalancing (http mode)
* Use SSL Certificate for your frontend and enable redirect to https
* Add forwardfor to your backend, to forward the real client ip address to your backend servers
* Enable Layer4 & Layer7 Healthchecks to your backend servers
* Add Session Persistence to your backend servers by using client ip or a cookie.
Homepage Summary
Get an overview of your entire configuration on the homepage:
Count ACLs, frontends, and backends
View load balancing methods in use
Quick access to critical configuration details
## Roadmap Overview
### Performance and Flexibility
1.HTTP Keep-Alive Option: Implement an option to enable HTTP Keep-Alive within the frontend configurations. This feature enhances connection efficiency by allowing multiple requests and responses to be sent over a single TCP connection.
2.Backup Servers for Backend: Enhance backend resilience by adding support for specifying backup servers. These servers will be used when the primary servers are unavailable, improving overall service availability.
3.Dynamic Backend Server Addition: Introduce an intuitive button to dynamically add backend servers directly from the GUI, eliminating the need to manually edit configuration files.
### Phase 3: Optimizations and Security
1.Frontend Caching Mechanism: Implement a frontend caching mechanism to optimize content delivery and reduce backend load. This feature will help accelerate user experiences and decrease response times.
2.Advanced WAF Protection: Bolster our existing WAF features with a more robust and comprehensive set of protections against emerging threats. Enhancements will include:
* Advanced SQL injection detection and prevention
* Enhanced XSS attack mitigation
* Fine-grained controls for blocking sensitive data leaks
* Improved anomaly detection for DOS attacks
Feedback and Contributions
Your feedback and suggestions for improvements are welcome! Please feel free to open issues or submit pull requests on our GitHub repository.
Note: This tool is provided as-is, and we do not offer any warranties or guarantees regarding its performance or suitability for any specific use case.
## License
This project is licensed under the MIT License. Feel free to use, modify, and distribute it in accordance with the terms of the license.
Thank you for using the HAProxy Configurator! We hope it simplifies your HAProxy configuration process and makes managing your load balancer easier. If you have any questions or encounter any issues, please let us know. Happy load balancing!

122
app.py Normal file
View File

@@ -0,0 +1,122 @@
from flask import Flask, render_template, render_template_string
import configparser
import ssl
from routes.main_routes import main_bp
from routes.edit_routes import edit_bp
from utils.stats_utils import fetch_haproxy_stats, parse_haproxy_stats
from auth.auth_middleware import setup_auth
from log_parser import parse_log_file
app = Flask(__name__)
# Load basic auth credentials
auth_config = configparser.ConfigParser()
auth_config.read('/etc/haproxy-configurator/auth/auth.cfg')
BASIC_AUTH_USERNAME = auth_config.get('auth', 'username')
BASIC_AUTH_PASSWORD = auth_config.get('auth', 'password')
# Register blueprints
app.register_blueprint(main_bp)
app.register_blueprint(edit_bp)
# Setup authentication (placeholder, not currently used)
setup_auth(app)
# SSL Configuration
config2 = configparser.ConfigParser()
config2.read('/etc/haproxy-configurator/ssl.ini')
certificate_path = config2.get('ssl', 'certificate_path')
private_key_path = config2.get('ssl', 'private_key_path')
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
ssl_context.load_cert_chain(certfile=certificate_path, keyfile=private_key_path)
# Statistics Route
@app.route('/statistics')
def display_haproxy_stats():
haproxy_stats = fetch_haproxy_stats()
parsed_stats = parse_haproxy_stats(haproxy_stats)
return render_template_string('''
<style>
/* Custom CSS for the header */
header {
background-color: #f2f2f2;
padding: 20px;
display: flex;
padding-left: 100px;
align-items: center;
}
.logo {
width: 300px; /* Adjust the width as needed */
height: auto;
}
.menu-link {
text-decoration: none;
padding: 10px 20px;
color: #333;
font-weight: bold;
}
.menu-link:hover {
background-color: #3B444B;
color: white;
text-decoration: none;
}
</style>
<header>
<a href="/home" style="text-decoration: none;">
<h3 style="color: grey; font-size: 22px;" class="logo">
<i style="margin: 8px;" class="fas fa-globe"></i>Haproxy Configurator
</h3>
</a>
<a href="/home" class="menu-link">Home</a>
<a href="/" class="menu-link">Add Frontend&Backend</a>
<a href="/edit" class="menu-link">Edit HAProxy Config</a>
<a href="/logs" class="menu-link">Security Events</a>
<a href="/statistics" class="menu-link">Statistics</a>
<a href="http://{{ request.host.split(':')[0] }}:8080/stats" class="menu-link">HAProxy Stats</a>
</header>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<div class="container">
<h1 class="my-4">HAProxy Stats</h1>
<div class="table-responsive">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Frontend Name</th>
<th>Server Name</th>
<th>4xx Errors</th>
<th>5xx Errors</th>
<th>Bytes In (MB)</th>
<th>Bytes Out (MB)</th>
<th>Total Connections</th>
</tr>
</thead>
<tbody>
{% for stat in stats %}
<tr>
<td>{{ stat.frontend_name }}</td>
<td>{{ stat.server_name }}</td>
<td>{{ stat['4xx_errors'] }}</td>
<td>{{ stat['5xx_errors'] }}</td>
<td>{{ stat.bytes_in_mb }}</td>
<td>{{ stat.bytes_out_mb }}</td>
<td>{{ stat.conn_tot }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
''', stats=parsed_stats)
# Logs Route
@app.route('/logs')
def display_logs():
log_file_path = '/var/log/haproxy.log'
parsed_entries = parse_log_file(log_file_path)
return render_template('logs.html', entries=parsed_entries)
if __name__ == '__main__':
app.run(host='::', port=5000, ssl_context=ssl_context, debug=True)

3
auth/auth.cfg Normal file
View File

@@ -0,0 +1,3 @@
[auth]
username = admin
password = secret123

33
auth/auth_middleware.py Normal file
View File

@@ -0,0 +1,33 @@
from functools import wraps
from flask import request, Response
import configparser
# Load basic auth credentials
auth_config = configparser.ConfigParser()
auth_config.read('./app/auth/auth.cfg')
BASIC_AUTH_USERNAME = auth_config.get('auth', 'username')
BASIC_AUTH_PASSWORD = auth_config.get('auth', 'password')
def check_auth(username, password):
return username == BASIC_AUTH_USERNAME and password == BASIC_AUTH_PASSWORD
def authenticate():
return Response(
'Authentication required.', 401,
{'WWW-Authenticate': 'Basic realm="Login Required"'}
)
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
return authenticate()
return f(*args, **kwargs)
return decorated
def setup_auth(app):
"""Setup authentication for the Flask app"""
# This function can be used to register auth-related configurations
# if needed in the future
pass

42
docker-compose.yml Normal file
View File

@@ -0,0 +1,42 @@
version: '3.9'
services:
haproxy-configurator:
build:
context: .
dockerfile: Dockerfile
container_name: haproxy-configurator
restart: unless-stopped
ports:
- "15000:5000"
- "7780:80"
- "7443:443"
- "8404:8404"
volumes:
- ./config:/app/config
- ./haproxy:/etc/haproxy
- ./logs:/var/log
environment:
- FLASK_ENV=production
- FLASK_APP=app.py
- PYTHONUNBUFFERED=1
cap_add:
- NET_ADMIN
- SYS_ADMIN
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
- haproxy-net
networks:
haproxy-net:
driver: bridge

77
entrypoint.sh Normal file
View File

@@ -0,0 +1,77 @@
#!/bin/bash
set -e
echo "[$(date)] Starting HAProxy Configurator..."
# Create directories if they don't exist
mkdir -p /app/config/auth
mkdir -p /app/config/ssl
mkdir -p /etc/haproxy
mkdir -p /var/log/supervisor
# Create default auth.cfg if doesn't exist
if [ ! -f /app/config/auth/auth.cfg ]; then
cat > /app/config/auth/auth.cfg <<EOF
[auth]
username = admin
password = admin123
EOF
echo "[$(date)] Created default auth.cfg"
fi
# Create default ssl.ini if doesn't exist
if [ ! -f /app/config/ssl.ini ]; then
cat > /app/config/ssl.ini <<EOF
[ssl]
certificate_path = /app/config/ssl/haproxy-configurator.pem
private_key_path = /app/config/ssl/haproxy-configurator.pem
EOF
echo "[$(date)] Created default ssl.ini"
fi
# Generate self-signed certificate if doesn't exist
if [ ! -f /app/config/ssl/haproxy-configurator.pem ]; then
openssl req -x509 -newkey rsa:2048 -keyout /app/config/ssl/haproxy-configurator.pem \
-out /app/config/ssl/haproxy-configurator.pem -days 365 -nodes \
-subj "/C=PL/ST=State/L=City/O=Organization/CN=haproxy-configurator.local"
chmod 600 /app/config/ssl/haproxy-configurator.pem
echo "[$(date)] Generated SSL certificate"
fi
# Create default haproxy.cfg if doesn't exist or is empty
if [ ! -s /etc/haproxy/haproxy.cfg ]; then
cat > /etc/haproxy/haproxy.cfg <<'HAPROXYCFG'
global
log stdout local0
maxconn 4096
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
listen stats
bind *:8404
stats enable
stats uri /stats
stats refresh 30s
stats show-legends
HAPROXYCFG
echo "[$(date)] Created default haproxy.cfg"
fi
# Set proper permissions
chmod 600 /app/config/ssl/haproxy-configurator.pem 2>/dev/null || true
chmod 644 /app/config/auth/auth.cfg
chmod 644 /app/config/ssl.ini
chmod 644 /etc/haproxy/haproxy.cfg
echo "[$(date)] Configuration ready"
echo "[$(date)] Starting supervisord..."
# Start supervisord
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf

38
install.sh Executable file
View File

@@ -0,0 +1,38 @@
#!/bin/bash
# Create the folder inside /etc
sudo mkdir -p /etc/haproxy-configurator
# Copy files to the 'haproxy-configurator' folder
sudo cp -r templates/ /etc/haproxy-configurator/
sudo cp app.py /etc/haproxy-configurator/
sudo cp Makefile /etc/haproxy-configurator/
sudo cp requirements.txt /etc/haproxy-configurator/
sudo cp ssl.ini /etc/haproxy-configurator/
sudo cp -r ssl/ /etc/haproxy-configurator/
sudo cp -r routes/ /etc/haproxy-configurator/
sudo cp -r utils/ /etc/haproxy-configurator/
sudo cp -r auth/ /etc/haproxy-configurator/
sudo cp log_parser.py /etc/haproxy-configurator/
# Create the service file for 'haproxy-configurator'
cat << EOF | sudo tee /etc/systemd/system/haproxy-configurator.service
[Unit]
Description=Haproxy-Configurator Service By Alon Zur
[Service]
ExecStart=/usr/bin/python3 /etc/haproxy-configurator/app.py
Restart=always
RestartSec=3
StandardOutput=/var/log/haproxy-configurator_std_output.log
StandardError=/var/log/haproxy-configurator_error.log
[Install]
WantedBy=multi-user.target
EOF
# Reload systemd to load the new service
sudo systemctl daemon-reload
# Enable and start the service
sudo systemctl enable haproxy-configurator.service
sudo systemctl start haproxy-configurator.service

103
log_parser.py Normal file
View File

@@ -0,0 +1,103 @@
import re
def parse_log_file(log_file_path):
parsed_entries = []
xss_patterns = [
r'<\s*script\s*',
r'javascript:',
r'<\s*img\s*src\s*=?',
r'<\s*a\s*href\s*=?',
r'<\s*iframe\s*src\s*=?',
r'on\w+\s*=?',
r'<\s*input\s*[^>]*\s*value\s*=?',
r'<\s*form\s*action\s*=?',
r'<\s*svg\s*on\w+\s*=?',
r'script',
r'alert',
r'onerror',
r'onload',
r'javascript'
]
sql_patterns = [
r';',
r'substring',
r'extract',
r'union\s+all',
r'order\s+by',
r'--\+',
r'union',
r'select',
r'insert',
r'update',
r'delete',
r'drop',
r'@@',
r'1=1',
r'`1',
r'union',
r'select',
r'insert',
r'update',
r'delete',
r'drop',
r'@@',
r'1=1',
r'`1'
]
webshells_patterns = [
r'payload',
r'eval|system|passthru|shell_exec|exec|popen|proc_open|pcntl_exec|cmd|shell|backdoor|webshell|phpspy|c99|kacak|b374k|log4j|log4shell|wsos|madspot|malicious|evil.*\.php.*'
]
combined_xss_pattern = re.compile('|'.join(xss_patterns), re.IGNORECASE)
combined_sql_pattern = re.compile('|'.join(sql_patterns), re.IGNORECASE)
combined_webshells_pattern = re.compile('|'.join(webshells_patterns), re.IGNORECASE)
with open(log_file_path, 'r') as log_file:
log_lines = log_file.readlines()
for line in log_lines:
if " 403 " in line: # Check if the line contains " 403 " indicating a 403 status code
match = re.search(r'(\w+\s+\d+\s\d+:\d+:\d+).*\s(\d+\.\d+\.\d+\.\d+).*"\s*(GET|POST|PUT|DELETE)\s+([^"]+)"', line)
if match:
timestamp = match.group(1) # Extract the date and time
ip_address = match.group(2)
http_method = match.group(3)
requested_url = match.group(4)
if combined_xss_pattern.search(line):
xss_alert = 'Possible XSS Attack Was Identified.'
else:
xss_alert = ''
if combined_sql_pattern.search(line):
sql_alert = 'Possible SQL Injection Attempt Was Made.'
else:
sql_alert = ''
if "PUT" in line:
put_method = 'Possible Remote File Upload Attempt Was Made.'
else:
put_method = ''
if "admin" in line:
illegal_resource = 'Possible Illegal Resource Access Attempt Was Made.'
else:
illegal_resource = ''
if combined_webshells_pattern.search(line):
webshell_alert = 'Possible WebShell Attack Attempt Was Made.'
else:
webshell_alert = ''
parsed_entries.append({
'timestamp': timestamp,
'ip_address': ip_address,
'http_method': http_method,
'requested_url': requested_url,
'xss_alert': xss_alert,
'sql_alert': sql_alert,
'put_method': put_method,
'illegal_resource': illegal_resource,
'webshell_alert': webshell_alert
})
return parsed_entries

3
requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
flask
requests
pyOpenSSL

57
routes/edit_routes.py Normal file
View File

@@ -0,0 +1,57 @@
from flask import Blueprint, render_template, request
import subprocess
from auth.auth_middleware import requires_auth # Updated import
edit_bp = Blueprint('edit', __name__)
@edit_bp.route('/edit', methods=['GET', 'POST'])
@requires_auth
def edit_haproxy_config():
if request.method == 'POST':
edited_config = request.form['haproxy_config']
# Save the edited config to the haproxy.cfg file
with open('/etc/haproxy/haproxy.cfg', 'w') as f:
f.write(edited_config)
check_output = ""
if 'save_check' in request.form:
# Run haproxy -c -V -f to check the configuration
check_result = subprocess.run(['haproxy', '-c', '-V', '-f', '/etc/haproxy/haproxy.cfg'], capture_output=True, text=True)
check_output = check_result.stdout
# Check if there was an error, and if so, append it to the output
if check_result.returncode != 0:
error_message = check_result.stderr
check_output += f"\n\nError occurred:\n{error_message}"
elif 'save_reload' in request.form:
# Run haproxy -c -V -f to check the configuration
check_result = subprocess.run(['haproxy', '-c', '-V', '-f', '/etc/haproxy/haproxy.cfg'], capture_output=True, text=True)
check_output = check_result.stdout
# Check if there was an error, and if so, append it to the output
if check_result.returncode != 0:
error_message = check_result.stderr
check_output += f"\n\nError occurred:\n{error_message}"
else:
# If no error, run systemctl restart haproxy to reload HAProxy
reload_result = subprocess.run(['systemctl', 'restart', 'haproxy'], capture_output=True, text=True)
check_output += f"\n\nHAProxy Restart Output:\n{reload_result.stdout}"
# Also add stderr if there are any warnings or errors during restart
if reload_result.stderr:
check_output += f"\nRestart Stderr:\n{reload_result.stderr}"
return render_template('edit.html', config_content=edited_config, check_output=check_output)
# GET request - Read the current contents of haproxy.cfg
try:
with open('/etc/haproxy/haproxy.cfg', 'r') as f:
config_content = f.read()
except FileNotFoundError:
config_content = "# HAProxy configuration file not found\n# Please create /etc/haproxy/haproxy.cfg"
except PermissionError:
config_content = "# Permission denied reading HAProxy configuration file"
return render_template('edit.html', config_content=config_content)

111
routes/main_routes.py Normal file
View File

@@ -0,0 +1,111 @@
from flask import Blueprint, render_template, request
from auth.auth_middleware import requires_auth # Updated import
from utils.haproxy_config import update_haproxy_config, is_frontend_exist, count_frontends_and_backends
main_bp = Blueprint('main', __name__)
@main_bp.route('/', methods=['GET', 'POST'])
@requires_auth
def index():
if request.method == 'POST':
frontend_name = request.form['frontend_name']
frontend_ip = request.form['frontend_ip']
frontend_port = request.form['frontend_port']
lb_method = request.form['lb_method']
protocol = request.form['protocol']
backend_name = request.form['backend_name']
add_header = 'add_header' in request.form if 'add_header' in request.form else ''
header_name = request.form['header_name']
header_value = request.form['header_value']
# Get all backend servers data
backend_server_names = request.form.getlist('backend_server_names[]')
backend_server_ips = request.form.getlist('backend_server_ips[]')
backend_server_ports = request.form.getlist('backend_server_ports[]')
backend_server_maxconns = request.form.getlist('backend_server_maxconns[]')
is_acl = 'add_acl' in request.form
acl_name = request.form['acl'] if 'acl' in request.form else ''
acl_action = request.form['acl_action'] if 'acl_action' in request.form else ''
acl_backend_name = request.form['backend_name_acl'] if 'backend_name_acl' in request.form else ''
use_ssl = 'ssl_checkbox' in request.form
ssl_cert_path = request.form['ssl_cert_path']
https_redirect = 'ssl_redirect_checkbox' in request.form
is_dos = 'add_dos' in request.form if 'add_dos' in request.form else ''
ban_duration = request.form["ban_duration"]
limit_requests = request.form["limit_requests"]
forward_for = 'forward_for_check' in request.form
is_forbidden_path = 'add_acl_path' in request.form
forbidden_name = request.form["forbidden_name"]
allowed_ip = request.form["allowed_ip"]
forbidden_path = request.form["forbidden_path"]
sql_injection_check = 'sql_injection_check' in request.form if 'sql_injection_check' in request.form else ''
is_xss = 'xss_check' in request.form if 'xss_check' in request.form else ''
is_remote_upload = 'remote_uploads_check' in request.form if 'remote_uploads_check' in request.form else ''
add_path_based = 'add_path_based' in request.form
redirect_domain_name = request.form["redirect_domain_name"]
root_redirect = request.form["root_redirect"]
redirect_to = request.form["redirect_to"]
is_webshells = 'webshells_check' in request.form if 'webshells_check' in request.form else ''
# Combine backend server info into a list of tuples (name, ip, port, maxconns)
backend_servers = []
for i in range(len(backend_server_ips)):
name = backend_server_names[i] if i < len(backend_server_names) else f"server{i+1}"
ip = backend_server_ips[i] if i < len(backend_server_ips) else ''
port = backend_server_ports[i] if i < len(backend_server_ports) else ''
maxconn = backend_server_maxconns[i] if i < len(backend_server_maxconns) else None
if ip and port: # Only add if we have IP and port
backend_servers.append((name, ip, port, maxconn))
# Check if frontend or port already exists
if is_frontend_exist(frontend_name, frontend_ip, frontend_port):
return render_template('index.html', message="Frontend or Port already exists. Cannot add duplicate.")
# Get health check related fields if the protocol is HTTP
health_check = False
health_check_link = ""
if protocol == 'http':
health_check = 'health_check' in request.form
if health_check:
health_check_link = request.form['health_check_link']
health_check_tcp = False
if protocol == 'tcp':
health_check_tcp = 'health_check2' in request.form
# Get sticky session related fields
sticky_session = False
sticky_session_type = ""
if 'sticky_session' in request.form:
sticky_session = True
sticky_session_type = request.form['sticky_session_type']
# Update the HAProxy config file
message = update_haproxy_config(
frontend_name, frontend_ip, frontend_port, lb_method, protocol, backend_name,
backend_servers, health_check, health_check_tcp, health_check_link, sticky_session,
add_header, header_name, header_value, sticky_session_type, is_acl, acl_name,
acl_action, acl_backend_name, use_ssl, ssl_cert_path, https_redirect, is_dos,
ban_duration, limit_requests, forward_for, is_forbidden_path, forbidden_name,
allowed_ip, forbidden_path, sql_injection_check, is_xss, is_remote_upload,
add_path_based, redirect_domain_name, root_redirect, redirect_to, is_webshells
)
return render_template('index.html', message=message)
return render_template('index.html')
@main_bp.route('/home')
@requires_auth
def home():
frontend_count, backend_count, acl_count, layer7_count, layer4_count = count_frontends_and_backends()
return render_template('home.html',
frontend_count=frontend_count,
backend_count=backend_count,
acl_count=acl_count,
layer7_count=layer7_count,
layer4_count=layer4_count)

3
ssl.ini Normal file
View File

@@ -0,0 +1,3 @@
[ssl]
certificate_path = /etc/haproxy-configurator/ssl/haproxy-configurator.pem
private_key_path = /etc/haproxy-configurator/ssl/haproxy-configurator.pem

View File

@@ -0,0 +1,50 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDN+nMQ2lWy79q6
DwMwIK2eO+N/aammtYpNvgI2l6PqD/3MT9Vn6ngWvr2OXQkST8SjQD966QwY5btj
KLYNU7r01AqfHSJe4Z6x99KEHvjPsZeIiE5BGsPuVmBCISOGmHk4PeNmQZ3KegzA
1GlJh5NnLPRZmHIJYgTrhGg8AdOvhqlfVrwpd2fp6AmrilLTmAxW+tXJvkJAPmUP
WXEfie66el4zeTQTb0ywJ+46zLqFVw5+UFI0xKRBTE/EBgS+Wr0NgIEaBykjmU75
8VBFcVLxeB9TqEpEpCz9ejvkqTAKyEnIcnVGu0gOq7z8yxL3D2xVWfZ5Pfoc7fi6
mjwnYIIDAgMBAAECggEAEQQNmWjQB5Y09YXj94fbj+TWqGHsN+9rW3zE5gmk6uMj
TkuSD6GZBhf/xND+vNqGHg5isB6sdyoTTt/AGl2+ZhdWQUrA2iG9YGpeo7eDSnUb
VZYdENfLM9dC4HOoYaga64CBVqM5C88FWrCxefePP8jA7t3fHdNRILuxeLOV7zwE
jNEiBVj0xLnvLxP2gGepbEJ0C3IC0H3qMu5R6cDmqUiUFj08DouZZr/o815KSab/
76LOsF7i6hVm1KJyGsCOJFyDNWLNumL6+5LSgKkTz9Qfi7RufK68If7Y0FMg9HSy
U+WBzMn2QVxbl4lkrkYTfiSryTcs2bTx1um2aJmkMQKBgQDRm9B8eiwOUD+2XIOS
SklBotZTSNXzwLReYLXuUJjloYw11lHIu38Wzke2/1fBUeG/2bMWh55dfwLbXkHk
It/FMO2e1SRS/Pp5IFVm/48ygYT2sqjMpQFLlmX6dGFBBi6EzzdgSDf4VvIIXPzl
MrC/n2ziQDUfB97jftM6mgkkPwKBgQD7kPHUU1OSc5t32/m4q3CPsimaJpc/iEy2
WkIQeF8whjG1zSgROqNT8GCnzeL5ZJLVF0rAxlM7FLyY5lIKnEze2hWBPtVCqRfR
2hdSLNebbfeJ2sf4IhWmQxipgR36kzpn+8q/ZQmlPZtl8oHZzhMf4tbDj20AfXk2
JoU3uclhPQKBgQCyr+76vrfh991om9N6GaP7tVKaq0PhSU06C53srH67e41os+0C
f3xnN7EBOwpXfnNBtteqBDEoKUeJNQBc+LHki8J2HeOab5kjO0IyzPgJKUrtd/7Y
fm3tPyhua1ohMaDkjTX6+XXlWlF4ebUJur3cjY8oDW57pp0HOS3fDkNuxwKBgQCZ
yIZ81J9vFKsCf3/N60e5ICGccWDeXrT+eHiQVvVh1XcH+y02MxFrG7QKgKry6A92
onF5HQP+RvPvyER45gl75fdldgKqaHH+QAMnoe//vR6osAy0XCm95KBr5llsN5pp
vJu5mstlrx6TGMdWXUqFoIbHL6NjvFBf0PZ5FWLMxQKBgGN23ZPuHg3y8OaUwk1c
JQJNxxviCBOzAWywKtg96K4n8/+fsPx2sEbDwGQRfY3GySal4weICutlxnu+744q
AKaJUe1pJFIb6WJJVnpTY4QGQI/B7+AB6IKA1jXIWQD4gLoSR/X6kWqs8bIwzRUz
klDX9vTv8HLfcM8/j+FVhJN2
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDtTCCAp2gAwIBAgIUJJljaGQR2C0+LZanyI5bdmo+TawwDQYJKoZIhvcNAQEL
BQAwajELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEjMCEGA1UEAwwaaGFwcm94eS1jb25m
aWd1cmF0b3IubG9jYWwwHhcNMjMwODA5MDgzODUyWhcNMjQwODA4MDgzODUyWjBq
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMSMwIQYDVQQDDBpoYXByb3h5LWNvbmZpZ3Vy
YXRvci5sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM36cxDa
VbLv2roPAzAgrZ47439pqaa1ik2+AjaXo+oP/cxP1WfqeBa+vY5dCRJPxKNAP3rp
DBjlu2Motg1TuvTUCp8dIl7hnrH30oQe+M+xl4iITkEaw+5WYEIhI4aYeTg942ZB
ncp6DMDUaUmHk2cs9FmYcgliBOuEaDwB06+GqV9WvCl3Z+noCauKUtOYDFb61cm+
QkA+ZQ9ZcR+J7rp6XjN5NBNvTLAn7jrMuoVXDn5QUjTEpEFMT8QGBL5avQ2AgRoH
KSOZTvnxUEVxUvF4H1OoSkSkLP16O+SpMArISchydUa7SA6rvPzLEvcPbFVZ9nk9
+hzt+LqaPCdgggMCAwEAAaNTMFEwHQYDVR0OBBYEFOjnT/mD5f+vobsI9yOKBIQR
pZWUMB8GA1UdIwQYMBaAFOjnT/mD5f+vobsI9yOKBIQRpZWUMA8GA1UdEwEB/wQF
MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAEPtsEcHlTSrM0ekj8nV6IcawXHXRabT
S7s8c3tdeksIZ9W29ZtTE9KG5jr033pdjUA9C8HpZJZRoETkWF/6n+xGwh6Ci7Mm
JzoKeOItXBNHku6xGIcu4hCELeWMLGqj3NA8CmCAOfVtSe64aMKXODNohi9S1/Lw
iCaDBTEsl8NyPAwMnefAEi8yJl1Rda+HQ1MCZjlBgkJwSAUqrn1HzuAwk74UbdMa
bjSdRIzSCPvONQNXqb9eYyW0uPGrf83h16Edinn7i0FWXug6x57Im8P8/eJj1SWN
iNS1m5FFfwwC6VJn1RTrpWCwtLyLHRbQ9RD3NF+nwGlCfZ5oYMrOazE=
-----END CERTIFICATE-----

36
supervisord.conf Normal file
View File

@@ -0,0 +1,36 @@
[supervisord]
nodaemon=true
user=root
loglevel=info
logfile=/var/log/supervisor/supervisord.log
pidfile=/var/run/supervisord.pid
[program:haproxy]
command=/usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg
autostart=true
autorestart=true
stderr_logfile=/var/log/supervisor/haproxy.err.log
stdout_logfile=/var/log/supervisor/haproxy.out.log
priority=100
stopasgroup=true
killasgroup=true
[program:flask_app]
command=/usr/bin/python3 /app/app.py
directory=/app
autostart=true
autorestart=true
stderr_logfile=/var/log/supervisor/flask_app.err.log
stdout_logfile=/var/log/supervisor/flask_app.out.log
priority=999
environment=FLASK_APP=/app/app.py,FLASK_ENV=production,PYTHONUNBUFFERED=1
[unix_http_server]
file=/var/run/supervisor.sock
chmod=0700
[supervisorctl]
serverurl=unix:///var/run/supervisor.sock
[rpcinterface:supervisor]
supervisor.rpc_interface_factory = supervisor.rpcinterface:make_main_rpcinterface

211
templates/edit.html Normal file
View File

@@ -0,0 +1,211 @@
<!DOCTYPE html>
<html>
<head>
<title>Edit HAProxy Config</title>
<!-- Add Bootstrap CSS link here -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<style>
/* Custom CSS for the header */
header {
background-color: #f2f2f2;
padding: 20px;
display: flex;
padding-left: 100px;
align-items: center;
}
#editor_container{
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
.logo {
width: 300px; /* Adjust the width as needed */
height: auto;
}
.menu-link {
text-decoration: none;
padding: 10px 20px;
color: #333;
font-weight: bold;
}
.menu-link:hover {
background-color: #3B444B;
color: white;
text-decoration: none;
}
/* Custom CSS for dark mode */
.dark-mode {
background-color: #121B2E;
color: white;
}
.dark-mode .header1{
background-color: #25354e;
color: white;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
.dark-mode .menu-link{
text-decoration: none;
padding: 10px 20px;
color: white;
font-weight: bold;
}
.dark-mode .logo {
color: #2bb9c7;
font-weight: bold;
}
.dark-mode .fas-fa-globe{
color: #2bb9c7;
}
.dark-mode .menu-link:hover{
text-decoration: none;
padding: 10px 20px;
color: #2bb9c7;
font-weight: bold;
}
.dark-mode #editor_container{
box-shadow: 0 0 15px 5px rgba(43, 185, 199, 0.05);
background-color: #1E2C42;
}
.dark-mode textarea {
background-color: #1a2131;
color: white;
border: 1px solid #ccc; /* Add a border for visibility */
padding: 5px; /* Add padding for a better visual appearance */
scrollbar-width: thin;
scrollbar-color: #1E2C42 #f1f1f1;
::-webkit-scrollbar-thumb {
background-color: #888888;
border-radius: 6px; /* rounded thumb */
}
}
/* Apply specific styles when the textarea is focused */
.dark-mode textarea:focus {
background-color: #1a2131;
color: white;
border-color: #fff; /* Change border color on focus */
outline: none; /* Remove default focus outline */
}
h3.edit_conf{
color: grey;
}
.dark-mode h3.edit_conf {
color: white !important;
}
.dark-mode #save_check{
background-color: #2bb9c7;
border: none;
}
</style>
</head>
<body>
<header class="header1" id="header1">
<a href="/home" style="text-decoration: none;">
<h3 style="font-size: 22px;" class="logo">
<i style="margin: 8px;" class="fas fa-globe"></i>Haproxy Configurator
</h3>
</a>
<a href="/home" class="menu-link">Home</a>
<a href="/" class="menu-link">Add Frontend & Backend</a>
<a href="/edit" class="menu-link">Edit HAProxy Config</a>
<a href="/logs" class="menu-link">Security Events</a>
<a href="/statistics" class="menu-link">Statictics</a>
<a href="http://{{ request.host.split(':')[0] }}:8080/stats" class="menu-link" target="_blank">HAProxy Stats</a>
<div class="custom-control custom-switch ml-auto">
<input type="checkbox" class="custom-control-input" id="darkModeSwitch">
<label class="custom-control-label" for="darkModeSwitch">Dark Mode</label>
</div>
<script>
// Function to toggle dark mode
function toggleDarkMode() {
const body = document.body;
body.classList.toggle('dark-mode');
// Save user's preference to localStorage
const isDarkMode = body.classList.contains('dark-mode');
localStorage.setItem('darkMode', isDarkMode); // Store the actual value
}
// Check if dark mode preference is saved in localStorage
const savedDarkMode = localStorage.getItem('darkMode');
if (savedDarkMode === 'true') {
document.body.classList.add('dark-mode');
}
// Add event listener to the switch
const darkModeSwitch = document.getElementById('darkModeSwitch');
darkModeSwitch.addEventListener('change', toggleDarkMode);
</script>
</header>
<div style=" border-radius: 5px;" id="editor_container" class="container mt-5">
<h3 style="color: grey; padding: 15px;" id="edit_conf" class="edit_conf">Edit HAProxy Config</h3>
<form method="POST">
<div class="form-group">
<label for="haproxy_config">Configuration:</label>
<textarea style="padding: 15px;" class="form-control" name="haproxy_config" rows="25" cols="120">{{ config_content }}</textarea>
</div>
<div style="padding-bottom: 20px;" class="form-group">
<input type="submit" class="btn btn-warning" id="save_check" name="save_check" value="Save & Check">
<input type="submit" class="btn btn-primary" name="save_reload" value="Save & Restart">
</div>
</form>
{% if check_output %}
<div style="padding-bottom: 15px;">
{% if 'Fatal errors' in check_output %}
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<pre class="mt-3">{{ check_output }}</pre>
</div>
{% elif 'Warnings' in check_output %}
<div class="alert alert-warning alert-dismissible fade show" role="alert">
<pre class="mt-3">{{ check_output }}</pre>
</div>
{% elif 'error detected while parsing an' in check_output %}
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<pre class="mt-3">{{ check_output }}</pre>
</div>
{% else %}
<div class="alert alert-success alert-dismissible fade show" role="alert">
<pre class="mt-3">{{ check_output }}</pre>
</div>
{% endif %}
</div>
{% endif %}
<!-- Add Bootstrap JS and jQuery scripts here (if needed) -->
<!-- You can get them from the official Bootstrap website or use CDN links -->
</body>
</html>

152
templates/home.html Normal file
View File

@@ -0,0 +1,152 @@
<!DOCTYPE html>
<html>
<head>
<title>Home Page</title>
<!-- Add Bootstrap CSS link here -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<style>
/* Custom CSS for the header */
header {
background-color: #f2f2f2;
padding: 20px;
display: flex;
padding-left: 100px;
align-items: center;
}
.logo {
width: 300px; /* Adjust the width as needed */
height: auto;
}
.menu-link {
text-decoration: none;
padding: 10px 20px;
color: #333;
font-weight: bold;
}
.menu-link:hover {
background-color: #3B444B;
color: white;
text-decoration: none;
}
#summary_container{
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
/* Custom CSS for dark mode */
.dark-mode {
background-color: #121B2E;
color: white;
}
.dark-mode .header1{
background-color: #25354e;
color: white;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
.dark-mode .menu-link{
text-decoration: none;
padding: 10px 20px;
color: white;
font-weight: bold;
}
.dark-mode .logo {
color: #2bb9c7;
font-weight: bold;
}
.dark-mode .fas-fa-globe{
color: #2bb9c7;
}
.dark-mode .menu-link:hover{
text-decoration: none;
padding: 10px 20px;
color: #2bb9c7;
font-weight: bold;
}
.dark-mode #summary_container{
box-shadow: 0 0 15px 5px rgba(43, 185, 199, 0.05);
border: none;
}
</style>
</head>
<body>
<header class="header1" id="header1">
<a href="/home" style="text-decoration: none;">
<h3 style="font-size: 22px;" class="logo">
<i style="margin: 8px;" class="fas fa-globe"></i>Haproxy Configurator
</h3>
</a>
<a href="/home" class="menu-link">Home</a>
<a href="/" class="menu-link">Add Frontend & Backend</a>
<a href="/edit" class="menu-link">Edit HAProxy Config</a>
<a href="/logs" class="menu-link">Security Events</a>
<a href="/statistics" class="menu-link">Statictics</a>
<a href="http://{{ request.host.split(':')[0] }}:8080/stats" class="menu-link" >HAProxy Stats</a>
<div class="custom-control custom-switch ml-auto">
<input type="checkbox" class="custom-control-input" id="darkModeSwitch">
<label class="custom-control-label" for="darkModeSwitch">Dark Mode</label>
</div>
</header>
<div style=" border-radius: 5px; padding: 40px;" id="summary_container" class="container mt-5">
<h3 style="margin-bottom: 20px;" class="mt-4">Welcome to Your HAProxy Configurator. Here's A Short Summary:</h3>
<p class="lead"><i style="margin: 8px;" class="fas fa-globe"></i> <strong>{{ frontend_count }}</strong> frontends</p>
<p class="lead"><i style="margin-right: 8px;" class="fas fa-sitemap"></i> <strong>{{ backend_count }}</strong> backends</p>
<p class="lead"><i style="margin: 8px;" class="fas fa-user-lock"></i> <strong>{{ acl_count }}</strong> acl's</p>
<p class="lead"><i style="margin: 8px;" class="fas fa-code"></i> <strong>{{ layer7_count }}</strong> layer7(mode http) loadbalanced frontends</p>
<p class="lead"><i style="margin: 8px;" class="fas fa-network-wired"></i> <strong>{{ layer4_count }}</strong> layer4(mode tcp)loadbalanced frontends</p>
<div class="mt-4">
<a href="/" class="btn btn-primary"><i style="margin: 8px;" class="fas fa-plus"></i>Add New Frontend/Backend</a>
</div>
</div>
<!-- Add Bootstrap JS and jQuery links here (if needed) -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.0.7/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script>
// Function to toggle dark mode
function toggleDarkMode() {
const body = document.body;
body.classList.toggle('dark-mode');
// Save user's preference to localStorage
const isDarkMode = body.classList.contains('dark-mode');
localStorage.setItem('darkMode', isDarkMode); // Store the actual value
}
// Check if dark mode preference is saved in localStorage
const savedDarkMode = localStorage.getItem('darkMode');
if (savedDarkMode === 'true') {
document.body.classList.add('dark-mode');
}
// Add event listener to the switch
const darkModeSwitch = document.getElementById('darkModeSwitch');
darkModeSwitch.addEventListener('change', toggleDarkMode);
</script>
</body>
</html>

867
templates/index.html Normal file
View File

@@ -0,0 +1,867 @@
<!DOCTYPE html>
<html>
<head>
<title>HAProxy Configurator</title>
<!-- Add Bootstrap CSS link -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<style>
/* Custom CSS for the header */
header {
background-color: #f2f2f2;
padding: 20px;
display: flex;
padding-left: 100px;
align-items: center;
}
#frontend_container{
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
#backend_container{
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
.logo {
width: 300px; /* Adjust the width as needed */
height: auto;
color: grey;
}
.menu-link {
text-decoration: none;
padding: 10px 20px;
color: #333;
font-weight: bold;
}
.menu-link:hover {
background-color: #3B444B;
color: white;
text-decoration: none;
}
/* Custom CSS for dark mode */
.dark-mode {
background-color: #121B2E;
color: white;
}
.dark-mode .header1{
background-color: #25354e;
color: white;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
.dark-mode .menu-link{
text-decoration: none;
padding: 10px 20px;
color: white;
font-weight: bold;
}
.dark-mode .logo {
color: #2bb9c7;
font-weight: bold;
}
.dark-mode .fas-fa-globe{
color: #2bb9c7;
}
.dark-mode .menu-link:hover{
text-decoration: none;
padding: 10px 20px;
color: #2bb9c7;
font-weight: bold;
}
.dark-mode input[type=text] {
color: #BDBDBD;
background-color: #25354e;
border: none;
}
.dark-mode input[type=number] {
color: white;
background-color: #25354e;
border: none;
}
.dark-mode #protocol{
color: white;
background-color: #25354e;
border: none;
}
.dark-mode #lb_method{
color: white;
background-color: #25354e;
border: none;
}
.dark-mode .logo {
color: #2bb9c7;
font-weight: bold;
}
.dark-mode #frontend_container{
box-shadow: 0 0 15px 5px rgba(43, 185, 199, 0.05);
}
.dark-mode #backend_container{
box-shadow: 0 0 15px 5px rgba(43, 185, 199, 0.05);
}
.dark-mode #succes_btn{
background-color: #2bb9c7;
border: none;
}
</style>
</head>
<body>
<!-- Header with the Edit link as a menu -->
<header id="header1" class="header1">
<a href="/home" style="text-decoration: none;">
<h3 style="font-size: 22px;" class="logo">
<i style="margin: 8px;" class="fas fa-globe"></i>Haproxy Configurator
</h3>
</a>
<a href="/home" class="menu-link" >Home</a>
<a href="/" class="menu-link">Add Frontend&Backend</a>
<a href="/edit" class="menu-link">Edit HAProxy Config</a>
<a href="/logs" class="menu-link">Security Events</a>
<a href="/statistics" class="menu-link">Statictics</a>
<a href="http://{{ request.host.split(':')[0] }}:8080/stats" class="menu-link" >HAProxy Stats</a>
<div class="custom-control custom-switch ml-auto">
<input type="checkbox" class="custom-control-input" id="darkModeSwitch">
<label class="custom-control-label" for="darkModeSwitch">Dark Mode</label>
</div>
<script>
// Function to toggle dark mode
function toggleDarkMode() {
const body = document.body;
body.classList.toggle('dark-mode');
// Save user's preference to localStorage
const isDarkMode = body.classList.contains('dark-mode');
localStorage.setItem('darkMode', isDarkMode); // Store the actual value
}
// Check if dark mode preference is saved in localStorage
const savedDarkMode = localStorage.getItem('darkMode');
if (savedDarkMode === 'true') {
document.body.classList.add('dark-mode');
}
// Add event listener to the switch
const darkModeSwitch = document.getElementById('darkModeSwitch');
darkModeSwitch.addEventListener('change', toggleDarkMode);
</script>
</header>
<div class="container mt-4">
<div style=" border-radius: 5px; padding: 20px;" class="container mt-5" id="frontend_container">
<label style="margin: 20px auto; font-size: 18px; font-weight: bold;"><i style="margin: 8px;" class="fas fa-globe"></i> Add New Frontend:</label><br>
<!-- Display success message when the form is submitted successfully -->
{% if message %}
<div class="alert {% if 'already exists' in message %}alert-danger{% else %}alert-success{% endif %} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
{% endif %}
<form method="post" action="/">
<div class="form-row">
<div class="form-group col-md-4">
<label for="frontend_name">Frontend Name:</label>
<input type="text" class="form-control" name="frontend_name" required>
</div>
<div class="form-group col-md-4">
<label for="frontend_ip">Frontend IP:</label>
<input type="text" class="form-control" name="frontend_ip" required>
</div>
<div class="form-group col-md-3">
<label for="frontend_port">Frontend Port:</label>
<input type="number" class="form-control" name="frontend_port" required>
</div>
</div>
<!-- Checkbox for SSL Certificate -->
<div class="form-check">
<input type="checkbox" class="form-check-input" id="ssl_checkbox" name="ssl_checkbox">
<label class="form-check-label" for="ssl_checkbox"><i style="margin: 8px;" class="fas fa-lock"></i>Use SSL Certificate</label>
</div>
<!-- Fields for SSL Certificate Path and SSL Key Path (Hidden by default) -->
<div class="form-group" style="display: none;" id="ssl_fields">
<label for="ssl_cert_path">SSL Certificate Path:</label>
<input type="text" id="ssl_cert_path" class="form-control" name="ssl_cert_path">
<div style="padding-bottom: 15px;" class="form-check">
<input type="checkbox" class="form-check-input" id="ssl_redirect_checkbox" name="ssl_redirect_checkbox">
<label class="form-check-label" for="ssl_redirect_checkbox"><i style="margin: 8px;" class="fas fa-arrow-circle-right"></i>Add Redirect to HTTPS</label>
</div>
</div>
<div style="margin-top: 15px;" class="form-group">
<label for="lb_method">Load Balancing Method:</label>
<select class="form-control" name="lb_method" id="lb_method">
<option value="roundrobin">Round Robin</option>
<option value="leastconn">Least Connections</option>
<option value="source">Source</option>
<option value="wrr">WRR</option>
<option value="wlc">WLC</option>
<option value="random">Random</option>
</select>
</div>
<div class="form-group">
<label for="protocol">Protocol:</label>
<select class="form-control" name="protocol" id="protocol" required>
<option value="" disabled selected>--Select mode--</option>
<option value="tcp">TCP</option>
<option value="http">HTTP</option>
</select>
</div>
<!-- DOS Protection -->
<div class="form-check">
<input type="checkbox" class="form-check-input" name="add_dos" id="add_dos">
<label style="margin-bottom: 10px;" class="form-check-label" for="add_dos"><i style="margin: 8px;" class="fas fa-shield-alt"></i> Add DOS Protection</label>
</div>
<div class="form-group" id="dos_fields" style="display: none; padding-bottom: 20px;">
<label for="limit_requests">Limit Requests, e.g: 20:</label>
<input type="text" class="form-control" name="limit_requests">
<label for="ban_duration">IP Ban Duration(in seconds, e.g: 15s):</label>
<input type="text" class="form-control" name="ban_duration">
</div>
<!-- SQL Injection -->
<div style="margin-top: 8px; padding-bottom: 15px; display: none;" class="form-check" id="sql_injection_container">
<input type="checkbox" class="form-check-input" id="sql_injection_check" name="sql_injection_check">
<label class="form-check-label" for="sql_injection_check"><i style="margin: 8px;" class="fas fa-shield-alt"></i>Activate SQL Injection Protection</label>
</div>
<!-- XSS -->
<div style="margin-top: 8px; padding-bottom: 15px; display: none;" class="form-check" id="XSS_container">
<input type="checkbox" class="form-check-input" id="xss_check" name="xss_check">
<label class="form-check-label" for="xss_check"><i style="margin: 8px;" class="fas fa-shield-alt"></i>Activate XSS Protection</label>
</div>
<!-- Remote File Uploads -->
<div style="margin-top: 8px; padding-bottom: 15px; display: none;" class="form-check" id="remote_uploads_container">
<input type="checkbox" class="form-check-input" id="remote_uploads_check" name="remote_uploads_check">
<label class="form-check-label" for="remote_uploads_check"><i style="margin: 8px;" class="fas fa-shield-alt"></i>Activate Remote File Upload Protection</label>
</div>
<!-- Deny Webshells -->
<div style="margin-top: 8px; padding-bottom: 15px; display: none;" class="form-check" id="webshells_container">
<input type="checkbox" class="form-check-input" id="webshells_check" name="webshells_check">
<label class="form-check-label" for="webshells_check"><i style="margin: 8px;" class="fas fa-shield-alt"></i>Activate Webshells Protection</label>
</div>
<div class="form-check" style="margin-top: 8px;">
<input type="checkbox" class="form-check-input" name="add_acl" id="add_acl">
<label style="margin-bottom: 10px;" class="form-check-label" for="add_acl"><i style="margin: 8px;" class="fas fa-user-lock"></i> Add ACL for Frontend</label>
</div>
<div class="form-group" id="acl_fields" style="display: none; padding-bottom: 20px;">
<label for="acl">ACL:</label>
<input type="text" class="form-control" name="acl" placeholder="example: acl_name">
<label for="acl_action">ACL Action:</label>
<input type="text" class="form-control" name="acl_action" placeholder="example: hdr(host) -i test.com">
<label for="backend_name_acl">Backend Name:</label>
<input type="text" class="form-control" name="backend_name_acl" placeholder="example: somebackend">
</div>
<div class="form-check" id="forbidden_acl_container" style="display: none; margin-top: 8px;">
<input type="checkbox" class="form-check-input" name="add_acl_path" id="add_acl_path">
<label style="margin-bottom: 10px;" class="form-check-label" for="add_acl_path"><i style="margin: 8px;" class="fas fa-ban"></i> Block Sensitive Path</label>
</div>
<div class="form-group" id="forbidden_fields" style="display: none; padding-bottom: 20px;">
<label for="forbidden_name">ACL Name:</label>
<input type="text" class="form-control" name="forbidden_name" id="forbidden_name">
<label for="allowed_ip">Allowed IP Addresses(e.g: 192.168.3.13 193.168.3.14):</label>
<input type="text" class="form-control" name="allowed_ip">
<label for="forbidden_path">Path(e.g: /admin):</label>
<input type="text" class="form-control" name="forbidden_path">
</div>
<div class="form-check" id="path_based_container" style="display: none; margin-top: 8px;">
<input type="checkbox" class="form-check-input" name="add_path_based" id="add_path_based">
<label style="margin-bottom: 10px;" class="form-check-label" for="add_path_based"><i style="margin: 8px;" class="fas fa-arrow-circle-right"></i>Add Path Based Redirect</label>
</div>
<div class="form-group" id="base_redirect_fields" style="display: none; padding-bottom: 20px;">
<label for="redirect_domain_name">Domain to be redirected:</label>
<input type="text" class="form-control" name="redirect_domain_name" id="redirect_domain_name" placeholder="e.g: test2.com or test2.com:8888 (port matters incase of unusual ports)">
<label for="root_redirect">Root Path To Be Redirected:</label>
<input type="text" class="form-control" name="root_redirect" placeholder="e.g: /">
<label for="redirect_to">Redirect To Path:</label>
<input type="text" class="form-control" name="redirect_to" placeholder="e.g: /test **this will redirect test2.com/ to test2.com/test**">
</div>
<div style="margin-top: 8px; padding-bottom: 15px; display: none;" class="form-check" id="forward_for_container">
<input type="checkbox" class="form-check-input" id="forward_for_check" name="forward_for_check">
<label class="form-check-label" for="forward_for_check"><i style="margin: 8px;" class="fas fa-network-wired"></i>Use option forwardfor</label>
</div>
<div style="border-radius: 5px; padding: 20px; margin-bottom: 20px;" class="container mt-5" id="backend_container">
<div class="form-group">
<label style="margin-top: 40px; margin-bottom: 30px; font-size: 18px; font-weight: bold;"><i style="margin-right: 8px;" class="fas fa-sitemap"></i> Backend Servers Pool:</label><br>
<!-- HTTP Health Check -->
<div class="form-group" id="health_check_container_http" style="display: none;">
<input type="checkbox" name="health_check" id="health_check" onchange="toggleHealthCheck()">
<label for="health_check">
<i style="margin: 8px;" class="fas fa-heartbeat"></i>
Enable Health Check <strong>(HTTP mode only)</strong>
</label>
</div>
<div class="form-group" id="health_check_field" style="display: none;">
<label for="health_check_link">Health Check URL (e.g: your-website.com/health.html):</label>
<input type="text" class="form-control" name="health_check_link">
</div>
<!-- TCP Health Check -->
<div class="form-group" id="health_check_container_tcp" style="display: none;">
<input type="checkbox" name="health_check2" id="health_check2" onchange="toggleHealthCheck2()">
<label for="health_check2">
<i style="margin: 8px;" class="fas fa-heartbeat"></i>
Enable Health Check <strong>(TCP mode)</strong>
</label>
</div>
<!-- Sticky Session -->
<div class="form-group">
<input type="checkbox" name="sticky_session" id="sticky_session" onclick="toggleStickySession()">
<label for="sticky_session"><i style="margin: 8px;" class="fas fa-link"></i> Enable Sticky Session</label>
</div>
<div class="form-group" id="sticky_session_field" style="display: none;">
<label for="sticky_session_type">Session Persistence Type:</label>
<select class="form-control" name="sticky_session_type">
<option value="cookie">Cookie SERVERID</option>
<option value="stick-table">Stick-Table ip</option>
</select>
</div>
<!-- Custom Header -->
<div class="form-group">
<input type="checkbox" name="add_header" id="add_header" onclick="toggleCustomHeader()">
<label for="add_header"><i style="margin: 8px;" class="fas fa-reply"></i> Add Custom Header</label>
</div>
<div class="form-group" id="header_field" style="display: none;">
<label for="header_name">Header Name:</label>
<input type="text" class="form-control" name="header_name" placeholder="e.g: X-Client-IP">
<label for="header_value">Header Value:</label>
<input type="text" class="form-control" name="header_value" placeholder="e.g: test">
</div>
<div class="form-group">
<label for="backend_name">Backend Name:</label>
<input type="text" class="form-control" name="backend_name" required>
</div>
<!-- Backend Servers Section -->
<div id="backend_servers_container">
<!-- Initial Backend Server Row -->
<div class="form-row backend-server-row">
<div class="form-group col-md-3">
<label for="name1">Backend Server Name:</label>
<input type="text" id="name1" class="form-control" name="backend_server_names[]" placeholder="server1" required>
</div>
<div class="form-group col-md-3">
<label for="ip1">Backend Server IP:</label>
<input type="text" id="ip1" class="form-control" name="backend_server_ips[]" required>
</div>
<div class="form-group col-md-3">
<label for="port1">Backend Server Port:</label>
<input type="number" id="port1" class="form-control" name="backend_server_ports[]" required>
</div>
<div class="form-group col-md-3">
<label for="maxconn1">MaxConn:</label>
<input type="number" id="maxconn1" class="form-control" name="backend_server_maxconns[]">
</div>
</div>
</div>
<button type="button" class="btn btn-secondary" onclick="addBackendServer()">+ Add Backend Server</button>
<input type="submit" id="success_btn" class="btn btn-success" value="Submit">
</div>
</div>
<script>
// Function to add a new backend server row
let serverCount = 1;
function addBackendServer() {
serverCount++;
const container = document.getElementById('backend_servers_container');
const row = document.createElement('div');
row.classList.add('form-row', 'backend-server-row', 'mt-2');
// Generate unique IDs
const nameId = `name${serverCount}`;
const ipId = `ip${serverCount}`;
const portId = `port${serverCount}`;
const maxconnId = `maxconn${serverCount}`;
row.innerHTML = `
<div class="form-group col-md-3">
<label for="${nameId}">Backend Server Name:</label>
<input type="text" id="${nameId}" class="form-control" name="backend_server_names[]" placeholder="server${serverCount}" required>
</div>
<div class="form-group col-md-3">
<label for="${ipId}">Backend Server IP:</label>
<input type="text" id="${ipId}" class="form-control" name="backend_server_ips[]" required>
</div>
<div class="form-group col-md-3">
<label for="${portId}">Backend Server Port:</label>
<input type="number" id="${portId}" class="form-control" name="backend_server_ports[]" required>
</div>
<div class="form-group col-md-3">
<label for="${maxconnId}">MaxConn:</label>
<input type="number" id="${maxconnId}" class="form-control" name="backend_server_maxconns[]">
<button type="button" class="btn btn-danger btn-sm mt-2" onclick="removeBackendServer(this)">Remove</button>
</div>
`;
container.appendChild(row);
}
function removeBackendServer(button) {
const row = button.closest('.backend-server-row');
if (document.querySelectorAll('.backend-server-row').length > 1) {
row.remove();
} else {
alert("You must have at least one backend server.");
}
}
// Protocol change handler to show/hide health check options
document.getElementById('protocol').addEventListener('change', function() {
const protocol = this.value;
const httpHealthCheck = document.getElementById('health_check_container_http');
const tcpHealthCheck = document.getElementById('health_check_container_tcp');
if (protocol === 'http') {
httpHealthCheck.style.display = 'block';
tcpHealthCheck.style.display = 'none';
} else if (protocol === 'tcp') {
httpHealthCheck.style.display = 'none';
tcpHealthCheck.style.display = 'block';
} else {
httpHealthCheck.style.display = 'none';
tcpHealthCheck.style.display = 'none';
}
});
// Toggle functions (you'll need to implement these based on your existing JavaScript)
function toggleHealthCheck() {
const field = document.getElementById('health_check_field');
const checkbox = document.getElementById('health_check');
field.style.display = checkbox.checked ? 'block' : 'none';
}
function toggleHealthCheck2() {
// Implement TCP health check toggle if needed
}
function toggleStickySession() {
const field = document.getElementById('sticky_session_field');
const checkbox = document.getElementById('sticky_session');
field.style.display = checkbox.checked ? 'block' : 'none';
}
function toggleCustomHeader() {
const field = document.getElementById('header_field');
const checkbox = document.getElementById('add_header');
field.style.display = checkbox.checked ? 'block' : 'none';
}
</script>
</form>
<!-- Add Bootstrap JS and jQuery (required for some Bootstrap components) -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.0.7/dist/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script>
// Function to hide the success message after a few seconds
$(document).ready(function() {
setTimeout(function() {
$('.alert').alert('close');
}, 5000); // Hide the message after 3 seconds (adjust the time as needed)
});
</script>
<script>
// Function to toggle the sticky session field based on checkbox selection
function toggleCustomHeader() {
const CustomHeaderbox = document.getElementById('add_header');
const CustomHeaderField = document.getElementById('header_field');
if (CustomHeaderbox.checked) {
CustomHeaderField.style.display = 'block';
} else {
CustomHeaderField.style.display = 'none';
}
}
// Event listener to toggle the sticky session field when checkbox is clicked
document.getElementById('add_header').addEventListener('click', toggleCustomHeader);
</script>
<script>
// Function to toggle the health check field based on the protocol selected
function toggleHealthCheck() {
const healthCheckField = document.getElementById('health_check_field');
const healthCheckCheckbox = document.getElementById('health_check');
if (healthCheckCheckbox.checked) {
healthCheckField.style.display = 'block';
} else {
healthCheckField.style.display = 'none';
}
}
// Event listener to toggle the health check field when protocol changes
document.querySelector('select[name="protocol"]').addEventListener('change', toggleHealthCheck);
</script>
<script>
function toggleHealthCheck2() {
const protocolSelect = document.getElementById('protocol');
const healthCheckContainer = document.getElementById('health_check_container');
const healthCheckContainer2 = document.getElementById('health_check_container2');
const healthCheckCheckbox2 = document.getElementById('health_check2');
if (protocolSelect.value !== 'http') {
healthCheckContainer.style.display = 'none';
healthCheckContainer2.style.display = 'block';
}
else {
healthCheckContainer.style.display = 'block';
}
}
document.getElementById('protocol').addEventListener('change', toggleHealthCheck2);
</script>
<script>
function toggleHealthCheck() {
const protocolSelect = document.getElementById('protocol');
const healthCheckContainer = document.getElementById('health_check_container');
const healthCheckField = document.getElementById('health_check_field');
const healthCheckCheckbox = document.getElementById('health_check');
const healthCheckContainer2 = document.getElementById('health_check_container2');
const healthCheckCheckbox2 = document.getElementById('health_check2');
if (protocolSelect.value === 'http') {
healthCheckContainer.style.display = 'block';
if (healthCheckCheckbox.checked) {
healthCheckField.style.display = 'block';
} else {
healthCheckField.style.display = 'none';
healthCheckContainer2.style.display = 'none';
healthCheckCheckbox2.style.display = 'none';
}
} else {
healthCheckContainer.style.display = 'none';
healthCheckField.style.display = 'none';
}
}
document.getElementById('protocol').addEventListener('change', toggleHealthCheck);
</script>
<script>
// Function to toggle the sticky session field based on checkbox selection
function toggleStickySession() {
const stickySessionCheckbox = document.getElementById('sticky_session');
const stickySessionField = document.getElementById('sticky_session_field');
if (stickySessionCheckbox.checked) {
stickySessionField.style.display = 'block';
} else {
stickySessionField.style.display = 'none';
}
}
// Event listener to toggle the sticky session field when checkbox is clicked
document.getElementById('sticky_session').addEventListener('click', toggleStickySession);
</script>
<script>
// Function to toggle the visibility of ACL fields based on the checkbox
const DosCheckbox = document.getElementById('add_dos');
const DosFields = document.getElementById('dos_fields');
DosCheckbox.addEventListener('change', () => {
if (DosCheckbox.checked) {
DosFields.style.display = 'block';
} else {
DosFields.style.display = 'none';
}
});
</script>
<script>
// Function to toggle the visibility of ACL fields based on the checkbox
const aclCheckbox = document.getElementById('add_acl');
const aclFields = document.getElementById('acl_fields');
aclCheckbox.addEventListener('change', () => {
if (aclCheckbox.checked) {
aclFields.style.display = 'block';
} else {
aclFields.style.display = 'none';
}
});
</script>
<script>
function toggleSqlInjection() {
const protocolSelect5 = document.getElementById('protocol');
const SqlInjectionContainer = document.getElementById('sql_injection_container');
if (protocolSelect5.value === 'http') {
SqlInjectionContainer.style.display = 'block';
}
else {
SqlInjectionContainer.style.display = 'none';
}
}
document.getElementById('protocol').addEventListener('change', toggleSqlInjection);
</script>
<script>
function Webshells() {
const protocolSelect8 = document.getElementById('protocol');
const WebshellsContainer = document.getElementById('webshells_container');
if (protocolSelect8.value === 'http') {
WebshellsContainer.style.display = 'block';
}
else {
WebshellsContainer.style.display = 'none';
}
}
document.getElementById('protocol').addEventListener('change', Webshells);
</script>
<script>
function RemoteUploads() {
const protocolSelect7 = document.getElementById('protocol');
const RemoteUploadsContainer = document.getElementById('remote_uploads_container');
if (protocolSelect7.value === 'http') {
RemoteUploadsContainer.style.display = 'block';
}
else {
RemoteUploadsContainer.style.display = 'none';
}
}
document.getElementById('protocol').addEventListener('change', RemoteUploads);
</script>
<script>
function toggleXSS() {
const protocolSelect6 = document.getElementById('protocol');
const XSSContainer = document.getElementById('XSS_container');
if (protocolSelect6.value === 'http') {
XSSContainer.style.display = 'block';
}
else {
XSSContainer.style.display = 'none';
}
}
document.getElementById('protocol').addEventListener('change', toggleXSS);
</script>
<script>
function toggleForwardFor() {
const protocolSelect4 = document.getElementById('protocol');
const ForwardForContainer = document.getElementById('forward_for_container');
if (protocolSelect4.value === 'http') {
ForwardForContainer.style.display = 'block';
}
else {
ForwardForContainer.style.display = 'none';
}
}
document.getElementById('protocol').addEventListener('change', toggleForwardFor);
</script>
<script>
function toggleForbiddenCheck() {
const protocolSelect3 = document.getElementById('protocol');
const forbiddenContainer = document.getElementById('forbidden_acl_container');
if (protocolSelect3.value === 'http') {
forbiddenContainer.style.display = 'block';
}
else {
forbiddenContainer.style.display = 'none';
}
}
document.getElementById('protocol').addEventListener('change', toggleForbiddenCheck);
</script>
<script>
function PathBasedToggle() {
const protocolSelect7 = document.getElementById('protocol');
const path_based_container = document.getElementById('path_based_container');
if (protocolSelect7.value === 'http') {
path_based_container.style.display = 'block';
}
else {
path_based_container.style.display = 'none';
}
}
document.getElementById('protocol').addEventListener('change', PathBasedToggle);
</script>
<script>
// Function to toggle the visibility of based path redirect fields based on the checkbox
const add_path_based_checkbox = document.getElementById('add_path_based');
const redirect_path_fields = document.getElementById('base_redirect_fields');
add_path_based_checkbox.addEventListener('change', () => {
if (add_path_based_checkbox.checked) {
redirect_path_fields.style.display = 'block';
} else {
redirect_path_fields.style.display = 'none';
}
});
</script>
<script>
// Function to toggle the visibility of ACL forbidden paths fields based on the checkbox
const aclCheckbox2 = document.getElementById('add_acl_path');
const aclFields2 = document.getElementById('forbidden_fields');
aclCheckbox2.addEventListener('change', () => {
if (aclCheckbox2.checked) {
aclFields2.style.display = 'block';
} else {
aclFields2.style.display = 'none';
}
});
</script>
<script>
document.getElementById('ssl_checkbox').addEventListener('change', function () {
const sslFields = document.getElementById('ssl_fields');
sslFields.style.display = this.checked ? 'block' : 'none';
});
</script>
</body>
</html>

209
templates/logs.html Normal file
View File

@@ -0,0 +1,209 @@
<!DOCTYPE html>
<html>
<head>
<title>Log Entries</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<style>
body {
}
.log-entry {
padding: 10px;
border: 1px solid #ddd;
margin-bottom: 10px;
border-radius: 5px;
}
.collapse-trigger {
cursor: pointer;
}
/* Custom CSS for the header */
header {
background-color: #f2f2f2;
padding: 20px;
display: flex;
padding-left: 100px;
align-items: center;
}
.log-entry{
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
h3{
color: grey;
}
.logo {
width: 300px; /* Adjust the width as needed */
height: auto;
}
.menu-link {
text-decoration: none;
padding: 10px 20px;
color: #333;
font-weight: bold;
}
.menu-link:hover {
background-color: #3B444B;
color: white;
text-decoration: none;
}
/* Custom CSS for dark mode */
.dark-mode {
background-color: #121B2E;
color: white;
}
.dark-mode .header1{
background-color: #25354e;
color: white;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
.dark-mode .menu-link{
text-decoration: none;
padding: 10px 20px;
color: white;
font-weight: bold;
}
.dark-mode .logo {
color: #2bb9c7;
font-weight: bold;
}
.dark-mode .fas-fa-globe{
color: #2bb9c7;
}
.dark-mode .menu-link:hover{
text-decoration: none;
padding: 10px 20px;
color: #2bb9c7;
font-weight: bold;
}
.dark-mode #status_header{
color: #2bb9c7;
}
.dark-mode .log-entry{
box-shadow: 0 0 15px 5px rgba(43, 185, 199, 0.05);
background-color: #1E2C42;
border: none;
}
.dark-mode #requested_url{
color: #2bb9c7;
}
</style>
</head>
<body>
<header class="header1" id="header1>
<a href="/home" style="text-decoration: none;">
<h3 style="font-size: 22px;" class="logo">
<i style="margin: 8px;" class="fas fa-globe"></i>Haproxy Configurator
</h3>
</a>
<a href="/home" class="menu-link">Home</a>
<a href="/edit" class="menu-link">Edit HAProxy Config</a>
<a href="/" class="menu-link">Add Frontend&Backend</a>
<a href="/logs" class="menu-link">Security Events</a>
<a href="/statistics" class="menu-link">Statictics</a>
<a href="http://{{ request.host.split(':')[0] }}:8080/stats" class="menu-link" >HAProxy Stats</a>
<div class="custom-control custom-switch ml-auto">
<input type="checkbox" class="custom-control-input" id="darkModeSwitch">
<label class="custom-control-label" for="darkModeSwitch">Dark Mode</label>
</div>
<script>
// Function to toggle dark mode
function toggleDarkMode() {
const body = document.body;
body.classList.toggle('dark-mode');
// Save user's preference to localStorage
const isDarkMode = body.classList.contains('dark-mode');
localStorage.setItem('darkMode', isDarkMode); // Store the actual value
}
// Check if dark mode preference is saved in localStorage
const savedDarkMode = localStorage.getItem('darkMode');
if (savedDarkMode === 'true') {
document.body.classList.add('dark-mode');
}
// Add event listener to the switch
const darkModeSwitch = document.getElementById('darkModeSwitch');
darkModeSwitch.addEventListener('change', toggleDarkMode);
</script>
</header>
<h3 style="margin-top: 30px; margin-bottom: 30px; margin-left: 10%;" id="status_header">Status 403 Forbidden Log Entries</h3>
<div>
{% for entry in entries %}
<div class="log-entry" style="padding: 20px; width: 80%; margin-left: 10%; margin-bottom: 1%;">
<p><strong>Time Stamp:</strong> {{ entry['timestamp'] }}</p>
<p><strong>IP Address:</strong> {{ entry['ip_address'] }}</p>
<p><strong>HTTP Method:</strong> {{ entry['http_method'] }}</p>
<p id="requested_url"><strong>Requested URL:</strong> {{ entry['requested_url'] }}</p>
<!-- XSS Category -->
{% if entry['xss_alert'] %}
<p class="collapse-trigger" data-bs-toggle="collapse" data-bs-target="#xssCollapse{{ loop.index }}">XSS Alert <span class="text-danger">(Click to show details)</span></p>
<div id="xssCollapse{{ loop.index }}" class="collapse">
<p style="color: red"><strong>{{ entry['xss_alert'] }}</strong></p>
</div>
{% endif %}
<!-- SQL Category -->
{% if entry['sql_alert'] %}
<p class="collapse-trigger" data-bs-toggle="collapse" data-bs-target="#sqlCollapse{{ loop.index }}">SQL Alert <span class="text-danger">(Click to show details)</span></p>
<div id="sqlCollapse{{ loop.index }}" class="collapse">
<p style="color: red"><strong>{{ entry['sql_alert'] }}</strong></p>
</div>
{% endif %}
<!-- PUT Method Category -->
{% if entry['put_method'] %}
<p class="collapse-trigger" data-bs-toggle="collapse" data-bs-target="#putMethodCollapse{{ loop.index }}">PUT Method Alert <span class="text-danger">(Click to show details)</span></p>
<div id="putMethodCollapse{{ loop.index }}" class="collapse">
<p style="color: red"><strong>{{ entry['put_method'] }}</strong></p>
</div>
{% endif %}
<!-- Illegal Resource Access -->
{% if entry['illegal_resource'] %}
<p class="collapse-trigger" data-bs-toggle="collapse" data-bs-target="#putMethodCollapse{{ loop.index }}">Illegal Resource Access Alert <span class="text-danger">(Click to show details)</span></p>
<div id="putMethodCollapse{{ loop.index }}" class="collapse">
<p style="color: red"><strong>{{ entry['illegal_resource'] }}</strong></p>
</div>
{% endif %}
<!-- Illegal Resource Access -->
{% if entry['webshell_alert'] %}
<p class="collapse-trigger" data-bs-toggle="collapse" data-bs-target="#putMethodCollapse{{ loop.index }}">WebShell Attack Alert <span class="text-danger">(Click to show details)</span></p>
<div id="putMethodCollapse{{ loop.index }}" class="collapse">
<p style="color: red"><strong>{{ entry['webshell_alert'] }}</strong></p>
</div>
{% endif %}
<p><strong>Status Code:</strong> 403</p>
</div>
{% endfor %}
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

151
utils/haproxy_config.py Normal file
View File

@@ -0,0 +1,151 @@
def is_frontend_exist(frontend_name, frontend_ip, frontend_port):
with open('/etc/haproxy/haproxy.cfg', 'r') as haproxy_cfg:
frontend_found = False
for line in haproxy_cfg:
if line.strip().startswith('frontend'):
_, existing_frontend_name = line.strip().split(' ', 1)
if existing_frontend_name.strip() == frontend_name:
frontend_found = True
else:
frontend_found = False
elif frontend_found and line.strip().startswith('bind'):
_, bind_info = line.strip().split(' ', 1)
existing_ip, existing_port = bind_info.split(':', 1)
if existing_ip.strip() == frontend_ip and existing_port.strip() == frontend_port:
return True
return False
def is_backend_exist(backend_name):
with open('/etc/haproxy/haproxy.cfg', 'r') as haproxy_cfg:
for line in haproxy_cfg:
line = line.strip()
if line.startswith('backend') and not line.startswith('#'):
parts = line.split()
if len(parts) >= 2 and parts[1] == backend_name:
return True
return False
def update_haproxy_config(frontend_name, frontend_ip, frontend_port, lb_method, protocol, backend_name, backend_servers, health_check, health_check_tcp, health_check_link, sticky_session, add_header, header_name, header_value, sticky_session_type, is_acl, acl_name, acl_action, acl_backend_name, use_ssl, ssl_cert_path, https_redirect, is_dos, ban_duration, limit_requests, forward_for, is_forbidden_path, forbidden_name, allowed_ip, forbidden_path, sql_injection_check, is_xss, is_remote_upload, add_path_based, redirect_domain_name, root_redirect, redirect_to, is_webshells):
if is_backend_exist(backend_name):
return f"Backend {backend_name} already exists. Cannot add duplicate."
with open('/etc/haproxy/haproxy.cfg', 'a') as haproxy_cfg:
haproxy_cfg.write(f"\nfrontend {frontend_name}\n")
if is_frontend_exist(frontend_name, frontend_ip, frontend_port):
return "Frontend or Port already exists. Cannot add duplicate."
haproxy_cfg.write(f" bind {frontend_ip}:{frontend_port}")
if use_ssl:
haproxy_cfg.write(f" ssl crt {ssl_cert_path}\n")
if https_redirect:
haproxy_cfg.write(f" redirect scheme https code 301 if !{{ ssl_fc }}")
haproxy_cfg.write("\n")
if forward_for:
haproxy_cfg.write(f" option forwardfor\n")
haproxy_cfg.write(f" mode {protocol}\n")
haproxy_cfg.write(f" balance {lb_method}\n")
if is_dos:
haproxy_cfg.write(f" stick-table type ip size 1m expire {ban_duration} store http_req_rate(1m)\n")
haproxy_cfg.write(f" http-request track-sc0 src\n")
haproxy_cfg.write(f" acl abuse sc_http_req_rate(0) gt {limit_requests}\n")
haproxy_cfg.write(f" http-request silent-drop if abuse\n")
if sql_injection_check:
haproxy_cfg.write(f" acl is_sql_injection urlp_reg -i (union|select|insert|update|delete|drop|@@|1=1|`1)\n")
haproxy_cfg.write(f" acl is_long_uri path_len gt 400\n")
haproxy_cfg.write(f" acl semicolon_path path_reg -i ^.*;.*\n")
haproxy_cfg.write(f" acl is_sql_injection2 urlp_reg -i (;|substring|extract|union\s+all|order\s+by)\s+(\d+|--\+)\n")
haproxy_cfg.write(f" http-request deny if is_sql_injection or is_long_uri or semicolon_path or is_sql_injection2\n")
if is_xss:
haproxy_cfg.write(f" acl is_xss_attack urlp_reg -i (<|>|script|alert|onerror|onload|javascript)\n")
haproxy_cfg.write(f" acl is_xss_attack_2 urlp_reg -i (<\s*script\s*|javascript:|<\s*img\s*src\s*=|<\s*a\s*href\s*=|<\s*iframe\s*src\s*=|\bon\w+\s*=|<\s*input\s*[^>]*\s*value\s*=|<\s*form\s*action\s*=|<\s*svg\s*on\w+\s*=)\n")
haproxy_cfg.write(f" acl is_xss_attack_hdr hdr_reg(Cookie|Referer|User-Agent) -i (<|>|script|alert|onerror|onload|javascript)\n")
haproxy_cfg.write(" acl is_xss_cookie hdr_beg(Cookie) -i \"<script\" \"javascript:\" \"on\" \"alert(\" \"iframe\" \"onload\" \"onerror\" \"onclick\" \"onmouseover\"\n")
haproxy_cfg.write(f" http-request deny if is_xss_attack or is_xss_attack_hdr or is_xss_attack_2 or is_xss_cookie\n")
if is_remote_upload:
haproxy_cfg.write(f" acl is_put_request method PUT\n")
haproxy_cfg.write(f" http-request deny if is_put_request\n")
if is_acl:
haproxy_cfg.write(f" acl {acl_name} {acl_action}\n")
haproxy_cfg.write(f" use_backend {acl_backend_name} if {acl_name}\n")
if is_forbidden_path:
haproxy_cfg.write(f" acl {forbidden_name} src {allowed_ip}\n")
haproxy_cfg.write(f" http-request deny if !{forbidden_name} {{ path_beg {forbidden_path} }}\n")
if add_path_based:
haproxy_cfg.write(f" acl is_test_com hdr(host) -i {redirect_domain_name}\n")
haproxy_cfg.write(f" acl is_root path {root_redirect}\n")
haproxy_cfg.write(f" http-request redirect location {redirect_to} if is_test_com or is_root\n")
if is_webshells:
haproxy_cfg.write(f" option http-buffer-request\n")
haproxy_cfg.write(f" acl is_webshell urlp_reg(payload,eval|system|passthru|shell_exec|exec|popen|proc_open|pcntl_exec)\n")
haproxy_cfg.write(f" acl is_potential_webshell urlp_reg(payload,php|jsp|asp|aspx)\n")
haproxy_cfg.write(f" acl blocked_webshell path_reg -i /(cmd|shell|backdoor|webshell|phpspy|c99|kacak|b374k|log4j|log4shell|wsos|madspot|malicious|evil).*\.php.*\n")
haproxy_cfg.write(f" acl is_suspicious_post hdr(Content-Type) -i application/x-www-form-urlencoded multipart/form-data\n")
haproxy_cfg.write(f" http-request deny if blocked_webshell or is_webshell or is_potential_webshell or is_suspicious_post \n")
haproxy_cfg.write(f" default_backend {backend_name}\n")
with open('/etc/haproxy/haproxy.cfg', 'a') as haproxy_cfg:
haproxy_cfg.write(f"\nbackend {backend_name}\n")
if sticky_session and sticky_session_type == 'cookie':
haproxy_cfg.write(" cookie SERVERID insert indirect nocache\n")
if sticky_session and sticky_session_type == 'stick-table':
haproxy_cfg.write(" stick-table type ip size 200k expire 5m\n")
haproxy_cfg.write(" stick on src\n")
if add_header:
haproxy_cfg.write(f" http-request set-header {header_name} \"{header_value}\"\n")
if protocol == 'http':
if health_check:
haproxy_cfg.write(f" option httpchk GET {health_check_link}\n")
haproxy_cfg.write(f" http-check disable-on-404\n")
haproxy_cfg.write(f" http-check expect string OK\n")
if protocol == 'tcp':
if health_check_tcp:
haproxy_cfg.write(f" option tcp-check\n")
haproxy_cfg.write(" tcp-check send PING" + r"\r\n" + "\n")
haproxy_cfg.write(" tcp-check send QUIT" + r"\r\n" + "\n")
# Process all backend servers
for i, backend_server in enumerate(backend_servers, 1):
if len(backend_server) >= 3: # Ensure we have name, ip and port
backend_server_name = backend_server[0] or f"server{i}"
backend_server_ip = backend_server[1]
backend_server_port = backend_server[2]
backend_server_maxconn = backend_server[3] if len(backend_server) > 3 else None
line = f" server {backend_server_name} {backend_server_ip}:{backend_server_port} check"
if sticky_session and sticky_session_type == 'cookie':
line += f" cookie {backend_server_name}"
if backend_server_maxconn:
line += f" maxconn {backend_server_maxconn}"
haproxy_cfg.write(line + "\n")
return "Frontend and Backend added successfully."
def count_frontends_and_backends():
frontend_count = 0
backend_count = 0
acl_count = 0
layer7_count = 0
layer4_count = 0
with open('/etc/haproxy/haproxy.cfg', 'r') as haproxy_cfg:
lines = haproxy_cfg.readlines()
for line in lines:
line = line.strip()
if line.startswith('frontend '):
frontend_count += 1
if line.startswith('acl '):
acl_count += 1
if line.startswith('mode http'):
layer7_count += 1
if line.startswith('mode tcp'):
layer4_count += 1
elif line.startswith('backend '):
backend_count += 1
return frontend_count, backend_count, acl_count, layer7_count, layer4_count

31
utils/stats_utils.py Normal file
View File

@@ -0,0 +1,31 @@
import requests
import csv
HAPROXY_STATS_URL = 'http://127.0.0.1:8080/;csv'
def fetch_haproxy_stats():
try:
response = requests.get(HAPROXY_STATS_URL)
response.raise_for_status()
return response.text
except requests.exceptions.RequestException as e:
return str(e)
def parse_haproxy_stats(stats_data):
data = []
# Remove the '#' character from the header row
header_row = stats_data.splitlines()[0].replace('# ', '')
reader = csv.DictReader(stats_data.splitlines(), fieldnames=header_row.split(','))
next(reader) # Skip the header row
for row in reader:
if row['svname'] != 'BACKEND':
data.append({
'frontend_name': row['pxname'],
'server_name': row['svname'],
'4xx_errors': row['hrsp_4xx'],
'5xx_errors': row['hrsp_5xx'],
'bytes_in_mb': f'{float(row["bin"]) / (1024 * 1024):.2f}',
'bytes_out_mb': f'{float(row["bout"]) / (1024 * 1024):.2f}',
'conn_tot': row['conn_tot'],
})
return data