diff --git a/pkg/services/private/server/healthcheck.go b/pkg/services/private/server/healthcheck.go new file mode 100644 index 000000000..daf41aa55 --- /dev/null +++ b/pkg/services/private/server/healthcheck.go @@ -0,0 +1,35 @@ +package private + +import ( + "context" + + "github.com/nspcc-dev/neofs-node/pkg/services/private" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// HealthCheck returns health status of the local node. +// +// If request is unsigned or signed by disallowed key, permission error returns. +func (s *Server) HealthCheck(_ context.Context, req *private.HealthCheckRequest) (*private.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(private.HealthCheckResponse) + + body := new(private.HealthCheckResponse_Body) + resp.SetBody(body) + + // FIXME: calculate the status + body.SetStatus(private.HealthStatus_ONLINE) + + // sign the response + if err := SignMessage(s.key, resp); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return resp, nil +} diff --git a/pkg/services/private/server/server.go b/pkg/services/private/server/server.go new file mode 100644 index 000000000..027226722 --- /dev/null +++ b/pkg/services/private/server/server.go @@ -0,0 +1,53 @@ +package private + +import ( + "crypto/ecdsa" +) + +// Server is an entity that serves +// Private service on storage node. +type Server struct { + *cfg +} + +// Option of the Server's constructor. +type Option func(*cfg) + +type cfg struct { + key *ecdsa.PrivateKey + + allowedKeys [][]byte +} + +func defaultCfg() *cfg { + return &cfg{} +} + +// New creates, initializes and returns new Server instance. +func New(opts ...Option) *Server { + c := defaultCfg() + + for _, opt := range opts { + opt(c) + } + + return &Server{ + cfg: c, + } +} + +// WithKey returns option to set private key +// used for signing responses. +func WithKey(key *ecdsa.PrivateKey) Option { + return func(c *cfg) { + c.key = key + } +} + +// WithAllowedKeys returns option to add list of public +// keys that have rights to use private service. +func WithAllowedKeys(keys [][]byte) Option { + return func(c *cfg) { + c.allowedKeys = append(c.allowedKeys, keys...) + } +} diff --git a/pkg/services/private/server/sign.go b/pkg/services/private/server/sign.go new file mode 100644 index 000000000..56c0ebdd3 --- /dev/null +++ b/pkg/services/private/server/sign.go @@ -0,0 +1,54 @@ +package private + +import ( + "bytes" + "crypto/ecdsa" + "errors" + + "github.com/nspcc-dev/neofs-api-go/util/signature" + "github.com/nspcc-dev/neofs-node/pkg/services/private" +) + +// SignedMessage is an interface of Private service message. +type SignedMessage interface { + signature.DataSource + GetSignature() *private.Signature + SetSignature(*private.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 Private 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(private.Signature) + s.SetKey(key) + s.SetSign(sig) + + msg.SetSignature(s) + }) +}