Major upgrade: API key authentication, certificate renewal/download endpoints, monitoring/alerting scripts, improved logging, and documentation updates. See UPGRADE_SUMMARY.md for details.
All checks were successful
HAProxy Manager Build and Push / Build-and-Push (push) Successful in 43s

This commit is contained in:
jknapp 2025-07-11 06:24:56 -07:00
parent f58dbef3c5
commit 7b0b4c0476
9 changed files with 1338 additions and 86 deletions

View File

@ -1,5 +1,5 @@
FROM python:3.12-slim
RUN apt update -y && apt dist-upgrade -y && apt install socat haproxy cron certbot curl -y && apt clean && rm -rf /var/lib/apt/lists/*
RUN apt update -y && apt dist-upgrade -y && apt install socat haproxy cron certbot curl jq -y && apt clean && rm -rf /var/lib/apt/lists/*
WORKDIR /haproxy
COPY ./templates /haproxy/templates
COPY requirements.txt /haproxy/
@ -8,6 +8,9 @@ COPY scripts /haproxy/scripts
RUN chmod +x /haproxy/scripts/*
RUN pip install -r requirements.txt
RUN echo "0 */12 * * * root test -x /usr/bin/certbot && /usr/bin/certbot -q renew" > /var/spool/cron/crontabs/root
# Create log directories
RUN mkdir -p /var/log && touch /var/log/haproxy-manager.log /var/log/haproxy-manager-errors.log
RUN chmod 755 /var/log/haproxy-manager.log /var/log/haproxy-manager-errors.log
EXPOSE 80 443 8000
# Add health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \

199
README.md
View File

