From 1111d59c2b88fa32ca122f73e30cb6e1677d15dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Sat, 1 Nov 2025 19:15:49 +0100 Subject: [PATCH] first commit --- .dockerignore | 9 + Dockerfile | 49 ++ Makefile | 4 + README.md | 201 ++++++++ app.py | 122 +++++ auth/auth.cfg | 3 + auth/auth_middleware.py | 33 ++ docker-compose.yml | 42 ++ entrypoint.sh | 77 ++++ install.sh | 38 ++ log_parser.py | 103 +++++ requirements.txt | 3 + routes/edit_routes.py | 57 +++ routes/main_routes.py | 111 +++++ ssl.ini | 3 + ssl/haproxy-configurator.pem | 50 ++ supervisord.conf | 36 ++ templates/edit.html | 211 +++++++++ templates/home.html | 152 ++++++ templates/index.html | 867 +++++++++++++++++++++++++++++++++++ templates/logs.html | 209 +++++++++ utils/haproxy_config.py | 151 ++++++ utils/stats_utils.py | 31 ++ 23 files changed, 2562 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 README.md create mode 100644 app.py create mode 100644 auth/auth.cfg create mode 100644 auth/auth_middleware.py create mode 100644 docker-compose.yml create mode 100644 entrypoint.sh create mode 100755 install.sh create mode 100644 log_parser.py create mode 100644 requirements.txt create mode 100644 routes/edit_routes.py create mode 100644 routes/main_routes.py create mode 100644 ssl.ini create mode 100644 ssl/haproxy-configurator.pem create mode 100644 supervisord.conf create mode 100644 templates/edit.html create mode 100644 templates/home.html create mode 100644 templates/index.html create mode 100644 templates/logs.html create mode 100644 utils/haproxy_config.py create mode 100644 utils/stats_utils.py diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c3140a4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +__pycache__ +*.pyc +.git +.gitignore +.env +*.log +.pytest_cache +venv/ +.vscode diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2a9eb85 --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f7c9f71 --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +.PHONY: install + +install: + @pip install -r requirements.txt diff --git a/README.md b/README.md new file mode 100644 index 0000000..4741679 --- /dev/null +++ b/README.md @@ -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. + +--- + +## πŸš€ What’s 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! diff --git a/app.py b/app.py new file mode 100644 index 0000000..23691de --- /dev/null +++ b/app.py @@ -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(''' + +
+ + + + Home + Add Frontend&Backend + Edit HAProxy Config + Security Events + Statistics + HAProxy Stats +
+ +
+

HAProxy Stats

