certificate/rpcerrs: move logging sanitizer into certificate

Currently that is the only place using it and it's tied to zap
implementation. We don't want to have zap in common to reduce common
dependencies.

Change-Id: I72c064008f83ad3a8a3aa21944753208d4844c85
This commit is contained in:
Egon Elbre 2020-10-14 14:11:36 +03:00
parent c54dd45755
commit e2d589f3cc
3 changed files with 195 additions and 12 deletions

View File

@ -8,17 +8,17 @@ import (
"go.uber.org/zap"
"storj.io/common/errs2"
"storj.io/common/identity"
"storj.io/common/pb"
"storj.io/common/rpc/rpcpeer"
"storj.io/common/rpc/rpcstatus"
"storj.io/storj/certificate/authorization"
"storj.io/storj/certificate/rpcerrs"
)
// Endpoint implements pb.CertificatesServer.
type Endpoint struct {
sanitizer *errs2.LoggingSanitizer
rpclog *rpcerrs.Log
log *zap.Logger
ca *identity.FullCertificateAuthority
authorizationDB *authorization.DB
@ -27,16 +27,15 @@ type Endpoint struct {
// NewEndpoint creates a new certificate signing server.
func NewEndpoint(log *zap.Logger, ca *identity.FullCertificateAuthority, authorizationDB *authorization.DB, minDifficulty uint16) *Endpoint {
codeMap := errs2.CodeMap{
rpclog := rpcerrs.NewLog(&Error, log, rpcerrs.StatusMap{
&authorization.ErrNotFound: rpcstatus.Unauthenticated,
&authorization.ErrInvalidClaim: rpcstatus.InvalidArgument,
&authorization.ErrInvalidToken: rpcstatus.InvalidArgument,
&authorization.ErrAlreadyClaimed: rpcstatus.AlreadyExists,
}
sanitizer := errs2.NewLoggingSanitizer(&Error, log, codeMap)
})
return &Endpoint{
sanitizer: sanitizer,
rpclog: rpclog,
log: log,
ca: ca,
authorizationDB: authorizationDB,
@ -51,19 +50,19 @@ func (endpoint Endpoint) Sign(ctx context.Context, req *pb.SigningRequest) (_ *p
peer, err := rpcpeer.FromContext(ctx)
if err != nil {
msg := "error getting peer from context"
return nil, endpoint.sanitizer.Error(msg, err)
return nil, endpoint.rpclog.Error(msg, err)
}
peerIdent, err := identity.PeerIdentityFromPeer(peer)
if err != nil {
msg := "error getting peer identity"
return nil, endpoint.sanitizer.Error(msg, err)
return nil, endpoint.rpclog.Error(msg, err)
}
signedPeerCA, err := endpoint.ca.Sign(peerIdent.CA)
if err != nil {
msg := "error signing peer CA"
return nil, endpoint.sanitizer.Error(msg, err)
return nil, endpoint.rpclog.Error(msg, err)
}
signedChainBytes := [][]byte{signedPeerCA.Raw, endpoint.ca.Cert.Raw}
@ -76,18 +75,18 @@ func (endpoint Endpoint) Sign(ctx context.Context, req *pb.SigningRequest) (_ *p
})
if err != nil {
msg := "error claiming authorization"
return nil, endpoint.sanitizer.Error(msg, err)
return nil, endpoint.rpclog.Error(msg, err)
}
difficulty, err := peerIdent.ID.Difficulty()
if err != nil {
msg := "error checking difficulty"
return nil, endpoint.sanitizer.Error(msg, err)
return nil, endpoint.rpclog.Error(msg, err)
}
token, err := authorization.ParseToken(req.AuthToken)
if err != nil {
msg := "error parsing auth token"
return nil, endpoint.sanitizer.Error(msg, err)
return nil, endpoint.rpclog.Error(msg, err)
}
tokenFormatter := authorization.Authorization{
Token: *token,

View File

@ -0,0 +1,52 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
package rpcerrs
import (
"github.com/zeebo/errs"
"go.uber.org/zap"
"storj.io/common/rpc/rpcstatus"
)
// StatusMap is used to apply the correct rpc status code to error classes.
type StatusMap map[*errs.Class]rpcstatus.StatusCode
// Log consolidates logging of original errors with sanitization of internal errors.
type Log struct {
wrapper *errs.Class
log *zap.Logger
codeMap StatusMap
}
// NewLog creates a new Log.
func NewLog(wrapper *errs.Class, log *zap.Logger, codeMap StatusMap) *Log {
return &Log{
wrapper: wrapper,
log: log,
codeMap: codeMap,
}
}
// Error logs the message and error to the logger and returns the mapped rpcstatus code.
func (sanitizer *Log) Error(msg string, err error) error {
if sanitizer.wrapper != nil {
err = sanitizer.wrapper.Wrap(err)
}
if sanitizer.log != nil {
sanitizer.log.Error(msg, zap.Error(err))
}
for errClass, code := range sanitizer.codeMap {
if errClass.Has(err) {
return rpcstatus.Error(code, err.Error())
}
}
if sanitizer.wrapper == nil {
return rpcstatus.Error(rpcstatus.Internal, msg)
}
return rpcstatus.Error(rpcstatus.Internal, sanitizer.wrapper.New(msg).Error())
}

View File

@ -0,0 +1,132 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package rpcerrs_test
import (
"fmt"
"io/ioutil"
"os"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/zeebo/errs"
"go.uber.org/zap"
"storj.io/common/rpc/rpcstatus"
"storj.io/common/testcontext"
"storj.io/storj/certificate/rpcerrs"
)
func TestLoggingSanitizer_Error(t *testing.T) {
ctx := testcontext.New(t)
defer ctx.Cleanup()
logPath := ctx.File("log")
logFile, err := os.Create(logPath)
require.NoError(t, err)
defer ctx.Check(logFile.Close)
wrapper := errs.Class("wrapper class")
unauthenticatedClass := errs.Class("unauthorized class")
notFoundClass := errs.Class("not found class")
internalClass := errs.Class("internal class")
internalErr := internalClass.New("internal error")
msg := "message"
statusMap := rpcerrs.StatusMap{
&unauthenticatedClass: rpcstatus.Unauthenticated,
&notFoundClass: rpcstatus.NotFound,
}
testLogConfig := zap.NewDevelopmentConfig()
testLogConfig.OutputPaths = []string{logPath}
testLog, err := testLogConfig.Build()
require.NoError(t, err)
scenarios := []struct {
name string
wrapper *errs.Class
log *zap.Logger
}{
{
"with wrapper and log",
&wrapper,
testLog,
},
{
"with wrapper, no log",
&wrapper,
nil,
},
{
"with log, no wrapper",
nil,
testLog,
},
{
"no wrapper or log",
nil,
nil,
},
}
for _, s := range scenarios {
s := s
t.Run(s.name, func(t *testing.T) {
sanitizer := rpcerrs.NewLog(s.wrapper, s.log, statusMap)
t.Log("exposed errors")
for errClass, code := range statusMap {
errInstance := errClass.New("%s", strings.Replace(string(*errClass), "class", "error", 1))
sanitizedErr := sanitizer.Error(msg, errInstance)
require.Error(t, sanitizedErr)
require.Equal(t, code, rpcstatus.Code(sanitizedErr))
require.Contains(t, sanitizedErr.Error(), *errClass)
if s.wrapper == nil {
require.Contains(t, sanitizedErr.Error(), errInstance.Error())
} else {
require.Contains(t, sanitizedErr.Error(), wrapper.Wrap(errInstance).Error())
}
if s.log != nil {
logData, err := ioutil.ReadAll(logFile)
require.NoError(t, err)
logStr := string(logData)
require.Contains(t, logStr, msg)
if s.wrapper == nil {
require.Contains(t, logStr, fmt.Sprintf(`"error": "%s"`, errInstance))
} else {
require.Contains(t, logStr, fmt.Sprintf(`"error": "%s: %s"`, wrapper, errInstance))
}
}
}
t.Log("internal error")
sanitizedErr := sanitizer.Error(msg, internalErr)
require.Error(t, sanitizedErr)
require.Equal(t, rpcstatus.Internal, rpcstatus.Code(sanitizedErr))
require.NotContains(t, sanitizedErr.Error(), internalClass)
if s.wrapper == nil {
require.Contains(t, sanitizedErr.Error(), msg)
} else {
require.Equal(t, wrapper.New(msg).Error(), sanitizedErr.Error())
}
if s.log != nil {
logData, err := ioutil.ReadAll(logFile)
require.NoError(t, err)
logStr := string(logData)
require.Contains(t, logStr, msg)
if s.wrapper == nil {
require.Contains(t, logStr, fmt.Sprintf(`"error": "%s"`, internalErr))
} else {
require.Contains(t, logStr, fmt.Sprintf(`"error": "%s: %s"`, wrapper, internalErr))
}
}
})
}
}