
Knocker, un servicio de control de acceso basado en knock para tu homelab

Knocker es un servicio autoalojado que proporciona una pasarela de autorización de paquete único (SPA) "knock-knock" basada en HTTP para tu Homelab, con clientes web, cli + gnome y Android. Puede utilizarse como autenticación para tu proxy inverso como Caddy, o incluso a nivel de firewall mediante la integración con FirewallD. Te permite mantener tus servicios completamente privados, abriéndolos bajo demanda únicamente para direcciones IP autorizadas.
Esto es ideal para entornos de homelab donde quieres exponer servicios a internet sin una conexión VPN persistente, minimizando al mismo tiempo tu superficie de ataque pública.
Knocker-Web Aplicación web PWA estática que admite knocking (lista blanca) al recargar
Knocker-CLI Una CLI escrita en Go con soporte para knocks en segundo plano, opcionalmente activados por cambios de IP.
Knocker-gnome una extensión de GNOME construida sobre Knocker-cli.
Knocker-EXPO Una aplicación Android experimental escrita en React EXPO con soporte para solicitudes de knocking en segundo plano
sequenceDiagram
participant User
participant Caddy as Reverse Proxy (Caddy)
participant Knocker
participant Service as Protected Service
User->>Caddy: HTTP request to protected service
Caddy->>Knocker: GET /verify (copies X-Forwarded-For)
Knocker-->>Knocker: check always_allowed_ips / excluded_paths / whitelist
alt IP whitelisted
Knocker-->>Caddy: 200 OK (empty body)
Caddy->>Service: forward request
Service-->>Caddy: 200 OK
Caddy-->>User: 200 OK
else IP not whitelisted
Knocker-->>Caddy: 401 Unauthorized (empty body)
Caddy-->>User: 401 Unauthorized
end
Note over User,Knocker: Performing a "knock" (to add whitelist entry)
User->>Knocker: POST /knock (X-Api-Key, optional ip_address, ttl)
Knocker->>Knocker: validate API key, determine client IP
Knocker->>Knocker: update whitelist.json with expiry
Knocker-->>User: 200 OK (whitelisted_entry, expires_at, expires_in_seconds)
Este proyecto está diseñado para desplegarse como un contenedor Docker utilizando el archivo docker-compose.yml proporcionado. Utiliza las imágenes docker precompiladas con soporte para AMD64, ARMv8 y ARMv7
Knocker proporciona diferentes etiquetas de imagen para diferentes casos de uso:
latest Última versión estable (recomendada para producción)v1.2.3 Etiquetas de versiones específicas (versiones fijadas)main Rama de desarrollo (actualizaciones continuas, puede ser inestable)Configuración:
knocker.example.yaml a knocker.yaml.knocker.yaml por tus propias cadenas aleatorias y seguras.trusted_proxies en knocker.yaml; deben coincidir con la subred de la red del proxy inverso (docker network inspect xxx)whitelist.storage_path dentro del directorio de trabajo de la aplicación, /data o /tmp.firewalld.enabled: true y ajustando las opciones relacionadas. Nota: Esto requiere que el contenedor se ejecute como root.Ejecutar el servicio:
docker compose up -d
Knocker funciona actuando como una pasarela de autenticación para tu proxy inverso. Ofrece un endpoint de verificación para comprobar si la IP solicitante está o no en la lista blanca; si no lo está, responderá con un 401 y el proxy inverso rechazará la conexión.
Caddy tiene la directiva forward_auth para comprobar las conexiones mediante un endpoint de autenticación.
Define un snippet reutilizable: Es una buena práctica definir un snippet en tu Caddyfile para la comprobación de autenticación.
Protege tus servicios: Importa el snippet para cualquier servicio que quieras proteger.
Ejemplo de Caddyfile:
# Caddyfile
# Define a reusable snippet for the knock-knock check.
# It points to the knocker service using Docker's internal DNS.
(knocker_auth) {
forward_auth knocker:8000 {
uri /verify
}
}
# The public endpoint for performing the knock.
# Make sure this domain points to your Caddy server's IP.
knock.your-domain.com {
reverse_proxy knocker:8000
}
# An example protected service.
jellyfin.your-domain.com {
import knocker_auth # Apply the forward_auth check
reverse_proxy jellyfin_service_name:8096
}
Cuando un usuario no está en la lista blanca, la directiva forward_auth de Caddy devolverá una respuesta 401 Unauthorized con el cuerpo vacío.
Nota importante: la directiva handle_errors de Caddy no funciona con las respuestas de forward_auth. La respuesta de error proviene directamente del servicio de autenticación (knocker), no de Caddy en sí, por lo que handle_errors no puede interceptar ni modificar estas respuestas.
Knocker proporciona una integración avanzada con el firewall a través de firewalld, creando reglas de firewall dinámicas y temporales que expiran automáticamente según el TTL especificado en las solicitudes de knock. Esta función opera a nivel de red, lo que te permite usar knocker para servicios no HTTP como SSH o servidores de juego.
sequenceDiagram
participant Client as User
participant Firewall as Firewalld (knocker zone)
participant Knocker
participant Service as Protected Service (port 22)
Note over Client,Firewall: Initial state — monitored port is blocked by default
Client->>Firewall: TCP SYN to Service:22
Firewall-->>Client: DROP (no response)
Note over Client,Knocker: User performs a knock to whitelist their IP
Client->>Knocker: POST /knock (X-Api-Key, optional ip_address, ttl)
Knocker->>Knocker: validate API key & determine client IP
Knocker->>Firewall: add rich accept rule for client IP on port 22 with timeout
Firewall-->>Knocker: success
Note over Firewall,Client: New rule overrides DROP due to higher priority
Client->>Firewall: TCP SYN to Service:22
Firewall->>Service: forward packet
Service-->>Client: TCP SYN-ACK (connection established)
Knocker->>Knocker: update whitelist.json with expiry
Knocker requiere FirewallD 2.0+ debido a su dependencia de la función de prioridad de zonas. Está disponible en Debian 13, Ubuntu 24.04 LTS y otras distribuciones estables recientes.
FirewallD fue elegido por su capacidad de separar la interfaz CLI del daemon. Esto permite que Knocker controle firewalld desde dentro de un contenedor Docker montando el socket D-Bus del sistema. FirewallD también tiene soporte para reglas temporizadas, por lo que las reglas de knocker expiran automáticamente al final del TTL.
FIREWALLD NO FUNCIONARÁ CON PUERTOS PUBLICADOS POR DOCKER; consulta este problema para más detalles
Requisitos previos
Configuración
Monitorea las reglas activas:
# Check knocker zone
firewall-cmd --zone=knocker --list-all
# View rich rules
firewall-cmd --zone=knocker --list-rich-rules
# Monitor rule changes
journalctl -u firewalld -f
Para obtener información detallada sobre configuración, arquitectura y solución de problemas, consulta la Guía de integración con FirewallD completa.
Si estás habilitando el knocking para IPs detrás de Tailscale u otras IPs, puedes enfrentar problemas debido a cómo funciona userland-proxy; es posible que obtengas una IP de solicitud diferente de la dirección IP real.
Deshabilitar Userland-proxy debería solucionarlo, pero asegúrate de probar tu configuración. También podrías usar la red del host (host networking).
/knock (POST)Este endpoint valida una clave API y añade una IP a la lista blanca.
Headers:
X-Api-Key: Tu clave API secreta.Body (opcional):
allow_remote_whitelist: true):
{"ip_address": "YOUR_TARGET_IP_OR_CIDR"}
Ejemplo (añadir tu propia IP a la lista blanca):
curl -i -H "X-Api-Key: YOUR_SECRET_KEY" https://knock.your-domain.com/knock
Respuesta de éxito (200 OK):
{
"whitelisted_entry": "1.2.3.4",
"expires_at": 1672534800,
"expires_in_seconds": 3600
}
/verify (GET)Este endpoint es utilizado por forward_auth de Caddy para comprobar si la IP del cliente está en la lista blanca. Devuelve 200 OK en caso de éxito y 401 Unauthorized en caso de fallo. X-Forwarded-For, X-Forwarded-Host y X-Forwarded-Uri solo se confían cuando la solicitud se origina desde server.trusted_proxies.
Caddy ya reenvía las cabeceras de solicitud X-Forwarded-* relevantes a Knocker para que /verify pueda tomar la decisión de autenticación.
El proyecto incluye un conjunto completo de pruebas
Este proyecto utiliza el conjunto de herramientas Python de Astral:
uv para la gestión de dependencias, entornos y ejecución de comandosruff para linting y formateoty para la comprobación de tiposPara ejecutar las pruebas localmente:
Instala uv:
curl -LsSf https://astral.sh/uv/install.sh | sh
Sincroniza el entorno del proyecto:
uv sync --all-groups
Ejecuta las comprobaciones:
uv run pytest
uv run --group lint ruff check .
uv run --group lint ruff format --check .
uv run --group type ty check
Hay un entorno de desarrollo en dev, con scripts bash para pruebas de integración con Caddy y otro separado con firewalld.
Los stacks de prueba estándar son dev/docker-compose.yml y dev/docker-compose.ci.yml; ambos exponen Caddy en http://localhost:18080 y https://localhost:18443.
El CI ejecuta las pruebas de Caddy, pero firewalld necesita un runner privilegiado, por lo que debe ejecutarse localmente y no forma parte del CI.
Los endpoints de documentación interactiva (/docs, /redoc, /openapi.json) están deshabilitados por defecto. Para exponerlos, establece lo siguiente en knocker.yaml:
documentation:
enabled: true
openapi_output_path: "openapi.json"
Cuando la documentación está deshabilitada (por defecto), Knocker elimina estos endpoints y borra cualquier archivo de esquema generado previamente para evitar artefactos obsoletos.
Para una especificación formal de la API y un resumen de las decisiones arquitectónicas, consulta la documentación.
Knocker fue totalmente vibe-coded. La implementación inicial se realizó con Gemini 2.5 Pro, gracias a los tokens proporcionados en el hackathon de roo code/requesty.
Las funciones adicionales se realizaron principalmente con el agente de GitHub Copilot (sonnet 4/más tarde 4.5), que necesitó muchas correcciones, hechas principalmente por GPT-5 mini/CODEX en Roo code, Opencode y la extensión estándar de Copilot.
Hice todo lo posible con esto, planificando siempre los cambios y probando todo después de cada cambio, pero si eres anti-IA, probablemente no podría cambiar tu opinión al respecto.
Esto descargará la imagen knocker precompilada e iniciará los servicios knocker y caddy.