package util import ( "crypto/rand" "fmt" "math/big" "net/http" "github.com/k3s-io/k3s/pkg/generated/clientset/versioned/scheme" "github.com/pkg/errors" "github.com/sirupsen/logrus" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" ) var ErrAPINotReady = errors.New("apiserver not ready") var ErrAPIDisabled = errors.New("apiserver disabled") // SendErrorWithID sends and logs a random error ID so that logs can be correlated // between the REST API (which does not provide any detailed error output, to avoid // information disclosure) and the server logs. func SendErrorWithID(err error, component string, resp http.ResponseWriter, req *http.Request, status ...int) { errID, _ := rand.Int(rand.Reader, big.NewInt(99999)) logrus.Errorf("%s error ID %05d: %v", component, errID, err) SendError(fmt.Errorf("%s error ID %05d", component, errID), resp, req, status...) } // SendError sends a properly formatted error response func SendError(err error, resp http.ResponseWriter, req *http.Request, status ...int) { var code int if len(status) == 1 { code = status[0] } if code == 0 || code == http.StatusOK { code = http.StatusInternalServerError } // Don't log "apiserver not ready" or "apiserver disabled" errors, they are frequent during startup if !errors.Is(err, ErrAPINotReady) && !errors.Is(err, ErrAPIDisabled) { logrus.Errorf("Sending HTTP %d response to %s: %v", code, req.RemoteAddr, err) } var serr *apierrors.StatusError switch code { case http.StatusBadRequest: serr = apierrors.NewBadRequest(err.Error()) case http.StatusUnauthorized: serr = apierrors.NewUnauthorized(err.Error()) case http.StatusForbidden: serr = newForbidden(err) case http.StatusInternalServerError: serr = apierrors.NewInternalError(err) case http.StatusBadGateway: serr = newBadGateway(err) case http.StatusServiceUnavailable: serr = apierrors.NewServiceUnavailable(err.Error()) default: serr = apierrors.NewGenericServerResponse(code, req.Method, schema.GroupResource{}, req.URL.Path, err.Error(), 0, true) } responsewriters.ErrorNegotiated(serr, scheme.Codecs.WithoutConversion(), schema.GroupVersion{}, resp, req) } func newForbidden(err error) *apierrors.StatusError { return &apierrors.StatusError{ ErrStatus: metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusForbidden, Reason: metav1.StatusReasonForbidden, Message: err.Error(), }} } func newBadGateway(err error) *apierrors.StatusError { return &apierrors.StatusError{ ErrStatus: metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusBadGateway, Reason: metav1.StatusReasonInternalError, Message: err.Error(), }} }