func WithActingAs(handler http.Handler, requestContextMapper api.RequestContextMapper, a authorizer.Authorizer) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { requestedSubject := req.Header.Get("Impersonate-User") if len(requestedSubject) == 0 { handler.ServeHTTP(w, req) return } ctx, exists := requestContextMapper.Get(req) if !exists { forbidden(w, req) return } requestor, exists := api.UserFrom(ctx) if !exists { forbidden(w, req) return } actingAsAttributes := &authorizer.AttributesRecord{ User: requestor, Verb: "impersonate", APIGroup: api.GroupName, Resource: "users", // ResourceName: requestedSubject, ResourceRequest: true, } err := a.Authorize(actingAsAttributes) if err != nil { forbidden(w, req) return } switch { case strings.HasPrefix(requestedSubject, serviceaccount.ServiceAccountUsernamePrefix): namespace, name, err := serviceaccount.SplitUsername(requestedSubject) if err != nil { forbidden(w, req) return } requestContextMapper.Update(req, api.WithUser(ctx, serviceaccount.UserInfo(namespace, name, ""))) default: newUser := &user.DefaultInfo{ Name: requestedSubject, } requestContextMapper.Update(req, api.WithUser(ctx, newUser)) } newCtx, _ := requestContextMapper.Get(req) oldUser, _ := api.UserFrom(ctx) newUser, _ := api.UserFrom(newCtx) httplog.LogOf(req, w).Addf("%v is acting as %v", oldUser, newUser) handler.ServeHTTP(w, req) }) }
// ServeHTTP serves a series of JSON encoded events via straight HTTP with // Transfer-Encoding: chunked. func (self *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { loggedW := httplog.LogOf(req, w) w = httplog.Unlogged(w) timeoutCh, cleanup := self.t.TimeoutCh() defer cleanup() defer self.watching.Stop() cn, ok := w.(http.CloseNotifier) if !ok { loggedW.Addf("unable to get CloseNotifier: %#v", w) http.NotFound(w, req) return } flusher, ok := w.(http.Flusher) if !ok { loggedW.Addf("unable to get Flusher: %#v", w) http.NotFound(w, req) return } w.Header().Set("Transfer-Encoding", "chunked") w.WriteHeader(http.StatusOK) flusher.Flush() // TODO: use arbitrary serialization on watch encoder := watchjson.NewEncoder(w, self.encoder) for { select { case <-cn.CloseNotify(): return case <-timeoutCh: return case event, ok := <-self.watching.ResultChan(): if !ok { // End of results. return } self.fixup(event.Object) if err := encoder.Encode(&event); err != nil { // Client disconnect. return } flusher.Flush() } } }
func (c *MasterConfig) impersonationFilter(handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { requestedUser := req.Header.Get(authenticationapi.ImpersonateUserHeader) if len(requestedUser) == 0 { handler.ServeHTTP(w, req) return } subjects := authorizationapi.BuildSubjects([]string{requestedUser}, req.Header[authenticationapi.ImpersonateGroupHeader], // validates whether the usernames are regular users or system users uservalidation.ValidateUserName, // validates group names are regular groups or system groups uservalidation.ValidateGroupName) ctx, exists := c.RequestContextMapper.Get(req) if !exists { forbidden("context not found", nil, w, req) return } // if groups are not specified, then we need to look them up differently depending on the type of user // if they are specified, then they are the authority groupsSpecified := len(req.Header[authenticationapi.ImpersonateGroupHeader]) > 0 // make sure we're allowed to impersonate each subject. While we're iterating through, start building username // and group information username := "" groups := []string{} for _, subject := range subjects { actingAsAttributes := &authorizer.DefaultAuthorizationAttributes{ Verb: "impersonate", } switch subject.GetObjectKind().GroupVersionKind().GroupKind() { case userapi.Kind(authorizationapi.GroupKind): actingAsAttributes.APIGroup = userapi.GroupName actingAsAttributes.Resource = authorizationapi.GroupResource actingAsAttributes.ResourceName = subject.Name groups = append(groups, subject.Name) case userapi.Kind(authorizationapi.SystemGroupKind): actingAsAttributes.APIGroup = userapi.GroupName actingAsAttributes.Resource = authorizationapi.SystemGroupResource actingAsAttributes.ResourceName = subject.Name groups = append(groups, subject.Name) case userapi.Kind(authorizationapi.UserKind): actingAsAttributes.APIGroup = userapi.GroupName actingAsAttributes.Resource = authorizationapi.UserResource actingAsAttributes.ResourceName = subject.Name username = subject.Name if !groupsSpecified { if actualGroups, err := c.GroupCache.GroupsFor(subject.Name); err == nil { for _, group := range actualGroups { groups = append(groups, group.Name) } } groups = append(groups, bootstrappolicy.AuthenticatedGroup, bootstrappolicy.AuthenticatedOAuthGroup) } case userapi.Kind(authorizationapi.SystemUserKind): actingAsAttributes.APIGroup = userapi.GroupName actingAsAttributes.Resource = authorizationapi.SystemUserResource actingAsAttributes.ResourceName = subject.Name username = subject.Name if !groupsSpecified { if subject.Name == bootstrappolicy.UnauthenticatedUsername { groups = append(groups, bootstrappolicy.UnauthenticatedGroup) } else { groups = append(groups, bootstrappolicy.AuthenticatedGroup) } } case kapi.Kind(authorizationapi.ServiceAccountKind): actingAsAttributes.APIGroup = kapi.GroupName actingAsAttributes.Resource = authorizationapi.ServiceAccountResource actingAsAttributes.ResourceName = subject.Name username = serviceaccount.MakeUsername(subject.Namespace, subject.Name) if !groupsSpecified { groups = append(serviceaccount.MakeGroupNames(subject.Namespace, subject.Name), bootstrappolicy.AuthenticatedGroup) } default: forbidden(fmt.Sprintf("unknown subject type: %v", subject), actingAsAttributes, w, req) return } authCheckCtx := kapi.WithNamespace(ctx, subject.Namespace) allowed, reason, err := c.Authorizer.Authorize(authCheckCtx, actingAsAttributes) if err != nil { forbidden(err.Error(), actingAsAttributes, w, req) return } if !allowed { forbidden(reason, actingAsAttributes, w, req) return } } var extra map[string][]string if requestScopes, ok := req.Header[authenticationapi.ImpersonateUserScopeHeader]; ok { extra = map[string][]string{authorizationapi.ScopesKey: requestScopes} } newUser := &user.DefaultInfo{ Name: username, Groups: groups, Extra: extra, } c.RequestContextMapper.Update(req, kapi.WithUser(ctx, newUser)) oldUser, _ := kapi.UserFrom(ctx) httplog.LogOf(req, w).Addf("%v is acting as %v", oldUser, newUser) handler.ServeHTTP(w, req) }) }
func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { proxyHandlerTraceID := rand.Int63() var verb string var apiResource string var httpCode int reqStart := time.Now() defer metrics.Monitor(&verb, &apiResource, net.GetHTTPClient(req), w.Header().Get("Content-Type"), httpCode, reqStart) ctx, ok := r.mapper.Get(req) if !ok { internalError(w, req, errors.New("Error getting request context")) httpCode = http.StatusInternalServerError return } requestInfo, ok := request.RequestInfoFrom(ctx) if !ok { internalError(w, req, errors.New("Error getting RequestInfo from context")) httpCode = http.StatusInternalServerError return } if !requestInfo.IsResourceRequest { notFound(w, req) httpCode = http.StatusNotFound return } verb = requestInfo.Verb namespace, resource, parts := requestInfo.Namespace, requestInfo.Resource, requestInfo.Parts ctx = api.WithNamespace(ctx, namespace) if len(parts) < 2 { notFound(w, req) httpCode = http.StatusNotFound return } id := parts[1] remainder := "" if len(parts) > 2 { proxyParts := parts[2:] remainder = strings.Join(proxyParts, "/") if strings.HasSuffix(req.URL.Path, "/") { // The original path had a trailing slash, which has been stripped // by KindAndNamespace(). We should add it back because some // servers (like etcd) require it. remainder = remainder + "/" } } storage, ok := r.storage[resource] if !ok { httplog.LogOf(req, w).Addf("'%v' has no storage object", resource) notFound(w, req) httpCode = http.StatusNotFound return } apiResource = resource gv := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion} redirector, ok := storage.(rest.Redirector) if !ok { httplog.LogOf(req, w).Addf("'%v' is not a redirector", resource) httpCode = errorNegotiated(apierrors.NewMethodNotSupported(api.Resource(resource), "proxy"), r.serializer, gv, w, req) return } location, roundTripper, err := redirector.ResourceLocation(ctx, id) if err != nil { httplog.LogOf(req, w).Addf("Error getting ResourceLocation: %v", err) httpCode = errorNegotiated(err, r.serializer, gv, w, req) return } if location == nil { httplog.LogOf(req, w).Addf("ResourceLocation for %v returned nil", id) notFound(w, req) httpCode = http.StatusNotFound return } if roundTripper != nil { glog.V(5).Infof("[%x: %v] using transport %T...", proxyHandlerTraceID, req.URL, roundTripper) } // Default to http if location.Scheme == "" { location.Scheme = "http" } // Add the subpath if len(remainder) > 0 { location.Path = singleJoiningSlash(location.Path, remainder) } // Start with anything returned from the storage, and add the original request's parameters values := location.Query() for k, vs := range req.URL.Query() { for _, v := range vs { values.Add(k, v) } } location.RawQuery = values.Encode() newReq, err := http.NewRequest(req.Method, location.String(), req.Body) if err != nil { httpCode = errorNegotiated(err, r.serializer, gv, w, req) return } httpCode = http.StatusOK newReq.Header = req.Header newReq.ContentLength = req.ContentLength // Copy the TransferEncoding is for future-proofing. Currently Go only supports "chunked" and // it can determine the TransferEncoding based on ContentLength and the Body. newReq.TransferEncoding = req.TransferEncoding // TODO convert this entire proxy to an UpgradeAwareProxy similar to // https://github.com/openshift/origin/blob/master/pkg/util/httpproxy/upgradeawareproxy.go. // That proxy needs to be modified to support multiple backends, not just 1. if r.tryUpgrade(w, req, newReq, location, roundTripper, gv) { return } // Redirect requests of the form "/{resource}/{name}" to "/{resource}/{name}/" // This is essentially a hack for http://issue.k8s.io/4958. // Note: Keep this code after tryUpgrade to not break that flow. if len(parts) == 2 && !strings.HasSuffix(req.URL.Path, "/") { var queryPart string if len(req.URL.RawQuery) > 0 { queryPart = "?" + req.URL.RawQuery } w.Header().Set("Location", req.URL.Path+"/"+queryPart) w.WriteHeader(http.StatusMovedPermanently) return } start := time.Now() glog.V(4).Infof("[%x] Beginning proxy %s...", proxyHandlerTraceID, req.URL) defer func() { glog.V(4).Infof("[%x] Proxy %v finished %v.", proxyHandlerTraceID, req.URL, time.Now().Sub(start)) }() proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: location.Scheme, Host: location.Host}) alreadyRewriting := false if roundTripper != nil { _, alreadyRewriting = roundTripper.(*proxyutil.Transport) glog.V(5).Infof("[%x] Not making a rewriting transport for proxy %s...", proxyHandlerTraceID, req.URL) } if !alreadyRewriting { glog.V(5).Infof("[%x] making a transport for proxy %s...", proxyHandlerTraceID, req.URL) prepend := path.Join(r.prefix, resource, id) if len(namespace) > 0 { prepend = path.Join(r.prefix, "namespaces", namespace, resource, id) } pTransport := &proxyutil.Transport{ Scheme: req.URL.Scheme, Host: req.URL.Host, PathPrepend: prepend, RoundTripper: roundTripper, } roundTripper = pTransport } proxy.Transport = roundTripper proxy.FlushInterval = 200 * time.Millisecond proxy.ServeHTTP(w, newReq) }
func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { proxyHandlerTraceID := rand.Int63() var verb string var apiResource string var httpCode int reqStart := time.Now() defer metrics.Monitor(&verb, &apiResource, util.GetClient(req), &httpCode, reqStart) requestInfo, err := r.apiRequestInfoResolver.GetAPIRequestInfo(req) if err != nil { notFound(w, req) httpCode = http.StatusNotFound return } verb = requestInfo.Verb namespace, resource, parts := requestInfo.Namespace, requestInfo.Resource, requestInfo.Parts ctx, ok := r.context.Get(req) if !ok { ctx = api.NewContext() } ctx = api.WithNamespace(ctx, namespace) if len(parts) < 2 { notFound(w, req) httpCode = http.StatusNotFound return } id := parts[1] remainder := "" if len(parts) > 2 { proxyParts := parts[2:] remainder = strings.Join(proxyParts, "/") if strings.HasSuffix(req.URL.Path, "/") { // The original path had a trailing slash, which has been stripped // by KindAndNamespace(). We should add it back because some // servers (like etcd) require it. remainder = remainder + "/" } } storage, ok := r.storage[resource] if !ok { httplog.LogOf(req, w).Addf("'%v' has no storage object", resource) notFound(w, req) httpCode = http.StatusNotFound return } apiResource = resource redirector, ok := storage.(rest.Redirector) if !ok { httplog.LogOf(req, w).Addf("'%v' is not a redirector", resource) httpCode = errorJSON(errors.NewMethodNotSupported(resource, "proxy"), r.codec, w) return } location, roundTripper, err := redirector.ResourceLocation(ctx, id) if err != nil { httplog.LogOf(req, w).Addf("Error getting ResourceLocation: %v", err) status := errToAPIStatus(err) writeJSON(status.Code, r.codec, status, w, true) httpCode = status.Code return } if location == nil { httplog.LogOf(req, w).Addf("ResourceLocation for %v returned nil", id) notFound(w, req) httpCode = http.StatusNotFound return } // If we have a custom dialer, and no pre-existing transport, initialize it to use the dialer. if roundTripper == nil && r.dial != nil { glog.V(5).Infof("[%x: %v] making a dial-only transport...", proxyHandlerTraceID, req.URL) roundTripper = &http.Transport{Dial: r.dial} } else if roundTripper != nil { glog.V(5).Infof("[%x: %v] using transport %T...", proxyHandlerTraceID, req.URL, roundTripper) } // Default to http if location.Scheme == "" { location.Scheme = "http" } // Add the subpath if len(remainder) > 0 { location.Path = singleJoiningSlash(location.Path, remainder) } // Start with anything returned from the storage, and add the original request's parameters values := location.Query() for k, vs := range req.URL.Query() { for _, v := range vs { values.Add(k, v) } } location.RawQuery = values.Encode() newReq, err := http.NewRequest(req.Method, location.String(), req.Body) if err != nil { status := errToAPIStatus(err) writeJSON(status.Code, r.codec, status, w, true) notFound(w, req) httpCode = status.Code return } httpCode = http.StatusOK newReq.Header = req.Header // TODO convert this entire proxy to an UpgradeAwareProxy similar to // https://github.com/openshift/origin/blob/master/pkg/util/httpproxy/upgradeawareproxy.go. // That proxy needs to be modified to support multiple backends, not just 1. if r.tryUpgrade(w, req, newReq, location, roundTripper) { return } // Redirect requests of the form "/{resource}/{name}" to "/{resource}/{name}/" // This is essentially a hack for https://github.com/GoogleCloudPlatform/kubernetes/issues/4958. // Note: Keep this code after tryUpgrade to not break that flow. if len(parts) == 2 && !strings.HasSuffix(req.URL.Path, "/") { var queryPart string if len(req.URL.RawQuery) > 0 { queryPart = "?" + req.URL.RawQuery } w.Header().Set("Location", req.URL.Path+"/"+queryPart) w.WriteHeader(http.StatusMovedPermanently) return } start := time.Now() glog.V(4).Infof("[%x] Beginning proxy %s...", proxyHandlerTraceID, req.URL) defer func() { glog.V(4).Infof("[%x] Proxy %v finished %v.", proxyHandlerTraceID, req.URL, time.Now().Sub(start)) }() proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: location.Scheme, Host: location.Host}) alreadyRewriting := false if roundTripper != nil { _, alreadyRewriting = roundTripper.(*proxyutil.Transport) glog.V(5).Infof("[%x] Not making a reriting transport for proxy %s...", proxyHandlerTraceID, req.URL) } if !alreadyRewriting { glog.V(5).Infof("[%x] making a transport for proxy %s...", proxyHandlerTraceID, req.URL) prepend := path.Join(r.prefix, resource, id) if len(namespace) > 0 { prepend = path.Join(r.prefix, "namespaces", namespace, resource, id) } pTransport := &proxyutil.Transport{ Scheme: req.URL.Scheme, Host: req.URL.Host, PathPrepend: prepend, RoundTripper: roundTripper, } roundTripper = pTransport } proxy.Transport = roundTripper proxy.FlushInterval = 200 * time.Millisecond proxy.ServeHTTP(w, newReq) }
// WithImpersonation is a filter that will inspect and check requests that attempt to change the user.Info for their requests func WithImpersonation(handler http.Handler, requestContextMapper api.RequestContextMapper, a authorizer.Authorizer) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { impersonationRequests, err := buildImpersonationRequests(req.Header) if err != nil { glog.V(4).Infof("%v", err) internalError(w, req, err) return } if len(impersonationRequests) == 0 { handler.ServeHTTP(w, req) return } ctx, exists := requestContextMapper.Get(req) if !exists { internalError(w, req, errors.New("no context found for request")) return } requestor, exists := api.UserFrom(ctx) if !exists { internalError(w, req, errors.New("no user found for request")) return } // if groups are not specified, then we need to look them up differently depending on the type of user // if they are specified, then they are the authority groupsSpecified := len(req.Header[authenticationapi.ImpersonateGroupHeader]) > 0 // make sure we're allowed to impersonate each thing we're requesting. While we're iterating through, start building username // and group information username := "" groups := []string{} userExtra := map[string][]string{} for _, impersonationRequest := range impersonationRequests { actingAsAttributes := &authorizer.AttributesRecord{ User: requestor, Verb: "impersonate", APIGroup: impersonationRequest.GetObjectKind().GroupVersionKind().Group, Namespace: impersonationRequest.Namespace, Name: impersonationRequest.Name, ResourceRequest: true, } switch impersonationRequest.GetObjectKind().GroupVersionKind().GroupKind() { case api.Kind("ServiceAccount"): actingAsAttributes.Resource = "serviceaccounts" username = serviceaccount.MakeUsername(impersonationRequest.Namespace, impersonationRequest.Name) if !groupsSpecified { // if groups aren't specified for a service account, we know the groups because its a fixed mapping. Add them groups = serviceaccount.MakeGroupNames(impersonationRequest.Namespace, impersonationRequest.Name) } case api.Kind("User"): actingAsAttributes.Resource = "users" username = impersonationRequest.Name case api.Kind("Group"): actingAsAttributes.Resource = "groups" groups = append(groups, impersonationRequest.Name) case authenticationapi.Kind("UserExtra"): extraKey := impersonationRequest.FieldPath extraValue := impersonationRequest.Name actingAsAttributes.Resource = "userextras" actingAsAttributes.Subresource = extraKey userExtra[extraKey] = append(userExtra[extraKey], extraValue) default: glog.V(4).Infof("unknown impersonation request type: %v", impersonationRequest) forbidden(actingAsAttributes, w, req, fmt.Sprintf("unknown impersonation request type: %v", impersonationRequest)) return } allowed, reason, err := a.Authorize(actingAsAttributes) if err != nil || !allowed { glog.V(4).Infof("Forbidden: %#v, Reason: %s, Error: %v", req.RequestURI, reason, err) forbidden(actingAsAttributes, w, req, reason) return } } newUser := &user.DefaultInfo{ Name: username, Groups: groups, Extra: userExtra, } requestContextMapper.Update(req, api.WithUser(ctx, newUser)) oldUser, _ := api.UserFrom(ctx) httplog.LogOf(req, w).Addf("%v is acting as %v", oldUser, newUser) // clear all the impersonation headers from the request req.Header.Del(authenticationapi.ImpersonateUserHeader) req.Header.Del(authenticationapi.ImpersonateGroupHeader) for headerName := range req.Header { if strings.HasPrefix(headerName, authenticationapi.ImpersonateUserExtraHeaderPrefix) { req.Header.Del(headerName) } } handler.ServeHTTP(w, req) }) }
func (c *MasterConfig) impersonationFilter(handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { requestedSubject := req.Header.Get(authenticationapi.ImpersonateUserHeader) if len(requestedSubject) == 0 { handler.ServeHTTP(w, req) return } resource, namespace, name, err := parseRequestedSubject(requestedSubject) if err != nil { forbidden(err.Error(), nil, w, req) return } ctx, exists := c.RequestContextMapper.Get(req) if !exists { forbidden("context not found", nil, w, req) return } actingAsAttributes := &authorizer.DefaultAuthorizationAttributes{ Verb: "impersonate", APIGroup: resource.Group, Resource: resource.Resource, ResourceName: name, } authCheckCtx := kapi.WithNamespace(ctx, namespace) allowed, reason, err := c.Authorizer.Authorize(authCheckCtx, actingAsAttributes) if err != nil { forbidden(err.Error(), actingAsAttributes, w, req) return } if !allowed { forbidden(reason, actingAsAttributes, w, req) return } var extra map[string][]string if requestScopes, ok := req.Header[authenticationapi.ImpersonateUserScopeHeader]; ok { extra = map[string][]string{authorizationapi.ScopesKey: requestScopes} } switch resource { case kapi.Resource(authorizationapi.ServiceAccountResource): newUser := &user.DefaultInfo{ Name: serviceaccount.MakeUsername(namespace, name), Groups: serviceaccount.MakeGroupNames(namespace, name), Extra: extra, } newUser.Groups = append(newUser.Groups, bootstrappolicy.AuthenticatedGroup) c.RequestContextMapper.Update(req, kapi.WithUser(ctx, newUser)) case userapi.Resource(authorizationapi.UserResource): newUser := &user.DefaultInfo{ Name: name, Extra: extra, } groups, err := c.GroupCache.GroupsFor(name) if err == nil { for _, group := range groups { newUser.Groups = append(newUser.Groups, group.Name) } } newUser.Groups = append(newUser.Groups, bootstrappolicy.AuthenticatedGroup, bootstrappolicy.AuthenticatedOAuthGroup) c.RequestContextMapper.Update(req, kapi.WithUser(ctx, newUser)) case userapi.Resource(authorizationapi.SystemUserResource): newUser := &user.DefaultInfo{ Name: name, Extra: extra, } if name == bootstrappolicy.UnauthenticatedUsername { newUser.Groups = append(newUser.Groups, bootstrappolicy.UnauthenticatedGroup) } else { newUser.Groups = append(newUser.Groups, bootstrappolicy.AuthenticatedGroup) } c.RequestContextMapper.Update(req, kapi.WithUser(ctx, newUser)) default: forbidden(fmt.Sprintf("%v is an unhandled resource for acting-as", resource), nil, w, req) return } newCtx, _ := c.RequestContextMapper.Get(req) oldUser, _ := kapi.UserFrom(ctx) newUser, _ := kapi.UserFrom(newCtx) httplog.LogOf(req, w).Addf("%v is acting as %v", oldUser, newUser) handler.ServeHTTP(w, req) }) }