-
Notifications
You must be signed in to change notification settings - Fork 0
feat(awskms): add awskms support for grpc signing #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
98f06b0
1308053
3b972c3
ded89ae
1f655ed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -143,13 +143,17 @@ type GRPCConfig struct { | |||||
| Keys []GRPCKey `yaml:"keys"` | ||||||
| } | ||||||
|
|
||||||
| // GRPCKey binds a signing key to a key_id. Backend selects the custodian and | ||||||
| // Algorithm the key type; both default to the only implemented combination | ||||||
| // (file/secp256k1) when empty. The server performs no caller authorization, | ||||||
| // so every configured key is usable by any connecting client. | ||||||
| // GRPCKey binds a signing key to an id (the SignerService key handle clients | ||||||
| // address). Backend selects the custodian and Algorithm the key type. The | ||||||
| // supported combinations are file/secp256k1 (the default) and awskms/ed25519; | ||||||
| // PKCS#11 is not yet supported over gRPC. The server performs no caller | ||||||
| // authorization, so every configured key is usable by any connecting client. | ||||||
| type GRPCKey struct { | ||||||
| ID string `yaml:"id"` | ||||||
| Backend string `yaml:"backend"` // "file" (default) | ||||||
| Algorithm string `yaml:"algorithm"` // "secp256k1" (default) | ||||||
| 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) | ||||||
| KeyID string `yaml:"key_id"` // awskms: KMS id, ARN, or alias/<name> | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| FileConfig `yaml:",inline"` | ||||||
| AWSKMSConfig `yaml:",inline"` | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -232,19 +232,40 @@ func (c *Config) validateGRPC(home string) error { | |
| return fmt.Errorf("config: grpc requires at least one grpc.key entry") | ||
| } | ||
| seen := map[string]bool{} | ||
| for i, k := range g.Keys { | ||
| for i := range g.Keys { | ||
| k := &g.Keys[i] | ||
| if k.ID == "" { | ||
| return fmt.Errorf("config: grpc.key[%d].id is required", i) | ||
| } | ||
| if seen[k.ID] { | ||
| return fmt.Errorf("config: duplicate grpc.key id %q", k.ID) | ||
| } | ||
| seen[k.ID] = true | ||
| if k.KeyFile == "" { | ||
| return fmt.Errorf("config: grpc.key[%d].key_file is required", i) | ||
|
|
||
| // 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 == "" { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| k.Backend = BackendFile | ||
| } | ||
| if _, err := os.Stat(AbsPath(home, k.KeyFile)); err != nil { | ||
| return fmt.Errorf("config: grpc.key[%d].key_file %q: %w", i, k.KeyFile, err) | ||
| switch k.Backend { | ||
| case BackendFile: | ||
| if k.KeyFile == "" { | ||
| return fmt.Errorf("config: grpc.key[%d] (file) requires key_file", i) | ||
| } | ||
| if _, err := os.Stat(AbsPath(home, k.KeyFile)); err != nil { | ||
| return fmt.Errorf("config: grpc.key[%d].key_file %q: %w", i, k.KeyFile, err) | ||
| } | ||
|
mattac21 marked this conversation as resolved.
|
||
| k.KeyFile = AbsPath(home, k.KeyFile) | ||
| case BackendAWSKMS: | ||
| if k.KeyID == "" { | ||
| return fmt.Errorf("config: grpc.key[%d] (awskms) requires key_id", i) | ||
| } | ||
| if k.Algorithm != "" && !supportedAWSKMSAlgorithms[k.Algorithm] { | ||
| return fmt.Errorf("config: grpc.key[%d] (awskms) has unknown algorithm %q", i, k.Algorithm) | ||
| } | ||
| default: | ||
| return fmt.Errorf("config: grpc.key[%d] has unsupported backend %q", i, k.Backend) | ||
| } | ||
| } | ||
| return nil | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -165,53 +165,81 @@ func newPrivvalBackend(k config.Key) (signing.Backend, error) { | |
| // BuildGRPC constructs the SignerService gRPC server and its listener from the | ||
| // 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) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we wrap all of this into |
||
| if c.GRPC == nil { | ||
| return nil, nil, nil | ||
| return nil, nil, nil, nil | ||
| } | ||
| g := c.GRPC | ||
|
|
||
| var closers []io.Closer | ||
| cleanup = func() { | ||
| for _, cl := range closers { | ||
| _ = cl.Close() | ||
| } | ||
| } | ||
| // On error, release anything already opened before returning. | ||
| defer func() { | ||
| if err != nil { | ||
| cleanup() | ||
| } | ||
| }() | ||
|
|
||
| // keyID -> signing.Signer. The server performs no caller auth: any client | ||
| // reaching the listener may use any key (see signerservice.Server). | ||
| keys := map[string]signing.Key{} | ||
| for _, k := range g.Keys { | ||
| s, err := newGRPCSigner(home, k) | ||
| if err != nil { | ||
| return nil, nil, err | ||
| return nil, cleanup, nil, err | ||
| } | ||
| closers = append(closers, s) | ||
| keys[k.ID] = signing.Key{ID: k.ID, Signer: s} | ||
| } | ||
| srv := signerservice.NewServer(keys) | ||
|
|
||
| creds, err := credentials.NewServerTLSFromFile(config.AbsPath(home, g.TLSCert), config.AbsPath(home, g.TLSKey)) | ||
| if err != nil { | ||
| return nil, nil, fmt.Errorf("app: grpc tls: %w", err) | ||
| return nil, cleanup, nil, fmt.Errorf("app: grpc tls: %w", err) | ||
| } | ||
| gs := grpc.NewServer(grpc.Creds(creds)) | ||
| gs = grpc.NewServer(grpc.Creds(creds)) | ||
| gensignerservice.RegisterSignerServiceServer(gs, srv) | ||
|
|
||
| lis, err := net.Listen("tcp", g.Listen) | ||
| lis, err = net.Listen("tcp", g.Listen) | ||
| if err != nil { | ||
| return nil, nil, fmt.Errorf("app: grpc listen %q: %w", g.Listen, err) | ||
| return nil, cleanup, nil, fmt.Errorf("app: grpc listen %q: %w", g.Listen, err) | ||
| } | ||
| logger.Info("signerservice gRPC server configured", "listen", g.Listen, "keys", len(keys)) | ||
| return gs, lis, nil | ||
| return gs, cleanup, lis, nil | ||
| } | ||
|
|
||
| // newGRPCSigner constructs the signing.Signer for one grpc.key entry from its | ||
| // backend/algorithm. Empty backend/algorithm default to file/secp256k1, the | ||
| // only implemented combination. | ||
| // backend/algorithm. The algorithm default depends on the backend: file defaults | ||
| // to secp256k1, awskms to ed25519. Supported combinations are file/secp256k1 and | ||
| // awskms/ed25519. | ||
| func newGRPCSigner(home string, k config.GRPCKey) (signing.Signer, error) { | ||
| be, algo := k.Backend, k.Algorithm | ||
| if be == "" { | ||
| be = "file" | ||
| be = config.BackendFile | ||
| } | ||
| if algo == "" { | ||
| algo = "secp256k1" | ||
| switch be { | ||
| case config.BackendAWSKMS: | ||
| algo = "ed25519" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let' move all algo types to |
||
| default: | ||
| algo = "secp256k1" | ||
| } | ||
| } | ||
| switch { | ||
| case be == "file" && algo == "secp256k1": | ||
| return file.LoadSecp256k1(config.AbsPath(home, k.KeyFile)) | ||
| case be == config.BackendFile && algo == "secp256k1": | ||
| return file.LoadSecp256k1(k.KeyFile) | ||
| case be == config.BackendAWSKMS && algo == "ed25519": | ||
| return awskms.OpenSigner(context.Background(), awskms.Config{ | ||
| KeyID: k.KeyID, | ||
| Region: k.Region, | ||
| Profile: k.Profile, | ||
| Endpoint: k.Endpoint, | ||
| Algorithm: algo, | ||
| }) | ||
| default: | ||
| return nil, fmt.Errorf("app: grpc key %q: unsupported backend/algorithm %q/%q", k.ID, be, algo) | ||
| } | ||
|
|
||
There was a problem hiding this comment.
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