Featured image of post Sign your own binaries with go-tool-base

Sign your own binaries with go-tool-base

If your CLI tool can update itself, it has a decision to make that nobody is watching: when it pulls down a new version, should it trust what just landed? A checksum tells it the bytes match a manifest. It does not tell it who wrote the manifest. Close that gap and your users get updates they can actually trust; leave it open and a compromised release host can hand them anything it likes. This series is the end-to-end “how”, using the signing tooling built into go-tool-base.

By the end you’ll have a CLI that ships releases signed by a key you control, verifies its own updates against that key, and does the whole thing with no gpg wrangling and no long-lived secrets sitting in CI. We did the why and the how it works in two deep-dives already, a signature the platform can’t forge and a signing key that never leaves KMS. This is the use-it counterpart.

What you’re protecting against

Nobody’s coming to clean your supply chain, so it’s worth being clear about the threat before you spend an afternoon on the fix. A checksum file sits next to the binary on the same release page. Whoever can swap the binary can swap the checksum in the same breath, and the hash still matches. A signature is different: it’s made by a private key the release platform never holds, and verified against a public key your tool fetches from somewhere the platform can’t reach. To forge a release that passes, an attacker would have to steal a key that, done right, was never anywhere they could get at it.

That “done right” is the whole series.

Two paths through it

You don’t need a cloud account to start. The series runs in two stages:

  • Learn it locally. Part 1 signs and verifies on your laptop with a plain key on disk. No AWS, no CI, no cost. It’s the fastest way to see every moving part for real.
  • Do it for production. Parts 2 onward move the private key into AWS KMS, where it’s generated and never leaves, and wire your release pipeline to sign through it over short-lived OIDC credentials.

Each part stands on its own and ends with something that works. They build in order, but you can stop after Part 1 with a genuinely useful skill and come back for the cloud parts when you need them.

Before you start

You’ll want a CLI built on go-tool-base to sign. If you haven’t got one, the Building a CLI with go-tool-base series gets you there in an afternoon; this one picks up where releases come in. You’ll also need the gtb CLI installed (the installation docs have the one-liner), and for the cloud parts, an AWS account and a GitLab or GitHub project to release from.

The parts

  1. Sign and verify on your laptop: gtb keys generate, gtb sign, and gpg --verify, the whole loop with a local key.
  2. A signing key in AWS KMS: stand up an asymmetric KMS key with the terraform-aws-signing-kms module.
  3. Keyless CI signing with OIDC: federate GitLab and GitHub into the signer role, no stored credentials.
  4. Mint and publish your public key: gtb keys mint from KMS, then gtb keys wkd to publish it off-platform.
  5. Embed the key and require verification: bake the trust anchor into your binary and turn enforcement on safely.
  6. Sign every release with GoReleaser: wire signing into a real tagged-release pipeline.
  7. Rotation and break-glass: the part everyone skips, and how to do it without locking anyone out.

Start with Part 1. By the time you reach the end, the chain runs from a key born in a vault to a binary on a stranger’s machine checking, on its own, that the update it just fetched is really yours.

Built with Hugo
Theme Stack designed by Jimmy