
Knocker, 홈랩을 위한 노크 기반 접근 제어 서비스

Knocker는 웹, CLI + GNOME, Android 클라이언트를 사용하여 홈랩에 HTTP 기반 "노크-노크" 단일 패킷 인증(SPA) 게이트웨이를 제공하는 자체 호스팅 서비스입니다. Caddy와 같은 리버스 프록시의 인증으로 사용하거나, FirewallD 통합을 통해 방화벽 수준에서도 사용할 수 있습니다. 이 서비스를 사용하면 서비스를 완전히 비공개로 유지하고, 인증된 IP 주소에 대해서만 필요할 때 열어줄 수 있습니다.
영구적인 VPN 연결 없이 인터넷에 서비스를 노출하면서 공격 표면을 최소화하려는 홈랩 환경에 이상적입니다.
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 컨테이너로 배포되도록 설계되었습니다. AMD64, ARMv8 및 ARMv7을 지원하는 사전 빌드된 도커 이미지를 사용합니다.
Knocker는 다양한 사용 사례에 대해 서로 다른 이미지 태그를 제공합니다.
latest - 최신 안정 릴리스 (프로덕션 권장)v1.2.3 - 특정 버전 태그 (고정 버전)main - 개발 브랜치 (롤링 업데이트, 불안정할 수 있음)구성:
knocker.example.yaml의 이름을 knocker.yaml로 변경합니다.knocker.yaml의 기본 API 키를 자신의 안전하고 임의적인 문자열로 변경하세요.knocker.yaml의 trusted_proxies 목록을 검토하세요. 리버스 프록시 네트워크의 서브넷과 일치해야 합니다 (docker network inspect xxx).whitelist.storage_path를 앱 작업 디렉터리, /data 또는 /tmp 아래에 유지하세요.firewalld.enabled: true로 설정하고 관련 설정을 조정하여 firewalld 통합을 구성하세요. 참고: 이를 위해서는 컨테이너를 루트로 실행해야 합니다.서비스 실행:
docker compose up -d
이 명령은 사전 빌드된 knocker 이미지를 가져와 및 서비스를 시작합니다.
Knocker는 리버스 프록시의 인증 게이트웨이 역할을 합니다. 요청 IP가 허용 목록에 있는지 확인하는 verify 엔드포인트를 제공하며, 허용되지 않은 경우 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 응답에 대해 작동하지 않습니다. 오류 응답은 Caddy 자체가 아닌 인증 서비스(knocker)에서 직접 오기 때문에 handle_errors가 이러한 응답을 가로채거나 수정할 수 없습니다.
Knocker는 firewalld를 통해 고급 방화벽 통합을 제공하여, 노크 요청에서 지정된 TTL을 기준으로 자동으로 만료되는 동적 시간 기반 방화벽 규칙을 생성합니다. 이 기능은 네트워크 수준에서 작동하므로, ssh나 게임 서버와 같은 비HTTP 서비스에도 knocker를 사용할 수 있습니다.
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는 CLI 인터페이스와 데몬을 분리할 수 있기 때문에 선택되었습니다. 이를 통해 Knocker는 시스템의 D-Bus 소켓을 마운트하여 Docker 컨테이너 내에서 firewalld를 제어할 수 있으며, FirewallD는 시간 제한 규칙을 지원하므로 TTL이 끝나면 knocker 규칙이 자동으로 만료됩니다.
FIREWALLD는 Docker 게시 포트와 함께 작동하지 않습니다. 자세한 내용은 이 이슈를 확인하세요.
사전 요구 사항
구성
활성 규칙 모니터링:
# 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에 대해 노킹을 활성화하는 경우 userland-proxy 작동 방식으로 인해 문제가 발생할 수 있으며, 실제 IP 주소와 다른 요청 IP를 받을 수 있습니다.
Userland-proxy를 비활성화하면 문제가 해결되지만, 설정을 반드시 테스트하세요. 호스트 네트워킹을 사용할 수도 있습니다.
/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 아래에 개발 환경이 있으며, Caddy 및 Firewalld 각각에 대한 통합 테스트용 bash 스크립트가 있습니다.
표준 테스트 스택은 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는 완전히 바이브 코딩되었습니다. 초기 구현은 Gemini 2.5 pro로 이루어졌으며, roo code/requesty 해커톤에서 제공된 토큰 덕분입니다.
추가 기능은 주로 GitHub Copilot Agent(Sonnet 4/이후 4.5)로 작업되었으며, Roo code, Opencode 및 표준 Copilot 확장 프로그램에서 GPT-5 mini/CODEX가 대부분 수정했습니다.
저는 항상 변경 사항을 계획하고 모든 변경 후에 테스트하며 최선을 다했지만, AI 반대 입장이라면 제가 이에 대한 당신의 의견을 바꿀 수 없을 것입니다.
knockercaddy