package gateway import ( "net" "net/http" "net/http/httputil" "net/url" "strings" ) // servePluginProxy forwards a plugin-owned prefix to an explicit loopback // origin. No destination or path may be supplied by the client. Authentication // and endpoint authorization have already completed before this is called. func (g *Gateway) servePluginProxy(w http.ResponseWriter, r *http.Request, target, prefix string) { u, err := url.Parse(target) if err != nil || u.Scheme != "http" || u.User != nil || u.RawQuery != "" || u.Fragment != "" || (u.Path != "" && u.Path != "/") { writeError(w, http.StatusBadGateway, "plugin_proxy", "proxy must name a loopback HTTP origin") return } ip := net.ParseIP(u.Hostname()) if ip == nil || !ip.IsLoopback() || u.Port() == "" { writeError(w, http.StatusBadGateway, "plugin_proxy", "proxy requires a literal loopback address and port") return } // WebSocket requests are browser-initiated and carry ambient cookies. Do not // allow a foreign website to use an administrator's session to open a tunnel. if origin := r.Header.Get("Origin"); origin != "" { o, e := url.Parse(origin) if e != nil || !strings.EqualFold(o.Host, r.Host) { writeError(w, http.StatusForbidden, "forbidden", "cross-origin proxy request refused") return } } proxy := httputil.NewSingleHostReverseProxy(u) director := proxy.Director proxy.Director = func(out *http.Request) { director(out) out.URL.Path = strings.TrimPrefix(r.URL.Path, prefix) if out.URL.Path == "" { out.URL.Path = "/" } out.URL.RawPath = "" out.Host = u.Host out.Header.Del("Authorization") out.Header.Del("Cookie") q := out.URL.Query() q.Del("api_key") out.URL.RawQuery = q.Encode() } proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { writeError(w, http.StatusBadGateway, "plugin_proxy", "plugin service unavailable") } proxy.ServeHTTP(w, r) }