
Knocker, сервис контроля доступа на основе стука для вашего домашнего сервера

Knocker — это самоуправляемый сервис, предоставляющий HTTP-шлюз для одноразовой авторизации (SPA) по принципу «тук-тук» для вашей домашней лаборатории. Имеет веб-, CLI + GNOME и Android-клиенты. Он может использоваться как аутентификация для вашего обратного прокси, например Caddy, или даже на уровне межсетевого экрана с помощью интеграции FirewallD. Он позволяет держать ваши сервисы полностью приватными, открывая их по запросу только для авторизованных IP-адресов.
Это идеально подходит для сред домашних лабораторий, где вы хотите выставлять сервисы в интернет без постоянного VPN-соединения, минимизируя свою публичную поверхность атаки.
Knocker-Web Статическое PWA-веб-приложение, поддерживающее стук (внесение в белый список) при перезагрузке.
Knocker-CLI CLI, написанный на Go, с поддержкой фоновых стуков, опционально запускаемых при изменении IP.
Knocker-gnome — расширение для GNOME, построенное поверх Knocker-cli.
Knocker-EXPO Экспериментальное Android-приложение, написанное на React EXPO, с поддержкой фоновых запросов стука.
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)
Этот проект предназначен для развёртывания в Docker-контейнере с помощью предоставленного файла docker-compose.yml. Используются предварительно собранные образы Docker с поддержкой AMD64, ARMv8 и ARMv7.
Knocker предоставляет разные теги образов для разных случаев использования:
latest — последний стабильный релиз (рекомендуется для продакшена)v1.2.3 — теги конкретных версий (фиксированные версии)main — ветка разработки (содержит последние обновления, может быть нестабильной)Конфигурация:
knocker.example.yaml в knocker.yaml.knocker.yaml на свои собственные безопасные случайные строки.trusted_proxies в knocker.yaml; они должны соответствовать подсети сети обратного прокси (docker network inspect xxx).whitelist.storage_path в рабочей директории приложения, /data или /tmp.firewalld.enabled: true и настроив соответствующие параметры. Примечание: Для этого требуется запускать контейнер от root.Запуск сервиса:
docker compose up -d
Knocker работает как шлюз аутентификации для вашего обратного прокси. Он предоставляет конечную точку verify для проверки, находится ли IP-адрес запроса в белом списке; если нет, он отвечает 401, и обратный прокси отклоняет соединение.
У Caddy есть директива forward_auth для проверки соединений через конечную точку аутентификации.
Определите переиспользуемый фрагмент: Лучшая практика — определить фрагмент в вашем Caddyfile для проверки аутентификации.
Защитите свои сервисы: Импортируйте фрагмент для любого сервиса, который хотите защитить.
Пример 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
}
Когда пользователь не находится в белом списке, директива Caddy forward_auth возвращает ответ 401 Unauthorized с пустым телом.
Важное замечание: Директива Caddy handle_errors не работает с ответами forward_auth. Ответ об ошибке приходит непосредственно от сервиса аутентификации (knocker), а не от самого Caddy, поэтому handle_errors не может перехватывать или изменять эти ответы.
Knocker предоставляет расширенную интеграцию с межсетевым экраном через firewalld, создавая динамические временные правила межсетевого экрана, которые автоматически истекают на основе TTL, указанного в запросах стука. Эта функция работает на уровне сети, позволяя использовать knocker для не-HTTP сервисов, таких как ssh или игровые серверы.
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 требует FirewallD 2.0+ из-за зависимости от функции приоритета зон. Он доступен в Debian 13, Ubuntu 24.04 LTS и других недавних стабильных дистрибутивах.
FirewallD был выбран из-за возможности разделить интерфейс командной строки и демон. Это позволяет Knocker управлять firewalld из Docker-контейнера, монтируя системный D-Bus сокет. Также FirewallD поддерживает временные правила, поэтому правила knocker автоматически истекают по окончании TTL.
FIREWALLD НЕ БУДЕТ РАБОТАТЬ С ОПУБЛИКОВАННЫМИ ПОРТАМИ DOCKER, проверьте этот issue для получения дополнительной информации.
Предварительные требования
Конфигурация
Отслеживание активных правил:
# 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
Подробная информация о конфигурации, архитектуре и устранении неполадок представлена в полном Руководстве по интеграции с FirewallD.
Если вы включаете стук для IP-адресов за tailscale или другими IP, вы можете столкнуться с проблемами из-за работы userland-proxy; вы можете получить IP запроса, отличный от фактического IP-адреса.
Отключение userland-proxy должно это исправить, но обязательно протестируйте свою установку. Вы также можете использовать host-сеть.
/knock (POST)Эта конечная точка проверяет API-ключ и добавляет IP в белый список.
Заголовки:
X-Api-Key: Ваш секретный API-ключ.Тело (опционально):
allow_remote_whitelist: true):
{"ip_address": "YOUR_TARGET_IP_OR_CIDR"}
Пример (добавление своего IP):
curl -i -H "X-Api-Key: YOUR_SECRET_KEY" https://knock.your-domain.com/knock
Успешный ответ (200 OK):
{
"whitelisted_entry": "1.2.3.4",
"expires_at": 1672534800,
"expires_in_seconds": 3600
}
/verify (GET)Эта конечная точка используется директивой Caddy forward_auth для проверки, находится ли IP-адрес клиента в белом списке. Она возвращает 200 OK в случае успеха и 401 Unauthorized в случае неудачи. Заголовки X-Forwarded-For, X-Forwarded-Host и X-Forwarded-Uri считаются доверенными только в том случае, если запрос исходит из server.trusted_proxies.
Caddy уже передаёт соответствующие заголовки X-Forwarded-* в Knocker, чтобы /verify мог принять решение об аутентификации.
Проект содержит полный набор тестов.
В проекте используется инструментарий Astral для Python:
uv для управления зависимостями, окружениями и выполнением командruff для линтинга и форматированияty для проверки типовДля запуска тестов локально:
Установите uv:
curl -LsSf https://astral.sh/uv/install.sh | sh
Синхронизируйте окружение проекта:
uv sync --all-groups
Запустите проверки:
uv run pytest
uv run --group lint ruff check .
uv run --group lint ruff format --check .
uv run --group type ty check
Есть среда разработки в папке dev с bash-скриптами для интеграционных тестов с Caddy и отдельно с firewalld.
Стандартные тестовые стеки — dev/docker-compose.yml и dev/docker-compose.ci.yml; оба открывают Caddy на http://localhost:18080 и https://localhost:18443.
CI запускает тесты Caddy, но firewalld требует привилегированного раннера, поэтому его нужно запускать локально, и он не является частью CI.
Интерактивные конечные точки документации (/docs, /redoc, /openapi.json) по умолчанию отключены. Чтобы их открыть, установите в knocker.yaml:
documentation:
enabled: true
openapi_output_path: "openapi.json"
Когда документация отключена (по умолчанию), Knocker удаляет эти конечные точки и удаляет любой ранее сгенерированный файл схемы, чтобы предотвратить устаревшие артефакты.
Формальную спецификацию API и сводку архитектурных решений см. в документации.
Knocker был полностью создан в стиле «vibe-coding». Первоначальная реализация была выполнена с помощью Gemini 2.5 pro, благодаря токенам, предоставленным в рамках хакатона roo code/requesty.
Последующие функции в основном реализовывались с помощью агента GitHub Copilot (sonnet 4/позже 4.5), что потребовало множества исправлений, в основном сделанных GPT-5 mini/CODEX в Roo code, Opencode и стандартном расширении Copilot.
Я старался изо всех сил, всегда планируя изменения и тестируя всё после каждого изменения, но если вы против ИИ, я, вероятно, не смогу изменить ваше мнение об этом.
Это загрузит предварительно собранный образ knocker и запустит сервисы knocker и caddy.