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
▶
sudo apt install -y dnsmasq
/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
sudo systemctl enable dnsmasq
sudo systemctl restart dnsmasq
# Verificar resolución desde un cliente VPN
dig panel.obsidium.es @10.0.0.1
# Debe devolver 10.0.0.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
▶
# 1. Verificar túnel VPN activo
sudo wg show
# 2. Verificar que el agente está enviando métricas
sudo systemctl status obsidium-agent
sudo journalctl -u obsidium-agent -f
# 3. Verificar datos en la BD
mysql -u dashboard_user -p dashboard -e "SELECT * FROM monitorizacion ORDER BY id DESC LIMIT 5;"
# 4. Acceder a la dashboard desde el cliente VPN
# https://panel.obsidium.es:8080/
# 5. Verificar logs de Apache
sudo tail -f /var/log/apache2/dashboard_access.log
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
OBSIDIUM