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
|
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
|
WORKDIR /haproxy
|
||||||
COPY ./templates /haproxy/templates
|
COPY ./templates /haproxy/templates
|
||||||
COPY requirements.txt /haproxy/
|
COPY requirements.txt /haproxy/
|
||||||
@ -8,6 +8,9 @@ COPY scripts /haproxy/scripts
|
|||||||
RUN chmod +x /haproxy/scripts/*
|
RUN chmod +x /haproxy/scripts/*
|
||||||
RUN pip install -r requirements.txt
|
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
|
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
|
EXPOSE 80 443 8000
|
||||||
# Add health check
|
# Add health check
|
||||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
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:
|
To run the container:
|
||||||
```bash
|
```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
|
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
|
## Features
|
||||||
|
|
||||||
- RESTful API for HAProxy configuration management
|
- 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
|
- Template override support for custom backend configurations
|
||||||
- Process monitoring and auto-restart capabilities
|
- Process monitoring and auto-restart capabilities
|
||||||
- Socket-based HAProxy runtime API integration
|
- 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
|
## 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 Domain
|
||||||
Add a new domain with backend servers configuration.
|
Add a new domain with backend servers configuration.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
POST /api/domain
|
POST /api/domain
|
||||||
|
Authorization: Bearer your-api-key
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -100,6 +145,7 @@ Request and configure SSL certificate for a domain using Let's Encrypt.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
POST /api/ssl
|
POST /api/ssl
|
||||||
|
Authorization: Bearer your-api-key
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -117,6 +163,7 @@ Remove a domain and its associated backend configuration.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
DELETE /api/domain
|
DELETE /api/domain
|
||||||
|
Authorization: Bearer your-api-key
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -129,3 +176,153 @@ Content-Type: application/json
|
|||||||
"message": "Domain configuration removed"
|
"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 sqlite3
|
||||||
import os
|
import os
|
||||||
from flask import Flask, request, jsonify, render_template
|
from flask import Flask, request, jsonify, render_template, send_file
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import subprocess
|
import subprocess
|
||||||
import jinja2
|
import jinja2
|
||||||
import socket
|
import socket
|
||||||
import psutil
|
import psutil
|
||||||
|
import functools
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
import json
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
# Configuration
|
||||||
DB_FILE = '/etc/haproxy/haproxy_config.db'
|
DB_FILE = '/etc/haproxy/haproxy_config.db'
|
||||||
TEMPLATE_DIR = Path('templates')
|
TEMPLATE_DIR = Path('templates')
|
||||||
HAPROXY_CONFIG_PATH = '/etc/haproxy/haproxy.cfg'
|
HAPROXY_CONFIG_PATH = '/etc/haproxy/haproxy.cfg'
|
||||||
SSL_CERTS_DIR = '/etc/haproxy/certs'
|
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():
|
def init_db():
|
||||||
with sqlite3.connect(DB_FILE) as conn:
|
with sqlite3.connect(DB_FILE) as conn:
|
||||||
@ -102,6 +150,7 @@ template_loader = jinja2.FileSystemLoader(TEMPLATE_DIR)
|
|||||||
template_env = jinja2.Environment(loader=template_loader)
|
template_env = jinja2.Environment(loader=template_loader)
|
||||||
|
|
||||||
@app.route('/api/domains', methods=['GET'])
|
@app.route('/api/domains', methods=['GET'])
|
||||||
|
@require_api_key
|
||||||
def get_domains():
|
def get_domains():
|
||||||
try:
|
try:
|
||||||
with sqlite3.connect(DB_FILE) as conn:
|
with sqlite3.connect(DB_FILE) as conn:
|
||||||
@ -113,8 +162,10 @@ def get_domains():
|
|||||||
LEFT JOIN backends b ON d.id = b.domain_id
|
LEFT JOIN backends b ON d.id = b.domain_id
|
||||||
''')
|
''')
|
||||||
domains = [dict(row) for row in cursor.fetchall()]
|
domains = [dict(row) for row in cursor.fetchall()]
|
||||||
|
log_operation('get_domains', True)
|
||||||
return jsonify(domains)
|
return jsonify(domains)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
log_operation('get_domains', False, str(e))
|
||||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||||
|
|
||||||
@app.route('/health', methods=['GET'])
|
@app.route('/health', methods=['GET'])
|
||||||
@ -141,27 +192,32 @@ def health_check():
|
|||||||
}), 500
|
}), 500
|
||||||
|
|
||||||
@app.route('/api/regenerate', methods=['GET'])
|
@app.route('/api/regenerate', methods=['GET'])
|
||||||
|
@require_api_key
|
||||||
def regenerate_conf():
|
def regenerate_conf():
|
||||||
try:
|
try:
|
||||||
generate_config()
|
generate_config()
|
||||||
|
log_operation('regenerate_config', True)
|
||||||
return jsonify({'status': 'success'}), 200
|
return jsonify({'status': 'success'}), 200
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
log_operation('regenerate_config', False, str(e))
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'status': 'failed',
|
'status': 'failed',
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}), 500
|
}), 500
|
||||||
|
|
||||||
@app.route('/api/reload', methods=['GET'])
|
@app.route('/api/reload', methods=['GET'])
|
||||||
|
@require_api_key
|
||||||
def reload_haproxy():
|
def reload_haproxy():
|
||||||
|
try:
|
||||||
if is_process_running('haproxy'):
|
if is_process_running('haproxy'):
|
||||||
# Use a proper shell command string when shell=True is set
|
# Use a proper shell command string when shell=True is set
|
||||||
result = subprocess.run('echo "reload" | socat stdio /tmp/haproxy-cli',
|
result = subprocess.run('echo "reload" | socat stdio /tmp/haproxy-cli',
|
||||||
check=True, capture_output=True, text=True, shell=True)
|
check=True, capture_output=True, text=True, shell=True)
|
||||||
print(f"Reload result: {result.stdout}, {result.stderr}, {result.returncode}")
|
print(f"Reload result: {result.stdout}, {result.stderr}, {result.returncode}")
|
||||||
|
log_operation('reload_haproxy', True)
|
||||||
return jsonify({'status': 'success'}), 200
|
return jsonify({'status': 'success'}), 200
|
||||||
else:
|
else:
|
||||||
# Start HAProxy if it's not running
|
# Start HAProxy if it's not running
|
||||||
try:
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
['haproxy', '-W', '-S', '/tmp/haproxy-cli,level,admin', '-f', HAPROXY_CONFIG_PATH],
|
['haproxy', '-W', '-S', '/tmp/haproxy-cli,level,admin', '-f', HAPROXY_CONFIG_PATH],
|
||||||
check=True,
|
check=True,
|
||||||
@ -170,18 +226,21 @@ def reload_haproxy():
|
|||||||
)
|
)
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
print("HAProxy started successfully")
|
print("HAProxy started successfully")
|
||||||
|
log_operation('start_haproxy', True)
|
||||||
return jsonify({'status': 'success'}), 200
|
return jsonify({'status': 'success'}), 200
|
||||||
else:
|
else:
|
||||||
print(f"HAProxy start command returned: {result.stdout}")
|
error_msg = f"HAProxy start command returned: {result.stdout}\nError output: {result.stderr}"
|
||||||
print(f"Error 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:
|
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}"
|
||||||
return jsonify({
|
print(error_msg)
|
||||||
'status': 'failed',
|
log_operation('reload_haproxy', False, error_msg)
|
||||||
'error': f"Failed to start HAProxy: {e.stdout}\n{e.stderr}"
|
return jsonify({'status': 'failed', 'error': error_msg}), 500
|
||||||
}), 500
|
|
||||||
|
|
||||||
@app.route('/api/domain', methods=['POST'])
|
@app.route('/api/domain', methods=['POST'])
|
||||||
|
@require_api_key
|
||||||
def add_domain():
|
def add_domain():
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
domain = data.get('domain')
|
domain = data.get('domain')
|
||||||
@ -189,6 +248,11 @@ def add_domain():
|
|||||||
backend_name = data.get('backend_name')
|
backend_name = data.get('backend_name')
|
||||||
servers = data.get('servers', [])
|
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:
|
with sqlite3.connect(DB_FILE) as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
@ -212,23 +276,33 @@ def add_domain():
|
|||||||
cursor.close()
|
cursor.close()
|
||||||
conn.close()
|
conn.close()
|
||||||
generate_config()
|
generate_config()
|
||||||
|
log_operation('add_domain', True, f'Domain {domain} added successfully')
|
||||||
return jsonify({'status': 'success', 'domain_id': domain_id})
|
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('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
return render_template('index.html')
|
return render_template('index.html')
|
||||||
|
|
||||||
@app.route('/api/ssl', methods=['POST'])
|
@app.route('/api/ssl', methods=['POST'])
|
||||||
|
@require_api_key
|
||||||
def request_ssl():
|
def request_ssl():
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
domain = data.get('domain')
|
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
|
# Request Let's Encrypt certificate
|
||||||
result = subprocess.run([
|
result = subprocess.run([
|
||||||
'certbot', 'certonly', '-n', '--standalone',
|
'certbot', 'certonly', '-n', '--standalone',
|
||||||
'--preferred-challenges', 'http', '--http-01-port=8688',
|
'--preferred-challenges', 'http', '--http-01-port=8688',
|
||||||
'-d', domain
|
'-d', domain
|
||||||
])
|
], capture_output=True, text=True)
|
||||||
|
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
# Combine cert files and copy to HAProxy certs directory
|
# Combine cert files and copy to HAProxy certs directory
|
||||||
@ -251,15 +325,180 @@ def request_ssl():
|
|||||||
cursor.close()
|
cursor.close()
|
||||||
conn.close()
|
conn.close()
|
||||||
generate_config()
|
generate_config()
|
||||||
|
log_operation('request_ssl', True, f'SSL certificate obtained for {domain}')
|
||||||
return jsonify({'status': 'success'})
|
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'])
|
@app.route('/api/domain', methods=['DELETE'])
|
||||||
|
@require_api_key
|
||||||
def remove_domain():
|
def remove_domain():
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
domain = data.get('domain')
|
domain = data.get('domain')
|
||||||
|
|
||||||
if not domain:
|
if not domain:
|
||||||
|
log_operation('remove_domain', False, 'Domain is required')
|
||||||
return jsonify({'status': 'error', 'message': 'Domain is required'}), 400
|
return jsonify({'status': 'error', 'message': 'Domain is required'}), 400
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -271,6 +510,7 @@ def remove_domain():
|
|||||||
domain_result = cursor.fetchone()
|
domain_result = cursor.fetchone()
|
||||||
|
|
||||||
if not domain_result:
|
if not domain_result:
|
||||||
|
log_operation('remove_domain', False, f'Domain {domain} not found')
|
||||||
return jsonify({'status': 'error', 'message': 'Domain not found'}), 404
|
return jsonify({'status': 'error', 'message': 'Domain not found'}), 404
|
||||||
|
|
||||||
domain_id = domain_result[0]
|
domain_id = domain_result[0]
|
||||||
@ -301,9 +541,11 @@ def remove_domain():
|
|||||||
# Regenerate HAProxy config
|
# Regenerate HAProxy config
|
||||||
generate_config()
|
generate_config()
|
||||||
|
|
||||||
|
log_operation('remove_domain', True, f'Domain {domain} removed successfully')
|
||||||
return jsonify({'status': 'success', 'message': 'Domain configuration removed'})
|
return jsonify({'status': 'success', 'message': 'Domain configuration removed'})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
log_operation('remove_domain', False, str(e))
|
||||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||||
|
|
||||||
def generate_config():
|
def generate_config():
|
||||||
@ -349,7 +591,7 @@ def generate_config():
|
|||||||
# Add domain configurations
|
# Add domain configurations
|
||||||
for domain in domains:
|
for domain in domains:
|
||||||
if not domain['backend_name']:
|
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
|
continue
|
||||||
|
|
||||||
# Add domain ACL
|
# Add domain ACL
|
||||||
@ -359,9 +601,9 @@ def generate_config():
|
|||||||
name=domain['backend_name']
|
name=domain['backend_name']
|
||||||
)
|
)
|
||||||
config_acls.append(domain_acl)
|
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:
|
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
|
continue
|
||||||
|
|
||||||
# Add backend configuration
|
# Add backend configuration
|
||||||
@ -372,11 +614,11 @@ def generate_config():
|
|||||||
servers = [dict(server) for server in cursor.fetchall()]
|
servers = [dict(server) for server in cursor.fetchall()]
|
||||||
|
|
||||||
if not servers:
|
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
|
continue
|
||||||
|
|
||||||
if domain['template_override'] is not None:
|
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"
|
template_file = domain['template_override'] + ".tpl"
|
||||||
backend_block = template_env.get_template(template_file).render(
|
backend_block = template_env.get_template(template_file).render(
|
||||||
name=domain['backend_name'],
|
name=domain['backend_name'],
|
||||||
@ -390,9 +632,9 @@ def generate_config():
|
|||||||
servers=servers
|
servers=servers
|
||||||
)
|
)
|
||||||
config_backends.append(backend_block)
|
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:
|
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
|
continue
|
||||||
|
|
||||||
# Add ACLS
|
# Add ACLS
|
||||||
@ -406,17 +648,25 @@ def generate_config():
|
|||||||
temp_config_path = "/etc/haproxy/haproxy.cfg"
|
temp_config_path = "/etc/haproxy/haproxy.cfg"
|
||||||
|
|
||||||
config_content = '\n'.join(config_parts)
|
config_content = '\n'.join(config_parts)
|
||||||
print("Final config content:", config_content) # Debug log
|
logger.debug("Generated HAProxy configuration")
|
||||||
|
|
||||||
# Write complete configuration to tmp
|
# Write complete configuration to tmp
|
||||||
# Check HAProxy Configuration, and reload if it works
|
# Check HAProxy Configuration, and reload if it works
|
||||||
with open(temp_config_path, 'w') as f:
|
with open(temp_config_path, 'w') as f:
|
||||||
f.write(config_content)
|
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:
|
if result.returncode == 0:
|
||||||
print("HAProxy configuration check passed")
|
logger.info("HAProxy configuration check passed")
|
||||||
if is_process_running('haproxy'):
|
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:
|
else:
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
@ -426,15 +676,26 @@ def generate_config():
|
|||||||
text=True
|
text=True
|
||||||
)
|
)
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
print("HAProxy started successfully")
|
logger.info("HAProxy started successfully")
|
||||||
|
log_operation('generate_config', True, 'Configuration generated and HAProxy started')
|
||||||
else:
|
else:
|
||||||
print(f"HAProxy start command returned: {result.stdout}")
|
error_msg = f"HAProxy start command returned: {result.stdout}\nError output: {result.stderr}"
|
||||||
print(f"Error output: {result.stderr}")
|
logger.error(error_msg)
|
||||||
|
log_operation('generate_config', False, error_msg)
|
||||||
except subprocess.CalledProcessError as e:
|
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
|
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:
|
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
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise
|
raise
|
||||||
@ -449,12 +710,16 @@ def start_haproxy():
|
|||||||
text=True
|
text=True
|
||||||
)
|
)
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
print("HAProxy started successfully")
|
logger.info("HAProxy started successfully")
|
||||||
|
log_operation('start_haproxy', True, 'HAProxy started successfully')
|
||||||
else:
|
else:
|
||||||
print(f"HAProxy start command returned: {result.stdout}")
|
error_msg = f"HAProxy start command returned: {result.stdout}\nError output: {result.stderr}"
|
||||||
print(f"Error output: {result.stderr}")
|
logger.error(error_msg)
|
||||||
|
log_operation('start_haproxy', False, error_msg)
|
||||||
except subprocess.CalledProcessError as e:
|
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
|
raise
|
||||||
|
|
||||||
if __name__ == '__main__':
|
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