Skip to content

Add a signing backend

The standard gtb binary ships with two backends: aws-kms and local (PEM file on disk). If you need to sign against something else (GCP KMS, Azure Key Vault, HashiCorp Vault Transit, a YubiKey) you implement a signing.Backend, register it from your own main package, and gtb keys mint --backend <name> / gtb sign --backend <name> picks it up.

Canonical guide lives in the signing module

The backend registry was extracted from go-tool-base into the standalone signing module (gitlab.com/phpboyscout/go/signing, v0.1.0). The canonical, in-depth "Implement a custom backend" how-to: plus the trust model, threat model, and per-backend guides. Now lives in the signing module documentation, with the API on pkg.go.dev/gitlab.com/phpboyscout/go/signing.

This page keeps the gtb-side essentials: the contract, how to register, and how to activate a backend in a gtb-derived binary.

The Backend contract

The contract is CLI-agnostic, two methods, both backed by stdlib seams (crypto.Signer, context.Context):

// gitlab.com/phpboyscout/go/signing
type Backend interface {
    Name() string
    NewSigner(ctx context.Context, keyID string) (crypto.Signer, error)
}
  • Name(): the identifier the user types after --backend. Lowercase, kebab-case, must be unique across the process. Duplicate registration panics at init() time (fail-fast).
  • NewSigner(ctx, keyID): given the user's --key-id, return a crypto.Signer whose Public() is an *rsa.PublicKey and whose Sign() makes the remote signing call. Public() must return RSA in v0.1, Ed25519 minting goes through gtb keys generate, not through the backend registry.

RegisterFlags is not part of the Backend contract. A backend that needs CLI flags (e.g. aws-kms's --kms-region) implements an optional interface that the CLI front-end type-asserts for; a backend with no flags simply omits it and stays free of any CLI dependency. See the signing module's how-to for that optional interface's exact shape.

Register from init()

// yourtool/signing/gcp/gcp.go
package gcp

import (
    "context"
    "crypto"

    "gitlab.com/phpboyscout/go/signing"
)

type backend struct{ /* optional flag-bound state */ }

func (b *backend) Name() string { return "gcp-kms" }

func (b *backend) NewSigner(ctx context.Context, keyID string) (crypto.Signer, error) {
    // resolve the key, construct the SDK client, return a crypto.Signer
}

func init() {
    signing.Register(&backend{})
}

The implementation pattern matches the two reference backends:

  • gitlab.com/phpboyscout/go/signing-aws-kms (package awskms): a separate module wrapping AWS KMS, and the example of a backend that contributes a CLI flag (--kms-region) via the optional interface.
  • gitlab.com/phpboyscout/go/signing/local: the on-disk PEM backend, the example of a flag-less backend.

Activate it in your binary

Backends register by side effect, so a blank import is all it takes:

// cmd/your-cli/main.go
import (
    "gitlab.com/phpboyscout/go-tool-base/cli/pkg/cmd/root"

    // Activate the backends you want. Standard gtb ships aws-kms +
    // local; your tool can ship anything.
    _ "gitlab.com/yourorg/yourtool/signing/gcp"
)

That's it. your-cli keys mint --backend gcp-kms --key-id <id> ... now works, and omitting a backend's import drops it (and its SDK) from the linked binary entirely.

See also