#!/usr/bin/env python3 import os import time import logging import difflib from flask import Flask, request, jsonify, abort from flask_sslify import SSLify from datetime import datetime, timezone, timedelta from croniter import croniter import psutil app = Flask(__name__) sslify = SSLify(app) # Konfiguracja tokenu autoryzacyjnego (przyjmujemy pre-shared secret) API_TOKEN = os.environ.get("HOSTS_DAEMON_API_TOKEN", "changeme") # Konfiguracja logowania – logi zapisywane do pliku /opt/hosts_daemon/logs/daemon.log LOG_DIR = "/opt/hosts_daemon/logs" os.makedirs(LOG_DIR, exist_ok=True) LOG_FILE = os.path.join(LOG_DIR, "daemon.log") logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", handlers=[ logging.FileHandler(LOG_FILE), logging.StreamHandler() ] ) logger = logging.getLogger("hosts_daemon") # Globalne metryki metrics = { "total_requests": 0, "total_time": 0.0, "endpoints": {}, "hosts_get": 0, "hosts_post": 0, } # Funkcja sprawdzająca autoryzację def require_auth(): token = request.headers.get("Authorization") if token != API_TOKEN: abort(401, description="Unauthorized") # Logowanie i zbieranie metryk – logujemy czas rozpoczęcia, adres IP, endpoint i metodę @app.before_request def before_request_logging(): request.start_time = time.time() client_ip = request.remote_addr endpoint = request.path logger.info(f"Request from {client_ip} to {endpoint} [{request.method}], Auth: {request.headers.get('Authorization')}") metrics["total_requests"] += 1 if endpoint not in metrics["endpoints"]: metrics["endpoints"][endpoint] = {"count": 0, "total_time": 0.0} metrics["endpoints"][endpoint]["count"] += 1 @app.after_request def after_request_logging(response): elapsed = time.time() - request.start_time metrics["total_time"] += elapsed endpoint = request.path if endpoint in metrics["endpoints"]: metrics["endpoints"][endpoint]["total_time"] += elapsed logger.info(f"Completed {endpoint} in {elapsed:.3f} sec with status {response.status_code}") return response # Endpoint GET /hosts – pobiera zawartość /etc/hosts @app.route('/hosts', methods=['GET']) def get_hosts(): require_auth() metrics["hosts_get"] += 1 try: with open('/etc/hosts', 'r') as f: content = f.read() logger.info(f"/hosts GET successful from {request.remote_addr}") return jsonify({"hosts": content}) except Exception as e: logger.error(f"/hosts GET error: {str(e)}") return jsonify({"error": str(e)}), 500 # Endpoint POST /hosts – aktualizuje plik /etc/hosts @app.route('/hosts', methods=['POST']) def update_hosts(): require_auth() metrics["hosts_post"] += 1 data = request.get_json() if not data or "hosts" not in data: logger.warning(f"/hosts POST: missing 'hosts' key from {request.remote_addr}") return jsonify({"error": "Invalid request, missing 'hosts' key"}), 400 new_content = data["hosts"] try: with open('/etc/hosts', 'w') as f: f.write(new_content) logger.info(f"/hosts POST updated by {request.remote_addr}") return jsonify({"message": "File updated successfully"}) except Exception as e: logger.error(f"/hosts POST error: {str(e)}") return jsonify({"error": str(e)}), 500 # Endpoint /health – zwraca status usługi @app.route('/health', methods=['GET']) def health(): #require_auth() uptime = time.time() - psutil.boot_time() now = datetime.now(timezone.utc).isoformat() logger.info(f"/health check from {request.remote_addr}") return jsonify({"status": "ok", "time": now, "uptime": uptime}) # Endpoint /metrics – zwraca metryki @app.route('/metrics', methods=['GET']) def metrics_endpoint(): #require_auth() avg_time = metrics["total_time"] / metrics["total_requests"] if metrics["total_requests"] > 0 else 0.0 ep_data = {} for ep, data in metrics["endpoints"].items(): ep_avg = data["total_time"] / data["count"] if data["count"] > 0 else 0.0 ep_data[ep] = {"count": data["count"], "avg_time": ep_avg} response_data = { "total_requests": metrics["total_requests"], "avg_response_time": avg_time, "endpoints": ep_data, "hosts_get": metrics.get("hosts_get", 0), "hosts_post": metrics.get("hosts_post", 0) } logger.info(f"/metrics accessed by {request.remote_addr}") return jsonify(response_data) if __name__ == '__main__': app.run( host='0.0.0.0', port=8000, ssl_context=('/opt/hosts_daemon/ssl/hosts_daemon.crt', '/opt/hosts_daemon/ssl/hosts_daemon.key') )