OBSIDIUM — Guía de Implementación
00
Preparación del Entorno (VirtualBox)
INFRAESTRUCTURA
Crear máquinas virtuales en VirtualBox
Una VM servidor (10.0.0.1) y al menos una VM cliente (10.0.0.10)
▶

Necesitas dos VMs con Ubuntu Server 24.04.3 LTS. Ambas con adaptador en modo puente (bridge) para que sean visibles en la red física.

Especificaciones mínimas por VM:

RAM: 2 GB mínimo (4 GB recomendado) CPU: 2 núcleos Disco: 20 GB Red: Adaptador puente (Bridged Adapter)
Descarga Ubuntu Server 24.04.3 LTS desde https://ubuntu.com/download/server
Actualizar el sistema en ambas VMs
Primer arranque tras instalar Ubuntu Server
▶
sudo apt update && sudo apt upgrade -y
# Instalar herramientas básicas sudo apt install -y curl wget net-tools ufw git
01
Implementación de la VPN WireGuard
RED PRIVADA
Instalar WireGuard en el servidor
VM Servidor — 10.0.0.1
▶
sudo apt install -y wireguard wireguard-tools
Generar claves criptográficas en el servidor
Par de claves pública/privada con permisos restrictivos
▶
# Generar clave privada del servidor wg genkey | sudo tee /etc/wireguard/server_private.key sudo chmod 600 /etc/wireguard/server_private.key # Derivar la clave pública del servidor sudo cat /etc/wireguard/server_private.key | wg pubkey | sudo tee /etc/wireguard/server_public.key # Ver las claves generadas (guárdalas) sudo cat /etc/wireguard/server_private.key sudo cat /etc/wireguard/server_public.key
Generar claves en el cliente
VM Cliente — 10.0.0.10
▶
sudo apt install -y wireguard wireguard-tools wg genkey | sudo tee /etc/wireguard/client_private.key sudo chmod 600 /etc/wireguard/client_private.key sudo cat /etc/wireguard/client_private.key | wg pubkey | sudo tee /etc/wireguard/client_public.key # Anotar estas claves — las usarás en el servidor sudo cat /etc/wireguard/client_private.key sudo cat /etc/wireguard/client_public.key
Habilitar IP Forwarding en el servidor
Permite que el servidor actúe como pasarela de red
▶
# Editar sysctl.conf sudo nano /etc/sysctl.conf # Añadir o descomentar esta línea: net.ipv4.ip_forward=1 # Aplicar cambios sin reiniciar sudo sysctl -p
Si esta línea no está activa, el túnel WireGuard no enrutará tráfico entre nodos.
Crear configuración del servidor WireGuard
/etc/wireguard/wg0.conf en el servidor
▶

Sustituye <CLAVE_PRIVADA_SERVIDOR> y <CLAVE_PUBLICA_CLIENTE> por las claves generadas.

/etc/wireguard/wg0.conf (SERVIDOR)
[Interface] Address = 10.0.0.1/24 ListenPort = 51820 PrivateKey = <CLAVE_PRIVADA_SERVIDOR> PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o enp0s3 -j MASQUERADE PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o enp0s3 -j MASQUERADE [Peer] PublicKey = <CLAVE_PUBLICA_CLIENTE> AllowedIPs = 10.0.0.10/32
# Crear el archivo con permisos seguros sudo nano /etc/wireguard/wg0.conf sudo chmod 600 /etc/wireguard/wg0.conf
enp0s3 es el nombre de tu interfaz de red. Verifica el tuyo con: ip link show
Crear configuración del cliente WireGuard
/etc/wireguard/wg0.conf en el cliente
▶

Sustituye <CLAVE_PRIVADA_CLIENTE> y <CLAVE_PUBLICA_SERVIDOR>. En Endpoint, pon la IP pública real del servidor.

