feat(security): Ed25519 signing verification for agent updates#758
Open
matztam wants to merge 5 commits into
Open
feat(security): Ed25519 signing verification for agent updates#758matztam wants to merge 5 commits into
matztam wants to merge 5 commits into
Conversation
added 5 commits
May 6, 2026 13:34
Closes PatchMon#754. Implements asymmetric cryptographic signing for the agent auto-update mechanism to close the trust gap where both the binary and its SHA256 hash were served by the same server. Changes: - Agent: verify Ed25519 minisign signature before installing any update, using a public key embedded at build time via -ldflags. Refuses update if no key is compiled in, if the signature is invalid, or if the signed version is not newer than the current version (downgrade protection). - Agent: new fetchAgentSignature() fetches .minisig file from server; verifyMinisignSignature() implements minisign wire format using pure stdlib (crypto/ed25519 + golang.org/x/crypto/blake2b), no new deps. - Server: new ServeAgentSignature() handler serves .minisig files from the agents directory; registered at GET /api/v1/hosts/agent/signature. - Build: PATCHMON_SIGNING_PUBLIC_KEY (env var) is required for all release builds (build-linux/freebsd/windows/all); build fails fast if unset. AGENT_SIGNING_PRIVATE_KEY (GitHub secret) is used to sign each binary with minisign in the release workflow. - Makefile: new sign-all target; build-all and build-all-for-docker copy .minisig files alongside binaries.
…rfile - Makefile: skip .minisig files in sign-all loop to prevent signing signatures - Makefile: read signing password once via MINISIGN_PASSWORD env var - Dockerfile: replace 'node' with 'nodejs' (correct Alpine package name) - Dockerfile: remove silent frontend build fallback to surface errors
…tion Public key uses 'Ed' (0x45 0x64) but minisign signature files use 'ED' (0x45 0x44) — the byte check was wrong, causing all updates to be rejected.
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.
Closes #754.
The agent auto-update mechanism previously verified downloaded binaries via SHA256, but both the binary and the hash were served by the same server. A compromised server could deliver a malicious binary alongside a matching hash — the check would pass.
This PR adds asymmetric Ed25519 signing via minisign as a second, independent verification layer:
-ldflagsChanges
internal/pkgversion/signing_keys.go— new file;SigningPublicKeyvariable embedded at build time via-ldflags="-X ...SigningPublicKey=RW...". Build without key set aborts.cmd/patchmon-agent/commands/version_update.go— after SHA256 check: fetch.minisigfrom server, verify Ed25519 signature, enforce downgrade protection (version in trusted comment must be strictly greater than current)cmd/patchmon-agent/commands/version_update_test.go— unit tests for signature verification, key ID mismatch, downgrade protection, and version comparison using real Ed25519 keypairs (no external dependencies)server-source-code/internal/handler/install.go— newServeAgentSignaturehandler servingpatchmon-agent-{os}-{arch}.minisigserver-source-code/internal/server/router.go— routeGET /api/v1/hosts/agent/signatureMakefile—check-signing-keyguard,sign-alltarget,PATCHMON_SIGNING_PUBLIC_KEY/PATCHMON_SIGNING_PRIVATE_KEYenv vars; local dev build (make build) does not require a key.github/workflows/agent-release.yml— minisign install, sign step,.minisigupload to release artifactsdocker/server.Dockerfile— fixnode→nodejs(correct Alpine package name), remove silent frontend build fallbackSecurity model
vars.PATCHMON_SIGNING_PUBLIC_KEY)-ldflagsFirst installation (bootstrap) is not covered by signature verification — known, accepted limitation (chicken-and-egg). All subsequent updates are verified.
Test plan
go test ./...)update-agentcommand with real minisign keypair