Skip to content

Commit

Permalink
Move error response generation code into util
Browse files Browse the repository at this point in the history
Signed-off-by: Brad Davidson <brad.davidson@rancher.com>
  • Loading branch information
brandond committed Mar 26, 2024
1 parent 8aecc26 commit 7a2a2d0
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 100 deletions.
31 changes: 3 additions & 28 deletions pkg/daemons/control/tunnel.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (

"github.com/k3s-io/k3s/pkg/daemons/config"
"github.com/k3s-io/k3s/pkg/daemons/control/proxy"
"github.com/k3s-io/k3s/pkg/generated/clientset/versioned/scheme"
"github.com/k3s-io/k3s/pkg/nodeconfig"
"github.com/k3s-io/k3s/pkg/util"
"github.com/k3s-io/k3s/pkg/version"
Expand All @@ -22,10 +21,6 @@ import (
"github.com/sirupsen/logrus"
"github.com/yl2chen/cidranger"
v1 "k8s.io/api/core/v1"
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"
"k8s.io/client-go/kubernetes"
)
Expand Down Expand Up @@ -173,29 +168,20 @@ func (t *TunnelServer) onChangePod(podName string, pod *v1.Pod) (*v1.Pod, error)
func (t *TunnelServer) serveConnect(resp http.ResponseWriter, req *http.Request) {
bconn, err := t.dialBackend(req.Context(), req.Host)
if err != nil {
responsewriters.ErrorNegotiated(
newBadGateway(err.Error()),
scheme.Codecs.WithoutConversion(), schema.GroupVersion{}, resp, req,
)
util.SendError(err, resp, req, http.StatusBadGateway)
return
}

hijacker, ok := resp.(http.Hijacker)
if !ok {
responsewriters.ErrorNegotiated(
apierrors.NewInternalError(errors.New("hijacking not supported")),
scheme.Codecs.WithoutConversion(), schema.GroupVersion{}, resp, req,
)
util.SendError(errors.New("hijacking not supported"), resp, req, http.StatusInternalServerError)
return
}
resp.WriteHeader(http.StatusOK)

rconn, bufrw, err := hijacker.Hijack()
if err != nil {
responsewriters.ErrorNegotiated(
apierrors.NewInternalError(err),
scheme.Codecs.WithoutConversion(), schema.GroupVersion{}, resp, req,
)
util.SendError(err, resp, req, http.StatusInternalServerError)
return
}

Expand Down Expand Up @@ -301,14 +287,3 @@ func (crw *connReadWriteCloser) Close() (err error) {
crw.once.Do(func() { err = crw.conn.Close() })
return
}

func newBadGateway(message string) *apierrors.StatusError {
return &apierrors.StatusError{
ErrStatus: metav1.Status{
Status: metav1.StatusFailure,
Code: http.StatusBadGateway,
Reason: metav1.StatusReasonInternalError,
Message: message,
},
}
}
39 changes: 6 additions & 33 deletions pkg/server/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,9 @@ 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/k3s-io/k3s/pkg/util"
"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"
"k8s.io/apiserver/pkg/endpoints/request"
)

Expand All @@ -29,23 +26,23 @@ func doAuth(roles []string, serverConfig *config.Control, next http.Handler, rw
switch {
case serverConfig == nil:
logrus.Errorf("Authenticate not initialized: serverConfig is nil")
unauthorized(rw, req)
util.SendError(errors.New("not authorized"), rw, req, http.StatusUnauthorized)
return
case serverConfig.Runtime.Authenticator == nil:
logrus.Errorf("Authenticate not initialized: serverConfig.Runtime.Authenticator is nil")
unauthorized(rw, req)
util.SendError(errors.New("not authorized"), rw, req, http.StatusUnauthorized)
return
}

resp, ok, err := serverConfig.Runtime.Authenticator.AuthenticateRequest(req)
if err != nil {
logrus.Errorf("Failed to authenticate request from %s: %v", req.RemoteAddr, err)
unauthorized(rw, req)
util.SendError(errors.New("not authorized"), rw, req, http.StatusUnauthorized)
return
}

if !ok || !hasRole(roles, resp.User.GetGroups()) {
forbidden(rw, req)
util.SendError(errors.New("forbidden"), rw, req, http.StatusForbidden)
return
}

Expand All @@ -61,27 +58,3 @@ 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,
)
}
50 changes: 11 additions & 39 deletions pkg/server/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ 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"
Expand All @@ -28,14 +27,11 @@ import (
coreclient "github.com/rancher/wrangler/pkg/generated/controllers/core/v1"
"github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
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/types"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
"k8s.io/apiserver/pkg/endpoints/request"
bootstrapapi "k8s.io/cluster-bootstrap/token/api"
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
Expand Down Expand Up @@ -108,20 +104,14 @@ func apiserver(runtime *config.ControlRuntime) http.Handler {
if runtime != nil && runtime.APIServer != nil {
runtime.APIServer.ServeHTTP(resp, req)
} else {
responsewriters.ErrorNegotiated(
apierrors.NewServiceUnavailable("apiserver not ready"),
scheme.Codecs.WithoutConversion(), schema.GroupVersion{}, resp, req,
)
util.SendError(util.ErrNotReady, resp, req, http.StatusServiceUnavailable)
}
})
}