/etc/wireguard/wg0.conf (CLIENTE)
[Interface] Address = 10.0.0.10/24 PrivateKey = <CLAVE_PRIVADA_CLIENTE> DNS = 10.0.0.1 [Peer] PublicKey = <CLAVE_PUBLICA_SERVIDOR> Endpoint = vpn.obsidium.es:51820 AllowedIPs = 10.0.0.0/24 PersistentKeepalive = 25
Arrancar WireGuard y habilitar en el inicio
En servidor y cliente
▶
# Iniciar el túnel sudo wg-quick up wg0 # Habilitar inicio automático sudo systemctl enable wg-quick@wg0 # Verificar el túnel sudo wg show # Probar conectividad (desde cliente al servidor) ping 10.0.0.1
# Abrir el puerto UDP en el firewall del SERVIDOR sudo ufw allow 51820/udp sudo ufw allow OpenSSH sudo ufw enable
Si el handshake no aparece en "wg show", el problema más común es el puerto 51820 UDP bloqueado en el firewall.
02
Despliegue del Servidor Web Apache
SERVIDOR WEB
Instalar Apache
En la VM servidor
▶
sudo apt install -y apache2 sudo systemctl enable apache2 sudo systemctl start apache2 # Habilitar módulos necesarios sudo a2enmod ssl sudo a2enmod rewrite sudo a2enmod headers
Crear directorio del proyecto y usuario
Estructura de directorios segura
▶
# Crear usuario del sistema para la dashboard sudo adduser --system --no-create-home --group dashboard # Crear estructura de directorios sudo mkdir -p /home/dashboard/web/public sudo chown -R dashboard:dashboard /home/dashboard/ # Enlace simbólico Apache → proyecto sudo ln -s /home/dashboard/web/public/ /var/www/dashboard # Ajustar permisos sudo chown -R www-data:www-data /home/dashboard/web/public/ sudo chmod -R 755 /home/dashboard/web/public/
Generar certificado SSL autofirmado
Para cifrado HTTPS dentro de la VPN
▶
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout /etc/ssl/private/obsidium.key \ -out /etc/ssl/certs/obsidium.crt \ -subj "/C=ES/ST=Canarias/L=LasPalmas/O=Obsidium/CN=panel.obsidium.es" sudo chmod 600 /etc/ssl/private/obsidium.key
Configurar VirtualHost de la dashboard
Acceso restringido solo desde la VPN (10.0.0.0/24)
▶
# Añadir puerto 8080 a Apache echo "Listen 8080" | sudo tee -a /etc/apache2/ports.conf sudo nano /etc/apache2/sites-available/dashboard.conf
/etc/apache2/sites-available/dashboard.conf
<VirtualHost *:8080> ServerName panel.obsidium.es DocumentRoot /var/www/dashboard <Directory /var/www/dashboard> Options -Indexes +FollowSymLinks AllowOverride None Require ip 10.0.0.0/24 </Directory> SSLEngine on SSLCertificateFile /etc/ssl/certs/obsidium.crt SSLCertificateKeyFile /etc/ssl/private/obsidium.key ErrorLog ${APACHE_LOG_DIR}/dashboard_error.log CustomLog ${APACHE_LOG_DIR}/dashboard_access.log combined </VirtualHost>
# Activar el sitio y reiniciar Apache sudo a2ensite dashboard.conf sudo a2dissite 000-default.conf sudo systemctl restart apache2
La directiva "Require ip 10.0.0.0/24" es el pilar de seguridad: bloquea todo acceso que no venga por el túnel WireGuard.
03
Configuración del Entorno PHP
BACKEND
Instalar PHP 8.3 con extensiones
Intérprete + módulo Apache + conector MySQL
▶
sudo apt install -y php libapache2-mod-php php-mysql php-json php-curl # Verificar versión instalada php --version # Reiniciar Apache para cargar el módulo PHP sudo systemctl restart apache2
Verificar integración PHP + Apache
Prueba temporal — eliminar después
▶
# Crear archivo de prueba temporal echo "<?php phpinfo(); ?>" | sudo tee /var/www/dashboard/info.php # Acceder desde el cliente VPN: # https://10.0.0.1:8080/info.php # IMPORTANTE: Eliminar tras la prueba sudo rm /var/www/dashboard/info.php
Nunca dejes info.php en producción. Expone información sensible del servidor.
04
Sistema de Gestión de Base de Datos (MariaDB)
BASE DE DATOS
Instalar MariaDB y securizar
Instalación base + hardening inicial
▶
sudo apt install -y mariadb-server mariadb-client sudo systemctl enable mariadb sudo systemctl start mariadb # Script de securización interactivo sudo mysql_secure_installation

Durante el script, responde:

