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
All checks were successful
HAProxy Manager Build and Push / Build-and-Push (push) Successful in 43s
This commit is contained in:
parent
f58dbef3c5
commit
7b0b4c0476
@ -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 \
|
||||
|
197
README.md
197
README.md
@ -5,8 +5,13 @@ A Flask-based API service for managing HAProxy configurations with dynamic SSL c
|
||||
|
||||
To run the container:
|
||||
```bash
|
||||
# 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
173
UPGRADE_SUMMARY.md
Normal 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
|
@ -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():
|
||||
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
|
||||
try:
|
||||
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}")
|
||||
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:
|
||||
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"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,6 +248,11 @@ def add_domain():
|
||||
backend_name = data.get('backend_name')
|
||||
servers = data.get('servers', [])
|
||||
|
||||
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
|
||||
|
||||
try:
|
||||
with sqlite3.connect(DB_FILE) as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
@ -212,23 +276,33 @@ def add_domain():
|
||||
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')
|
||||
|
||||
if not domain:
|
||||
log_operation('request_ssl', False, 'Domain not provided')
|
||||
return jsonify({'status': 'error', 'message': 'Domain is required'}), 400
|
||||
|
||||
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)
|
||||
|
||||
if result.returncode == 0:
|
||||
# Combine cert files and copy to HAProxy certs directory
|
||||
@ -251,15 +325,180 @@ def request_ssl():
|
||||
cursor.close()
|
||||
conn.close()
|
||||
generate_config()
|
||||
log_operation('request_ssl', True, f'SSL certificate obtained for {domain}')
|
||||
return jsonify({'status': 'success'})
|
||||
return jsonify({'status': 'error', 'message': 'Failed to obtain SSL certificate'})
|
||||
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('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__':
|
||||
|
42
scripts/external-monitoring-example.conf
Normal file
42
scripts/external-monitoring-example.conf
Normal 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
|
224
scripts/monitor-errors-external.sh
Executable file
224
scripts/monitor-errors-external.sh
Executable 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
159
scripts/monitor-errors.sh
Executable 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
|
28
scripts/monitoring-example.conf
Normal file
28
scripts/monitoring-example.conf
Normal 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
161
scripts/test-api.sh
Executable 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 "$@"
|
Loading…
x
Reference in New Issue
Block a user