func apiserverDisabled() http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
responsewriters.ErrorNegotiated(
apierrors.NewServiceUnavailable("apiserver disabled"),
scheme.Codecs.WithoutConversion(), schema.GroupVersion{}, resp, req,
)
util.SendError(errors.New("apiserver disabled"), resp, req, http.StatusServiceUnavailable)
})
}

Expand All @@ -131,10 +121,7 @@ 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)
responsewriters.ErrorNegotiated(
apierrors.NewBadRequest("etcd disabled"),
scheme.Codecs.WithoutConversion(), schema.GroupVersion{}, resp, req,
)
util.SendError(errors.New("etcd disabled"), resp, req, http.StatusBadRequest)
})
}

Expand All @@ -145,7 +132,7 @@ func cacerts(serverCA string) http.Handler {
var err error
ca, err = os.ReadFile(serverCA)
if err != nil {
sendError(err, resp, req)
util.SendError(err, resp, req)
return
}
}
Expand Down Expand Up @@ -220,13 +207,13 @@ func servingKubeletCert(server *config.Control, keyFile string, auth nodePassBoo

nodeName, errCode, err := auth(req)
if err != nil {
sendError(err, resp, req, errCode)
util.SendError(err, resp, req, errCode)
return
}

caCerts, caKey, key, err := getCACertAndKeys(server.Runtime.ServerCA, server.Runtime.ServerCAKey, server.Runtime.ServingKubeletKey)
if err != nil {
sendError(err, resp, req)
util.SendError(err, resp, req)
return
}

Expand All @@ -236,7 +223,7 @@ func servingKubeletCert(server *config.Control, keyFile string, auth nodePassBoo
for _, v := range strings.Split(nodeIP, ",") {
ip := net.ParseIP(v)
if ip == nil {
sendError(fmt.Errorf("invalid node IP address %s", ip), resp, req)
util.SendError(fmt.Errorf("invalid node IP address %s", ip), resp, req)
return
}
ips = append(ips, ip)
Expand All @@ -252,7 +239,7 @@ func servingKubeletCert(server *config.Control, keyFile string, auth nodePassBoo
},
}, key, caCerts[0], caKey)
if err != nil {
sendError(err, resp, req)
util.SendError(err, resp, req)
return
}

Expand All @@ -276,13 +263,13 @@ func clientKubeletCert(server *config.Control, keyFile string, auth nodePassBoot

nodeName, errCode, err := auth(req)
if err != nil {
sendError(err, resp, req, errCode)
util.SendError(err, resp, req, errCode)
return
}

caCerts, caKey, key, err := getCACertAndKeys(server.Runtime.ClientCA, server.Runtime.ClientCAKey, server.Runtime.ClientKubeletKey)
if err != nil {
sendError(err, resp, req)
util.SendError(err, resp, req)
return
}

Expand All @@ -292,7 +279,7 @@ func clientKubeletCert(server *config.Control, keyFile string, auth nodePassBoot
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}, key, caCerts[0], caKey)
if err != nil {
sendError(err, resp, req)
util.SendError(err, resp, req)
return
}

Expand Down Expand Up @@ -402,21 +389,6 @@ func serveStatic(urlPrefix, staticDir string) http.Handler {
return http.StripPrefix(urlPrefix, http.FileServer(http.Dir(staticDir)))
}

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
}
logrus.Errorf("Sending HTTP %d response to %s: %v", code, req.RemoteAddr, err)
responsewriters.ErrorNegotiated(
apierrors.NewGenericServerResponse(code, req.Method, schema.GroupResource{}, req.URL.Path, err.Error(), 0, true),
scheme.Codecs.WithoutConversion(), schema.GroupVersion{}, resp, req,
)
}

// nodePassBootstrapper returns a node name, or http error code and error
type nodePassBootstrapper func(req *http.Request) (string, int, error)

Expand Down
71 changes: 71 additions & 0 deletions pkg/util/apierrors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package util

import (
"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 ErrNotReady = errors.New("apiserver not ready")

// 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" errors, they are frequent during startup
if !errors.Is(err, ErrNotReady) {
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(),
}}
}

0 comments on commit 7a2a2d0

Please sign in to comment.