Skip to content

Backends are the consumer's responsibility

The sign, keys mint and keys minisign commands need a signing backend: AWS KMS, an on-disk PEM key, or something else. This module ships none of them. This page explains why, and how the commands find the backends the host compiled in.

This module imports no concrete backend

signing-cli never imports signing-aws-kms, signing/local, or any other backend package. Instead it talks to the go/signing registry:

  • signing.Names(): the names of every backend registered in this binary.
  • signing.Get(name): resolve one to its registered default signing.Backend.
  • signing.NewInstance(type, instance): mint a separate, named instance of a type whose package also registered a factory.

sign, keys mint and keys minisign build their --backend help text from signing.Names(). A bare --backend aws-kms resolves through signing.Get(); a named selector such as aws-kms:prod goes through signing.NewInstance() instead (see Selecting a backend). Either way, the backends the host binary registered are exactly the backends the commands offer, no more and no fewer.

Activation by blank import

Backends register themselves from init(). A host turns one on by blank-importing its package in main, the same side-effect pattern as net/http/pprof, the image/* decoders, and database drivers:

import (
    _ "gitlab.com/phpboyscout/go/signing-aws-kms" // registers "aws-kms"
    _ "gitlab.com/phpboyscout/go/signing/local"   // registers "local"
)

With those two imports present, sign --backend aws-kms … and sign --backend local … both work, and --help lists both names. Drop the KMS import and rebuild, and aws-kms disappears from the command with no code change in signing-cli or in the command wiring.

Why push backends out to the host

The same dependency-inversion logic that keeps go/signing light applies here:

  • The AWS SDK is enormous. A tool that only ever signs with the local PEM backend should not link it. Because the SDK is imported only by signing-aws-kms, and that package is imported only when the host blank-imports it, linker dead-code elimination keeps the SDK out of any binary that does not opt in.
  • Regulated / FIPS-only builds can drop a backend cleanly. go-tool-base isolates its backend imports in a single file (cli/cmd/gtb/signing.go); a downstream that must exclude the AWS SDK omits that one blank import and rebuilds. Its companion cli/cmd/gtb-no-aws-smoke binary exists to prove the AWS SDK is not pulled in transitively by anything inside signing-cli.
  • The command surface stays universal. Because the commands discover backends at runtime, one build of signing-cli serves every host regardless of which backends that host chose.

Per-backend flags

Some backends need their own flags. aws-kms, for example, adds --kms-region. The signing.Backend contract is CLI-agnostic, so a backend that needs flags implements an optional interface:

interface{ RegisterFlags(*pflag.FlagSet) }

When NewCmdSign, NewCmdKeysMint and NewCmdKeysMinisign build their command, they iterate the registered backends and type-assert for that interface, letting each backend declare its flags onto the command's flag set. So --kms-region appears in sign --help only when a KMS-capable backend is compiled in. The front-end type-asserts for this and the library never does, which keeps go/signing free of any flag machinery.

Those flags land on the registered default, because that is the object the commands hand to RegisterFlags when they are built. A named instance is a different object, minted after the flags have been parsed, so it gets its own private flag set and takes its values from the selector's ?settings. That is why --backend aws-kms:prod --kms-region eu-west-1 is refused: the flag would configure an object the command is not going to use. Refusing it is louder than honouring it as a default, and the louder failure is the one that cannot produce a signature from the wrong region's key.

Which commands consult the backend registry

Three of the seven do, and it is worth knowing which, because the other four cannot fail for a backend reason at all.

Command Backend Why
sign required It needs a crypto.Signer to sign with.
keys mint required It wraps an existing signer in OpenPGP framing.
keys minisign required It reads the signer's public half to derive the minisign key.
keys generate none It generates a fresh keypair entirely in-process.
keys wkd none It only reads already-minted armored public keys from disk.
keys publish none It reads a minisign public-key file, deliberately, because publishing must not need cloud credentials.
keys (the parent) none It only groups the subcommands.

See also