Volver al blog
Proyecto Alejandro-Cluster en Infisical: carpetas de secretos por servicio (grafana, herschel, langfuse, sebastian, zetesis-auth, zetesis-portal) con los entornos Development, Staging y Production.

Montar un homelab desde cero: seguridad y operaciones - Homelab (04/06)

Caso realHomelabSeguridadOperaciones

En el Post 3 vimos cómo las aplicaciones pasan de Git a contenedores en ejecución. Pero nos saltamos una pregunta crítica: ¿cómo llegan los secretos a esos contenedores?

Una contraseña de base de datos, un token de API, un certificado TLS — nada de esto puede vivir en Git en texto plano. Y una vez desplegados, hay que respaldarlos. Y si todo falla, necesitas una forma de reconstruir desde cero.

Este post cubre las tres capas de gestión de secretos, la estrategia de backup, la domótica, y lo que necesitarías para recuperarte de un desastre total. Es lo aburrido pero imprescindible que marca la diferencia entre un proyecto de fin de semana y una infraestructura en la que puedes confiar.

El problema de los secretos

Todo el homelab se gestiona desde un único repositorio Git. Genial para reproducibilidad y auditoría, pero genera una tensión: las configuraciones tienen que estar en Git para que funcione el despliegue GitOps, los secretos no pueden estar en Git por razones obvias de seguridad, y aun así los secretos tienen que llegar a los servicios de forma automática en el momento del despliegue.

No hay una sola herramienta que resuelva esto. Son tres, cada una para una parte del problema.

Capa 1: Infisical (la caja fuerte)

Infisical es un gestor de secretos autoalojado. Pensad en él como un almacén de contraseñas para la infraestructura. Todos los secretos del homelab — contraseñas de bases de datos, tokens de API, credenciales OAuth, contraseñas SMTP — viven en Infisical como fuente única de verdad.

Infisical corre en el clúster escipion (red Roma) y es accesible en secrets.zetesis.localhost. Al ser autoalojado, los secretos nunca salen del control del homelab.

Dos vías de salida

Para servicios Docker Compose — ptolomeo (el agente de CD) obtiene los secretos de Infisical en el momento del despliegue y los inyecta como variables de entorno:

# .doco-cd.yaml — ptolomeo lee esto
external_secrets:
  DB_PASSWORD: <project-id>:prod:/my-app/DB_PASSWORD
  SMTP_HOST:   <project-id>:prod:/my-app/SMTP_HOST

Cuando ptolomeo despliega un stack, llama a la API de Infisical, obtiene los valores, los escribe en .env y ejecuta docker compose up. El contenedor ve DB_PASSWORD=xyz123 como una variable de entorno normal.

Para Kubernetes — el External Secrets Operator (ESO) corre dentro del clúster y sincroniza periódicamente los secretos de Infisical como Secrets nativos de Kubernetes:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: web-secrets
spec:
  refreshInterval: 1m
  secretStoreRef:
    name: infisical-production
    kind: ClusterSecretStore
  data:
    - secretKey: AUTH_SECRET
      remoteRef:
        key: AUTH_SECRET
    - secretKey: AUTH_KEYCLOAK_SECRET
      remoteRef:
        key: AUTH_KEYCLOAK_SECRET

ESO crea un Secret de Kubernetes llamado web-secrets con los valores de Infisical. Los pods lo montan como variables de entorno o ficheros — sin saber de dónde vienen los secretos.

ESO refresca cada minuto. Si rotas una contraseña en Infisical, el Secret de Kubernetes se actualiza automáticamente. Sin necesidad de redesplegar.

El patrón ClusterSecretStore

En alejandro, los secretos se organizan con componentes Kustomize siguiendo el mismo patrón base/overlay que la infraestructura (del Post 3):

unknown node

La base define una plantilla de ClusterSecretStore. Cada overlay la parchea con el proyecto y entorno de Infisical correctos. Así, producción lee del entorno prod de Infisical y staging lee de staging — misma estructura, credenciales distintas.

Capa 2: SOPS/age (el candado de los ficheros)

Hay ficheros con secretos que tienen que existir en el repositorio Git. Por ejemplo, Talos Linux necesita los certificados del clúster y los tokens de arranque para generar las configuraciones de máquina. Estos viven en talsecret.sops.yaml junto a talconfig.yaml.

SOPS cifra los valores de un fichero YAML dejando las claves legibles. Combinado con age (una herramienta de cifrado), genera ficheros que se pueden commitear sin riesgo:

# Lo que vive en Git (cifrado)
cluster:
    id: ENC[AES256_GCM,data:QqUK9Xr6cWv0ed9F...,tag:tnMuAX...,type:str]
    secret: ENC[AES256_GCM,data:ncoTvU2yA5He7n2r...,tag:o5SrlE...,type:str]