+
+ + + + + + + + + + + + + + {% for stat in stats %} + + + + + + + + + + {% endfor %} + +
Frontend NameServer Name4xx Errors5xx ErrorsBytes In (MB)Bytes Out (MB)Total Connections
{{ stat.frontend_name }}{{ stat.server_name }}{{ stat['4xx_errors'] }}{{ stat['5xx_errors'] }}{{ stat.bytes_in_mb }}{{ stat.bytes_out_mb }}{{ stat.conn_tot }}
+
+
+ ''', 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) diff --git a/auth/auth.cfg b/auth/auth.cfg new file mode 100644 index 0000000..dd7a194 --- /dev/null +++ b/auth/auth.cfg @@ -0,0 +1,3 @@ +[auth] +username = admin +password = secret123 diff --git a/auth/auth_middleware.py b/auth/auth_middleware.py new file mode 100644 index 0000000..1d49cb0 --- /dev/null +++ b/auth/auth_middleware.py @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..4f95045 --- /dev/null +++ b/docker-compose.yml @@ -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 diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..c9834e9 --- /dev/null +++ b/entrypoint.sh @@ -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 < /app/config/ssl.ini < /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 diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..5f18036 --- /dev/null +++ b/install.sh @@ -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 diff --git a/log_parser.py b/log_parser.py new file mode 100644 index 0000000..414b506 --- /dev/null +++ b/log_parser.py @@ -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 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d8d69c3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +flask +requests +pyOpenSSL diff --git a/routes/edit_routes.py b/routes/edit_routes.py new file mode 100644 index 0000000..560644b --- /dev/null +++ b/routes/edit_routes.py @@ -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) diff --git a/routes/main_routes.py b/routes/main_routes.py new file mode 100644 index 0000000..f2095b8 --- /dev/null +++ b/routes/main_routes.py @@ -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) diff --git a/ssl.ini b/ssl.ini new file mode 100644 index 0000000..07454ef --- /dev/null +++ b/ssl.ini @@ -0,0 +1,3 @@ +[ssl] +certificate_path = /etc/haproxy-configurator/ssl/haproxy-configurator.pem +private_key_path = /etc/haproxy-configurator/ssl/haproxy-configurator.pem \ No newline at end of file diff --git a/ssl/haproxy-configurator.pem b/ssl/haproxy-configurator.pem new file mode 100644 index 0000000..d19c2e3 --- /dev/null +++ b/ssl/haproxy-configurator.pem @@ -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----- diff --git a/supervisord.conf b/supervisord.conf new file mode 100644 index 0000000..2290700 --- /dev/null +++ b/supervisord.conf @@ -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 diff --git a/templates/edit.html b/templates/edit.html new file mode 100644 index 0000000..9434bc4 --- /dev/null +++ b/templates/edit.html @@ -0,0 +1,211 @@ + + + + Edit HAProxy Config + + + + + + +
+ + + + Home + Add Frontend & Backend + Edit HAProxy Config + Security Events + Statictics + HAProxy Stats + +
+ + +
+ + + +
+
+

Edit HAProxy Config

+
+
+ + +
+
+ + +
+
+ {% if check_output %} +
+ {% if 'Fatal errors' in check_output %} + + {% elif 'Warnings' in check_output %} + + {% elif 'error detected while parsing an' in check_output %} + + {% else %} + + {% endif %} +
+ {% endif %} + + + + + + + + diff --git a/templates/home.html b/templates/home.html new file mode 100644 index 0000000..364be6a --- /dev/null +++ b/templates/home.html @@ -0,0 +1,152 @@ + + + + Home Page + + + + + + + +
+ + + + Home + Add Frontend & Backend + Edit HAProxy Config + Security Events + Statictics + HAProxy Stats + +
+ + +
+ +
+ +
+

Welcome to Your HAProxy Configurator. Here's A Short Summary:

+ +

{{ frontend_count }} frontends

+

{{ backend_count }} backends

+

{{ acl_count }} acl's

+

{{ layer7_count }} layer7(mode http) loadbalanced frontends

+

{{ layer4_count }} layer4(mode tcp)loadbalanced frontends

+ +
+ + + + + + + + + + + diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..f0109a2 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,867 @@ + + + + HAProxy Configurator + + + + + + + + +
+ + + + Home + Add Frontend&Backend + Edit HAProxy Config + Security Events + Statictics + HAProxy Stats +
+ + +
+ + +
+ + +
+
+
+ + {% if message %} + + {% endif %} + +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+ + +
+ + + + +
+ + +
+ +
+ + +
+ + +
+ + +
+ + + + + + + + + + + + + + + +
+ + +
+ + + + + + + + + + + + + +
+
+
+ + + + + + + + + + +
+ + +
+ + + + +
+ + +
+ + + +
+ + +
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/logs.html b/templates/logs.html new file mode 100644 index 0000000..db90a55 --- /dev/null +++ b/templates/logs.html @@ -0,0 +1,209 @@ + + + + Log Entries + + + + + + + +
+ + + Home + Edit HAProxy Config + Add Frontend&Backend + Security Events + Statictics + HAProxy Stats +
+ + +
+ + + + +
+

Status 403 Forbidden Log Entries

+
+ {% for entry in entries %} +
+

Time Stamp: {{ entry['timestamp'] }}

+

IP Address: {{ entry['ip_address'] }}

+

HTTP Method: {{ entry['http_method'] }}

+

Requested URL: {{ entry['requested_url'] }}

+ + + {% if entry['xss_alert'] %} +

XSS Alert (Click to show details)

+
+

{{ entry['xss_alert'] }}

+
+ {% endif %} + + + {% if entry['sql_alert'] %} +

SQL Alert (Click to show details)

+
+

{{ entry['sql_alert'] }}

+
+ {% endif %} + + + {% if entry['put_method'] %} +

PUT Method Alert (Click to show details)

+
+

{{ entry['put_method'] }}

+
+ {% endif %} + + + {% if entry['illegal_resource'] %} +

Illegal Resource Access Alert (Click to show details)

+
+

{{ entry['illegal_resource'] }}

+
+ {% endif %} + + + {% if entry['webshell_alert'] %} +

WebShell Attack Alert (Click to show details)

+
+

{{ entry['webshell_alert'] }}

+
+ {% endif %} + +

Status Code: 403

+
+ {% endfor %} +
+ + + diff --git a/utils/haproxy_config.py b/utils/haproxy_config.py new file mode 100644 index 0000000..10afd2f --- /dev/null +++ b/utils/haproxy_config.py @@ -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 \"= 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 diff --git a/utils/stats_utils.py b/utils/stats_utils.py new file mode 100644 index 0000000..ddbe392 --- /dev/null +++ b/utils/stats_utils.py @@ -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 \ No newline at end of file