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 }