[#414] ir/control: Implement service server
Implement `ControlServiceServer` on `Server` type. The `Server` requires all requests to be signed with keys from the so-called whitelist. To obtain health status, it uses the abstraction in the form of `HealthChecker` interface. Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
parent
93803b1a90
commit
dcfe9a6504
6 changed files with 200 additions and 0 deletions
34
pkg/services/control/ir/server/calls.go
Normal file
34
pkg/services/control/ir/server/calls.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
control "github.com/nspcc-dev/neofs-node/pkg/services/control/ir"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// HealthCheck returns health status of the local IR node.
|
||||
//
|
||||
// If request is not signed with a key from white list, permission error returns.
|
||||
func (s *Server) HealthCheck(_ context.Context, req *control.HealthCheckRequest) (*control.HealthCheckResponse, error) {
|
||||
// verify request
|
||||
if err := s.isValidRequest(req); err != nil {
|
||||
return nil, status.Error(codes.PermissionDenied, err.Error())
|
||||
}
|
||||
|
||||
// create and fill response
|
||||
resp := new(control.HealthCheckResponse)
|
||||
|
||||
body := new(control.HealthCheckResponse_Body)
|
||||
resp.SetBody(body)
|
||||
|
||||
body.SetHealthStatus(s.prm.healthChecker.HealthStatus())
|
||||
|
||||
// sign the response
|
||||
if err := SignMessage(s.prm.key, resp); err != nil {
|
||||
return nil, status.Error(codes.Internal, err.Error())
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
13
pkg/services/control/ir/server/deps.go
Normal file
13
pkg/services/control/ir/server/deps.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package control
|
||||
|
||||
import control "github.com/nspcc-dev/neofs-node/pkg/services/control/ir"
|
||||
|
||||
// HealthChecker is component interface for calculating
|
||||
// the current health status of a node.
|
||||
type HealthChecker interface {
|
||||
// Must calculate and return current health status of the IR application.
|
||||
//
|
||||
// If status can not be calculated for any reason,
|
||||
// control.HealthStatus_HEALTH_STATUS_UNDEFINED should be returned.
|
||||
HealthStatus() control.HealthStatus
|
||||
}
|
20
pkg/services/control/ir/server/opts.go
Normal file
20
pkg/services/control/ir/server/opts.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package control
|
||||
|
||||
// Option specifies Server's optional parameter.
|
||||
type Option func(*options)
|
||||
|
||||
type options struct {
|
||||
allowedKeys [][]byte
|
||||
}
|
||||
|
||||
func defaultOptions() *options {
|
||||
return new(options)
|
||||
}
|
||||
|
||||
// WithAllowedKeys returns option to add public keys
|
||||
// to white list of the Control service.
|
||||
func WithAllowedKeys(keys [][]byte) Option {
|
||||
return func(o *options) {
|
||||
o.allowedKeys = append(o.allowedKeys, keys...)
|
||||
}
|
||||
}
|
24
pkg/services/control/ir/server/prm.go
Normal file
24
pkg/services/control/ir/server/prm.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
)
|
||||
|
||||
// Prm groups required parameters of
|
||||
// Server's constructor.
|
||||
type Prm struct {
|
||||
key *ecdsa.PrivateKey
|
||||
|
||||
healthChecker HealthChecker
|
||||
}
|
||||
|
||||
// SetPrivateKey sets private key to sign responses.
|
||||
func (x *Prm) SetPrivateKey(key *ecdsa.PrivateKey) {
|
||||
x.key = key
|
||||
}
|
||||
|
||||
// SetHealthChecker sets HealthChecker to calculate
|
||||
// health status.
|
||||
func (x *Prm) SetHealthChecker(hc HealthChecker) {
|
||||
x.healthChecker = hc
|
||||
}
|
55
pkg/services/control/ir/server/server.go
Normal file
55
pkg/services/control/ir/server/server.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
crypto "github.com/nspcc-dev/neofs-crypto"
|
||||
)
|
||||
|
||||
// Server is an entity that serves
|
||||
// Control service on IR node.
|
||||
//
|
||||
// To gain access to the service, any request must be
|
||||
// signed with a key from the white list.
|
||||
type Server struct {
|
||||
prm Prm
|
||||
|
||||
allowedKeys [][]byte
|
||||
}
|
||||
|
||||
func panicOnPrmValue(n string, v interface{}) {
|
||||
const invalidPrmValFmt = "invalid %s parameter (%T): %v"
|
||||
panic(fmt.Sprintf(invalidPrmValFmt, n, v, v))
|
||||
}
|
||||
|
||||
// New creates a new instance of the Server.
|
||||
//
|
||||
// Panics if:
|
||||
// - parameterized private key is nil;
|
||||
// - parameterized HealthChecker is nil.
|
||||
//
|
||||
// Forms white list from all keys specified via
|
||||
// WithAllowedKeys option and a public key of
|
||||
// the parameterized private key.
|
||||
func New(prm Prm, opts ...Option) *Server {
|
||||
// verify required parameters
|
||||
switch {
|
||||
case prm.key == nil:
|
||||
panicOnPrmValue("key", prm.key)
|
||||
case prm.healthChecker == nil:
|
||||
panicOnPrmValue("health checker", prm.healthChecker)
|
||||
}
|
||||
|
||||
// compute optional parameters
|
||||
o := defaultOptions()
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(o)
|
||||
}
|
||||
|
||||
return &Server{
|
||||
prm: prm,
|
||||
|
||||
allowedKeys: append(o.allowedKeys, crypto.MarshalPublicKey(&prm.key.PublicKey)),
|
||||
}
|
||||
}
|
54
pkg/services/control/ir/server/sign.go
Normal file
54
pkg/services/control/ir/server/sign.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
|
||||
"github.com/nspcc-dev/neofs-api-go/util/signature"
|
||||
control "github.com/nspcc-dev/neofs-node/pkg/services/control/ir"
|
||||
)
|
||||
|
||||
// SignedMessage is an interface of Control service message.
|
||||
type SignedMessage interface {
|
||||
signature.DataSource
|
||||
GetSignature() *control.Signature
|
||||
SetSignature(*control.Signature)
|
||||
}
|
||||
|
||||
var errDisallowedKey = errors.New("key is not in the allowed list")
|
||||
|
||||
func (s *Server) isValidRequest(req SignedMessage) error {
|
||||
var (
|
||||
sign = req.GetSignature()
|
||||
key = sign.GetKey()
|
||||
allowed = false
|
||||
)
|
||||
|
||||
// check if key is allowed
|
||||
for i := range s.allowedKeys {
|
||||
if allowed = bytes.Equal(s.allowedKeys[i], key); allowed {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !allowed {
|
||||
return errDisallowedKey
|
||||
}
|
||||
|
||||
// verify signature
|
||||
return signature.VerifyDataWithSource(req, func() ([]byte, []byte) {
|
||||
return key, sign.GetSign()
|
||||
})
|
||||
}
|
||||
|
||||
// SignMessage signs Control service message with private key.
|
||||
func SignMessage(key *ecdsa.PrivateKey, msg SignedMessage) error {
|
||||
return signature.SignDataWithHandler(key, msg, func(key []byte, sig []byte) {
|
||||
s := new(control.Signature)
|
||||
s.SetKey(key)
|
||||
s.SetSign(sig)
|
||||
|
||||
msg.SetSignature(s)
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue