k8s.io/apiserver@v0.31.1/pkg/endpoints/filters/authentication.go (about)

     1  /*
     2  Copyright 2014 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 filters
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"net/http"
    24  	"time"
    25  
    26  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    27  	"k8s.io/apimachinery/pkg/runtime"
    28  	"k8s.io/apimachinery/pkg/runtime/schema"
    29  	"k8s.io/apiserver/pkg/authentication/authenticator"
    30  	"k8s.io/apiserver/pkg/authentication/authenticatorfactory"
    31  	"k8s.io/apiserver/pkg/authentication/request/headerrequest"
    32  	"k8s.io/apiserver/pkg/authentication/user"
    33  	"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
    34  	genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
    35  	genericfeatures "k8s.io/apiserver/pkg/features"
    36  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    37  	"k8s.io/klog/v2"
    38  )
    39  
    40  type authenticationRecordMetricsFunc func(context.Context, *authenticator.Response, bool, error, authenticator.Audiences, time.Time, time.Time)
    41  
    42  // WithAuthentication creates an http handler that tries to authenticate the given request as a user, and then
    43  // stores any such user found onto the provided context for the request. If authentication fails or returns an error
    44  // the failed handler is used. On success, "Authorization" header is removed from the request and handler
    45  // is invoked to serve the request.
    46  func WithAuthentication(handler http.Handler, auth authenticator.Request, failed http.Handler, apiAuds authenticator.Audiences, requestHeaderConfig *authenticatorfactory.RequestHeaderConfig) http.Handler {
    47  	return withAuthentication(handler, auth, failed, apiAuds, requestHeaderConfig, recordAuthenticationMetrics)
    48  }
    49  
    50  func withAuthentication(handler http.Handler, auth authenticator.Request, failed http.Handler, apiAuds authenticator.Audiences, requestHeaderConfig *authenticatorfactory.RequestHeaderConfig, metrics authenticationRecordMetricsFunc) http.Handler {
    51  	if auth == nil {
    52  		klog.Warning("Authentication is disabled")
    53  		return handler
    54  	}
    55  	standardRequestHeaderConfig := &authenticatorfactory.RequestHeaderConfig{
    56  		UsernameHeaders:     headerrequest.StaticStringSlice{"X-Remote-User"},
    57  		GroupHeaders:        headerrequest.StaticStringSlice{"X-Remote-Group"},
    58  		ExtraHeaderPrefixes: headerrequest.StaticStringSlice{"X-Remote-Extra-"},
    59  	}
    60  	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
    61  		authenticationStart := time.Now()
    62  
    63  		if len(apiAuds) > 0 {
    64  			req = req.WithContext(authenticator.WithAudiences(req.Context(), apiAuds))
    65  		}
    66  		resp, ok, err := auth.AuthenticateRequest(req)
    67  		authenticationFinish := time.Now()
    68  		defer func() {
    69  			metrics(req.Context(), resp, ok, err, apiAuds, authenticationStart, authenticationFinish)
    70  		}()
    71  		if err != nil || !ok {
    72  			if err != nil {
    73  				klog.ErrorS(err, "Unable to authenticate the request")
    74  			}
    75  			failed.ServeHTTP(w, req)
    76  			return
    77  		}
    78  
    79  		if !audiencesAreAcceptable(apiAuds, resp.Audiences) {
    80  			err = fmt.Errorf("unable to match the audience: %v , accepted: %v", resp.Audiences, apiAuds)
    81  			klog.Error(err)
    82  			failed.ServeHTTP(w, req)
    83  			return
    84  		}
    85  
    86  		// authorization header is not required anymore in case of a successful authentication.
    87  		req.Header.Del("Authorization")
    88  
    89  		// delete standard front proxy headers
    90  		headerrequest.ClearAuthenticationHeaders(
    91  			req.Header,
    92  			standardRequestHeaderConfig.UsernameHeaders,
    93  			standardRequestHeaderConfig.GroupHeaders,
    94  			standardRequestHeaderConfig.ExtraHeaderPrefixes,
    95  		)
    96  
    97  		// also delete any custom front proxy headers
    98  		if requestHeaderConfig != nil {
    99  			headerrequest.ClearAuthenticationHeaders(
   100  				req.Header,
   101  				requestHeaderConfig.UsernameHeaders,
   102  				requestHeaderConfig.GroupHeaders,
   103  				requestHeaderConfig.ExtraHeaderPrefixes,
   104  			)
   105  		}
   106  
   107  		// http2 is an expensive protocol that is prone to abuse,
   108  		// see CVE-2023-44487 and CVE-2023-39325 for an example.
   109  		// Do not allow unauthenticated clients to keep these
   110  		// connections open (i.e. basically degrade them to the
   111  		// performance of http1 with keep-alive disabled).
   112  		if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.UnauthenticatedHTTP2DOSMitigation) && req.ProtoMajor == 2 && isAnonymousUser(resp.User) {
   113  			// limit this connection to just this request,
   114  			// and then send a GOAWAY and tear down the TCP connection
   115  			// https://github.com/golang/net/commit/97aa3a539ec716117a9d15a4659a911f50d13c3c
   116  			w.Header().Set("Connection", "close")
   117  		}
   118  
   119  		req = req.WithContext(genericapirequest.WithUser(req.Context(), resp.User))
   120  		handler.ServeHTTP(w, req)
   121  	})
   122  }
   123  
   124  func Unauthorized(s runtime.NegotiatedSerializer) http.Handler {
   125  	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   126  		// http2 is an expensive protocol that is prone to abuse,
   127  		// see CVE-2023-44487 and CVE-2023-39325 for an example.
   128  		// Do not allow unauthenticated clients to keep these
   129  		// connections open (i.e. basically degrade them to the
   130  		// performance of http1 with keep-alive disabled).
   131  		if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.UnauthenticatedHTTP2DOSMitigation) && req.ProtoMajor == 2 {
   132  			// limit this connection to just this request,
   133  			// and then send a GOAWAY and tear down the TCP connection
   134  			// https://github.com/golang/net/commit/97aa3a539ec716117a9d15a4659a911f50d13c3c
   135  			w.Header().Set("Connection", "close")
   136  		}
   137  		ctx := req.Context()
   138  		requestInfo, found := genericapirequest.RequestInfoFrom(ctx)
   139  		if !found {
   140  			responsewriters.InternalError(w, req, errors.New("no RequestInfo found in the context"))
   141  			return
   142  		}
   143  
   144  		gv := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
   145  		responsewriters.ErrorNegotiated(apierrors.NewUnauthorized("Unauthorized"), s, gv, w, req)
   146  	})
   147  }
   148  
   149  func audiencesAreAcceptable(apiAuds, responseAudiences authenticator.Audiences) bool {
   150  	if len(apiAuds) == 0 || len(responseAudiences) == 0 {
   151  		return true
   152  	}
   153  
   154  	return len(apiAuds.Intersect(responseAudiences)) > 0
   155  }
   156  
   157  func isAnonymousUser(u user.Info) bool {
   158  	if u.GetName() == user.Anonymous {
   159  		return true
   160  	}
   161  	for _, group := range u.GetGroups() {
   162  		if group == user.AllUnauthenticated {
   163  			return true
   164  		}
   165  	}
   166  	return false
   167  }