first commit
This commit is contained in:
9
.dockerignore
Normal file
9
.dockerignore
Normal file
@@ -0,0 +1,9 @@
|
||||
__pycache__
|
||||
*.pyc
|
||||
.git
|
||||
.gitignore
|
||||
.env
|
||||
*.log
|
||||
.pytest_cache
|
||||
venv/
|
||||
.vscode
|
||||
49
Dockerfile
Normal file
49
Dockerfile
Normal 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
4
Makefile
Normal file
@@ -0,0 +1,4 @@
|
||||
.PHONY: install
|
||||
|
||||
install:
|
||||
@pip install -r requirements.txt
|
||||
201
README.md
Normal file
201
README.md
Normal file
@@ -0,0 +1,201 @@
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
[](CHANGELOG.md)
|
||||
[](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:
|
||||
|
||||

|
||||
|
||||
|
||||
### Frontend Section:
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
### backend section:
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
### You may as well edit the config file itself via "Edit HAProxy Config":
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## 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
122
app.py
Normal 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
3
auth/auth.cfg
Normal file
@@ -0,0 +1,3 @@
|
||||
[auth]
|
||||
username = admin
|
||||
password = secret123
|
||||
33
auth/auth_middleware.py
Normal file
33
auth/auth_middleware.py
Normal 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
42
docker-compose.yml
Normal 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
77
entrypoint.sh
Normal 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
38
install.sh
Executable 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
103
log_parser.py
Normal 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
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
flask
|
||||
requests
|
||||
pyOpenSSL
|
||||
57
routes/edit_routes.py
Normal file
57
routes/edit_routes.py
Normal 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
111
routes/main_routes.py
Normal 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
3
ssl.ini
Normal 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
|
||||
50
ssl/haproxy-configurator.pem
Normal file
50
ssl/haproxy-configurator.pem
Normal 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
36
supervisord.conf
Normal 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
211
templates/edit.html
Normal 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
152
templates/home.html
Normal 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
867
templates/index.html
Normal 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">×</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
209
templates/logs.html
Normal 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
151
utils/haproxy_config.py
Normal 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
31
utils/stats_utils.py
Normal 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
|
||||
Reference in New Issue
Block a user