Skip to content
hexiyouPublic

About

A modern, authenticated port-knocking toolkit in Go: one daemon (knockd) that watches knock ports and executes actions, one client (knock) that performs the sequence.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

knockkit

A modern, authenticated port-knocking toolkit in Go: one daemon (knockd) that watches knock ports and executes actions, one client (knock) that performs the sequence.

cmd/knockd    server: knock ports → AEAD auth → rule action → delayed cleanup
cmd/knock     client: sends the sequence, prints the sealed response

Unlike classic knockd, the port sequence carries no secret. It is only a routing decoration: every packet must still pass XChaCha20-Poly1305 authentication with a per-packet HKDF-derived key. Knowing the port order gets an attacker nothing.

中文文档:README_zh-CN.md

Why not plain knockd?

classic knockd knockkit
Transport TCP or UDP both, mixed freely inside one sequence
Secret the sequence itself 32-byte PSK per client, AEAD per packet
Replay not covered nonce cache, fail-closed
Response none sealed status + trace id (no oracle for outsiders)
Actions firewall open/close allow, allow-close, exec, webhook, db-update, log
Delayed close external cron built-in scheduler: knock now, auto-close in N minutes

Quick start

make build

# 1. key material
./bin/knockd -gen-psk > psk.key          # 32 random bytes, base64

# 2. server config (see examples/knockd.yaml), then:
./bin/knockd -check -config knockd.yaml  # validate only
./bin/knockd -config knockd.yaml

# 3. client config (see examples/knock.yaml), then:
./bin/knock -config knock.yaml -rule ssh -v
ok rule=ssh trace=2b6c6061

Exit codes for knock: 0 accepted, 1 rejected/no answer, 2 usage error.

Sequences

A rule is an ordered list of steps; each step is a port plus a transport. Steps may mix transports:

rules:
  - name: ssh
    action: allow
    params: {ports: "22", proto: tcp}
    steps:
      - {port: 7001, proto: udp}
      - {port: 7002, proto: udp}
      - {port: 7003, proto: tcp}   # final step: the sealed reply arrives here

Rules are rejected at startup if one sequence is a prefix of another (the packet would mean two things). Every step of a knock is a complete, freshly sealed packet — an off-path attacker cannot corrupt server state by injecting a single spoofed datagram, because the payload would not authenticate.

Actions

Actions are a server-side whitelist; a client can only pick a rule, never a command, URL or SQL statement.

action purpose idempotent
allow open ports in the firewall (with TTL) yes
allow-close close them again — the natural cleanup yes
log audit entry only yes
exec run an argv-array binary (no shell), from a configured whitelist only with allow_as_cleanup: true
webhook POST to a pre-declared URL, no redirects followed no
db-update execute a pre-registered SQL statement with bound placeholders no

Parameter handling:

  • config params are authoritative — they may use $CLIENT_ID, $SOURCE_IP, $RULE, $TRACE, expanded server-side;
  • a client may add a param only if the key is listed in the rule's allow_params, and only if the action's schema accepts it (checked at startup);
  • every param — client or config — is validated against the action's schema; unknown keys are rejected.

Cleanup (delayed action B)

cleanup:
  after: 3m
  action: allow-close
  params: {ports: "22", proto: tcp}
  policy: refresh   # refresh (default) | ignore
  retry: {max_attempts: 5, base_delay: 5s, max_delay: 1m}
  • refresh (default): re-knocking resets the timer — the port stays open while the client keeps knocking, and closes 3 minutes after the last knock.
  • ignore: the first timer wins; re-knocks do not postpone it.
  • Tasks are persisted (scheduler.store: file) before the timer is armed, so a daemon crash loses nothing; overdue tasks run at the next start.
  • Only idempotent actions may be used as cleanup — enforced at startup.
  • ttl: 10m on an allow rule is sugar for exactly this block.

Security model

  • AEAD is the security boundary. XChaCha20-Poly1305 over the packet; the header (ports, timestamp, client id, flags) is additional authenticated data.
  • Per-packet key: HKDF-SHA256(psk, salt=knockkit-v1‖nonce, info=aead/knock) — nonce compromise never exposes the PSK.
  • Uniform rejection: every authentication failure produces the identical auth: rejected; the machine-readable reason exists only in local metrics. There is no oracle on the wire.
  • Replay: nonces enter the cache only after successful decryption, so an attacker without the PSK cannot fill it; the cache is bounded and fails closed.
  • Admission first: ban table → per-IP token bucket → global PPS → parse → AEAD. Expensive work happens only for traffic that passed rate limiting.
  • Fail closed on the interesting paths: a full nonce cache or a saturated knock executor drops the packet rather than admitting unverified work.

Operations

  • knockd -check — validate configuration and exit.
  • Metrics: metrics.listen exposes /metrics (Prometheus text format) and /healthz. Key series: knockd_auth_fail_by_reason_total, knockd_knock_ok_total, knockd_cleanup_err_total, knockd_rate_drop_total.
  • Every knock is logged with a trace id that is echoed to the client and reused by the cleanup task, so one id correlates A, B and client retries.
  • Logs never contain key material or ciphertext.

Firewall backends

  • nftables (Linux default): table knockkit, concat set with timeout, priority -10 chain — the kernel enforces the TTL even if the daemon dies.
  • iptables: injected at INPUT 1 with a knockkit comment; relies on the application-layer cleanup for closing.
  • noop: tests, non-Linux, or firewall managed elsewhere.

flush_on_exit: true (default) retracts everything the daemon added.

Layout

cmd/knockd, cmd/knock      binaries
pkg/protocol               wire format, params TLV, response, framing (fuzzed)
pkg/auth                   HKDF, AEAD, freshness, replay cache, keyring (fuzzed)
pkg/seq                    per-IP knock state machine + ambiguity checks
pkg/action                 action registry, schemas, built-in actions
pkg/firewall               nftables / iptables / noop backends
pkg/scheduler              delayed tasks: epochs, persistence, retry
pkg/ratelimit              token buckets, global PPS, ban table
internal/config            strict YAML (KnownFields), defaults, validation
internal/server            pipeline: admit → auth → seq → execute → respond

Development

make test    # unit + end-to-end
make race
make vet
make fuzz    # 30s per parser

Go ≥ 1.27 (per go.mod), no CGO. Server targets Linux; the client is cross-platform.

About

A modern, authenticated port-knocking toolkit in Go: one daemon (knockd) that watches knock ports and executes actions, one client (knock) that performs the sequence.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages