From 3635ab22986d607607cb2d919fb8ad014c87f03a Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Fri, 13 Mar 2026 15:35:08 +0200 Subject: [PATCH 1/3] Try to find more useful http error codes * Generic 500 for a client disconnect is not good for observability * Littering the code with GRPC error handling everywhere would not be great either Handle the specific cases of GRPC client disconnect and gateway timeout in the generic error handler (only when the current error code is 500). This should make it easier to find actual issues in logs Signed-off-by: Jussi Kukkonen --- pkg/api/error.go | 28 ++++++++++++++++++++++++++++ pkg/api/error_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 pkg/api/error_test.go diff --git a/pkg/api/error.go b/pkg/api/error.go index 0cee0487a..e8ba7a535 100644 --- a/pkg/api/error.go +++ b/pkg/api/error.go @@ -16,12 +16,15 @@ package api import ( + "errors" "fmt" "net/http" "regexp" "github.com/go-openapi/runtime/middleware" "github.com/go-openapi/strfmt" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/generated/restapi/operations/entries" @@ -31,6 +34,29 @@ import ( "github.com/sigstore/rekor/pkg/log" ) +func mapGRPCToHTTP(code int, err error) int { + // Only try to be smart if current code is a generic 500 + if code != http.StatusInternalServerError { + return code + } + + // Look for a GRPC error (even a wrapped one) to resolve a more useful HTTP code. + // The list of handled codes is intentionally limited to specific cases + for currErr := err; currErr != nil; currErr = errors.Unwrap(currErr) { + if st, ok := status.FromError(currErr); ok { + switch st.Code() { + case codes.Canceled: + return 499 // Client Closed Request + case codes.DeadlineExceeded: + return http.StatusGatewayTimeout + default: + return code + } + } + } + return code +} + const ( trillianCommunicationError = "unexpected error communicating with transparency log" trillianUnexpectedResult = "unexpected result from transparency log" @@ -60,6 +86,8 @@ func errorMsg(message string, code int) *models.Error { var re = regexp.MustCompile("^(.*)Params$") func handleRekorAPIError(params interface{}, code int, err error, message string, fields ...interface{}) middleware.Responder { + code = mapGRPCToHTTP(code, err) + if message == "" { message = http.StatusText(code) } diff --git a/pkg/api/error_test.go b/pkg/api/error_test.go new file mode 100644 index 000000000..94fce34e1 --- /dev/null +++ b/pkg/api/error_test.go @@ -0,0 +1,28 @@ +package api + +import ( + "net/http" + "testing" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func TestMapGRPCToHTTP(t *testing.T) { + tests := []struct { + code int + err error + want int + }{ + {http.StatusOK, status.Error(codes.Canceled, "context canceled"), http.StatusOK}, + {http.StatusInternalServerError, status.Error(codes.Canceled, "context canceled"), 499}, + {http.StatusInternalServerError, status.Error(codes.DeadlineExceeded, "deadline exceeded"), http.StatusGatewayTimeout}, + {http.StatusInternalServerError, status.Error(codes.DataLoss, "dataloss"), http.StatusInternalServerError}, + } + + for _, tt := range tests { + if got := mapGRPCToHTTP(tt.code, tt.err); got != tt.want { + t.Errorf("mapGRPCToHTTP(%v, %e) = %v, want %v", tt.code, tt.err, got, tt.want) + } + } +} From 343e46ff19ad4f917572c486a4b09477242b2841 Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Fri, 13 Mar 2026 16:06:26 +0200 Subject: [PATCH 2/3] Don't log client connection issues as Errors ERROR panic detected: write tcp 10.1.4.25:3000->35.191.75.48:57722: i/o timeout This looks scary in the logs but seems to be just a client connection issue: Rekor has not failed here, the client connection has just been lost. Make sure the middleware recoverer does not log client connection issues as errors. Signed-off-by: Jussi Kukkonen --- .../restapi/configure_rekor_server.go | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/pkg/generated/restapi/configure_rekor_server.go b/pkg/generated/restapi/configure_rekor_server.go index e72a36364..985f0a9e3 100644 --- a/pkg/generated/restapi/configure_rekor_server.go +++ b/pkg/generated/restapi/configure_rekor_server.go @@ -22,9 +22,12 @@ import ( "crypto/tls" go_errors "errors" "fmt" + "io" + "net" "net/http" "net/http/httputil" "strconv" + "syscall" "time" // using embed to add the static html page duing build time @@ -397,7 +400,22 @@ func recoverer(next http.Handler) http.Handler { fields = append(fields, zap.ByteString("request_headers", request)) } - log.ContextLogger(ctx).With(fields...).Errorf("panic detected: %v", rvr) + // Check if the panic is due to a connection issue: Don't log these + // cases as serious errors + isNetworkError := false + if err, ok := rvr.(error); ok { + if go_errors.Is(err, io.EOF) || go_errors.Is(err, syscall.EPIPE) || go_errors.Is(err, syscall.ECONNRESET) { + isNetworkError = true + } else if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + isNetworkError = true + } + } + + if isNetworkError { + log.ContextLogger(ctx).With(fields...).Debugf("client connection closed: %v", rvr) + } else { + log.ContextLogger(ctx).With(fields...).Errorf("panic detected: %v", rvr) + } errors.ServeError(w, r, nil) } From 07b76885664922bb8dd5eb0b884d3c48c1dc7e3a Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Fri, 13 Mar 2026 16:24:28 +0200 Subject: [PATCH 3/3] Add license to new test file Signed-off-by: Jussi Kukkonen --- pkg/api/error_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pkg/api/error_test.go b/pkg/api/error_test.go index 94fce34e1..c5e3b2425 100644 --- a/pkg/api/error_test.go +++ b/pkg/api/error_test.go @@ -1,3 +1,17 @@ +// Copyright 2026 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package api import (