diff --git a/operatorapi/proxy.go b/operatorapi/proxy.go index dbd823fae..cfd169cd8 100644 --- a/operatorapi/proxy.go +++ b/operatorapi/proxy.go @@ -29,6 +29,9 @@ import ( "net/http/cookiejar" url2 "net/url" "strings" + "time" + + "github.com/gorilla/websocket" v2 "github.com/minio/operator/pkg/apis/minio.min.io/v2" @@ -181,11 +184,15 @@ func serveProxy(responseWriter http.ResponseWriter, req *http.Request) { } defer loginResp.Body.Close() } + if tenantCookie == nil { log.Println(errors.New("couldn't login to tenant and get cookie")) - responseWriter.WriteHeader(500) + responseWriter.WriteHeader(403) return } + // at this point we have a valid cookie ready to either route HTTP or WS + // now we need to know if we are doing an /api/ call (http) or /ws/ call (ws) + callType := urlParts[5] targetURL, err := url2.Parse(tenantURL) if err != nil { @@ -206,7 +213,16 @@ func serveProxy(responseWriter http.ResponseWriter, req *http.Request) { proxyCookieJar, _ := cookiejar.New(nil) proxyCookieJar.SetCookies(targetURL, []*http.Cookie{proxiedCookie}) + switch callType { + case "ws": + handleWSRequest(responseWriter, req, proxyCookieJar, targetURL, tenantSchema) + default: + handleHTTPRequest(responseWriter, req, proxyCookieJar, tenantBase, targetURL) + } +} + +func handleHTTPRequest(responseWriter http.ResponseWriter, req *http.Request, proxyCookieJar *cookiejar.Jar, tenantBase string, targetURL *url2.URL) { tr := &http.Transport{ // FIXME: use restapi.GetConsoleHTTPClient() TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, @@ -259,5 +275,75 @@ func serveProxy(responseWriter http.ResponseWriter, req *http.Request) { responseWriter.WriteHeader(resp.StatusCode) io.Copy(responseWriter, resp.Body) - +} + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 0, + WriteBufferSize: 1024, +} + +func handleWSRequest(responseWriter http.ResponseWriter, req *http.Request, proxyCookieJar *cookiejar.Jar, targetURL *url2.URL, schema string) { + dialer := &websocket.Dialer{ + Proxy: http.ProxyFromEnvironment, + HandshakeTimeout: 45 * time.Second, + Jar: proxyCookieJar, + } + + upgrader.CheckOrigin = func(r *http.Request) bool { + return true + } + + c, err := upgrader.Upgrade(responseWriter, req, nil) + if err != nil { + log.Print("error upgrade connection:", err) + return + } + defer c.Close() + if schema == "http" { + targetURL.Scheme = "ws" + } else { + targetURL.Scheme = "wss" + } + + // establish a websocket to the tenant + tenantConn, _, err := dialer.Dial(targetURL.String(), nil) + if err != nil { + log.Println("dial:", err) + return + } + defer tenantConn.Close() + + doneTenant := make(chan struct{}) + done := make(chan struct{}) + + // start read pump from tenant connection + go func() { + defer close(doneTenant) + for { + msgType, message, err := tenantConn.ReadMessage() + if err != nil { + log.Println("error read from tenant:", err) + return + } + c.WriteMessage(msgType, message) + } + }() + + // start read pump from tenant connection + go func() { + defer close(done) + for { + msgType, message, err := c.ReadMessage() + if err != nil { + log.Println("error read from client:", err) + return + } + tenantConn.WriteMessage(msgType, message) + } + }() + + select { + case <-done: + case <-doneTenant: + } } diff --git a/portal-ui/src/screens/Console/Heal/Heal.tsx b/portal-ui/src/screens/Console/Heal/Heal.tsx index 169a0c92a..cda56bd0b 100644 --- a/portal-ui/src/screens/Console/Heal/Heal.tsx +++ b/portal-ui/src/screens/Console/Heal/Heal.tsx @@ -188,9 +188,13 @@ const Heal = ({ classes, distributedSetup }: IHeal) => { const isDev = process.env.NODE_ENV === "development"; const port = isDev ? "9090" : url.port; + // check if we are using base path, if not this always is `/` + const baseLocation = new URL(document.baseURI); + const baseUrl = baseLocation.pathname; + const wsProt = wsProtocol(url.protocol); const c = new W3CWebSocket( - `${wsProt}://${url.hostname}:${port}/ws/heal/${bucketName}?prefix=${prefix}&recursive=${recursive}&force-start=${forceStart}&force-stop=${forceStop}` + `${wsProt}://${url.hostname}:${port}${baseUrl}ws/heal/${bucketName}?prefix=${prefix}&recursive=${recursive}&force-start=${forceStart}&force-stop=${forceStop}` ); if (c !== null) { diff --git a/portal-ui/src/screens/Console/HealthInfo/HealthInfo.tsx b/portal-ui/src/screens/Console/HealthInfo/HealthInfo.tsx index 54dd32583..32e04174b 100644 --- a/portal-ui/src/screens/Console/HealthInfo/HealthInfo.tsx +++ b/portal-ui/src/screens/Console/HealthInfo/HealthInfo.tsx @@ -173,8 +173,12 @@ const HealthInfo = ({ const wsProt = wsProtocol(url.protocol); + // check if we are using base path, if not this always is `/` + const baseLocation = new URL(document.baseURI); + const baseUrl = baseLocation.pathname; + const c = new W3CWebSocket( - `${wsProt}://${url.hostname}:${port}/ws/health-info?deadline=1h` + `${wsProt}://${url.hostname}:${port}${baseUrl}ws/health-info?deadline=1h` ); let interval: any | null = null; diff --git a/portal-ui/src/screens/Console/Logs/ErrorLogs/ErrorLogs.tsx b/portal-ui/src/screens/Console/Logs/ErrorLogs/ErrorLogs.tsx index 91965600a..19ecf7c4a 100644 --- a/portal-ui/src/screens/Console/Logs/ErrorLogs/ErrorLogs.tsx +++ b/portal-ui/src/screens/Console/Logs/ErrorLogs/ErrorLogs.tsx @@ -158,11 +158,14 @@ const ErrorLogs = ({ const port = isDev ? "9090" : url.port; const wsProt = wsProtocol(url.protocol); + // check if we are using base path, if not this always is `/` + const baseLocation = new URL(document.baseURI); + const baseUrl = baseLocation.pathname; c = new W3CWebSocket( `${wsProt}://${ url.hostname - }:${port}/ws/console/?logType=${logType}&node=${ + }:${port}${baseUrl}ws/console/?logType=${logType}&node=${ selectedNode === "Select node" ? "" : selectedNode }` ); diff --git a/portal-ui/src/screens/Console/Speedtest/Speedtest.tsx b/portal-ui/src/screens/Console/Speedtest/Speedtest.tsx index 0a074cc2c..1471a8ed5 100644 --- a/portal-ui/src/screens/Console/Speedtest/Speedtest.tsx +++ b/portal-ui/src/screens/Console/Speedtest/Speedtest.tsx @@ -109,9 +109,13 @@ const Speedtest = ({ classes, distributedSetup }: ISpeedtest) => { const isDev = process.env.NODE_ENV === "development"; const port = isDev ? "9090" : url.port; + // check if we are using base path, if not this always is `/` + const baseLocation = new URL(document.baseURI); + const baseUrl = baseLocation.pathname; + const wsProt = wsProtocol(url.protocol); const c = new W3CWebSocket( - `${wsProt}://${url.hostname}:${port}/ws/speedtest?&size=${size}${sizeUnit}` + `${wsProt}://${url.hostname}:${port}${baseUrl}ws/speedtest?&size=${size}${sizeUnit}` ); const baseDate = moment(); diff --git a/portal-ui/src/screens/Console/Trace/Trace.tsx b/portal-ui/src/screens/Console/Trace/Trace.tsx index 300f9aef8..3ab783049 100644 --- a/portal-ui/src/screens/Console/Trace/Trace.tsx +++ b/portal-ui/src/screens/Console/Trace/Trace.tsx @@ -166,12 +166,15 @@ const Trace = ({ if (all) { calls = "all"; } + // check if we are using base path, if not this always is `/` + const baseLocation = new URL(document.baseURI); + const baseUrl = baseLocation.pathname; const wsProt = wsProtocol(url.protocol); c = new W3CWebSocket( `${wsProt}://${ url.hostname - }:${port}/ws/trace?calls=${calls}&threshold=${threshold}&onlyErrors=${ + }:${port}${baseUrl}ws/trace?calls=${calls}&threshold=${threshold}&onlyErrors=${ errors ? "yes" : "no" }&statusCode=${statusCode}&method=${method}&funcname=${func}&path=${path}` ); diff --git a/portal-ui/src/screens/Console/Watch/Watch.tsx b/portal-ui/src/screens/Console/Watch/Watch.tsx index 76cc87a02..05cb8f7a5 100644 --- a/portal-ui/src/screens/Console/Watch/Watch.tsx +++ b/portal-ui/src/screens/Console/Watch/Watch.tsx @@ -128,9 +128,13 @@ const Watch = ({ const isDev = process.env.NODE_ENV === "development"; const port = isDev ? "9090" : url.port; + // check if we are using base path, if not this always is `/` + const baseLocation = new URL(document.baseURI); + const baseUrl = baseLocation.pathname; + const wsProt = wsProtocol(url.protocol); const c = new W3CWebSocket( - `${wsProt}://${url.hostname}:${port}/ws/watch/${bucketName}?prefix=${prefix}&suffix=${suffix}` + `${wsProt}://${url.hostname}:${port}${baseUrl}ws/watch/${bucketName}?prefix=${prefix}&suffix=${suffix}` ); let interval: any | null = null; diff --git a/restapi/consts.go b/restapi/consts.go index 8a76d5045..92abbde72 100644 --- a/restapi/consts.go +++ b/restapi/consts.go @@ -19,13 +19,12 @@ package restapi // list of all console environment constants const ( // Constants for common configuration - ConsoleMinIOServer = "CONSOLE_MINIO_SERVER" - ConsoleSubnetProxy = "CONSOLE_SUBNET_PROXY" - ConsoleMinIORegion = "CONSOLE_MINIO_REGION" - ConsoleHostname = "CONSOLE_HOSTNAME" - ConsolePort = "CONSOLE_PORT" - ConsoleTLSPort = "CONSOLE_TLS_PORT" - ConsoleSubnetLicense = "CONSOLE_SUBNET_LICENSE" + ConsoleMinIOServer = "CONSOLE_MINIO_SERVER" + ConsoleSubnetProxy = "CONSOLE_SUBNET_PROXY" + ConsoleMinIORegion = "CONSOLE_MINIO_REGION" + ConsoleHostname = "CONSOLE_HOSTNAME" + ConsolePort = "CONSOLE_PORT" + ConsoleTLSPort = "CONSOLE_TLS_PORT" // Constants for Secure middleware ConsoleSecureAllowedHosts = "CONSOLE_SECURE_ALLOWED_HOSTS"