Switch to unix_socket auth → N | Change root password → Y | Remove anonymous users → Y | Disallow root login remotely → Y | Remove test database → Y | Reload privilege tables → Y
Crear base de datos y usuario dedicado
Principio de mínimo privilegio
▶
sudo mysql -u root -p
-- Crear base de datos CREATE DATABASE dashboard CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- Crear usuario con acceso solo desde localhost CREATE USER 'dashboard_user'@'localhost' IDENTIFIED BY 'TuPasswordSegura123!'; -- Otorgar permisos solo sobre la BD del proyecto GRANT ALL PRIVILEGES ON dashboard.* TO 'dashboard_user'@'localhost'; FLUSH PRIVILEGES; -- Verificar SHOW GRANTS FOR 'dashboard_user'@'localhost'; EXIT;
Crear el esquema de tablas (DDL)
Estructura completa de la base de datos Obsidium
▶
sudo mysql -u dashboard_user -p dashboard
-- DROP DATABASE dashboard; DROP DATABASE IF EXISTS dashboard; -- Crear base de datos CREATE DATABASE IF NOT EXISTS dashboard; USE dashboard; -- ===================== -- USUARIO -- ===================== CREATE TABLE usuario ( id INT AUTO_INCREMENT PRIMARY KEY, nombre VARCHAR(100), apellido VARCHAR(100), email VARCHAR(150) UNIQUE, telefono VARCHAR(30), password VARCHAR(255), rol ENUM('admin','operador','viewer') DEFAULT 'viewer' ) ENGINE=InnoDB; -- ===================== -- SERVIDOR -- ===================== CREATE TABLE servidor ( id INT AUTO_INCREMENT PRIMARY KEY, nombre VARCHAR(100), ip VARCHAR(45), localizacion VARCHAR(100), estado ENUM('online','offline','maintenance'), tipo VARCHAR(50), OS VARCHAR(50), dueño VARCHAR(100), fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB; -- ===================== -- AGENTE -- ===================== CREATE TABLE agente ( id INT AUTO_INCREMENT PRIMARY KEY, server_id INT, clave VARCHAR(64), activo BOOLEAN DEFAULT TRUE, FOREIGN KEY (server_id) REFERENCES servidor(id) ) ENGINE=InnoDB; -- ===================== -- MONITORIZACION -- ===================== CREATE TABLE monitorizacion ( id INT AUTO_INCREMENT PRIMARY KEY, server_id INT, cpu FLOAT, ram FLOAT, ping INT, ultimo_chekeo TIMESTAMP, tiempo_activo INT, servicios_activos JSON, FOREIGN KEY (server_id) REFERENCES servidor(id) ) ENGINE=InnoDB; -- ===================== -- ALERTAS -- ===================== CREATE TABLE alertas ( id INT AUTO_INCREMENT PRIMARY KEY, server_id INT, tipo VARCHAR(50), nivel ENUM('info','warning','critical'), mensaje TEXT, fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP, fecha_resolucion TIMESTAMP NULL, estado ENUM('activo','desconocido','resuelto') DEFAULT 'desconocido', FOREIGN KEY (server_id) REFERENCES servidor(id) ) ENGINE=InnoDB; -- ===================== -- NOTIFICACIONES -- ===================== CREATE TABLE notificaciones ( id INT AUTO_INCREMENT PRIMARY KEY, alerta_id INT, canal ENUM('email','sms'), destinatario INT, estado ENUM('pendiente','enviado','falló'), fecha TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (alerta_id) REFERENCES alertas(id), FOREIGN KEY (destinatario) REFERENCES usuario(id) ) ENGINE=InnoDB; -- ===================== -- LOG -- ===================== CREATE TABLE log ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NULL, agente_id INT NULL, accion VARCHAR(100), fecha TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES usuario(id), FOREIGN KEY (agente_id) REFERENCES agente(id) ) ENGINE=InnoDB; -- ===================== -- REGLAS -- ===================== CREATE TABLE reglas ( id INT AUTO_INCREMENT PRIMARY KEY, nombre VARCHAR(100), server_id INT NULL, metrica VARCHAR(50), operador ENUM('>','<','>=','<='), limite FLOAT, duracion INT, nivel ENUM('info','warning','critical'), activa BOOLEAN DEFAULT TRUE, fecha TIMESTAMP DEFAULT CURRENT_TIMESTAMP, creador INT, FOREIGN KEY (server_id) REFERENCES servidor(id), FOREIGN KEY (creador) REFERENCES usuario(id) ) ENGINE=InnoDB; EXIT;
Crear conexión PHP a MariaDB (PDO)
Archivo conexion.php con manejo de errores
▶
/home/dashboard/web/public/conexion.php
<?php $host = 'localhost'; $db = 'dashboard'; $user = 'USUARIO'; $pass = 'CONTRASEÑA'; $charset = 'utf8mb4'; $dsn = "mysql:host=$host;port=3310;dbname=$db;charset=$charset"; $options = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]; try { $pdo = new PDO($dsn, $user, $pass, $options); } catch (\PDOException $e) { throw new \PDOException($e->getMessage(), (int)$e->getCode()); } </?php>
05
Agente de Monitorización en Python
AGENTE
Instalar dependencias en el nodo cliente
Python 3 + psutil + requests
▶
sudo apt install -y python3 python3-pip pip3 install psutil requests --break-system-packages
Crear el agente de monitorización
Script Python que recoge y envía métricas
▶
mkdir -p /home/$USER/obsidium-agent nano /home/$USER/obsidium-agent/agent.py
agent.py
#!/usr/bin/env python3 import psutil, requests, json, time, socket, platform # --- CONFIGURACIÓN --- SERVER_URL = 'https://10.0.0.1:8080/api/receive_metrics.php' SERVER_ID = 1 # ID del servidor en la BD CLAVE = 'clave_agente' # Clave del agente en la BD (tabla agente.clave) INTERVALO = 60 # Segundos entre envíos def get_metrics(): cpu = psutil.cpu_percent(interval=1) mem = psutil.virtual_memory() disk = psutil.disk_usage('/') net = psutil.net_io_counters() conns = len(psutil.net_connections()) return { 'server_id': SERVER_ID, 'clave': CLAVE, 'cpu': cpu, 'ram': mem.percent, 'ping': 0, 'ultimo_chekeo': int(time.time()), 'tiempo_activo': int(time.time() - psutil.boot_time()), # JSON -> monitorizacion.servicios_activos 'servicios_activos': { 'hostname': socket.gethostname(), 'os': platform.system(), 'ram_total_mb': round(mem.total / 1024**2, 2), 'ram_used_mb': round(mem.used / 1024**2, 2), 'disk_total_gb': round(disk.total / 1024**3, 2), 'disk_used_gb': round(disk.used / 1024**3, 2), 'net_sent_mb': round(net.bytes_sent / 1024**2, 2), 'net_recv_mb': round(net.bytes_recv / 1024**2, 2), 'conexiones': conns } } print("=== OBSIDIUM Monitoring Agent ===") print(f"Servidor: {SERVER_URL}") print(f"Intervalo: {INTERVALO}s") while True: try: data = get_metrics() r = requests.post(SERVER_URL, json=data, verify=False, timeout=10) print(f"[{time.strftime('%H:%M:%S')}] Enviado — HTTP {r.status_code}") except Exception as e: print(f"[{time.strftime('%H:%M:%S')}] Error: {e}") time.sleep(INTERVALO)
Crear el endpoint PHP de recepción de métricas
Backend que valida y almacena la telemetría
▶
sudo mkdir -p /var/www/dashboard/api sudo nano /var/www/dashboard/api/receive_metrics.php
receive_metrics.php
<?php header("Content-Type: application/json"); require_once '../conexion.php'; // ===================== // 1. Leer JSON del agente // ===================== $data = json_decode(file_get_contents("php://input"), true); if (!$data) { http_response_code(400); die(json_encode([ "status" => "error", "message" => "JSON inválido" ])); } // ===================== // 2. Validar campos según tabla monitorizacion + agente // ===================== $required = [ "server_id", "clave", "cpu", "ram", "ping", "ultimo_chekeo", "tiempo_activo", "servicios_activos" ]; foreach ($required as $field) { if (!isset($data[$field])) { http_response_code(400); die(json_encode([ "status" => "error", "message" => "Falta: $field" ])); } } // ===================== // 3. Autenticación contra tabla agente // ===================== $stmt = $pdo->prepare(" SELECT id FROM agente WHERE server_id = ? AND clave = ? AND activo = 1 "); $stmt->execute([ $data['server_id'], $data['clave'] ]); if (!$stmt->fetch()) { http_response_code(403); die(json_encode([ "status" => "error", "message" => "Agente no autorizado" ])); } // ===================== // 4. Insertar en tabla monitorizacion (BD dashboard) // ===================== $stmt = $pdo->prepare(" INSERT INTO monitorizacion ( server_id, cpu, ram, ping, ultimo_chekeo, tiempo_activo, servicios_activos ) VALUES ( ?, ?, ?, ?, FROM_UNIXTIME(?), ?, ? ) "); $stmt->execute([ $data['server_id'], $data['cpu'], $data['ram'], $data['ping'], $data['ultimo_chekeo'], $data['tiempo_activo'], json_encode($data['servicios_activos']) ]); // ===================== // 5. Respuesta API // ===================== echo json_encode([ "status" => "ok" ]); ?>
# Registrar el servidor y agente en la BD antes de la primera ejecución mysql -u dashboard_user -p dashboard -- Insertar servidor INSERT INTO servidor (nombre, ip, estado, tipo) VALUES ('cliente1-VirtualBox', '10.0.0.10', 'offline', 'VM'); -- Insertar agente con su clave INSERT INTO agente (server_id, clave, activo) VALUES (1, 'clave_agente_segura', 1);
Configurar el agente como servicio systemd
Inicio automático y reinicio ante fallos
▶
sudo nano /etc/systemd/system/obsidium-agent.service
/etc/systemd/system/obsidium-agent.service
[Unit] Description=Obsidium Monitoring Agent After=network.target wg-quick@wg0.service [Service] Type=simple User=root ExecStart=/usr/bin/python3 /home/USUARIO/obsidium-agent/agent.py Restart=always RestartSec=10 [Install] WantedBy=multi-user.target
sudo systemctl daemon-reload sudo systemctl enable obsidium-agent sudo systemctl start obsidium-agent sudo systemctl status obsidium-agent
Cambia "USUARIO" por el nombre real de tu usuario en la ruta de ExecStart.
06
Seguridad del Servidor
SEGURIDAD
Configurar UFW (firewall)
Solo permitir lo necesario
▶
# En el SERVIDOR sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow OpenSSH # SSH sudo ufw allow 51820/udp # WireGuard sudo ufw allow from 10.0.0.0/24 to any port 8080 # Dashboard solo VPN sudo ufw enable sudo ufw status verbose
SSH con clave pública (opcional pero recomendado)
Deshabilitar autenticación por contraseña
▶
# En tu máquina local, generar par de claves SSH ssh-keygen -t ed25519 -C "obsidium-admin" # Copiar clave pública al servidor ssh-copy-id usuario@IP_SERVIDOR # En el servidor: deshabilitar login por contraseña sudo nano /etc/ssh/sshd_config # Cambiar: PasswordAuthentication no sudo systemctl restart sshd
Verificar que la dashboard NO es accesible desde fuera de la VPN
Test de seguridad de acceso
▶
# Desde un equipo SIN VPN — debe fallar (connection refused) curl -k https://IP_PUBLICA_SERVIDOR:8080/ # Desde un equipo CON VPN — debe funcionar curl -k https://10.0.0.1:8080/
Si el primer comando devuelve contenido, revisa la directiva "Require ip" en el VirtualHost.
07
DNS Privado con Split DNS (dnsmasq)
DNS
Instalar y configurar dnsmasq en el servidor
Resolución de panel.obsidium.es → 10.0.0.1 solo dentro de la VPN
▶
/etc/dnsmasq.conf — añadir al final
# Escuchar solo en la interfaz VPN interface=wg0 bind-interfaces # Resolución privada: panel y vpn → IP del servidor en VPN address=/panel.obsidium.es/10.0.0.1 address=/vpn.obsidium.es/10.0.0.1 # DNS público para el resto de dominios server=8.8.8.8 server=1.1.1.1
Si dnsmasq falla al arrancar, puede haber conflicto con systemd-resolved. Ejecuta: sudo systemctl disable systemd-resolved y edita /etc/resolv.conf
Prueba final del sistema completo
Verificar que todo el flujo funciona end-to-end
▶
Si llegas hasta este paso con todo funcionando, ¡el sistema Obsidium está operativo!

Stack tecnológico completo

Ubuntu Server 24.04.3 LTS
WireGuard (VPN)
Apache 2.4 + SSL
PHP 8.3 + PDO
MariaDB 10.11
Python 3 + psutil
dnsmasq (Split DNS)
UFW + systemd
0 pasos completados de 0