
Knocker 是一个自托管服务,为你的 Homelab 提供基于 HTTP 的“敲敲门”单包授权(SPA)网关,并配有 Web、CLI + GNOME 和 Android 客户端。 它可以用作 Caddy 等反向代理的身份验证,甚至可以通过 FirewallD 集成在防火墙层面使用。它让你能够将服务完全保持私有,仅在需要时对已授权的 IP 地址开放。
这对于希望在不使用持久 VPN 连接的情况下将服务暴露到互联网、同时尽量缩小公网攻击面的 Homelab 环境来说非常理想。
Knocker-Web 静态 PWA Web 应用,支持在重新加载时进行 knock(白名单)。
Knocker-CLI 用 go 编写的 CLI,支持后台 knock,可选由 IP 变化触发。
Knocker-gnome 一个基于 Knocker-cli 构建的 GNOME 扩展。
Knocker-EXPO 一个用 React EXPO 编写的实验性 Android 应用,支持后台 knock 请求。
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-compose.yml 文件以 Docker 容器方式部署。它使用预构建的 Docker 镜像,支持 AMD64、ARMv8 和 ARMv7。
Knocker 为不同用例提供不同的镜像标签:
latest 最新稳定版本(推荐用于生产环境)v1.2.3 特定版本标签(固定版本)main 开发分支(滚动更新,可能不稳定)配置:
knocker.example.yaml 重命名为 knocker.yaml。knocker.yaml 添加至少一个安全、随机的 API 密钥;示例中故意不包含任何可用的默认值。knocker.yaml 中的 trusted_proxies 列表:它必须只包含反向代理自身的地址,而不是其子网。因为 knocker 只信任来自列表中对等方的 X-Forwarded-For,整个子网条目(例如 172.16.238.0/24)会让该网络上的每一个容器都成为身份权威——其中任何一个都可以声称任意客户端 IP,或用普通密钥白名单任意地址。Docker 默认不会分配稳定的容器 IP,因此请使用 ipv4_address / ipv6_address 固定代理(参见 docker-compose.yml 中被注释掉的 caddy 服务),并列出这些确切的 /32 和 /128 地址。whitelist.storage_path 保持在应用工作目录、/data 或 /tmp 下。firewalld.enabled: true 并调整相关设置来配置 firewalld 集成。注意:这要求容器以 root 身份运行。运行服务:
docker compose up -d
这将拉取预构建的 knocker 镜像,并启动 knocker 和 caddy 两个服务。
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 {
request_body {
max_size 4KB
}
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 提供高级防火墙集成,创建动态的、基于时间的防火墙规则,这些规则会根据 knock 请求中指定的 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
由于依赖 zone priority 功能,Knocker 需要 FirewallD 2.0+。 它可在 Debian 13、Ubuntu 24.04 LTS 及其他近期稳定发行版中使用。
选择 FirewallD 是因为它能够将 CLI 接口与守护进程分离。这使 Knocker 能够通过挂载系统的 D-Bus socket 从 Docker 容器内部控制 firewalld,而且 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 集成指南。
如果你为 tailscale 后面的 IP 或其他 IP 启用 knock,可能会因为 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。仅当连接的直接对等方列在 server.trusted_proxies 中时,才会信任 X-Forwarded-For、X-Forwarded-Host 和 X-Forwarded-Uri。请将该列表限制为反向代理本身(/32 / /128 地址);关于为什么更宽的范围不安全,请参见上面的部署说明。