package grpc import ( "encoding/json" "errors" "strconv" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "github.com/letsencrypt/boulder/core" berrors "github.com/letsencrypt/boulder/errors" "github.com/letsencrypt/boulder/probs" ) // gRPC error codes used by Boulder. While the gRPC codes // end at 16 we start at 100 to provide a little leeway // in case they ever decide to add more // TODO(#2507): Deprecated, remove once boulder/errors code is deployed const ( MalformedRequestError = iota + 100 NotSupportedError UnauthorizedError NotFoundError LengthRequiredError SignatureValidationError RateLimitedError BadNonceError NoSuchRegistrationError InternalServerError ProblemDetails ) var ( errIncompleteRequest = errors.New("Incomplete gRPC request message") errIncompleteResponse = errors.New("Incomplete gRPC response message") ) func errorToCode(err error) codes.Code { switch err.(type) { case core.MalformedRequestError: return MalformedRequestError case core.NotSupportedError: return NotSupportedError case core.UnauthorizedError: return UnauthorizedError case core.NotFoundError: return NotFoundError case core.LengthRequiredError: return LengthRequiredError case core.SignatureValidationError: return SignatureValidationError case core.RateLimitedError: return RateLimitedError case core.BadNonceError: return BadNonceError case core.NoSuchRegistrationError: return NoSuchRegistrationError case core.InternalServerError: return InternalServerError case *probs.ProblemDetails: return ProblemDetails default: return codes.Unknown } } // wrapError wraps the internal error types we use for transport across the gRPC // layer and appends an appropriate errortype to the gRPC trailer via the provided // context. core.XXXError and probs.ProblemDetails error types are encoded using the gRPC // error status code which has been deprecated (#2507). errors.BoulderError error types // are encoded using the grpc/metadata in the context.Context for the RPC which is // considered to be the 'proper' method of encoding custom error types (grpc/grpc#4543 // and grpc/grpc-go#478) func wrapError(ctx context.Context, err error) error { if err == nil { return nil } if berr, ok := err.(*berrors.BoulderError); ok { // Ignoring the error return here is safe because if setting the metadata // fails, we'll still return an error, but it will be interpreted on the // other side as an InternalServerError instead of a more specific one. _ = grpc.SetTrailer(ctx, metadata.Pairs("errortype", strconv.Itoa(int(berr.Type)))) return grpc.Errorf(codes.Unknown, err.Error()) } // TODO(2589): deprecated, remove once boulder/errors code has been deployed code := errorToCode(err) var body string if code == ProblemDetails { pd := err.(*probs.ProblemDetails) bodyBytes, jsonErr := json.Marshal(pd) if jsonErr != nil { // Since gRPC will wrap this itself using grpc.Errorf(codes.Unknown, ...) // we just pass the original error back to the caller return err } body = string(bodyBytes) } else { body = err.Error() } return grpc.Errorf(code, body) } // unwrapError unwraps errors returned from gRPC client calls which were wrapped // with wrapError to their proper internal error type. If the provided metadata // object has an "errortype" field, that will be used to set the type of the // error. If the error is a core.XXXError or a probs.ProblemDetails the type // is determined using the gRPC error code which has been deprecated (#2507). func unwrapError(err error, md metadata.MD) error { if err == nil { return nil } if errTypeStrs, ok := md["errortype"]; ok { unwrappedErr := grpc.ErrorDesc(err) if len(errTypeStrs) != 1 { return berrors.InternalServerError( "multiple errorType metadata, wrapped error %q", unwrappedErr, ) } errType, decErr := strconv.Atoi(errTypeStrs[0]) if decErr != nil { return berrors.InternalServerError( "failed to decode error type, decoding error %q, wrapped error %q", decErr, unwrappedErr, ) } return berrors.New(berrors.ErrorType(errType), unwrappedErr) } // TODO(2589): deprecated, remove once boulder/errors code has been deployed code := grpc.Code(err) errBody := grpc.ErrorDesc(err) switch code { case InternalServerError: return core.InternalServerError(errBody) case NotSupportedError: return core.NotSupportedError(errBody) case MalformedRequestError: return core.MalformedRequestError(errBody) case UnauthorizedError: return core.UnauthorizedError(errBody) case NotFoundError: return core.NotFoundError(errBody) case SignatureValidationError: return core.SignatureValidationError(errBody) case NoSuchRegistrationError: return core.NoSuchRegistrationError(errBody) case RateLimitedError: return core.RateLimitedError(errBody) case LengthRequiredError: return core.LengthRequiredError(errBody) case BadNonceError: return core.BadNonceError(errBody) case ProblemDetails: pd := probs.ProblemDetails{} if json.Unmarshal([]byte(errBody), &pd) != nil { return err } return &pd default: return err } }