cloudfoundry-incubator/stratos

View on GitHub
src/jetstream/plugins/kubernetes/dashboard/proxy.go

Summary

Maintainability
B
5 hrs
Test Coverage
package dashboard

import (
    "fmt"
    "net/http"
    "net/http/httputil"
    "net/url"
    "strings"

    "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces"
    "github.com/labstack/echo/v4"
    log "github.com/sirupsen/logrus"
    utilnet "k8s.io/apimachinery/pkg/util/net"
    "k8s.io/client-go/rest"
)

// KubeDashboardProxy proxies a request to the Kube Dash service using the K8S API
func KubeDashboardProxy(c echo.Context, p interfaces.PortalProxy, config *rest.Config) error {
    log.Debugf("KubeDashboardProxy request for: %s", c.Request().RequestURI)

    cnsiGUID := c.Param("guid")
    prefix := "/pp/v1/apps/kubedash/ui/" + cnsiGUID + "/"
    path := c.Request().RequestURI[len(prefix):]

    cnsiRecord, err := p.GetCNSIRecord(cnsiGUID)
    if err != nil {
        return sendErrorPage(c, "Failed to access Kubernetes Dashboard - could not find endpoint")
    }

    session, err := p.GetSession(c)
    if err != nil {
        return sendErrorPage(c, "Failed to access Kubernetes Dashboard - could not get Stratos Session")
    }

    var kubeDashEndpointID = ""
    var token = ""
    svc := ServiceInfo{}

    sessionData, err := p.GetSessionDataStore().GetValues(session.ID, kubeDashSessionGroup)
    if err != nil {
        return sendErrorPage(c, "Failed to access to Kubernetes Dashboard - could not get Stratos Session data")
    }

    // We have to have cached the data we need via the /login endpoint
    var errors = 0
    var ok bool

    if kubeDashEndpointID, ok = sessionData[kubeDashSessionEndpointID]; !ok {
        errors = errors + 1
    }

    if svc.Namespace, ok = sessionData[kubeDashSessionNamespace]; !ok {
        errors = errors + 1
    }

    if svc.Scheme, ok = sessionData[kubeDashSessionScheme]; !ok {
        errors = errors + 1
    }

    if svc.ServiceName, ok = sessionData[kubeDashSessionServiceName]; !ok {
        errors = errors + 1
    }

    if token, ok = sessionData[kubeDashSessionToken]; !ok {
        errors = errors + 1
    }

    // The cached data must be all there and must be for the correct endpoint
    if errors > 0 || kubeDashEndpointID != cnsiGUID {
        return sendErrorPage(c, "Failed to access to Kubernetes Dashboard - session data invalid")
    }

    apiEndpoint := cnsiRecord.APIEndpoint
    log.Debug(apiEndpoint)

    target := fmt.Sprintf("%s/api/v1/namespaces/%s/services/%s:%s:/proxy/%s", apiEndpoint, svc.Namespace, svc.Scheme, svc.ServiceName, path)
    log.Debug(target)
    targetURL, _ := url.Parse(target)

    req := c.Request()
    w := c.Response().Writer

    loc := targetURL
    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/genericapiserver/endpoints/handlers/proxy.go#ServeHTTP:
    // Redirect requests with an empty path to a location that ends with a '/'
    // This is essentially a hack for http://issue.k8s.io/4958.
    // Note: Keep this code after tryUpgrade to not break that flow.
    if len(loc.Path) == 0 {
        log.Debug("Redirecting")
        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 nil
    }

    transport, err := rest.TransportFor(config)
    if err != nil {
        return err
    }

    // WithContext creates a shallow clone of the request with the new context.
    newReq := req.WithContext(req.Context())
    newReq.Header = utilnet.CloneHeader(req.Header)
    newReq.URL = loc

    proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: loc.Scheme, Host: loc.Host})
    proxy.Transport = transport
    proxy.FlushInterval = defaultFlushInterval
    proxy.ModifyResponse = func(response *http.Response) error {
        log.Debugf("Got proxy response for: %s (Status: %s)", loc.String(), response.StatusCode)
        // For the root page, set the session cookie so that the user is automatically logged in from
        // the login we did manually
        if len(path) == 0 {
            // TODO: Check the value for the cookie header - kube dash may well update with the value
            // that it wants to use
            cookie := fmt.Sprintf("jweToken=%s; Max-Age=36000", token)
            response.Header.Set("Set-Cookie", cookie)
        }
        return nil
    }

    // Proxy the request
    proxy.ServeHTTP(w, newReq)

    return nil
}