Skip to content

feat(awskms): add awskms support for grpc signing#11

Open
mattac21 wants to merge 5 commits into
mainfrom
ma/aws-kms-grpc
Open

feat(awskms): add awskms support for grpc signing#11
mattac21 wants to merge 5 commits into
mainfrom
ma/aws-kms-grpc

Conversation

@mattac21

Copy link
Copy Markdown
Collaborator

No description provided.

@mattac21 mattac21 requested a review from a team as a code owner June 23, 2026 21:29
@greptile-apps

greptile-apps Bot commented Jun 23, 2026

Copy link
Copy Markdown

Greptile Summary

This PR wires AWS KMS as a signing backend for the gRPC SignerService, complementing the existing consensus (privval) path. It also applies a consistent Backend rename across file, awskms, and pkcs11 packages to distinguish consensus-path types from the new gRPC Signer adapter.

  • New awskms.Signer (signing/awskms/signer.go) adapts *Backend to signing.Signer; OpenSigner rejects non-ed25519 algorithms before any AWS call and delegates Sign/PubKey/Close to the wrapped backend.
  • BuildGRPC extended with a closers/deferred-cleanup pattern matching Build, awskms support in newGRPCSigner, and a returned cleanup func() consumed by main.go.
  • validateGRPC now resolves and writes back the absolute key_file path for file-backend keys (closing the asymmetry noted in a prior review), and validates awskms keys with the same key_id/algorithm checks used by the consensus path.

Confidence Score: 5/5

Safe to merge; the new awskms gRPC path follows the same cleanup, validation, and error-return conventions as the established consensus path.

The cleanup-on-error pattern in BuildGRPC mirrors Build exactly (deferred func checks the named return err). Path resolution for file-backend gRPC keys is now done once in validateGRPC and written back, so newGRPCSigner correctly consumes the absolute path without a second AbsPath call. The awskms.Signer adapter is thin and correct — it reuses the Backend's cached public key and Sign path, and the compile-time interface assertion in signer.go guards all implementations. No logic errors were found in the changed paths.

No files require special attention.

Important Files Changed

Filename Overview
signing/awskms/signer.go New adapter wrapping Backend to satisfy the gRPC signing.Signer interface; correctly delegates PubKey, Scheme, Sign, and Close. OpenSigner adds a guard rejecting non-ed25519 algorithms before any AWS call.
internal/app/build.go BuildGRPC gains awskms support and a closers/cleanup pattern matching Build; deferred cleanup on error prevents leaks; newGRPCSigner now resolves file paths via Validate rather than an inline AbsPath call.
config/validate.go validateGRPC extended with awskms backend validation; file key now resolves and writes back the absolute path (k.KeyFile = AbsPath), closing the asymmetry noted in the prior review thread.
signing/signer.go Adds Close() error to the signing.Signer interface; all existing implementations (file, awskms, memEd25519 in tests) already have no-op implementations, and the compile-time assertion in server_test.go enforces the contract.
internal/app/build_awskms_test.go Splits the existing test into consensus/grpc sub-tests; grpc sub-test discards the cleanup function without registering it with t.Cleanup, inconsistent with the consensus sub-test pattern.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant main as cmd/kms/main.go
    participant build as app.BuildGRPC
    participant val as config.Validate
    participant ns as newGRPCSigner
    participant oss as awskms.OpenSigner
    participant kms as AWS KMS

    main->>val: cfg.Validate(home)
    val-->>main: ok (resolves key paths, validates key_id/algorithm)

    main->>build: BuildGRPC(cfg, home, logger)
    build->>build: setup closers + deferred cleanup
    loop for each grpc.key
        build->>ns: newGRPCSigner(home, k)
        alt "backend=awskms"
            ns->>oss: "OpenSigner(ctx, Config{KeyID, Region, ...})"
            oss->>kms: GetPublicKey(keyID)
            kms-->>oss: PublicKey bytes
            oss-->>ns: "*Signer"
        else "backend=file"
            ns-->>ns: file.LoadSecp256k1(k.KeyFile)
        end
        ns-->>build: signing.Signer
        build->>build: append to closers
    end
    build->>build: load TLS creds, grpc.NewServer, net.Listen
    build-->>main: gs, cleanupGRPC, lis, nil

    main->>main: defer cleanupGRPC()
    main->>main: defer gs.GracefulStop()
    main->>main: go gs.Serve(lis)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant main as cmd/kms/main.go
    participant build as app.BuildGRPC
    participant val as config.Validate
    participant ns as newGRPCSigner
    participant oss as awskms.OpenSigner
    participant kms as AWS KMS

    main->>val: cfg.Validate(home)
    val-->>main: ok (resolves key paths, validates key_id/algorithm)

    main->>build: BuildGRPC(cfg, home, logger)
    build->>build: setup closers + deferred cleanup
    loop for each grpc.key
        build->>ns: newGRPCSigner(home, k)
        alt "backend=awskms"
            ns->>oss: "OpenSigner(ctx, Config{KeyID, Region, ...})"
            oss->>kms: GetPublicKey(keyID)
            kms-->>oss: PublicKey bytes
            oss-->>ns: "*Signer"
        else "backend=file"
            ns-->>ns: file.LoadSecp256k1(k.KeyFile)
        end
        ns-->>build: signing.Signer
        build->>build: append to closers
    end
    build->>build: load TLS creds, grpc.NewServer, net.Listen
    build-->>main: gs, cleanupGRPC, lis, nil

    main->>main: defer cleanupGRPC()
    main->>main: defer gs.GracefulStop()
    main->>main: go gs.Serve(lis)