Se ve la estructura (cluster.id, cluster.secret) pero no los valores. Descifrar requiere la clave privada de age — que está almacenada en Infisical, nunca en Git.

El fichero .sops.yaml en la raíz del repo le indica a SOPS qué clave pública usar:

creation_rules:
  - path_regex: \.sops\.(yaml|yml)$
    age: age1ejemplo0000000000000000000000000000000000000000000000000
  - path_regex: secrets\.env$
    age: age1ejemplo0000000000000000000000000000000000000000000000000

Para descifrar (al aplicar configuraciones de Talos):

unknown node

Infisical proporciona la clave privada de age como variable de entorno. SOPS la usa para descifrar los ficheros. La salida descifrada va a clusterconfig/ (gitignored, nunca se commitea).

Qué hay cifrado en Git

unknown node

Todo lo demás se queda en Infisical y nunca toca Git.

Capa 3: el arranque del huevo y la gallina

El rompecabezas: ESO necesita credenciales para conectarse a Infisical. Pero esas credenciales son un Secret de Kubernetes. Y ESO es precisamente lo que crea Secrets de Kubernetes desde Infisical. ¿De dónde salen entonces las credenciales de ESO?

La respuesta: inline manifests de Talos. El fichero inline-secrets.sops.yaml contiene manifiestos de Secret de Kubernetes cifrados con SOPS. Cuando Talos arranca el plano de control, aplica estos manifiestos directamente — antes de que ArgoCD o ESO siquiera arranquen. Así se rompe la dependencia circular:

Loading diagram...

Estos secretos de arranque son los únicos gestionados vía SOPS. A partir de ahí, todo es automático a través de ESO.

Cómo encajan las tres capas

Loading diagram...

Infisical es la fuente única de verdad. ptolomeo hace de puente entre Infisical y los contenedores Docker (variables de entorno). ESO hace de puente entre Infisical y los pods de Kubernetes (Secrets nativos). Y SOPS/age cubre el caso especial de ficheros que tienen que existir en Git.

Estrategia de backup

Todos los datos del homelab se respaldan en un único lugar: MinIO en spinoza (10.1.0.11). La herramienta de backup es Restic — un programa de backup cifrado y deduplicado que soporta S3.

Hosts Docker: tolstoi

Todos los VPS y VMs corren un servicio llamado tolstoi — un wrapper de Restic que se ejecuta a diario a las 3:00 de la mañana. Respalda volúmenes Docker a MinIO a través de la pasarela Tailscale (cervantes):

# Configuración base de backup (services/tolstoi)
services:
  resticker-base:
    image: mazzolino/restic:1.8.2
    environment:
      BACKUP_CRON: "0 3 * * *"
      RUN_ON_STARTUP: "true"
      RESTIC_FORGET_ARGS: >-
        --keep-daily 7
        --keep-weekly 4
        --keep-monthly 12
        --keep-yearly 7

Cada despliegue extiende esta base con sus propios volúmenes y bucket S3:

unknown node

Kubernetes: K8up

En los clústeres Kubernetes, K8up sustituye a tolstoi. Es un operador de backup nativo de Kubernetes que descubre y respalda PVCs (Persistent Volume Claims) automáticamente. La programación se define como un recurso de Kubernetes:

apiVersion: k8up.io/v1
kind: Schedule
metadata:
  name: backup-schedule
  namespace: turing
spec:
  backend:
    s3:
      endpoint: http://minio-atenas.data.svc.cluster.local:9000
      bucket: escipion-restic
  backup:
    schedule: '30 2 * * *'    # 2:30 AM a diario
  prune:
    schedule: '0 4 * * 0'     # Domingos a las 4 AM
    retention:
      keepDaily: 7
      keepWeekly: 4
      keepMonthly: 6
  check:
    schedule: '0 5 * * 0'     # Domingos a las 5 AM

Tres operaciones automatizadas: backup diario a las 2:30, que hace snapshot de todos los PVCs del namespace; prune semanal los domingos, que elimina snapshots antiguos según la política de retención; y check semanal tras el prune, que verifica la integridad del backup.

K8up se configura por namespace. En escipion hay programaciones separadas para turing (Infisical), gauss (Harbor) y traefik. Cada uno respalda a su propio prefijo en el bucket.

Todo converge en spinoza

Loading diagram...

Los hosts Docker llegan a MinIO a través de la pasarela Tailscale cervantes. escipion y alejandro llegan por el mesh de Tailscale (Roma a Atenas).

Domótica

El homelab no es solo servidores — también controla la casa. Dos servicios corren en la VM aristoteles de la red Atenas (marco-aurelio).

rothbard: Zigbee2MQTT

