diff --git a/builtin/logical/pki/crl_util.go b/builtin/logical/pki/crl_util.go index c159dc429f50..d10b6e14f0b0 100644 --- a/builtin/logical/pki/crl_util.go +++ b/builtin/logical/pki/crl_util.go @@ -751,7 +751,7 @@ func (cb *crlBuilder) processRevocationQueue(sc *storageContext) error { } if err := sc.Storage.Put(sc.Context, confirmedEntry); err != nil { - return fmt.Errorf("error persisting cross-cluster revocation confirmation: %w\nThis may occur when the active node of the primary performance replication cluster is unavailable.", err) + return fmt.Errorf("error persisting cross-cluster revocation confirmation: %w", err) } } else { // Since we're the active node of the primary cluster, go ahead diff --git a/builtin/logical/pki/path_revoke.go b/builtin/logical/pki/path_revoke.go index f35daefb50fc..ff8393e03619 100644 --- a/builtin/logical/pki/path_revoke.go +++ b/builtin/logical/pki/path_revoke.go @@ -374,7 +374,7 @@ func (b *backend) maybeRevokeCrossCluster(sc *storageContext, config *crlConfig, } if err := sc.Storage.Put(sc.Context, reqEntry); err != nil { - return nil, fmt.Errorf("error persisting cross-cluster revocation request: %w\nThis may occur when the active node of the primary performance replication cluster is unavailable.", err) + return nil, fmt.Errorf("error persisting cross-cluster revocation request: %w", err) } resp := &logical.Response{ diff --git a/changelog/20643.txt b/changelog/20643.txt new file mode 100644 index 000000000000..340ec5b547ff --- /dev/null +++ b/changelog/20643.txt @@ -0,0 +1,3 @@ +```release-note:improvement +core: report intermediate error messages during request forwarding +``` diff --git a/sdk/logical/response_util.go b/sdk/logical/response_util.go index 4a9f61d563f6..6d31a16ee7ed 100644 --- a/sdk/logical/response_util.go +++ b/sdk/logical/response_util.go @@ -73,10 +73,21 @@ func RespondErrorCommon(req *Request, resp *Response, err error) (int, error) { var allErrors error var codedErr *ReplicationCodedError errwrap.Walk(err, func(inErr error) { + // The Walk function does not just traverse leaves, and execute the + // callback function on the entire error first. So, if the error is + // of type multierror.Error, we may want to skip storing the entire + // error first to avoid adding duplicate errors when walking down + // the leaf errors + if _, ok := inErr.(*multierror.Error); ok { + return + } newErr, ok := inErr.(*ReplicationCodedError) if ok { codedErr = newErr } else { + // if the error is of type fmt.wrapError which is typically + // made by calling fmt.Errorf("... %w", err), allErrors will + // contain duplicated error messages allErrors = multierror.Append(allErrors, inErr) } }) diff --git a/vault/request_handling.go b/vault/request_handling.go index 6584d9d94145..0810c70fe1d3 100644 --- a/vault/request_handling.go +++ b/vault/request_handling.go @@ -826,7 +826,30 @@ func (c *Core) doRouting(ctx context.Context, req *logical.Request) (*logical.Re // If we're replicating and we get a read-only error from a backend, need to forward to primary resp, err := c.router.Route(ctx, req) if shouldForward(c, resp, err) { - return forward(ctx, c, req) + fwdResp, fwdErr := forward(ctx, c, req) + if fwdErr != nil && err != logical.ErrReadOnly { + // When handling the request locally, we got an error that + // contained ErrReadOnly, but had additional information. + // Since we've now forwarded this request and got _another_ + // error, we should tell the user about both errors, so + // they know about both. + // + // When there is no error from forwarding, the request + // succeeded and so no additional context is necessary. When + // the initial error here was only ErrReadOnly, it's likely + // the plugin authors intended to forward this request + // remotely anyway. + repErr, ok := fwdErr.(*logical.ReplicationCodedError) + if ok { + fwdErr = &logical.ReplicationCodedError{ + Msg: fmt.Sprintf("errors from both primary and secondary; primary error was %s; secondary errors follow: %s", repErr.Error(), err.Error()), + Code: repErr.Code, + } + } else { + fwdErr = multierror.Append(fwdErr, err) + } + } + return fwdResp, fwdErr } return resp, err }