Repository navigation
feat: warn when the client and the broker run different versions - #93
Merged
Merged
Conversation
5 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merge after #88. The warning and the notice name
nodkey update, which #88 adds. Both must be in the same release.What this changes
The client and the broker now tell each other their version, and say so
when the versions differ. Today a mixed pair can fail with only "the
answer of the broker is not valid".
Nodkey-Versionwith the client'sversion. Each answer of the broker has it with the broker's version:
every status, also errors, 404, 405 and the 102 Processing (which still
has the short code). It is a header, not a JSON field, so old and new
versions still read each other.
header that matches
^[A-Za-z0-9][A-Za-z0-9.+_-]{0,63}$. Any othervalue counts as "no version" and is never printed or logged. Only
release versions (
X.Y.Z, numbers 0 to 999999, no leading zeros) arecompared, as numbers.
dev, a commit or1.2.0-rc.1is a build fromthe source: no warning and no notice on either side.
compares. When the releases differ, or the broker sends no valid
version, it prints one warning on stderr, before its last line or
before the command starts. It says which side is older and what to do
there:
sudo nodkey updateon this machine, ornodkey updateas rooton the broker host. A broker that sends no version runs 0.1, which has
no
nodkey update, so then the warning points to section 13 ofdocs/setup.md.
nodkey runandnodkey ssh-grantmake onerequest, so at most one line.
nodkey ssh-agentprints one line foreach version that the broker sends (at most 16). The warning never goes
into the command's environment or output.
check and no audit entry, and the request never waits for it. When the
claim is missing or names another release, the broker writes one
journal line, once for each client in each start, however many values
the client sends, and sends one silent notice. For a client that sends
no version, the notice points to section 13 of docs/setup.md, not to
nodkey update.telegram.Approver.Informsends thebroker's own text to the notice chat (
notice_chat_id, else theapprover's chat), silent, link previews off. It has its own count, at
most 3 in any 60 seconds, apart from the 20 of
Notifyand the 6 perclient of the approval core, so it never takes from the limits of the
notices of "never ask" uses. Each call is one try. The broker sends it
from a goroutine that the service tracks, so it never delays a
request;
Service.Closecancels it (stop step 2).notice that went out ends the tries in this start. A client has at
most one try at a time. After a failed try, also one that the limit of
3 stops, the next try comes with a request of that client at least 60
seconds after the failure; there is no timer. Each failed try gets one
journal line. So 4 or more old clients in the first minute, or a start
while Telegram cannot be reached, still get their notices later, and 3
client keys cannot use up the places to hide another old client. A try
that failed after Telegram took the message can rarely give the notice
more than once; the real bound is one try a minute for each client.
DESIGN.md says so. A try that fails while the broker stops says in the
journal that no new try comes.
internal/version(the rule,ParseRelease,Compare,Check), which feat: say when a newer release is out #90 can reuse. The audit log now usesversion.Validfor thebroker_startedversion, instead of its owncopy of the pattern.
api.VersionHeaderandapi.Versionhold theprotocol part. The binary's version is passed in explicitly:
broker.Parts.Version, a newnoticeargument ofService.Run, and aversionargument ofclient.Run,client.SSHAgentandclient.SSHGrant.cmd/nodkeypassesmain.version.line; no other header is printed), section 7 (the notice, its text,
its limit, its tries, the rare 429 of Telegram in a full minute, and
what happens when the polling stops), section 8 (the notice counts
toward neither "never ask" limit), section 10 (a new "Versions" part
of the protocol, and the log rule for a claimed release version),
section 15 (the warning line of each command, and the stop step),
section 17 (the e2e rule). THREAT-MODEL.md: a new T33 (a client claims
a false version; every pinned client learns the broker's version, so
after a security release, update the broker first), notes in T9 (own
limits, and the rare 429), T15 and T31 (the release version in the
output). docs/setup.md: sections 2, 13 and 14. AGENTS.md: the new
package.
Tests
internal/version: valid and bad versions, release parsing (leadingzeros, too big, empty, junk, other scripts), numeric compare, and
Checkfor every case, also builds from the source on each side.internal/api:Versiontakes only one valid header.internal/telegram:Informgoes to the notice chat, silent, escaped;one try after 429 and server errors, with no wait; its own limit;
20
Notifycalls still pass right after the informs; the token canarytest covers
Inform.internal/broker: the header on 200, 400, 403, 404, 405, 413, 415,429, 503 and the 102 (the short code still parses), and none from a
broker without a valid version; one journal line and one notice per
client per start with many claimed values; no notice for the same
version, builds from the source, a broker without a version, or a
client key that is not pinned; the same answers and audit entries with
any header value; 6 "never ask" uses with notices right after a version
notice, then the limit; a failed or hanging notice does not change or
delay the answer, and Close stops it; no notice before Run or after the
approver stopped; a failed try is tried again only with a request at
least 60 seconds after the failure, never by the time alone; 5 old
clients in one minute, with a limit of 3, all get their notice in the
end; 20 requests of one client at the same time never make two tries
at once; a try that fails at Close, or after the approver stopped,
says that no new try comes.
internal/client:nodkey runwith the same version, an older client,an older broker, no header, bad header values (never shown), builds
from the source, and error answers (the warning comes before the last
line); the request header;
nodkey ssh-grant;nodkey ssh-agentwitha broker that changes its version; the bound of 16 lines; the real
broker service with a version.
cmd/nodkey: a child broker built as release 0.2.0 sends its version,and sends one notice through the real Telegram approver to a fake
Telegram server; the stop is clean.
mark, a notice sent in the request goroutine, no memory of warned
versions, a check only on 200, a raw value in the warning, no retry, no
wait of 60 seconds, two tries at once, and the journal line on every
try each make a new test fail.
Results on 2026-10-17, after the fixes of the security review:
CI=true go test -race ./...with Go 1.27.1 and with Go 1.26.0: pass.go vet ./...(both versions) andgo vet -tags e2e ./test/e2e/...:pass.
gofmt -l .: nothing.go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.14.0 run --build-tags e2e:0 issues.
-race -count=20 -cpu 1,2,4, package by package:pass.
CI=true go test -race -tags e2e ./test/e2e/...: pass. Both sidesthere are
devbuilds, so no notice goes out, and the rule "no messagewithout buttons" stays as it was.
Left out, and why
writes itself to a request that is not valid HTTP, and the 403 of the
TLS layer for a request without a stored client name. Both come before
the broker's code, and the client never causes them. DESIGN.md says so.
nodkey updateonly when both sidessend their version. That command comes with feat: install script and nodkey update #88: merge feat: install script and nodkey update #88 before or
with this, in the same release.
tries of the notice (M1), no
nodkey updateadvice for a side withouta version (M2), older texts in DESIGN.md and THREAT-MODEL.md (M3), the
rare 429 (L1), what a pinned client learns (L2), and two wordings. The
fix review found only nits, also fixed: "about 18" "never ask" notices
for the rare 429, "rarely more than one" notice, the place of the
warning in DESIGN.md section 5, the log line of a try that fails while
the broker stops, and "nodkey 0.1" (also 0.1.1) in docs/setup.md.
Closes #89