Loading

Reviews (2): Last reviewed commit: "normalize config path for grpc key files..." | Re-trigger Greptile

Comment thread config/validate.go
@linear-code

linear-code Bot commented Jun 23, 2026

Copy link
Copy Markdown

FOU-365

@mattac21

mattac21 commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator Author

@greptile review again and update your previous review

Comment thread signing/file/ed25519.go
Comment on lines -17 to +18
// Signer is a file-backed Ed25519 key held in memory.
type Signer struct {
// Backend is a file-backed Ed25519 key held in memory.
type Backend struct {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did a refactor of the existing implementors of the Backend interface. All of the implementors were named Signer, which as the obvious name for the implementors of the Signing interface. I changed the Backend implementors to be called Backend to free up the Signer name for my new structs.

Comment thread signing/signer.go
Comment on lines +21 to +22
// Close contains any logic that should be called on cleanup.
Close() error

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not necessarily required until we get grpc signing support for pkcs11 keys, but ai kept flagging the asymmetry where we did not call close on the Signers on exit, so I added this and cleanup on exit of the grpc server that matches cleaning up the backends.

@mattac21 mattac21 requested review from dhfang and swift1337 June 25, 2026 17:42

@dhfang dhfang left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have full context on the motivation for starting with ed25519 support, but from what I understand this won't have any consumers. Validators don't rely on gRPC signing and we don't have solana relaying support planned yet. For the relayer and attestor we will rely on secp for the release.

Comment thread config/config.go
ID string `yaml:"id"`
Backend Backend `yaml:"backend"` // "file" (default) | "awskms"
Algorithm string `yaml:"algorithm"` // file: "secp256k1" (default); awskms: "ed25519" (default)
KeyID string `yaml:"key_id"` // awskms: KMS id, ARN, or alias/<name>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
KeyID string `yaml:"key_id"` // awskms: KMS id, ARN, or alias/<name>
AWSKeyID string `yaml:"aws_key_id"` // awskms: KMS id, ARN, or alias/<name>

Comment thread config/config.go
KeyFile string `yaml:"key_file"`
ID string `yaml:"id"`
Backend Backend `yaml:"backend"` // "file" (default) | "awskms"
Algorithm string `yaml:"algorithm"` // file: "secp256k1" (default); awskms: "ed25519" (default)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ed26619 is relevant not only to AWS Keys

Comment thread config/validate.go
// gRPC keys carry their own (small) backend validation rather than sharing
// the consensus per-backend validators: only file and awskms are supported
// here, and a gRPC key is bound by id, not chain_ids.
if k.Backend == "" {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo, we should explicitly fail if it's not specified. Even when the backend is "", we still need a key file path.

Comment thread internal/app/build.go
// grpc config. Returns (nil, nil, nil) when no grpc block is configured.
// The caller owns starting/stopping the server and closing the listener.
func BuildGRPC(c *config.Config, home string, logger log.Logger) (*grpc.Server, net.Listener, error) {
func BuildGRPC(c *config.Config, home string, logger log.Logger) (gs *grpc.Server, cleanup func(), lis net.Listener, err error) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we wrap all of this into Server with s.Close(), s.Listener(), etc... and return only (*Server, error) ?

Comment thread internal/app/build.go
algo = "secp256k1"
switch be {
case config.BackendAWSKMS:
algo = "ed25519"

@swift1337 swift1337 Jun 26, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let' move all algo types to const Algo* eg AlgoSecp256k1 (applies to all files)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants