Skip to content

The backend registry

The root signing package holds one process-global registry mapping a backend name to a Backend. It is guarded by a sync.RWMutex, so concurrent Get and Names calls are safe.

The Backend contract

type Backend interface {
    Name() string
    NewSigner(ctx context.Context, keyID string) (crypto.Signer, error)
}

Name returns a stable identifier: lowercase, kebab-case, unique across the process. NewSigner returns a crypto.Signer wrapping the held key; the backend decides what keyID means.

The interface is deliberately CLI-agnostic — it mentions no flag library — so the core module can stay dependency-light. A backend needing its own flags implements an optional interface defined by the CLI front end, which type-asserts for it. The core neither defines nor depends on such an interface.

Backends do not resolve credentials. The caller configures the SDK, agent or environment before the signer is used.

Register(b Backend)

Adds b to the registry. Called from a backend package's init(), so that a blank import activates it — the same activate-by-side-effect pattern as net/http/pprof and the image/* decoders.

It panics, rather than returning an error, in three cases:

Condition Panic message
b is nil signing: Register called with nil Backend
b.Name() returns "" signing: Backend.Name() returned empty string
the name is already registered signing: duplicate Backend registration: <name>

All three are programming errors at process init. Failing fast beats a silently overridden backend, which would mean signing with an unexpected key.

There is no Unregister. Once a backend package is imported, its backend is present for the life of the process.

Get(name string) (Backend, error)

Returns the registered backend, or ErrUnknownBackend wrapped with a listing so the message says what this binary actually supports:

  • with backends registered — "gcp-kms" (available: aws-kms, local)
  • with an empty registry — "local" (no backends are registered — this binary was built without any signing backends compiled in)

The second message almost always means a missing blank import.

Lookup is exact: no case folding, no aliasing, no fuzzy matching.

Names() []string

Returns every registered name, sorted alphabetically. It reports the backends this binary compiled in, which is exactly the set of backend packages the binary chose to import — useful for rendering the accepted values of a --backend flag.

ResetForTesting()

Clears the registry. The ForTesting suffix is the Go-standard signal that this is a test helper; production code must not call it. It is exported rather than hidden behind a test-only build so that tests in other packages can drive registry-mutating scenarios against fake backends.

What keyID means per backend

keyID is a single string whose format each backend defines.

Backend name Module keyID is Key types
local gitlab.com/phpboyscout/go/signing/local (ships in this module) A filesystem path to an unencrypted PEM file RSA (PKCS#1 or PKCS#8) and Ed25519 (PKCS#8)
aws-kms gitlab.com/phpboyscout/go/signing-aws-kms A KMS key ARN or alias RSA_4096 SIGN_VERIFY for the OpenPGP path, ECC_NIST_EDWARDS25519 for the minisign path

The local backend is stateless and declares no flags; the PEM path arrives per call as keyID. It reads whatever path it is given, following symlinks, with no canonicalisation — appropriate for an operator-supplied path, worth knowing if the path could come from somewhere less trusted.

Registering a configured backend

Backends that need configuration can be constructed and registered programmatically rather than relying on the package's init(). That is how the AWS backend takes a region or a logger:

signing.Register(awskms.New(awskms.WithRegion("us-east-1")))

Doing this and blank-importing the same backend panics on the duplicate name. Pick one.