Zigbee2MQTT hace de puente entre dispositivos Zigbee del hogar (luces, sensores, interruptores) y MQTT, un protocolo de mensajería ligero. Corre junto a un broker MQTT Mosquitto:

services:
  mqtt:
    image: eclipse-mosquitto:2.0
    ports:
      - "1883:1883"    # MQTT
      - "9001:9001"    # WebSocket
  zigbee2mqtt:
    image: koenkk/zigbee2mqtt:1.42.0
    depends_on: [mqtt]
    ports:
      - 8082:8080      # Dashboard web

El coordinador Zigbee (un dongle USB) habla con los dispositivos directamente — sin ningún servicio en la nube de por medio. Zigbee2MQTT traduce los mensajes de los dispositivos a topics MQTT, dejándolos disponibles para cualquier sistema de automatización.

mises: Homebridge

Homebridge expone dispositivos no-HomeKit a Apple Home. Corre en modo de red host para gestionar el descubrimiento mDNS:

services:
  homebridge:
    image: homebridge/homebridge:2026-02-25
    network_mode: host
    environment:
      - HOMEBRIDGE_INSECURE=1

Homebridge recoge los dispositivos publicados en MQTT por Zigbee2MQTT y los expone como accesorios HomeKit. Un "Oye Siri, apaga las luces del salón" acaba provocando un mensaje MQTT que pasa de Homebridge a Zigbee2MQTT, de ahí a la radio Zigbee, y de la radio a la bombilla.

Ambos servicios son accesibles a través del reverse proxy Caddy local (trajano) en rothbard.zetesis.localhost y mises.zetesis.localhost.

Recuperación ante desastres

Si todo se va al traste, hacen falta exactamente seis valores almacenados fuera de la infraestructura para reconstruir:

unknown node

Con estos seis valores y un clon del repositorio Git, el camino de reconstrucción es:

  1. Descifrar los ficheros SOPS con la clave age
  2. Arrancar el primer nodo Talos con talhelper genconfig + talosctl apply-config
  3. Bootstrap de ArgoCD con kubectl apply -f bootstrap.yaml
  4. Restaurar Infisical desde el backup de MinIO usando las claves de Restic y cifrado
  5. Esperar — ESO conecta con Infisical, los secretos se propagan, los servicios arrancan

Una vez que Infisical está arriba, el resto es automático. ESO sincroniza secretos, ArgoCD despliega aplicaciones, ptolomeo tira en los hosts Docker. La reconstrucción completa está documentada en el FIRST_STEPS.md del repo.

Escenarios de pérdida

unknown node

Lo esencial: la clave age y las credenciales del backup de Infisical tienen que existir en algún sitio fuera del homelab — un gestor de contraseñas, un papel en una caja fuerte, un USB offline. Sin ellos, se empieza de cero.

GitGuardian: la red de seguridad

Como toda la infraestructura está en Git, hay una capa más: GitGuardian escanea cada push y cada pull request en busca de secretos commiteados por accidente:

# .github/workflows/main.yml
name: GitGuardian scan
on: [push, pull_request]
jobs:
  scanning:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: GitGuardian/ggshield/actions/secret@v1.47.0
        env:
          GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}

Si alguien commitea una contraseña en texto plano por error, GitGuardian la detecta antes de que llegue a la rama principal.

Lo que hemos construido

Hagamos zoom out. A lo largo de esta serie hemos cubierto:

  1. La visión general — Hardware, nombres, qué corre dónde
  2. Redes — WireGuard sitio a sitio, Tailscale en malla, Cloudflare en el borde, reverse proxy Caddy
  3. Kubernetes y GitOps — Talos Linux, App-of-Apps de ArgoCD, ApplicationSets, ptolomeo
  4. Seguridad y operaciones — Tres capas de secretos (Infisical, SOPS, ESO), backups con Restic, domótica

Todo funciona desde un único repositorio Git. Dos sistemas de CD (ArgoCD + ptolomeo) despliegan en Kubernetes y hosts Docker respectivamente. Los secretos fluyen desde Infisical por tres mecanismos distintos según el destino. Todo se respalda a MinIO en spinoza.

¿Está sobredimensionado para un homelab? Seguramente. Pero cada pieza existe porque me topé con un problema real — y la solución me enseñó algo que ahora uso a nivel profesional. Ese es el verdadero valor de un homelab: un terreno de juego donde lo que te juegas es poco, pero las lecciones son de verdad.

Con los secretos resueltos y una respuesta escrita a «¿y si arde todo?», queda ver el sistema funcionando y saber recuperarlo. Eso es lo que viene en los dos últimos posts.


Siguiente: Post 5 - Observabilidad | Anterior: Post 3 - Kubernetes y GitOps | Volver a Post 1

unknown node