// transformDecodeError adds additional information when a decode fails. func transformDecodeError(typer runtime.ObjectTyper, baseErr error, into runtime.Object, body []byte) error { _, kind, err := typer.ObjectVersionAndKind(into) if err != nil { return err } if version, dataKind, err := typer.DataVersionAndKind(body); err == nil && len(dataKind) > 0 { return errors.NewBadRequest(fmt.Sprintf("%s in version %s cannot be handled as a %s: %v", dataKind, version, kind, baseErr)) } return errors.NewBadRequest(fmt.Sprintf("the object provided is unrecognized (must be of type %s): %v", kind, baseErr)) }
// NamespaceKeyFunc is the default function for constructing etcd paths to a resource relative to prefix enforcing namespace rules. // If no namespace is on context, it errors. func NamespaceKeyFunc(ctx api.Context, prefix string, name string) (string, error) { key := NamespaceKeyRootFunc(ctx, prefix) ns, ok := api.NamespaceFrom(ctx) if !ok || len(ns) == 0 { return "", qingerr.NewBadRequest("Namespace parameter required.") } if len(name) == 0 { return "", qingerr.NewBadRequest("Name parameter required.") } key = key + "/" + name return key, nil }
// ResourceLocation returns a URL to which one can send traffic for the specified pod. func ResourceLocation(getter ResourceGetter, ctx api.Context, id string) (*url.URL, http.RoundTripper, error) { // Allow ID as "podname" or "podname:port". If port is not specified, // try to use the first defined port on the pod. name, port, valid := util.SplitPort(id) if !valid { return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid pod request %q", id)) } // TODO: if port is not a number but a "(container)/(portname)", do a name lookup. pod, err := getPod(getter, ctx, name) if err != nil { return nil, nil, err } // Try to figure out a port. if port == "" { for i := range pod.Spec.Containers { if len(pod.Spec.Containers[i].Ports) > 0 { port = fmt.Sprintf("%d", pod.Spec.Containers[i].Ports[0].ContainerPort) break } } } // We leave off the scheme ('http://') because we have no idea what sort of server // is listening at this endpoint. loc := &url.URL{} if port == "" { loc.Host = pod.Status.PodIP } else { loc.Host = net.JoinHostPort(pod.Status.PodIP, port) } return loc, nil, nil }
// ResourceLocation returns a URL to which one can send traffic for the specified service. func (rs *REST) ResourceLocation(ctx api.Context, id string) (*url.URL, http.RoundTripper, error) { // Allow ID as "svcname" or "svcname:port". svcName, portStr, valid := util.SplitPort(id) if !valid { return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid service request %q", id)) } eps, err := rs.endpoints.GetEndpoints(ctx, svcName) if err != nil { return nil, nil, err } if len(eps.Subsets) == 0 { return nil, nil, fmt.Errorf("no endpoints available for %q", svcName) } // Pick a random Subset to start searching from. ssSeed := rand.Intn(len(eps.Subsets)) // Find a Subset that has the port. for ssi := 0; ssi < len(eps.Subsets); ssi++ { ss := &eps.Subsets[(ssSeed+ssi)%len(eps.Subsets)] for i := range ss.Ports { if ss.Ports[i].Name == portStr { // Pick a random address. ip := ss.Addresses[rand.Intn(len(ss.Addresses))].IP port := ss.Ports[i].Port // We leave off the scheme ('http://') because we have no idea what sort of server // is listening at this endpoint. return &url.URL{ Host: net.JoinHostPort(ip, strconv.Itoa(port)), }, nil, nil } } } return nil, nil, fmt.Errorf("no endpoints available for %q", id) }
// ResourceLocation returns an URL and transport which one can use to send traffic for the specified node. func ResourceLocation(getter ResourceGetter, connection client.ConnectionInfoGetter, ctx api.Context, id string) (*url.URL, http.RoundTripper, error) { name, portReq, valid := util.SplitPort(id) if !valid { return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid node request %q", id)) } nodeObj, err := getter.Get(ctx, name) if err != nil { return nil, nil, err } node := nodeObj.(*api.Node) hostIP, err := nodeutil.GetNodeHostIP(node) if err != nil { return nil, nil, err } host := hostIP.String() if portReq == "" || strconv.Itoa(ports.QingletPort) == portReq { scheme, port, transport, err := connection.GetConnectionInfo(host) if err != nil { return nil, nil, err } return &url.URL{ Scheme: scheme, Host: net.JoinHostPort( host, strconv.FormatUint(uint64(port), 10), ), }, transport, nil } return &url.URL{Host: net.JoinHostPort(host, portReq)}, nil, nil }
// checkName checks the provided name against the request func checkName(obj runtime.Object, name, namespace string, namer ScopeNamer) error { if objNamespace, objName, err := namer.ObjectName(obj); err == nil { if err != nil { return err } if objName != name { return errors.NewBadRequest(fmt.Sprintf( "the name of the object (%s) does not match the name on the URL (%s)", objName, name)) } if len(namespace) > 0 { if len(objNamespace) > 0 && objNamespace != namespace { return errors.NewBadRequest(fmt.Sprintf( "the namespace of the object (%s) does not match the namespace on the request (%s)", objNamespace, namespace)) } } } return nil }
// ServeHTTP handles the proxy request func (h *UpgradeAwareProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { h.err = nil if len(h.Location.Scheme) == 0 { h.Location.Scheme = "http" } if h.tryUpgrade(w, req) { return } if h.UpgradeRequired { h.err = errors.NewBadRequest("Upgrade request required") return } loc := *h.Location loc.RawQuery = req.URL.RawQuery // If original request URL ended in '/', append a '/' at the end of the // of the proxy URL if !strings.HasSuffix(loc.Path, "/") && strings.HasSuffix(req.URL.Path, "/") { loc.Path += "/" } // From pkg/apiserver/proxy.go#ServeHTTP: // Redirect requests with an empty path to a location that ends with a '/' // This is essentially a hack for https://github.com/qingyuancloud/QingYuan/issues/4958. // Note: Keep this code after tryUpgrade to not break that flow. if len(loc.Path) == 0 { 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 } if h.Transport == nil { h.Transport = h.defaultProxyTransport(req.URL) } newReq, err := http.NewRequest(req.Method, loc.String(), req.Body) if err != nil { h.err = err return } newReq.Header = req.Header proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: h.Location.Scheme, Host: h.Location.Host}) proxy.Transport = h.Transport proxy.FlushInterval = h.FlushInterval proxy.ServeHTTP(w, newReq) }
// ExecLocation returns the exec URL for a pod container. If opts.Container is blank // and only one container is present in the pod, that container is used. func ExecLocation(getter ResourceGetter, connInfo client.ConnectionInfoGetter, ctx api.Context, name string, opts *api.PodExecOptions) (*url.URL, http.RoundTripper, error) { pod, err := getPod(getter, ctx, name) if err != nil { return nil, nil, err } // Try to figure out a container container := opts.Container if container == "" { if len(pod.Spec.Containers) == 1 { container = pod.Spec.Containers[0].Name } else { return nil, nil, errors.NewBadRequest(fmt.Sprintf("a container name must be specified for pod %s", name)) } } nodeHost := pod.Spec.NodeName if len(nodeHost) == 0 { // If pod has not been assigned a host, return an empty location return nil, nil, fmt.Errorf("pod %s does not have a host assigned", name) } nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(nodeHost) if err != nil { return nil, nil, err } params := url.Values{} if opts.Stdin { params.Add(api.ExecStdinParam, "1") } if opts.Stdout { params.Add(api.ExecStdoutParam, "1") } if opts.Stderr { params.Add(api.ExecStderrParam, "1") } if opts.TTY { params.Add(api.ExecTTYParam, "1") } for _, c := range opts.Command { params.Add("command", c) } loc := &url.URL{ Scheme: nodeScheme, Host: fmt.Sprintf("%s:%d", nodeHost, nodePort), Path: fmt.Sprintf("/exec/%s/%s/%s", pod.Namespace, name, container), RawQuery: params.Encode(), } return loc, nodeTransport, nil }
// queryToObject converts query parameters into a structured internal object by // kind. The caller must cast the returned object to the matching internal Kind // to use it. // TODO: add appropriate structured error responses func queryToObject(query url.Values, scope RequestScope, kind string) (runtime.Object, error) { versioned, err := scope.Creater.New(scope.ServerAPIVersion, kind) if err != nil { // programmer error return nil, err } if err := scope.Convertor.Convert(&query, versioned); err != nil { return nil, errors.NewBadRequest(err.Error()) } out, err := scope.Convertor.ConvertToVersion(versioned, "") if err != nil { // programmer error return nil, err } return out, nil }
func (d *denyExecOnPrivileged) Admit(a admission.Attributes) (err error) { connectRequest, ok := a.GetObject().(*rest.ConnectRequest) if !ok { return errors.NewBadRequest("a connect request was received, but could not convert the request object.") } // Only handle exec requests on pods if connectRequest.ResourcePath != "pods/exec" { return nil } pod, err := d.client.Pods(a.GetNamespace()).Get(connectRequest.Name) if err != nil { return admission.NewForbidden(a, err) } if isPrivileged(pod) { return admission.NewForbidden(a, fmt.Errorf("Cannot exec into a privileged container")) } return nil }
// BeforeUpdate ensures that common operations for all resources are performed on update. It only returns // errors that can be converted to api.Status. It will invoke update validation with the provided existing // and updated objects. func BeforeUpdate(strategy RESTUpdateStrategy, ctx api.Context, obj, old runtime.Object) error { objectMeta, kind, kerr := objectMetaAndKind(strategy, obj) if kerr != nil { return kerr } if strategy.NamespaceScoped() { if !api.ValidNamespace(ctx, objectMeta) { return errors.NewBadRequest("the namespace of the provided object does not match the namespace sent on the request") } } else { objectMeta.Namespace = api.NamespaceNone } strategy.PrepareForUpdate(obj, old) if errs := strategy.ValidateUpdate(ctx, obj, old); len(errs) > 0 { return errors.NewInvalid(kind, objectMeta.Name, errs) } return nil }
// Admit will deny any SecurityContext that defines options that were not previously available in the api.Container // struct (Capabilities and Privileged) func (p *plugin) Admit(a admission.Attributes) (err error) { if a.GetResource() != string(api.ResourcePods) { return nil } pod, ok := a.GetObject().(*api.Pod) if !ok { return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted") } for _, v := range pod.Spec.Containers { if v.SecurityContext != nil { if v.SecurityContext.SELinuxOptions != nil { return apierrors.NewForbidden(a.GetResource(), pod.Name, fmt.Errorf("SecurityContext.SELinuxOptions is forbidden")) } if v.SecurityContext.RunAsUser != nil { return apierrors.NewForbidden(a.GetResource(), pod.Name, fmt.Errorf("SecurityContext.RunAsUser is forbidden")) } } } return nil }
// LogLocation returns a the log URL for a pod container. If opts.Container is blank // and only one container is present in the pod, that container is used. func LogLocation(getter ResourceGetter, connInfo client.ConnectionInfoGetter, ctx api.Context, name string, opts *api.PodLogOptions) (*url.URL, http.RoundTripper, error) { pod, err := getPod(getter, ctx, name) if err != nil { return nil, nil, err } // Try to figure out a container container := opts.Container if container == "" { if len(pod.Spec.Containers) == 1 { container = pod.Spec.Containers[0].Name } else { return nil, nil, errors.NewBadRequest(fmt.Sprintf("a container name must be specified for pod %s", name)) } } nodeHost := pod.Spec.NodeName if len(nodeHost) == 0 { // If pod has not been assigned a host, return an empty location return nil, nil, nil } nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(nodeHost) if err != nil { return nil, nil, err } params := url.Values{} if opts.Follow { params.Add("follow", "true") } if opts.Previous { params.Add("previous", "true") } loc := &url.URL{ Scheme: nodeScheme, Host: fmt.Sprintf("%s:%d", nodeHost, nodePort), Path: fmt.Sprintf("/containerLogs/%s/%s/%s", pod.Namespace, name, container), RawQuery: params.Encode(), } return loc, nodeTransport, nil }
// PortForwardLocation returns a the port-forward URL for a pod. func PortForwardLocation(getter ResourceGetter, connInfo client.ConnectionInfoGetter, ctx api.Context, name string) (*url.URL, http.RoundTripper, error) { pod, err := getPod(getter, ctx, name) if err != nil { return nil, nil, err } nodeHost := pod.Spec.NodeName if len(nodeHost) == 0 { // If pod has not been assigned a host, return an empty location return nil, nil, errors.NewBadRequest(fmt.Sprintf("pod %s does not have a host assigned", name)) } nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(nodeHost) if err != nil { return nil, nil, err } loc := &url.URL{ Scheme: nodeScheme, Host: fmt.Sprintf("%s:%d", nodeHost, nodePort), Path: fmt.Sprintf("/portForward/%s/%s", pod.Namespace, name), } return loc, nodeTransport, nil }
// ListResource returns a function that handles retrieving a list of resources from a rest.Storage object. func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch bool, minRequestTimeout time.Duration) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter namespace, err := scope.Namer.Namespace(req) if err != nil { errorJSON(err, scope.Codec, w) return } // Watches for single objects are routed to this function. // Treat a /name parameter the same as a field selector entry. hasName := true _, name, err := scope.Namer.Name(req) if err != nil { hasName = false } ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) out, err := queryToObject(req.Request.URL.Query(), scope, "ListOptions") if err != nil { errorJSON(err, scope.Codec, w) return } opts := *out.(*api.ListOptions) // transform fields // TODO: queryToObject should do this. fn := func(label, value string) (newLabel, newValue string, err error) { return scope.Convertor.ConvertFieldLabel(scope.APIVersion, scope.Kind, label, value) } if opts.FieldSelector, err = opts.FieldSelector.Transform(fn); err != nil { // TODO: allow bad request to set field causes based on query parameters err = errors.NewBadRequest(err.Error()) errorJSON(err, scope.Codec, w) return } if hasName { // metadata.name is the canonical internal name. // generic.SelectionPredicate will notice that this is // a request for a single object and optimize the // storage query accordingly. nameSelector := fields.OneTermEqualSelector("metadata.name", name) if opts.FieldSelector != nil && !opts.FieldSelector.Empty() { // It doesn't make sense to ask for both a name // and a field selector, since just the name is // sufficient to narrow down the request to a // single object. errorJSON( errors.NewBadRequest("both a name and a field selector provided; please provide one or the other."), scope.Codec, w, ) return } opts.FieldSelector = nameSelector } if (opts.Watch || forceWatch) && rw != nil { watcher, err := rw.Watch(ctx, opts.LabelSelector, opts.FieldSelector, opts.ResourceVersion) if err != nil { errorJSON(err, scope.Codec, w) return } serveWatch(watcher, scope, w, req, minRequestTimeout) return } result, err := r.List(ctx, opts.LabelSelector, opts.FieldSelector) if err != nil { errorJSON(err, scope.Codec, w) return } if err := setListSelfLink(result, req, scope.Namer); err != nil { errorJSON(err, scope.Codec, w) return } write(http.StatusOK, scope.APIVersion, scope.Codec, result, w, req.Request) } }
info *APIRequestInfoResolver prefix string // Path prefix where API resources are to be registered. minRequestTimeout time.Duration proxyDialerFn ProxyDialerFunc } // Struct capturing information about an action ("GET", "POST", "WATCH", PROXY", etc). type action struct { Verb string // Verb identifying the action ("GET", "POST", "WATCH", PROXY", etc). Path string // The path of the action Params []*restful.Parameter // List of parameters associated with the action. Namer ScopeNamer } // errEmptyName is returned when API requests do not fill the name section of the path. var errEmptyName = errors.NewBadRequest("name must be provided") // Installs handlers for API resources. func (a *APIInstaller) Install() (ws *restful.WebService, errors []error) { errors = make([]error, 0) // Create the WebService. ws = a.newWebService() proxyHandler := (&ProxyHandler{a.prefix + "/proxy/", a.group.Storage, a.group.Codec, a.group.Context, a.info, a.proxyDialerFn}) // Register the paths in a deterministic (sorted) order to get a deterministic swagger spec. paths := make([]string, len(a.group.Storage)) var i int = 0 for path := range a.group.Storage { paths[i] = path