...

Source file src/sigs.k8s.io/controller-runtime/pkg/webhook/authentication/http.go

Documentation: sigs.k8s.io/controller-runtime/pkg/webhook/authentication

     1  /*
     2  Copyright 2021 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8     http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package authentication
    18  
    19  import (
    20  	"encoding/json"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"net/http"
    25  
    26  	authenticationv1 "k8s.io/api/authentication/v1"
    27  	authenticationv1beta1 "k8s.io/api/authentication/v1beta1"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/runtime/schema"
    30  	"k8s.io/apimachinery/pkg/runtime/serializer"
    31  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    32  )
    33  
    34  var authenticationScheme = runtime.NewScheme()
    35  var authenticationCodecs = serializer.NewCodecFactory(authenticationScheme)
    36  
    37  // The TokenReview resource mostly contains a bearer token which
    38  // at most should have a few KB's of size, so we picked 1 MB to
    39  // have plenty of buffer.
    40  // If your use case requires larger max request sizes, please
    41  // open an issue (https://github.com/kubernetes-sigs/controller-runtime/issues/new).
    42  const maxRequestSize = int64(1 * 1024 * 1024)
    43  
    44  func init() {
    45  	utilruntime.Must(authenticationv1.AddToScheme(authenticationScheme))
    46  	utilruntime.Must(authenticationv1beta1.AddToScheme(authenticationScheme))
    47  }
    48  
    49  var _ http.Handler = &Webhook{}
    50  
    51  func (wh *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    52  	ctx := r.Context()
    53  	if wh.WithContextFunc != nil {
    54  		ctx = wh.WithContextFunc(ctx, r)
    55  	}
    56  
    57  	if r.Body == nil || r.Body == http.NoBody {
    58  		err := errors.New("request body is empty")
    59  		wh.getLogger(nil).Error(err, "bad request")
    60  		wh.writeResponse(w, Errored(err))
    61  		return
    62  	}
    63  
    64  	defer r.Body.Close()
    65  	limitedReader := &io.LimitedReader{R: r.Body, N: maxRequestSize}
    66  	body, err := io.ReadAll(limitedReader)
    67  	if err != nil {
    68  		wh.getLogger(nil).Error(err, "unable to read the body from the incoming request")
    69  		wh.writeResponse(w, Errored(err))
    70  		return
    71  	}
    72  	if limitedReader.N <= 0 {
    73  		err := fmt.Errorf("request entity is too large; limit is %d bytes", maxRequestSize)
    74  		wh.getLogger(nil).Error(err, "unable to read the body from the incoming request; limit reached")
    75  		wh.writeResponse(w, Errored(err))
    76  		return
    77  	}
    78  
    79  	// verify the content type is accurate
    80  	if contentType := r.Header.Get("Content-Type"); contentType != "application/json" {
    81  		err := fmt.Errorf("contentType=%s, expected application/json", contentType)
    82  		wh.getLogger(nil).Error(err, "unable to process a request with unknown content type")
    83  		wh.writeResponse(w, Errored(err))
    84  		return
    85  	}
    86  
    87  	// Both v1 and v1beta1 TokenReview types are exactly the same, so the v1beta1 type can
    88  	// be decoded into the v1 type. The v1beta1 api is deprecated as of 1.19 and will be
    89  	// removed in authenticationv1.22. However the runtime codec's decoder guesses which type to
    90  	// decode into by type name if an Object's TypeMeta isn't set. By setting TypeMeta of an
    91  	// unregistered type to the v1 GVK, the decoder will coerce a v1beta1 TokenReview to authenticationv1.
    92  	// The actual TokenReview GVK will be used to write a typed response in case the
    93  	// webhook config permits multiple versions, otherwise this response will fail.
    94  	req := Request{}
    95  	ar := unversionedTokenReview{}
    96  	// avoid an extra copy
    97  	ar.TokenReview = &req.TokenReview
    98  	ar.SetGroupVersionKind(authenticationv1.SchemeGroupVersion.WithKind("TokenReview"))
    99  	_, actualTokRevGVK, err := authenticationCodecs.UniversalDeserializer().Decode(body, nil, &ar)
   100  	if err != nil {
   101  		wh.getLogger(nil).Error(err, "unable to decode the request")
   102  		wh.writeResponse(w, Errored(err))
   103  		return
   104  	}
   105  	wh.getLogger(&req).V(5).Info("received request")
   106  
   107  	if req.Spec.Token == "" {
   108  		err := errors.New("token is empty")
   109  		wh.getLogger(&req).Error(err, "bad request")
   110  		wh.writeResponse(w, Errored(err))
   111  		return
   112  	}
   113  
   114  	wh.writeResponseTyped(w, wh.Handle(ctx, req), actualTokRevGVK)
   115  }
   116  
   117  // writeResponse writes response to w generically, i.e. without encoding GVK information.
   118  func (wh *Webhook) writeResponse(w io.Writer, response Response) {
   119  	wh.writeTokenResponse(w, response.TokenReview)
   120  }
   121  
   122  // writeResponseTyped writes response to w with GVK set to tokRevGVK, which is necessary
   123  // if multiple TokenReview versions are permitted by the webhook.
   124  func (wh *Webhook) writeResponseTyped(w io.Writer, response Response, tokRevGVK *schema.GroupVersionKind) {
   125  	ar := response.TokenReview
   126  
   127  	// Default to a v1 TokenReview, otherwise the API server may not recognize the request
   128  	// if multiple TokenReview versions are permitted by the webhook config.
   129  	if tokRevGVK == nil || *tokRevGVK == (schema.GroupVersionKind{}) {
   130  		ar.SetGroupVersionKind(authenticationv1.SchemeGroupVersion.WithKind("TokenReview"))
   131  	} else {
   132  		ar.SetGroupVersionKind(*tokRevGVK)
   133  	}
   134  	wh.writeTokenResponse(w, ar)
   135  }
   136  
   137  // writeTokenResponse writes ar to w.
   138  func (wh *Webhook) writeTokenResponse(w io.Writer, ar authenticationv1.TokenReview) {
   139  	if err := json.NewEncoder(w).Encode(ar); err != nil {
   140  		wh.getLogger(nil).Error(err, "unable to encode the response")
   141  		wh.writeResponse(w, Errored(err))
   142  	}
   143  	res := ar
   144  	wh.getLogger(nil).V(5).Info("wrote response", "requestID", res.UID, "authenticated", res.Status.Authenticated)
   145  }
   146  
   147  // unversionedTokenReview is used to decode both v1 and v1beta1 TokenReview types.
   148  type unversionedTokenReview struct {
   149  	*authenticationv1.TokenReview
   150  }
   151  
   152  var _ runtime.Object = &unversionedTokenReview{}
   153  

View as plain text