From 3d01ca13092377820b495fd64c748408cbb80e7b Mon Sep 17 00:00:00 2001 From: Brad Davidson Date: Wed, 27 Apr 2022 02:09:58 -0700 Subject: [PATCH] Make supervisor errors parsable by Kubernetes client libs This gives nicer errors from Kubernetes components during startup, and reduces LOC a bit by using the upstream responsewriters module instead of writing the headers and body by hand. Signed-off-by: Brad Davidson --- pkg/server/auth.go | 37 +++++++++++++++++++++++++++++++++---- pkg/server/router.go | 31 ++++++++++++++++--------------- 2 files changed, 49 insertions(+), 19 deletions(-) diff --git a/pkg/server/auth.go b/pkg/server/auth.go index 8dee4ce110..d0795e12fa 100644 --- a/pkg/server/auth.go +++ b/pkg/server/auth.go @@ -5,7 +5,12 @@ import ( "github.com/gorilla/mux" "github.com/k3s-io/k3s/pkg/daemons/config" + "github.com/k3s-io/k3s/pkg/generated/clientset/versioned/scheme" "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" "k8s.io/apiserver/pkg/endpoints/request" ) @@ -24,23 +29,23 @@ func doAuth(roles []string, serverConfig *config.Control, next http.Handler, rw switch { case serverConfig == nil: logrus.Errorf("Authenticate not initialized: serverConfig is nil") - rw.WriteHeader(http.StatusUnauthorized) + unauthorized(rw, req) return case serverConfig.Runtime.Authenticator == nil: logrus.Errorf("Authenticate not initialized: serverConfig.Runtime.Authenticator is nil") - rw.WriteHeader(http.StatusUnauthorized) + unauthorized(rw, req) return } resp, ok, err := serverConfig.Runtime.Authenticator.AuthenticateRequest(req) if err != nil { logrus.Errorf("Failed to authenticate request from %s: %v", req.RemoteAddr, err) - rw.WriteHeader(http.StatusUnauthorized) + unauthorized(rw, req) return } if !ok || !hasRole(roles, resp.User.GetGroups()) { - rw.WriteHeader(http.StatusForbidden) + forbidden(rw, req) return } @@ -56,3 +61,27 @@ func authMiddleware(serverConfig *config.Control, roles ...string) mux.Middlewar }) } } + +func unauthorized(resp http.ResponseWriter, req *http.Request) { + responsewriters.ErrorNegotiated( + &apierrors.StatusError{ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusUnauthorized, + Reason: metav1.StatusReasonUnauthorized, + Message: "not authorized", + }}, + scheme.Codecs.WithoutConversion(), schema.GroupVersion{}, resp, req, + ) +} + +func forbidden(resp http.ResponseWriter, req *http.Request) { + responsewriters.ErrorNegotiated( + &apierrors.StatusError{ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusForbidden, + Reason: metav1.StatusReasonForbidden, + Message: "forbidden", + }}, + scheme.Codecs.WithoutConversion(), schema.GroupVersion{}, resp, req, + ) +} diff --git a/pkg/server/router.go b/pkg/server/router.go index 86a5db3984..71334ad234 100644 --- a/pkg/server/router.go +++ b/pkg/server/router.go @@ -19,6 +19,7 @@ import ( "github.com/k3s-io/k3s/pkg/bootstrap" "github.com/k3s-io/k3s/pkg/cli/cmds" "github.com/k3s-io/k3s/pkg/daemons/config" + "github.com/k3s-io/k3s/pkg/generated/clientset/versioned/scheme" "github.com/k3s-io/k3s/pkg/nodepassword" "github.com/k3s-io/k3s/pkg/util" "github.com/k3s-io/k3s/pkg/version" @@ -26,9 +27,12 @@ import ( certutil "github.com/rancher/dynamiclistener/cert" coreclient "github.com/rancher/wrangler/pkg/generated/controllers/core/v1" "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/apimachinery/pkg/util/json" "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" ) const ( @@ -86,22 +90,20 @@ func apiserver(runtime *config.ControlRuntime) http.Handler { if runtime != nil && runtime.APIServer != nil { runtime.APIServer.ServeHTTP(resp, req) } else { - data := []byte("apiserver not ready") - resp.WriteHeader(http.StatusInternalServerError) - resp.Header().Set("Content-Type", "text/plain") - resp.Header().Set("Content-length", strconv.Itoa(len(data))) - resp.Write(data) + responsewriters.ErrorNegotiated( + apierrors.NewServiceUnavailable("apiserver not ready"), + scheme.Codecs.WithoutConversion(), schema.GroupVersion{}, resp, req, + ) } }) } func apiserverDisabled() http.Handler { return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { - data := []byte("apiserver disabled") - resp.WriteHeader(http.StatusServiceUnavailable) - resp.Header().Set("Content-Type", "text/plain") - resp.Header().Set("Content-length", strconv.Itoa(len(data))) - resp.Write(data) + responsewriters.ErrorNegotiated( + apierrors.NewServiceUnavailable("apiserver disabled"), + scheme.Codecs.WithoutConversion(), schema.GroupVersion{}, resp, req, + ) }) } @@ -111,11 +113,10 @@ func bootstrapHandler(runtime *config.ControlRuntime) http.Handler { } return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { logrus.Warnf("Received HTTP bootstrap request from %s, but embedded etcd is not enabled.", req.RemoteAddr) - data := []byte("etcd disabled") - resp.WriteHeader(http.StatusBadRequest) - resp.Header().Set("Content-Type", "text/plain") - resp.Header().Set("Content-length", strconv.Itoa(len(data))) - resp.Write(data) + responsewriters.ErrorNegotiated( + apierrors.NewBadRequest("etcd disabled"), + scheme.Codecs.WithoutConversion(), schema.GroupVersion{}, resp, req, + ) }) }