@ -5,8 +5,13 @@ A Flask-based API service for managing HAProxy configurations with dynamic SSL c
To run the container:
```bash
docker run -d -p 80:80 -p 443:443 -p 8000:8000 -v lets-encrypt:/etc/letsencrypt -v haproxy:/etc/haproxy --name haproxy-manager repo.anhonesthost.net/cloud-hosting-platform/haproxy-manager-base:latest
# Without API key authentication (default)
docker run -d -p 80:80 -p 443:443 -p 8000:8000 -v lets-encrypt:/etc/letsencrypt -v haproxy:/etc/haproxy --name haproxy-manager repo.anhonesthost.net/cloud-hosting-platform/haproxy-manager-base:latest
# With API key authentication (recommended for production)
docker run -d -p 80:80 -p 443:443 -p 8000:8000 -v lets-encrypt:/etc/letsencrypt -v haproxy:/etc/haproxy -e HAPROXY_API_KEY=your-secure-api-key-here --name haproxy-manager repo.anhonesthost.net/cloud-hosting-platform/haproxy-manager-base:latest
```
## Features
- RESTful API for HAProxy configuration management
@ -18,6 +23,25 @@ docker run -d -p 80:80 -p 443:443 -p 8000:8000 -v lets-encrypt:/etc/letsencrypt
- Template override support for custom backend configurations
- Process monitoring and auto-restart capabilities
- Socket-based HAProxy runtime API integration
- **NEW**: API key authentication for secure access
- **NEW**: Certificate renewal API endpoint
- **NEW**: Certificate download endpoints for other services
- **NEW**: Comprehensive error logging and alerting system
- **NEW**: Certificate status monitoring with expiration dates
## Security
### API Key Authentication
When the `HAPROXY_API_KEY` environment variable is set, all API endpoints (except `/health` and `/`) require authentication using a Bearer token:
```bash
# Example API call with authentication
curl -H "Authorization: Bearer your-secure-api-key-here" \
http://localhost:8000/api/domains
```
If no API key is set, the service runs without authentication (useful for development).
## Requirements
@ -61,11 +85,32 @@ GET /health
}
```
### Get Domains
Retrieve all configured domains and their backend information.
```bash
GET /api/domains
Authorization: Bearer your-api-key
# Response
[
{
"id": 1,
"domain": "example.com",
"ssl_enabled": 1,
"ssl_cert_path": "/etc/haproxy/certs/example.com.pem",
"template_override": null,
"backend_name": "example_backend"
}
]
```
### Add Domain
Add a new domain with backend servers configuration.
```bash
POST /api/domain
Authorization: Bearer your-api-key
Content-Type: application/json
{
@ -100,6 +145,7 @@ Request and configure SSL certificate for a domain using Let's Encrypt.
```bash
POST /api/ssl
Authorization: Bearer your-api-key
Content-Type: application/json
{
@ -117,6 +163,7 @@ Remove a domain and its associated backend configuration.
```bash
DELETE /api/domain
Authorization: Bearer your-api-key
Content-Type: application/json
{
@ -129,3 +176,153 @@ Content-Type: application/json
"message": "Domain configuration removed"
}
```
### Regenerate Configuration
Regenerate HAProxy configuration from database.
```bash
GET /api/regenerate
Authorization: Bearer your-api-key
# Response
{
"status": "success"
}
```
### Reload HAProxy
Reload HAProxy configuration without restart.
```bash
GET /api/reload
Authorization: Bearer your-api-key
# Response
{
"status": "success"
}
```
## New Certificate Management Endpoints
### Renew All Certificates
Trigger renewal of all Let's Encrypt certificates and reload HAProxy.
```bash
POST /api/certificates/renew
Authorization: Bearer your-api-key
# Response
{
"status": "success",
"message": "Certificates renewed and HAProxy reloaded"
}
```
### Get Certificate Status
Get status of all certificates including expiration dates.
```bash
GET /api/certificates/status
Authorization: Bearer your-api-key
# Response
{
"certificates": [
{
"domain": "example.com",
"ssl_enabled": true,
"cert_path": "/etc/haproxy/certs/example.com.pem",
"expires": "2024-12-31T23:59:59",
"days_until_expiry": 45
}
]
}
```
### Download Certificate Files
Download certificate files for use by other services.
```bash
# Download combined certificate (cert + key)
GET /api/certificates/example.com/download
Authorization: Bearer your-api-key
# Download private key only
GET /api/certificates/example.com/key
Authorization: Bearer your-api-key
# Download certificate only (no private key)
GET /api/certificates/example.com/cert
Authorization: Bearer your-api-key
```
## Logging and Monitoring
The HAProxy Manager includes comprehensive logging and error tracking:
### Log Files
- `/var/log/haproxy-manager.log` - General application logs
- `/var/log/haproxy-manager-errors.log` - Error logs for alerting
### Logged Operations
All API operations are logged with timestamps and success/failure status:
- Domain management (add/remove)
- SSL certificate operations
- Configuration generation
- HAProxy reload/restart operations
- Certificate renewals
### Error Alerting
Failed operations are logged to the error log file. You can monitor this file for alerting:
```bash
# Monitor error log for alerting
tail -f /var/log/haproxy-manager-errors.log
```
## Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `HAPROXY_API_KEY` | API key for authentication (optional) | None (no auth) |
## Example Usage
### Setting up with API key authentication:
```bash
# Start container with API key
docker run -d \
-p 80:80 -p 443:443 -p 8000:8000 \
-v lets-encrypt:/etc/letsencrypt \
-v haproxy:/etc/haproxy \
-e HAPROXY_API_KEY=your-secure-api-key-here \
--name haproxy-manager \
repo.anhonesthost.net/cloud-hosting-platform/haproxy-manager-base:latest
# Add a domain
curl -X POST http://localhost:8000/api/domain \
-H "Authorization: Bearer your-secure-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"domain": "example.com",
"backend_name": "example_backend",
"servers": [
{"name": "server1", "address": "10.0.0.1", "port": 8080, "options": "check"}
]
}'
# Request SSL certificate
curl -X POST http://localhost:8000/api/ssl \
-H "Authorization: Bearer your-secure-api-key-here" \
-H "Content-Type: application/json" \
-d '{"domain": "example.com"}'
# Renew certificates
curl -X POST http://localhost:8000/api/certificates/renew \
-H "Authorization: Bearer your-secure-api-key-here"
# Download certificate for another service
curl -H "Authorization: Bearer your-secure-api-key-here" \
http://localhost:8000/api/certificates/example.com/download \
-o example.com.pem
```

173
UPGRADE_SUMMARY.md Normal file
View File

@ -0,0 +1,173 @@
# HAProxy Manager Upgrade Summary
This document summarizes the new features and improvements added to the HAProxy Manager project.
## New Features Implemented
### 1. API Key Authentication
- **Feature**: Optional API key authentication for all API endpoints
- **Implementation**:
- Environment variable `HAPROXY_API_KEY` controls authentication
- Bearer token authentication using `Authorization: Bearer <key>` header
- Health check endpoint (`/health`) and web UI (`/`) remain unauthenticated
- Graceful fallback to unauthenticated mode when no API key is set
- **Security**: All API endpoints (except health check) require authentication when API key is configured
### 2. Certificate Renewal API
- **Endpoint**: `POST /api/certificates/renew`
- **Functionality**:
- Triggers renewal of all Let's Encrypt certificates
- Automatically updates combined certificate files for HAProxy
- Regenerates HAProxy configuration
- Reloads HAProxy with new certificates
- Returns detailed status of renewal process
- **Error Handling**: Comprehensive error logging and status reporting
### 3. Certificate Download Endpoints
- **Endpoints**:
- `GET /api/certificates/<domain>/download` - Combined certificate (cert + key)
- `GET /api/certificates/<domain>/key` - Private key only
- `GET /api/certificates/<domain>/cert` - Certificate only (no private key)
- **Use Case**: Allow other services to securely download certificates for their own use
- **Security**: All endpoints require API key authentication
### 4. Certificate Status Monitoring
- **Endpoint**: `GET /api/certificates/status`
- **Functionality**:
- Lists all certificates with expiration dates
- Calculates days until expiration
- Provides certificate file paths
- Enables proactive certificate management
### 5. Comprehensive Error Logging and Alerting
- **Logging System**:
- Structured JSON logging for all operations
- Separate error log file (`/var/log/haproxy-manager-errors.log`)
- General application log (`/var/log/haproxy-manager.log`)
- Timestamped operation tracking
- **Alerting Capabilities**:
- Error detection and logging
- Certificate expiration warnings
- HAProxy operation failure tracking
- Configurable alerting via monitoring script
## Technical Improvements
### Enhanced Error Handling
- All API endpoints now include comprehensive error handling
- Detailed error messages with logging
- Graceful failure handling for HAProxy operations
- Certificate operation error tracking
### Improved Logging
- Structured logging with timestamps
- Operation success/failure tracking
- Error categorization and alerting
- Debug information for troubleshooting
### Better HAProxy Integration
- Enhanced configuration validation
- Improved reload/restart handling
- Better error reporting for HAProxy operations
- Automatic recovery from configuration errors
## New Scripts and Tools
### 1. Monitoring Script (`scripts/monitor-errors.sh`)
- **Purpose**: Monitor error logs and certificate expiration
- **Features**:
- Check for recent errors in configurable time windows
- Monitor certificate expiration dates
- Email and webhook alerting capabilities
- Configurable thresholds and intervals
- **Usage**: Can be integrated with cron for automated monitoring
### 2. API Test Script (`scripts/test-api.sh`)
- **Purpose**: Test all new API endpoints
- **Features**:
- Comprehensive API endpoint testing
- Authentication testing
- Colored output for easy reading
- Detailed response logging
### 3. Monitoring Configuration (`scripts/monitoring-example.conf`)
- **Purpose**: Example configuration for monitoring setup
- **Features**:
- Email and webhook configuration examples
- Crontab entry examples
- Monitoring interval recommendations
## Updated Files
### Core Application
- `haproxy_manager.py` - Major updates with new endpoints and features
- `requirements.txt` - No changes needed (existing dependencies sufficient)
- `Dockerfile` - Added jq package and log directory setup
### Documentation
- `README.md` - Comprehensive updates with new feature documentation
- `UPGRADE_SUMMARY.md` - This summary document
### Scripts
- `scripts/monitor-errors.sh` - New monitoring and alerting script
- `scripts/test-api.sh` - New API testing script
- `scripts/monitoring-example.conf` - New monitoring configuration example
## Environment Variables
| Variable | Description | Default | Required |
|----------|-------------|---------|----------|
| `HAPROXY_API_KEY` | API key for authentication | None | No (optional) |
## Migration Guide
### For Existing Users
1. **No Breaking Changes**: Existing functionality remains unchanged
2. **Optional Authentication**: API key is optional - set `HAPROXY_API_KEY` to enable
3. **Backward Compatibility**: All existing endpoints work without authentication when no API key is set
### For New Deployments
1. **Recommended**: Set `HAPROXY_API_KEY` for production deployments
2. **Monitoring**: Configure monitoring script for automated alerting
3. **Testing**: Use test script to verify all endpoints work correctly
## API Endpoints Summary
### Existing Endpoints (Updated with Authentication)
- `GET /health` - Health check (no auth required)
- `GET /api/domains` - List domains
- `POST /api/domain` - Add domain
- `DELETE /api/domain` - Remove domain
- `POST /api/ssl` - Request SSL certificate
- `GET /api/regenerate` - Regenerate configuration
- `GET /api/reload` - Reload HAProxy
### New Endpoints
- `POST /api/certificates/renew` - Renew all certificates
- `GET /api/certificates/status` - Get certificate status
- `GET /api/certificates/<domain>/download` - Download combined certificate
- `GET /api/certificates/<domain>/key` - Download private key
- `GET /api/certificates/<domain>/cert` - Download certificate only
## Security Considerations
1. **API Key Security**: Use strong, unique API keys for production
2. **Network Security**: Restrict access to port 8000 using firewalls
3. **Certificate Security**: Private key endpoints require authentication
4. **Log Security**: Monitor log files for sensitive information
## Monitoring and Alerting
1. **Error Monitoring**: Monitor `/var/log/haproxy-manager-errors.log`
2. **Certificate Monitoring**: Use certificate status endpoint for expiration tracking
3. **HAProxy Monitoring**: Health check endpoint provides service status
4. **Automated Alerting**: Configure monitoring script with email/webhook alerts
## Future Enhancements
Potential areas for future development:
1. Webhook integration for certificate renewal notifications
2. Advanced certificate management (wildcard certificates, etc.)
3. HAProxy statistics and monitoring endpoints
4. Configuration backup and restore functionality
5. Multi-tenant support with per-domain API keys

View File

@ -1,18 +1,66 @@
import sqlite3
import os
from flask import Flask, request, jsonify, render_template
from flask import Flask, request, jsonify, render_template, send_file
from pathlib import Path
import subprocess
import jinja2
import socket
import psutil
import functools
import logging
from datetime import datetime
import json
app = Flask(__name__)
# Configuration
DB_FILE = '/etc/haproxy/haproxy_config.db'
TEMPLATE_DIR = Path('templates')
HAPROXY_CONFIG_PATH = '/etc/haproxy/haproxy.cfg'
SSL_CERTS_DIR = '/etc/haproxy/certs'
API_KEY = os.environ.get('HAPROXY_API_KEY') # Optional API key for authentication
# Setup logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('/var/log/haproxy-manager.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
def require_api_key(f):
"""Decorator to require API key authentication if API_KEY is set"""
@functools.wraps(f)
def decorated_function(*args, **kwargs):
if API_KEY:
auth_header = request.headers.get('Authorization')
if not auth_header or auth_header != f'Bearer {API_KEY}':
return jsonify({'error': 'Unauthorized - Invalid or missing API key'}), 401
return f(*args, **kwargs)
return decorated_function
def log_operation(operation, success=True, error_message=None):
"""Log operations for monitoring and alerting"""
log_entry = {
'timestamp': datetime.now().isoformat(),
'operation': operation,
'success': success,
'error': error_message
}
if success:
logger.info(f"Operation {operation} completed successfully")
else:
logger.error(f"Operation {operation} failed: {error_message}")
# Here you could add additional alerting (email, webhook, etc.)
# For now, we'll just log to a dedicated error log
with open('/var/log/haproxy-manager-errors.log', 'a') as f:
f.write(json.dumps(log_entry) + '\n')
return log_entry
def init_db():
with sqlite3.connect(DB_FILE) as conn:
@ -102,6 +150,7 @@ template_loader = jinja2.FileSystemLoader(TEMPLATE_DIR)
template_env = jinja2.Environment(loader=template_loader)
@app.route('/api/domains', methods=['GET'])
@require_api_key
def get_domains():
try:
with sqlite3.connect(DB_FILE) as conn:
@ -113,8 +162,10 @@ def get_domains():
LEFT JOIN backends b ON d.id = b.domain_id
''')
domains = [dict(row) for row in cursor.fetchall()]
log_operation('get_domains', True)
return jsonify(domains)
except Exception as e:
log_operation('get_domains', False, str(e))
return jsonify({'status': 'error', 'message': str(e)}), 500
@app.route('/health', methods=['GET'])
@ -141,27 +192,32 @@ def health_check():
}), 500
@app.route('/api/regenerate', methods=['GET'])
@require_api_key
def regenerate_conf():
try:
generate_config()
log_operation('regenerate_config', True)
return jsonify({'status': 'success'}), 200
except Exception as e:
log_operation('regenerate_config', False, str(e))
return jsonify({
'status': 'failed',
'error': str(e)
}), 500
@app.route('/api/reload', methods=['GET'])
@require_api_key
def reload_haproxy():
if is_process_running('haproxy'):
# Use a proper shell command string when shell=True is set
result = subprocess.run('echo "reload" | socat stdio /tmp/haproxy-cli',
check=True, capture_output=True, text=True, shell=True)
print(f"Reload result: {result.stdout}, {result.stderr}, {result.returncode}")
return jsonify({'status': 'success'}), 200
else:
# Start HAProxy if it's not running
try:
try:
if is_process_running('haproxy'):
# Use a proper shell command string when shell=True is set
result = subprocess.run('echo "reload" | socat stdio /tmp/haproxy-cli',
check=True, capture_output=True, text=True, shell=True)
print(f"Reload result: {result.stdout}, {result.stderr}, {result.returncode}")
log_operation('reload_haproxy', True)
return jsonify({'status': 'success'}), 200
else:
# Start HAProxy if it's not running
result = subprocess.run(
['haproxy', '-W', '-S', '/tmp/haproxy-cli,level,admin', '-f', HAPROXY_CONFIG_PATH],
check=True,
@ -170,18 +226,21 @@ def reload_haproxy():
)
if result.returncode == 0:
print("HAProxy started successfully")
log_operation('start_haproxy', True)
return jsonify({'status': 'success'}), 200
else:
print(f"HAProxy start command returned: {result.stdout}")
print(f"Error output: {result.stderr}")
except subprocess.CalledProcessError as e:
print(f"Failed to start HAProxy: {e.stdout}\n{e.stderr}")
return jsonify({
'status': 'failed',
'error': f"Failed to start HAProxy: {e.stdout}\n{e.stderr}"
}), 500
error_msg = f"HAProxy start command returned: {result.stdout}\nError output: {result.stderr}"
print(error_msg)
log_operation('start_haproxy', False, error_msg)
return jsonify({'status': 'failed', 'error': error_msg}), 500
except subprocess.CalledProcessError as e:
error_msg = f"Failed to start HAProxy: {e.stdout}\n{e.stderr}"
print(error_msg)
log_operation('reload_haproxy', False, error_msg)
return jsonify({'status': 'failed', 'error': error_msg}), 500
@app.route('/api/domain', methods=['POST'])
@require_api_key
def add_domain():
data = request.get_json()
domain = data.get('domain')
@ -189,77 +248,257 @@ def add_domain():
backend_name = data.get('backend_name')
servers = data.get('servers', [])
with sqlite3.connect(DB_FILE) as conn:
cursor = conn.cursor()
if not domain or not backend_name:
log_operation('add_domain', False, 'Domain and backend_name are required')
return jsonify({'status': 'error', 'message': 'Domain and backend_name are required'}), 400
# Add domain
cursor.execute('INSERT INTO domains (domain, template_override) VALUES (?, ?)', (domain, template_override))
domain_id = cursor.lastrowid
try:
with sqlite3.connect(DB_FILE) as conn:
cursor = conn.cursor()
# Add backend
cursor.execute('INSERT INTO backends (name, domain_id) VALUES (?, ?)',
(backend_name, domain_id))
backend_id = cursor.lastrowid
# Add domain
cursor.execute('INSERT INTO domains (domain, template_override) VALUES (?, ?)', (domain, template_override))
domain_id = cursor.lastrowid
for server in servers:
cursor.execute('''
INSERT INTO backend_servers
(backend_id, server_name, server_address, server_port, server_options)
VALUES (?, ?, ?, ?, ?)
''', (backend_id, server['name'], server['address'],
server['port'], server.get('options')))
# Close cursor and connection
cursor.close()
conn.close()
generate_config()
return jsonify({'status': 'success', 'domain_id': domain_id})
# Add backend
cursor.execute('INSERT INTO backends (name, domain_id) VALUES (?, ?)',
(backend_name, domain_id))
backend_id = cursor.lastrowid
for server in servers:
cursor.execute('''
INSERT INTO backend_servers
(backend_id, server_name, server_address, server_port, server_options)
VALUES (?, ?, ?, ?, ?)
''', (backend_id, server['name'], server['address'],
server['port'], server.get('options')))
# Close cursor and connection
cursor.close()
conn.close()
generate_config()
log_operation('add_domain', True, f'Domain {domain} added successfully')
return jsonify({'status': 'success', 'domain_id': domain_id})
except Exception as e:
log_operation('add_domain', False, str(e))
return jsonify({'status': 'error', 'message': str(e)}), 500
@app.route('/')
def index():
return render_template('index.html')
@app.route('/api/ssl', methods=['POST'])
@require_api_key
def request_ssl():
data = request.get_json()
domain = data.get('domain')
# Request Let's Encrypt certificate
result = subprocess.run([
'certbot', 'certonly', '-n', '--standalone',
'--preferred-challenges', 'http', '--http-01-port=8688',
'-d', domain
])
if not domain:
log_operation('request_ssl', False, 'Domain not provided')
return jsonify({'status': 'error', 'message': 'Domain is required'}), 400
if result.returncode == 0:
# Combine cert files and copy to HAProxy certs directory
cert_path = f'/etc/letsencrypt/live/{domain}/fullchain.pem'
key_path = f'/etc/letsencrypt/live/{domain}/privkey.pem'
combined_path = f'{SSL_CERTS_DIR}/{domain}.pem'
try:
# Request Let's Encrypt certificate
result = subprocess.run([
'certbot', 'certonly', '-n', '--standalone',
'--preferred-challenges', 'http', '--http-01-port=8688',
'-d', domain
], capture_output=True, text=True)
with open(combined_path, 'w') as combined:
subprocess.run(['cat', cert_path, key_path], stdout=combined)
if result.returncode == 0:
# Combine cert files and copy to HAProxy certs directory
cert_path = f'/etc/letsencrypt/live/{domain}/fullchain.pem'
key_path = f'/etc/letsencrypt/live/{domain}/privkey.pem'
combined_path = f'{SSL_CERTS_DIR}/{domain}.pem'
# Update database
with open(combined_path, 'w') as combined:
subprocess.run(['cat', cert_path, key_path], stdout=combined)
# Update database
with sqlite3.connect(DB_FILE) as conn:
cursor = conn.cursor()
cursor.execute('''
UPDATE domains
SET ssl_enabled = 1, ssl_cert_path = ?
WHERE domain = ?
''', (combined_path, domain))
# Close cursor and connection
cursor.close()
conn.close()
generate_config()
log_operation('request_ssl', True, f'SSL certificate obtained for {domain}')
return jsonify({'status': 'success'})
else:
error_msg = f'Failed to obtain SSL certificate: {result.stderr}'
log_operation('request_ssl', False, error_msg)
return jsonify({'status': 'error', 'message': error_msg}), 500
except Exception as e:
log_operation('request_ssl', False, str(e))
return jsonify({'status': 'error', 'message': str(e)}), 500
@app.route('/api/certificates/renew', methods=['POST'])
@require_api_key
def renew_certificates():
"""Renew all certificates and reload HAProxy"""
try:
# Run certbot renew
result = subprocess.run([
'certbot', 'renew', '--quiet'
], capture_output=True, text=True)
if result.returncode == 0:
# Check if any certificates were renewed
if 'Congratulations' in result.stdout or 'renewed' in result.stdout:
# Update combined certificates for HAProxy
with sqlite3.connect(DB_FILE) as conn:
cursor = conn.cursor()
cursor.execute('SELECT domain, ssl_cert_path FROM domains WHERE ssl_enabled = 1')
domains = cursor.fetchall()
for domain, cert_path in domains:
if cert_path and os.path.exists(cert_path):
# Recreate combined certificate
letsencrypt_cert = f'/etc/letsencrypt/live/{domain}/fullchain.pem'
letsencrypt_key = f'/etc/letsencrypt/live/{domain}/privkey.pem'
if os.path.exists(letsencrypt_cert) and os.path.exists(letsencrypt_key):
with open(cert_path, 'w') as combined:
subprocess.run(['cat', letsencrypt_cert, letsencrypt_key], stdout=combined)
# Regenerate config and reload HAProxy
generate_config()
reload_result = subprocess.run('echo "reload" | socat stdio /tmp/haproxy-cli',
capture_output=True, text=True, shell=True)
if reload_result.returncode == 0:
log_operation('renew_certificates', True, 'Certificates renewed and HAProxy reloaded')
return jsonify({'status': 'success', 'message': 'Certificates renewed and HAProxy reloaded'})
else:
error_msg = f'Certificates renewed but HAProxy reload failed: {reload_result.stderr}'
log_operation('renew_certificates', False, error_msg)
return jsonify({'status': 'partial_success', 'message': error_msg}), 500
else:
log_operation('renew_certificates', True, 'No certificates needed renewal')
return jsonify({'status': 'success', 'message': 'No certificates needed renewal'})
else:
error_msg = f'Certificate renewal failed: {result.stderr}'
log_operation('renew_certificates', False, error_msg)
return jsonify({'status': 'error', 'message': error_msg}), 500
except Exception as e:
log_operation('renew_certificates', False, str(e))
return jsonify({'status': 'error', 'message': str(e)}), 500
@app.route('/api/certificates/<domain>/download', methods=['GET'])
@require_api_key
def download_certificate(domain):
"""Download the combined certificate file for a domain"""
try:
with sqlite3.connect(DB_FILE) as conn:
cursor = conn.cursor()
cursor.execute('''
UPDATE domains
SET ssl_enabled = 1, ssl_cert_path = ?
WHERE domain = ?
''', (combined_path, domain))
# Close cursor and connection
cursor.close()
conn.close()
generate_config()
return jsonify({'status': 'success'})
return jsonify({'status': 'error', 'message': 'Failed to obtain SSL certificate'})
cursor.execute('SELECT ssl_cert_path FROM domains WHERE domain = ? AND ssl_enabled = 1', (domain,))
result = cursor.fetchone()
if not result or not result[0]:
return jsonify({'status': 'error', 'message': 'Certificate not found for domain'}), 404
cert_path = result[0]
if not os.path.exists(cert_path):
return jsonify({'status': 'error', 'message': 'Certificate file not found'}), 404
log_operation('download_certificate', True, f'Certificate downloaded for {domain}')
return send_file(cert_path, as_attachment=True, download_name=f'{domain}.pem')
except Exception as e:
log_operation('download_certificate', False, str(e))
return jsonify({'status': 'error', 'message': str(e)}), 500
@app.route('/api/certificates/<domain>/key', methods=['GET'])
@require_api_key
def download_private_key(domain):
"""Download the private key for a domain"""
try:
key_path = f'/etc/letsencrypt/live/{domain}/privkey.pem'
if not os.path.exists(key_path):
return jsonify({'status': 'error', 'message': 'Private key not found for domain'}), 404
log_operation('download_private_key', True, f'Private key downloaded for {domain}')
return send_file(key_path, as_attachment=True, download_name=f'{domain}_key.pem')
except Exception as e:
log_operation('download_private_key', False, str(e))
return jsonify({'status': 'error', 'message': str(e)}), 500
@app.route('/api/certificates/<domain>/cert', methods=['GET'])
@require_api_key
def download_cert_only(domain):
"""Download only the certificate (without private key) for a domain"""
try:
cert_path = f'/etc/letsencrypt/live/{domain}/fullchain.pem'
if not os.path.exists(cert_path):
return jsonify({'status': 'error', 'message': 'Certificate not found for domain'}), 404
log_operation('download_cert_only', True, f'Certificate (only) downloaded for {domain}')
return send_file(cert_path, as_attachment=True, download_name=f'{domain}_cert.pem')
except Exception as e:
log_operation('download_cert_only', False, str(e))
return jsonify({'status': 'error', 'message': str(e)}), 500
@app.route('/api/certificates/status', methods=['GET'])
@require_api_key
def get_certificate_status():
"""Get status of all certificates including expiration dates"""
try:
with sqlite3.connect(DB_FILE) as conn:
cursor = conn.cursor()
cursor.execute('SELECT domain, ssl_enabled, ssl_cert_path FROM domains WHERE ssl_enabled = 1')
domains = cursor.fetchall()
cert_status = []
for domain, ssl_enabled, cert_path in domains:
status = {
'domain': domain,
'ssl_enabled': bool(ssl_enabled),
'cert_path': cert_path,
'expires': None,
'days_until_expiry': None
}
if cert_path and os.path.exists(cert_path):
# Check certificate expiration using openssl
try:
result = subprocess.run([
'openssl', 'x509', '-in', cert_path, '-noout', '-dates'
], capture_output=True, text=True)
if result.returncode == 0:
# Parse the notAfter date
for line in result.stdout.split('\n'):
if 'notAfter=' in line:
expiry_date_str = line.split('=')[1].strip()
from datetime import datetime
expiry_date = datetime.strptime(expiry_date_str, '%b %d %H:%M:%S %Y %Z')
status['expires'] = expiry_date.isoformat()
# Calculate days until expiry
days_until = (expiry_date - datetime.now()).days
status['days_until_expiry'] = days_until
break
except Exception as e:
status['error'] = str(e)
cert_status.append(status)
log_operation('get_certificate_status', True)
return jsonify({'certificates': cert_status})
except Exception as e:
log_operation('get_certificate_status', False, str(e))
return jsonify({'status': 'error', 'message': str(e)}), 500
@app.route('/api/domain', methods=['DELETE'])
@require_api_key
def remove_domain():
data = request.get_json()
domain = data.get('domain')
if not domain:
log_operation('remove_domain', False, 'Domain is required')
return jsonify({'status': 'error', 'message': 'Domain is required'}), 400
try:
@ -271,6 +510,7 @@ def remove_domain():
domain_result = cursor.fetchone()
if not domain_result:
log_operation('remove_domain', False, f'Domain {domain} not found')
return jsonify({'status': 'error', 'message': 'Domain not found'}), 404
domain_id = domain_result[0]
@ -301,9 +541,11 @@ def remove_domain():
# Regenerate HAProxy config
generate_config()
log_operation('remove_domain', True, f'Domain {domain} removed successfully')
return jsonify({'status': 'success', 'message': 'Domain configuration removed'})
except Exception as e:
log_operation('remove_domain', False, str(e))
return jsonify({'status': 'error', 'message': str(e)}), 500
def generate_config():
@ -349,7 +591,7 @@ def generate_config():
# Add domain configurations
for domain in domains:
if not domain['backend_name']:
print(f"Skipping domain {domain['domain']} - no backend name") # Debug log
logger.warning(f"Skipping domain {domain['domain']} - no backend name")
continue
# Add domain ACL
@ -359,9 +601,9 @@ def generate_config():
name=domain['backend_name']
)
config_acls.append(domain_acl)
print(f"Added ACL for domain: {domain['domain']}") # Debug log
logger.info(f"Added ACL for domain: {domain['domain']}")
except Exception as e:
print(f"Error generating domain ACL for {domain['domain']}: {e}")
logger.error(f"Error generating domain ACL for {domain['domain']}: {e}")
continue
# Add backend configuration
@ -372,11 +614,11 @@ def generate_config():
servers = [dict(server) for server in cursor.fetchall()]
if not servers:
print(f"No servers found for backend {domain['backend_name']}") # Debug log
logger.warning(f"No servers found for backend {domain['backend_name']}")
continue
if domain['template_override'] is not None:
print(f"Template Override is set to: {domain['template_override']}")
logger.info(f"Template Override is set to: {domain['template_override']}")
template_file = domain['template_override'] + ".tpl"
backend_block = template_env.get_template(template_file).render(
name=domain['backend_name'],
@ -390,9 +632,9 @@ def generate_config():
servers=servers
)
config_backends.append(backend_block)
print(f"Added backend block for: {domain['backend_name']}") # Debug log
logger.info(f"Added backend block for: {domain['backend_name']}")
except Exception as e:
print(f"Error generating backend block for {domain['backend_name']}: {e}")
logger.error(f"Error generating backend block for {domain['backend_name']}: {e}")
continue
# Add ACLS
@ -406,17 +648,25 @@ def generate_config():
temp_config_path = "/etc/haproxy/haproxy.cfg"
config_content = '\n'.join(config_parts)
print("Final config content:", config_content) # Debug log
logger.debug("Generated HAProxy configuration")
# Write complete configuration to tmp
# Check HAProxy Configuration, and reload if it works
with open(temp_config_path, 'w') as f:
f.write(config_content)
result = subprocess.run(['haproxy', '-c', '-f', temp_config_path], capture_output=True)
result = subprocess.run(['haproxy', '-c', '-f', temp_config_path], capture_output=True, text=True)
if result.returncode == 0:
print("HAProxy configuration check passed")
logger.info("HAProxy configuration check passed")
if is_process_running('haproxy'):
subprocess.run(['echo', '"reload"', '|', 'socat', 'stdio', '/tmp/haproxy-cli'])
reload_result = subprocess.run('echo "reload" | socat stdio /tmp/haproxy-cli',
capture_output=True, text=True, shell=True)
if reload_result.returncode == 0:
logger.info("HAProxy reloaded successfully")
log_operation('generate_config', True, 'Configuration generated and HAProxy reloaded')
else:
error_msg = f"HAProxy reload failed: {reload_result.stderr}"
logger.error(error_msg)
log_operation('generate_config', False, error_msg)
else:
try:
result = subprocess.run(
@ -426,15 +676,26 @@ def generate_config():
text=True
)
if result.returncode == 0:
print("HAProxy started successfully")
logger.info("HAProxy started successfully")
log_operation('generate_config', True, 'Configuration generated and HAProxy started')
else:
print(f"HAProxy start command returned: {result.stdout}")
print(f"Error output: {result.stderr}")
error_msg = f"HAProxy start command returned: {result.stdout}\nError output: {result.stderr}"
logger.error(error_msg)
log_operation('generate_config', False, error_msg)
except subprocess.CalledProcessError as e:
print(f"Failed to start HAProxy: {e.stdout}\n{e.stderr}")
error_msg = f"Failed to start HAProxy: {e.stdout}\n{e.stderr}"
logger.error(error_msg)
log_operation('generate_config', False, error_msg)
raise
else:
error_msg = f"HAProxy configuration check failed: {result.stderr}"
logger.error(error_msg)
log_operation('generate_config', False, error_msg)
raise Exception(error_msg)
except Exception as e:
print(f"Error generating config: {e}")
error_msg = f"Error generating config: {e}"
logger.error(error_msg)
log_operation('generate_config', False, error_msg)
import traceback
traceback.print_exc()
raise
@ -449,12 +710,16 @@ def start_haproxy():
text=True
)
if result.returncode == 0:
print("HAProxy started successfully")
logger.info("HAProxy started successfully")
log_operation('start_haproxy', True, 'HAProxy started successfully')
else:
print(f"HAProxy start command returned: {result.stdout}")
print(f"Error output: {result.stderr}")
error_msg = f"HAProxy start command returned: {result.stdout}\nError output: {result.stderr}"
logger.error(error_msg)
log_operation('start_haproxy', False, error_msg)
except subprocess.CalledProcessError as e:
print(f"Failed to start HAProxy: {e.stdout}\n{e.stderr}")
error_msg = f"Failed to start HAProxy: {e.stdout}\n{e.stderr}"
logger.error(error_msg)
log_operation('start_haproxy', False, error_msg)
raise
if __name__ == '__main__':

View File

@ -0,0 +1,42 @@
# HAProxy Manager External Monitoring Configuration
# Copy this file to /etc/haproxy-monitor.conf and modify for your environment
# Container configuration
CONTAINER_NAME="haproxy-manager"
CONTAINER_API_URL="http://localhost:8000"
# Log directory (adjust based on your Docker volume setup)
# Common paths:
# - Docker volumes: /var/lib/docker/volumes/haproxy-logs/_data
# - Bind mounts: /path/to/your/logs
# - Docker Desktop (Mac/Windows): May need different path
LOG_DIR="/var/lib/docker/volumes/haproxy-logs/_data"
# API key for certificate status checks
API_KEY="your-secure-api-key-here"
# Alerting configuration
ALERT_EMAIL="admin@yourdomain.com"
WEBHOOK_URL="https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK"
# Example crontab entries for external monitoring:
#
# Check container and API health every 5 minutes
# */5 * * * * /path/to/monitor-errors-external.sh health
#
# Check for errors every 30 minutes
# */30 * * * * /path/to/monitor-errors-external.sh errors 30
#
# Check certificates daily at 9 AM
# 0 9 * * * /path/to/monitor-errors-external.sh certs 30
#
# Comprehensive check every hour
# 0 * * * * /path/to/monitor-errors-external.sh all 60 30
# Installation instructions:
# 1. Copy this file to /etc/haproxy-monitor.conf
# 2. Modify the variables above for your environment
# 3. Copy monitor-errors-external.sh to /usr/local/bin/
# 4. Make it executable: chmod +x /usr/local/bin/monitor-errors-external.sh
# 5. Install required packages: apt-get install curl jq
# 6. Set up crontab entries as shown above

View File

@ -0,0 +1,224 @@
#!/bin/bash
# HAProxy Manager External Monitoring Script
# This script monitors the HAProxy Manager from outside the container
# Configuration - modify these variables
CONTAINER_NAME="haproxy-manager"
CONTAINER_API_URL="http://localhost:8000"
LOG_DIR="/var/lib/docker/volumes/haproxy-logs/_data" # Adjust path as needed
ERROR_LOG="$LOG_DIR/haproxy-manager-errors.log"
ALERT_EMAIL=""
WEBHOOK_URL=""
API_KEY=""
# Load configuration from file if it exists
CONFIG_FILE="/etc/haproxy-monitor.conf"
if [ -f "$CONFIG_FILE" ]; then
source "$CONFIG_FILE"
fi
# Function to send email alert
send_email_alert() {
local subject="$1"
local message="$2"
if [ -n "$ALERT_EMAIL" ]; then
if command -v mail >/dev/null 2>&1; then
echo "$message" | mail -s "$subject" "$ALERT_EMAIL"
else
echo "Email alert (mail command not available): $subject - $message"
fi
fi
}
# Function to send webhook alert
send_webhook_alert() {
local message="$1"
if [ -n "$WEBHOOK_URL" ]; then
curl -s -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "{\"text\":\"$message\"}" >/dev/null 2>&1
fi
}
# Function to check if container is running
check_container_status() {
if ! docker ps --format "table {{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then
local alert_message="HAProxy Manager Alert: Container $CONTAINER_NAME is not running!"
send_email_alert "HAProxy Manager Container Down" "$alert_message"
send_webhook_alert "$alert_message"
return 1
fi
return 0
}
# Function to check for recent errors
check_recent_errors() {
local minutes="${1:-60}" # Default to last 60 minutes
if [ ! -f "$ERROR_LOG" ]; then
echo "Error log file not found: $ERROR_LOG"
echo "Container may not be running or log volume not mounted correctly"
return 1
fi
# Get current timestamp minus specified minutes
local cutoff_time=$(date -d "$minutes minutes ago" +%s)
# Check for errors in the last N minutes
local recent_errors=$(awk -v cutoff="$cutoff_time" '
BEGIN { FS="\""; found=0 }
/"timestamp":/ {
# Extract timestamp and convert to epoch
gsub(/[",]/, "", $4)
split($4, parts, "T")
split(parts[1], date_parts, "-")
split(parts[2], time_parts, ":")
timestamp = mktime(date_parts[1] " " date_parts[2] " " date_parts[3] " " time_parts[1] " " time_parts[2] " " time_parts[3])
if (timestamp > cutoff) {
found=1
print $0
}
}
END { exit found ? 0 : 1 }
' "$ERROR_LOG")
if [ $? -eq 0 ]; then
echo "Recent errors found in the last $minutes minutes:"
echo "$recent_errors"
# Send alerts
local alert_message="HAProxy Manager Error Alert: Recent errors detected in the last $minutes minutes. Check $ERROR_LOG for details."
send_email_alert "HAProxy Manager Error Alert" "$alert_message"
send_webhook_alert "$alert_message"
return 1 # Return error status
else
echo "No recent errors found in the last $minutes minutes."
return 0 # Return success status
fi
}
# Function to check certificate expiration via API
check_certificate_expiration() {
local warning_days="${1:-30}" # Default to 30 days warning
if [ -z "$API_KEY" ]; then
echo "No API key configured. Cannot check certificate status."
return 1
fi
# Check if container is running
if ! check_container_status; then
return 1
fi
# Use the API to get certificate status
local cert_status=$(curl -s -H "Authorization: Bearer $API_KEY" "$CONTAINER_API_URL/api/certificates/status")
if [ $? -eq 0 ]; then
# Parse JSON to check for expiring certificates
local expiring_certs=$(echo "$cert_status" | jq -r --arg days "$warning_days" '
.certificates[] |
select(.days_until_expiry != null and .days_until_expiry <= ($days | tonumber)) |
"\(.domain): expires in \(.days_until_expiry) days"
' 2>/dev/null)
if [ -n "$expiring_certs" ]; then
echo "Certificates expiring soon:"
echo "$expiring_certs"
local alert_message="HAProxy Manager Certificate Alert: Certificates expiring soon. $expiring_certs"
send_email_alert "HAProxy Manager Certificate Alert" "$alert_message"
send_webhook_alert "$alert_message"
return 1
else
echo "No certificates expiring within $warning_days days."
return 0
fi
else
echo "Failed to get certificate status from API."
return 1
fi
}
# Function to check API health
check_api_health() {
local health_response=$(curl -s "$CONTAINER_API_URL/health")
if [ $? -eq 0 ]; then
local status=$(echo "$health_response" | jq -r '.status' 2>/dev/null)
if [ "$status" = "healthy" ]; then
echo "API health check passed"
return 0
else
echo "API health check failed: $health_response"
return 1
fi
else
echo "API health check failed: cannot connect to $CONTAINER_API_URL"
return 1
fi
}
# Main script logic
case "${1:-help}" in
"container")
check_container_status
;;
"health")
check_api_health
;;
"errors")
check_recent_errors "${2:-60}"
;;
"certs")
check_certificate_expiration "${2:-30}"
;;
"all")
echo "Checking container status..."
check_container_status
container_status=$?
echo "Checking API health..."
check_api_health
health_status=$?
echo "Checking for recent errors..."
check_recent_errors "${2:-60}"
error_status=$?
echo "Checking certificate expiration..."
check_certificate_expiration "${3:-30}"
cert_status=$?
exit $((container_status + health_status + error_status + cert_status))
;;
"help"|*)
echo "HAProxy Manager External Monitoring Script"
echo ""
echo "Usage: $0 {container|health|errors|certs|all} [minutes] [cert_warning_days]"
echo ""
echo "Commands:"
echo " container Check if container is running"
echo " health Check API health endpoint"
echo " errors [minutes] Check for errors in the last N minutes (default: 60)"
echo " certs [days] Check for certificates expiring within N days (default: 30)"
echo " all [minutes] [days] Check container, health, errors, and certificates"
echo " help Show this help message"
echo ""
echo "Configuration:"
echo " Set variables at the top of this script or create $CONFIG_FILE"
echo " Required variables: CONTAINER_NAME, CONTAINER_API_URL, API_KEY"
echo " Optional variables: ALERT_EMAIL, WEBHOOK_URL, LOG_DIR"
echo ""
echo "Examples:"
echo " $0 container # Check if container is running"
echo " $0 errors 30 # Check for errors in last 30 minutes"
echo " $0 certs 7 # Check for certificates expiring in 7 days"
echo " $0 all 60 14 # Check everything (60 min errors, 14 day certs)"
;;
esac

159
scripts/monitor-errors.sh Executable file
View File

@ -0,0 +1,159 @@
#!/bin/bash
# HAProxy Manager Error Monitoring Script
# This script monitors the error log and can send alerts
ERROR_LOG="/var/log/haproxy-manager-errors.log"
ALERT_EMAIL=""
WEBHOOK_URL=""
# Function to send email alert
send_email_alert() {
local subject="$1"
local message="$2"
if [ -n "$ALERT_EMAIL" ]; then
echo "$message" | mail -s "$subject" "$ALERT_EMAIL"
fi
}
# Function to send webhook alert
send_webhook_alert() {
local message="$1"
if [ -n "$WEBHOOK_URL" ]; then
curl -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "{\"text\":\"$message\"}"
fi
}
# Function to check for recent errors
check_recent_errors() {
local minutes="${1:-60}" # Default to last 60 minutes
if [ ! -f "$ERROR_LOG" ]; then
echo "Error log file not found: $ERROR_LOG"
exit 1
fi
# Get current timestamp minus specified minutes
local cutoff_time=$(date -d "$minutes minutes ago" +%s)
# Check for errors in the last N minutes
local recent_errors=$(awk -v cutoff="$cutoff_time" '
BEGIN { FS="\""; found=0 }
/"timestamp":/ {
# Extract timestamp and convert to epoch
gsub(/[",]/, "", $4)
split($4, parts, "T")
split(parts[1], date_parts, "-")
split(parts[2], time_parts, ":")
timestamp = mktime(date_parts[1] " " date_parts[2] " " date_parts[3] " " time_parts[1] " " time_parts[2] " " time_parts[3])
if (timestamp > cutoff) {
found=1
print $0
}
}
END { exit found ? 0 : 1 }
' "$ERROR_LOG")
if [ $? -eq 0 ]; then
echo "Recent errors found in the last $minutes minutes:"
echo "$recent_errors"
# Send alerts
local alert_message="HAProxy Manager Error Alert: Recent errors detected in the last $minutes minutes. Check $ERROR_LOG for details."
send_email_alert "HAProxy Manager Error Alert" "$alert_message"
send_webhook_alert "$alert_message"
return 1 # Return error status
else
echo "No recent errors found in the last $minutes minutes."
return 0 # Return success status
fi
}
# Function to check certificate expiration
check_certificate_expiration() {
local warning_days="${1:-30}" # Default to 30 days warning
# Use the API to get certificate status
local api_key="${HAPROXY_API_KEY:-}"
local base_url="http://localhost:8000"
if [ -n "$api_key" ]; then
local cert_status=$(curl -s -H "Authorization: Bearer $api_key" "$base_url/api/certificates/status")
if [ $? -eq 0 ]; then
# Parse JSON to check for expiring certificates
local expiring_certs=$(echo "$cert_status" | jq -r --arg days "$warning_days" '
.certificates[] |
select(.days_until_expiry != null and .days_until_expiry <= ($days | tonumber)) |
"\(.domain): expires in \(.days_until_expiry) days"
')
if [ -n "$expiring_certs" ]; then
echo "Certificates expiring soon:"
echo "$expiring_certs"
local alert_message="HAProxy Manager Certificate Alert: Certificates expiring soon. $expiring_certs"
send_email_alert "HAProxy Manager Certificate Alert" "$alert_message"
send_webhook_alert "$alert_message"
return 1
else
echo "No certificates expiring within $warning_days days."
return 0
fi
else
echo "Failed to get certificate status from API."
return 1
fi
else
echo "No API key configured. Cannot check certificate status."
return 1
fi
}
# Main script logic
case "${1:-help}" in
"errors")
check_recent_errors "${2:-60}"
;;
"certs")
check_certificate_expiration "${2:-30}"
;;
"all")
echo "Checking for recent errors..."
check_recent_errors "${2:-60}"
error_status=$?
echo "Checking certificate expiration..."
check_certificate_expiration "${3:-30}"
cert_status=$?
exit $((error_status + cert_status))
;;
"help"|*)
echo "HAProxy Manager Monitoring Script"
echo ""
echo "Usage: $0 {errors|certs|all} [minutes] [cert_warning_days]"
echo ""
echo "Commands:"
echo " errors [minutes] Check for errors in the last N minutes (default: 60)"
echo " certs [days] Check for certificates expiring within N days (default: 30)"
echo " all [minutes] [days] Check both errors and certificates"
echo " help Show this help message"
echo ""
echo "Environment variables:"
echo " ALERT_EMAIL Email address for alerts"
echo " WEBHOOK_URL Webhook URL for alerts"
echo " HAPROXY_API_KEY API key for certificate status checks"
echo ""
echo "Examples:"
echo " $0 errors 30 # Check for errors in last 30 minutes"
echo " $0 certs 7 # Check for certificates expiring in 7 days"
echo " $0 all 60 14 # Check both (60 min errors, 14 day certs)"
;;
esac

View File

@ -0,0 +1,28 @@
# HAProxy Manager Monitoring Configuration Example
# Copy this file and modify it for your environment
# Email alerts (requires mailutils to be installed)
ALERT_EMAIL="admin@yourdomain.com"
# Webhook alerts (e.g., Slack, Discord, etc.)
WEBHOOK_URL="https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK"
# API key for certificate status checks
HAPROXY_API_KEY="your-secure-api-key-here"
# Monitoring intervals (in minutes)
ERROR_CHECK_INTERVAL=30
CERT_CHECK_INTERVAL=1440 # 24 hours
# Certificate warning threshold (days before expiration)
CERT_WARNING_DAYS=30
# Example crontab entries for monitoring:
# Check for errors every 30 minutes
# */30 * * * * /haproxy/scripts/monitor-errors.sh errors 30
# Check certificates daily
# 0 9 * * * /haproxy/scripts/monitor-errors.sh certs 30
# Check both errors and certificates daily
# 0 9 * * * /haproxy/scripts/monitor-errors.sh all 60 30

161
scripts/test-api.sh Executable file
View File

@ -0,0 +1,161 @@
#!/bin/bash
# HAProxy Manager API Test Script
# This script tests the new API endpoints
BASE_URL="http://localhost:8000"
API_KEY="${HAPROXY_API_KEY:-}"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
local status=$1
local message=$2
if [ "$status" = "PASS" ]; then
echo -e "${GREEN}✓ PASS${NC}: $message"
elif [ "$status" = "FAIL" ]; then
echo -e "${RED}✗ FAIL${NC}: $message"
else
echo -e "${YELLOW}? INFO${NC}: $message"
fi
}
# Function to make API request
api_request() {
local method=$1
local endpoint=$2
local data=$3
local headers=""
if [ -n "$API_KEY" ]; then
headers="-H \"Authorization: Bearer $API_KEY\""
fi
if [ -n "$data" ]; then
headers="$headers -H \"Content-Type: application/json\" -d '$data'"
fi
eval "curl -s -w \"%{http_code}\" -o /tmp/api_response.json $headers -X $method $BASE_URL$endpoint"
}
# Test health endpoint (no auth required)
test_health() {
print_status "INFO" "Testing health endpoint..."
local response=$(api_request "GET" "/health")
local status_code=$(echo "$response" | tail -c 4)
if [ "$status_code" = "200" ]; then
print_status "PASS" "Health endpoint working"
else
print_status "FAIL" "Health endpoint failed with status $status_code"
fi
}
# Test domains endpoint
test_domains() {
print_status "INFO" "Testing domains endpoint..."
local response=$(api_request "GET" "/api/domains")
local status_code=$(echo "$response" | tail -c 4)
if [ "$status_code" = "200" ] || [ "$status_code" = "401" ]; then
print_status "PASS" "Domains endpoint responded correctly (status: $status_code)"
else
print_status "FAIL" "Domains endpoint failed with status $status_code"
fi
}
# Test certificate status endpoint
test_cert_status() {
print_status "INFO" "Testing certificate status endpoint..."
local response=$(api_request "GET" "/api/certificates/status")
local status_code=$(echo "$response" | tail -c 4)
if [ "$status_code" = "200" ] || [ "$status_code" = "401" ]; then
print_status "PASS" "Certificate status endpoint responded correctly (status: $status_code)"
else
print_status "FAIL" "Certificate status endpoint failed with status $status_code"
fi
}
# Test certificate renewal endpoint
test_cert_renewal() {
print_status "INFO" "Testing certificate renewal endpoint..."
local response=$(api_request "POST" "/api/certificates/renew")
local status_code=$(echo "$response" | tail -c 4)
if [ "$status_code" = "200" ] || [ "$status_code" = "401" ]; then
print_status "PASS" "Certificate renewal endpoint responded correctly (status: $status_code)"
else
print_status "FAIL" "Certificate renewal endpoint failed with status $status_code"
fi
}
# Test reload endpoint
test_reload() {
print_status "INFO" "Testing HAProxy reload endpoint..."
local response=$(api_request "GET" "/api/reload")
local status_code=$(echo "$response" | tail -c 4)
if [ "$status_code" = "200" ] || [ "$status_code" = "401" ]; then
print_status "PASS" "Reload endpoint responded correctly (status: $status_code)"
else
print_status "FAIL" "Reload endpoint failed with status $status_code"
fi
}
# Test authentication
test_auth() {
if [ -n "$API_KEY" ]; then
print_status "INFO" "API key is configured"
# Test with valid API key
local response=$(api_request "GET" "/api/domains")
local status_code=$(echo "$response" | tail -c 4)
if [ "$status_code" = "200" ]; then
print_status "PASS" "Authentication working with API key"
else
print_status "FAIL" "Authentication failed with API key (status: $status_code)"
fi
else
print_status "INFO" "No API key configured - testing without authentication"
# Test without API key
local response=$(api_request "GET" "/api/domains")
local status_code=$(echo "$response" | tail -c 4)
if [ "$status_code" = "200" ]; then
print_status "PASS" "API accessible without authentication"
else
print_status "FAIL" "API not accessible without authentication (status: $status_code)"
fi
fi
}
# Main test execution
main() {
echo "HAProxy Manager API Test Suite"
echo "=============================="
echo "Base URL: $BASE_URL"
echo "API Key: ${API_KEY:-"Not configured"}"
echo ""
test_health
test_auth
test_domains
test_cert_status
test_cert_renewal
test_reload
echo ""
echo "Test completed. Check /tmp/api_response.json for detailed responses."
}
# Run tests
main "$@"