From e545ad19b7e4b6952417ab5ccbc627834683e73f Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Thu, 11 Apr 2024 16:32:19 +0000 Subject: [PATCH 01/12] feat: add switch http(s) button to error page --- coderd/tailnet.go | 53 +++++++++++++++++++++++++++++++++++++----- site/site.go | 14 ++++++----- site/static/error.html | 9 +++++++ 3 files changed, 64 insertions(+), 12 deletions(-) diff --git a/coderd/tailnet.go b/coderd/tailnet.go index 0bcf21bb9d3a1..de77b1f153232 100644 --- a/coderd/tailnet.go +++ b/coderd/tailnet.go @@ -4,11 +4,14 @@ import ( "bufio" "context" "crypto/tls" + "fmt" "net" "net/http" "net/http/httputil" "net/netip" "net/url" + "strconv" + "strings" "sync" "sync/atomic" "time" @@ -23,6 +26,8 @@ import ( "cdr.dev/slog" "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/coderd/workspaceapps" + "github.com/coder/coder/v2/coderd/workspaceapps/appurl" + "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/site" "github.com/coder/coder/v2/tailnet" @@ -351,13 +356,49 @@ func (s *ServerTailnet) ReverseProxy(targetURL, dashboardURL *url.URL, agentID u tgt.Host = net.JoinHostPort(tailnet.IPFromUUID(agentID).String(), port) proxy := httputil.NewSingleHostReverseProxy(&tgt) - proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { + proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, theErr error) { + var ( + switchProtoScheme codersdk.WorkspaceAgentPortShareProtocol + switchProtoLink = "" + ) + au, err := appurl.ParseSubdomainAppURL(tgt.String()) + if err != nil { + site.RenderStaticErrorPage(w, r, site.ErrorPageData{ + Status: http.StatusBadGateway, + Title: "Bad Gateway", + Description: "Failed to proxy request to application: " + err.Error(), + RetryEnabled: true, + DashboardURL: dashboardURL.String(), + }) + return + } + if strings.HasSuffix(au.AppSlugOrPort, "s") { + p := strings.TrimSuffix(au.AppSlugOrPort, "s") + _, err = strconv.ParseInt(p, 10, 64) + if err == nil { + au.AppSlugOrPort = p + switchProtoLink = au.String() + switchProtoScheme = codersdk.WorkspaceAgentPortShareProtocolHTTP + } + } else { + au.AppSlugOrPort += "s" + switchProtoLink = au.String() + switchProtoScheme = codersdk.WorkspaceAgentPortShareProtocolHTTPS + } + + desc := "Failed to proxy request to application: " + theErr.Error() + if strings.Contains(theErr.Error(), "tls:") { + desc = fmt.Sprintf("This error seems to be due to a protocol mistake, please try switching to %s. \n%s", switchProtoScheme, theErr.Error()) + } + site.RenderStaticErrorPage(w, r, site.ErrorPageData{ - Status: http.StatusBadGateway, - Title: "Bad Gateway", - Description: "Failed to proxy request to application: " + err.Error(), - RetryEnabled: true, - DashboardURL: dashboardURL.String(), + Status: http.StatusBadGateway, + Title: "Bad Gateway", + Description: desc, + RetryEnabled: true, + DashboardURL: dashboardURL.String(), + SwitchProtocolLink: switchProtoLink, + SwitchProtocolTarget: switchProtoScheme, }) } proxy.Director = s.director(agentID, proxy.Director) diff --git a/site/site.go b/site/site.go index 1f7a1120f19c8..f4710c790d474 100644 --- a/site/site.go +++ b/site/site.go @@ -786,12 +786,14 @@ func extractBin(dest string, r io.Reader) (numExtracted int, err error) { type ErrorPageData struct { Status int // HideStatus will remove the status code from the page. - HideStatus bool - Title string - Description string - RetryEnabled bool - DashboardURL string - Warnings []string + HideStatus bool + Title string + Description string + RetryEnabled bool + DashboardURL string + Warnings []string + SwitchProtocolLink string + SwitchProtocolTarget codersdk.WorkspaceAgentPortShareProtocol RenderDescriptionMarkdown bool } diff --git a/site/static/error.html b/site/static/error.html index a07c96b8ec5aa..cf74672b97b05 100644 --- a/site/static/error.html +++ b/site/static/error.html @@ -167,6 +167,12 @@

{{- if not .Error.HideStatus }}{{ .Error.Status }} - {{end}}{{ .Error.Title }}

+ {{- if .Error.SwitchProtocolLink }} +

It looks like the error was due to a protocol mismatch. Do you want to try using {{ .Error.SwitchProtocolTarget }}?

+
+ Use {{ .Error.SwitchProtocolTarget }} +
+ {{ end }} {{- if .Error.RenderDescriptionMarkdown }} {{ .ErrorDescriptionHTML }} {{ else }}

{{ .Error.Description }}

@@ -198,6 +204,9 @@

Warnings

{{- if .Error.RetryEnabled }} {{ end }} + {{- if .Error.SwitchProtocolLink }} + Switch to {{ .Error.SwitchProtocolTarget }} + {{ end }} Back to site From c97dc85a235fd8ed0f2494b14a19496a540bff44 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Thu, 11 Apr 2024 16:33:06 +0000 Subject: [PATCH 02/12] lang --- coderd/tailnet.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/tailnet.go b/coderd/tailnet.go index de77b1f153232..181e8677f32fd 100644 --- a/coderd/tailnet.go +++ b/coderd/tailnet.go @@ -388,7 +388,7 @@ func (s *ServerTailnet) ReverseProxy(targetURL, dashboardURL *url.URL, agentID u desc := "Failed to proxy request to application: " + theErr.Error() if strings.Contains(theErr.Error(), "tls:") { - desc = fmt.Sprintf("This error seems to be due to a protocol mistake, please try switching to %s. \n%s", switchProtoScheme, theErr.Error()) + desc = fmt.Sprintf("This error seems to be due to a protocol mismatch, please try switching to %s. \n%s", switchProtoScheme, theErr.Error()) } site.RenderStaticErrorPage(w, r, site.ErrorPageData{ From 1c3af6edfeab6f0c775fd3869c2de4ecb30f952f Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Thu, 11 Apr 2024 16:35:04 +0000 Subject: [PATCH 03/12] remove lang --- site/static/error.html | 6 ------ 1 file changed, 6 deletions(-) diff --git a/site/static/error.html b/site/static/error.html index cf74672b97b05..3280dddb92dbf 100644 --- a/site/static/error.html +++ b/site/static/error.html @@ -167,12 +167,6 @@

{{- if not .Error.HideStatus }}{{ .Error.Status }} - {{end}}{{ .Error.Title }}

- {{- if .Error.SwitchProtocolLink }} -

It looks like the error was due to a protocol mismatch. Do you want to try using {{ .Error.SwitchProtocolTarget }}?

- - {{ end }} {{- if .Error.RenderDescriptionMarkdown }} {{ .ErrorDescriptionHTML }} {{ else }}

{{ .Error.Description }}

From 4ff2b20ff6304a46a755cb9d736f6350f77d1951 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Fri, 19 Apr 2024 20:02:37 +0000 Subject: [PATCH 04/12] testing error cases --- coderd/tailnet.go | 66 +++++++++++++---------------------- coderd/workspaceapps/proxy.go | 18 +++++++--- site/static/error.html | 2 +- 3 files changed, 39 insertions(+), 47 deletions(-) diff --git a/coderd/tailnet.go b/coderd/tailnet.go index 181e8677f32fd..8e7c7be09f0f6 100644 --- a/coderd/tailnet.go +++ b/coderd/tailnet.go @@ -4,7 +4,6 @@ import ( "bufio" "context" "crypto/tls" - "fmt" "net" "net/http" "net/http/httputil" @@ -27,7 +26,6 @@ import ( "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/coderd/workspaceapps" "github.com/coder/coder/v2/coderd/workspaceapps/appurl" - "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/site" "github.com/coder/coder/v2/tailnet" @@ -346,7 +344,7 @@ type ServerTailnet struct { totalConns *prometheus.CounterVec } -func (s *ServerTailnet) ReverseProxy(targetURL, dashboardURL *url.URL, agentID uuid.UUID) *httputil.ReverseProxy { +func (s *ServerTailnet) ReverseProxy(targetURL, dashboardURL *url.URL, agentID uuid.UUID, app appurl.ApplicationURL) *httputil.ReverseProxy { // Rewrite the targetURL's Host to point to the agent's IP. This is // necessary because due to TCP connection caching, each agent needs to be // addressed invidivually. Otherwise, all connections get dialed as @@ -357,48 +355,34 @@ func (s *ServerTailnet) ReverseProxy(targetURL, dashboardURL *url.URL, agentID u proxy := httputil.NewSingleHostReverseProxy(&tgt) proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, theErr error) { - var ( - switchProtoScheme codersdk.WorkspaceAgentPortShareProtocol - switchProtoLink = "" - ) - au, err := appurl.ParseSubdomainAppURL(tgt.String()) - if err != nil { - site.RenderStaticErrorPage(w, r, site.ErrorPageData{ - Status: http.StatusBadGateway, - Title: "Bad Gateway", - Description: "Failed to proxy request to application: " + err.Error(), - RetryEnabled: true, - DashboardURL: dashboardURL.String(), - }) - return - } - if strings.HasSuffix(au.AppSlugOrPort, "s") { - p := strings.TrimSuffix(au.AppSlugOrPort, "s") - _, err = strconv.ParseInt(p, 10, 64) - if err == nil { - au.AppSlugOrPort = p - switchProtoLink = au.String() - switchProtoScheme = codersdk.WorkspaceAgentPortShareProtocolHTTP - } - } else { - au.AppSlugOrPort += "s" - switchProtoLink = au.String() - switchProtoScheme = codersdk.WorkspaceAgentPortShareProtocolHTTPS - } - - desc := "Failed to proxy request to application: " + theErr.Error() + desc := "Failed to proxy request to application: \n\n" + theErr.Error() + descAddition := "" if strings.Contains(theErr.Error(), "tls:") { - desc = fmt.Sprintf("This error seems to be due to a protocol mismatch, please try switching to %s. \n%s", switchProtoScheme, theErr.Error()) + // If the error is due to an HTTP/HTTPS mismatch, we can provide a + // more helpful error message with redirect buttons. + if strings.HasSuffix(app.AppSlugOrPort, "s") { + _, err := strconv.ParseInt(app.AppSlugOrPort, 10, 64) + if err == nil { + app.AppSlugOrPort = strings.TrimSuffix(app.AppSlugOrPort, "s") + descAddition += "This error seems to be due to an HTTPS mismatch, please try switching to HTTP." + } + } else { + _, err := strconv.ParseInt(app.AppSlugOrPort, 10, 64) + if err == nil { + app.AppSlugOrPort += "s" + descAddition += "This error seems to be due to an HTTP mismatch, please try switching to HTTPS." + } + } } + desc += descAddition site.RenderStaticErrorPage(w, r, site.ErrorPageData{ - Status: http.StatusBadGateway, - Title: "Bad Gateway", - Description: desc, - RetryEnabled: true, - DashboardURL: dashboardURL.String(), - SwitchProtocolLink: switchProtoLink, - SwitchProtocolTarget: switchProtoScheme, + Status: http.StatusBadGateway, + Title: "Bad Gateway Dood", + Description: desc, + RetryEnabled: true, + DashboardURL: dashboardURL.String(), + SwitchProtocolLink: app.String(), }) } proxy.Director = s.director(agentID, proxy.Director) diff --git a/coderd/workspaceapps/proxy.go b/coderd/workspaceapps/proxy.go index 5b14cbf340165..306bde7cb06ff 100644 --- a/coderd/workspaceapps/proxy.go +++ b/coderd/workspaceapps/proxy.go @@ -66,7 +66,7 @@ var nonCanonicalHeaders = map[string]string{ type AgentProvider interface { // ReverseProxy returns an httputil.ReverseProxy for proxying HTTP requests // to the specified agent. - ReverseProxy(targetURL, dashboardURL *url.URL, agentID uuid.UUID) *httputil.ReverseProxy + ReverseProxy(targetURL, dashboardURL *url.URL, agentID uuid.UUID, app appurl.ApplicationURL) *httputil.ReverseProxy // AgentConn returns a new connection to the specified agent. AgentConn(ctx context.Context, agentID uuid.UUID) (_ *workspacesdk.AgentConn, release func(), _ error) @@ -314,7 +314,7 @@ func (s *Server) workspaceAppsProxyPath(rw http.ResponseWriter, r *http.Request) return } - s.proxyWorkspaceApp(rw, r, *token, chiPath) + s.proxyWorkspaceApp(rw, r, *token, chiPath, appurl.ApplicationURL{}) } // HandleSubdomain handles subdomain-based application proxy requests (aka. @@ -417,7 +417,7 @@ func (s *Server) HandleSubdomain(middlewares ...func(http.Handler) http.Handler) if !ok { return } - s.proxyWorkspaceApp(rw, r, *token, r.URL.Path) + s.proxyWorkspaceApp(rw, r, *token, r.URL.Path, app) })).ServeHTTP(rw, r.WithContext(ctx)) }) } @@ -476,7 +476,7 @@ func (s *Server) parseHostname(rw http.ResponseWriter, r *http.Request, next htt return app, true } -func (s *Server) proxyWorkspaceApp(rw http.ResponseWriter, r *http.Request, appToken SignedToken, path string) { +func (s *Server) proxyWorkspaceApp(rw http.ResponseWriter, r *http.Request, appToken SignedToken, path string, app appurl.ApplicationURL) { ctx := r.Context() // Filter IP headers from untrusted origins. @@ -546,7 +546,15 @@ func (s *Server) proxyWorkspaceApp(rw http.ResponseWriter, r *http.Request, appT r.URL.Path = path appURL.RawQuery = "" - proxy := s.AgentProvider.ReverseProxy(appURL, s.DashboardURL, appToken.AgentID) + appURL.Scheme = "http" + if strings.HasSuffix(app.AppSlugOrPort, "s") { + _, err = strconv.ParseInt(strings.TrimSuffix(app.AppSlugOrPort, "s"), 10, 64) + if err == nil { + appURL.Scheme = "https" + } + } + + proxy := s.AgentProvider.ReverseProxy(appURL, s.DashboardURL, appToken.AgentID, app) proxy.ModifyResponse = func(r *http.Response) error { r.Header.Del(httpmw.AccessControlAllowOriginHeader) diff --git a/site/static/error.html b/site/static/error.html index 3280dddb92dbf..ddb4056a1f44e 100644 --- a/site/static/error.html +++ b/site/static/error.html @@ -199,7 +199,7 @@

Warnings

{{ end }} {{- if .Error.SwitchProtocolLink }} - Switch to {{ .Error.SwitchProtocolTarget }} + Switch to HTTPX {{ end }} Back to site From 6669d6d71476ae1c1e50c938c23b68f42a562091 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Mon, 22 Apr 2024 21:04:14 +0000 Subject: [PATCH 05/12] the site now renders, cleanup backend --- coderd/tailnet.go | 41 ++++++++++----------- coderd/workspaceapps/appurl/appurl.go | 53 +++++++++++++++++++++++++++ site/site.go | 1 + site/static/error.html | 8 +++- 4 files changed, 80 insertions(+), 23 deletions(-) diff --git a/coderd/tailnet.go b/coderd/tailnet.go index 8e7c7be09f0f6..9a3b421ad9ccd 100644 --- a/coderd/tailnet.go +++ b/coderd/tailnet.go @@ -4,12 +4,12 @@ import ( "bufio" "context" "crypto/tls" + "fmt" "net" "net/http" "net/http/httputil" "net/netip" "net/url" - "strconv" "strings" "sync" "sync/atomic" @@ -26,6 +26,7 @@ import ( "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/coderd/workspaceapps" "github.com/coder/coder/v2/coderd/workspaceapps/appurl" + "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/site" "github.com/coder/coder/v2/tailnet" @@ -355,34 +356,32 @@ func (s *ServerTailnet) ReverseProxy(targetURL, dashboardURL *url.URL, agentID u proxy := httputil.NewSingleHostReverseProxy(&tgt) proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, theErr error) { - desc := "Failed to proxy request to application: \n\n" + theErr.Error() - descAddition := "" + desc := "Failed to proxy request to application: " + theErr.Error() + additional := "" + if strings.Contains(theErr.Error(), "tls:") { // If the error is due to an HTTP/HTTPS mismatch, we can provide a // more helpful error message with redirect buttons. - if strings.HasSuffix(app.AppSlugOrPort, "s") { - _, err := strconv.ParseInt(app.AppSlugOrPort, 10, 64) - if err == nil { - app.AppSlugOrPort = strings.TrimSuffix(app.AppSlugOrPort, "s") - descAddition += "This error seems to be due to an HTTPS mismatch, please try switching to HTTP." - } - } else { - _, err := strconv.ParseInt(app.AppSlugOrPort, 10, 64) - if err == nil { - app.AppSlugOrPort += "s" - descAddition += "This error seems to be due to an HTTP mismatch, please try switching to HTTPS." + if app.IsPort() { + if app.Protocol() == codersdk.WorkspaceAgentPortShareProtocolHTTPS { + app.ChangePortProtocol(codersdk.WorkspaceAgentPortShareProtocolHTTP) + } else { + app.ChangePortProtocol(codersdk.WorkspaceAgentPortShareProtocolHTTPS) } + + additional += fmt.Sprintf("This error seems to be due to an app protocol mismatch, please try switching to %s.", app.Protocol()) } } - desc += descAddition site.RenderStaticErrorPage(w, r, site.ErrorPageData{ - Status: http.StatusBadGateway, - Title: "Bad Gateway Dood", - Description: desc, - RetryEnabled: true, - DashboardURL: dashboardURL.String(), - SwitchProtocolLink: app.String(), + Status: http.StatusBadGateway, + Title: "Bad Gateway Dood", + Description: desc, + RetryEnabled: true, + DashboardURL: dashboardURL.String(), + SwitchProtocolLink: app.String(), + SwitchProtocolTarget: string(app.Protocol()), + AdditionalInfo: additional, }) } proxy.Director = s.director(agentID, proxy.Director) diff --git a/coderd/workspaceapps/appurl/appurl.go b/coderd/workspaceapps/appurl/appurl.go index 8b8cfd74d36bd..cd2ead5849386 100644 --- a/coderd/workspaceapps/appurl/appurl.go +++ b/coderd/workspaceapps/appurl/appurl.go @@ -5,8 +5,10 @@ import ( "net" "net/url" "regexp" + "strconv" "strings" + "github.com/coder/coder/v2/codersdk" "golang.org/x/xerrors" ) @@ -83,6 +85,57 @@ func (a ApplicationURL) Path() string { return fmt.Sprintf("/@%s/%s.%s/apps/%s", a.Username, a.WorkspaceName, a.AgentName, a.AppSlugOrPort) } +func (a ApplicationURL) IsPort() bool { + // check if https port + if strings.HasSuffix(a.AppSlugOrPort, "s") { + trimmed := strings.TrimSuffix(a.AppSlugOrPort, "s") + _, err := strconv.ParseInt(trimmed, 10, 64) + if err != nil { + return false + } + + return true + } + + // check if port at all + _, err := strconv.ParseInt(a.AppSlugOrPort, 10, 64) + if err != nil { + return false + } + + return true + +} + +func (a ApplicationURL) Protocol() codersdk.WorkspaceAgentPortShareProtocol { + if strings.HasSuffix(a.AppSlugOrPort, "s") { + trimmed := strings.TrimSuffix(a.AppSlugOrPort, "s") + _, err := strconv.ParseInt(trimmed, 10, 64) + if err == nil { + return codersdk.WorkspaceAgentPortShareProtocolHTTPS + } + } + + return codersdk.WorkspaceAgentPortShareProtocolHTTP +} + +func (a ApplicationURL) ChangePortProtocol(target codersdk.WorkspaceAgentPortShareProtocol) error { + if target == codersdk.WorkspaceAgentPortShareProtocolHTTP { + if strings.HasSuffix(a.AppSlugOrPort, "s") { + trimmed := strings.TrimSuffix(a.AppSlugOrPort, "s") + _, err := strconv.ParseInt(trimmed, 10, 64) + if err != nil { + return xerrors.Errorf("invalid port: %s", a.AppSlugOrPort) + } + a.AppSlugOrPort = trimmed + } + } + + a.AppSlugOrPort = fmt.Sprintf("%s%s", a.AppSlugOrPort, "s") + + return nil +} + // ParseSubdomainAppURL parses an ApplicationURL from the given subdomain. If // the subdomain is not a valid application URL hostname, returns a non-nil // error. If the hostname is not a subdomain of the given base hostname, returns diff --git a/site/site.go b/site/site.go index f4710c790d474..e8f55c01a05ba 100644 --- a/site/site.go +++ b/site/site.go @@ -794,6 +794,7 @@ type ErrorPageData struct { Warnings []string SwitchProtocolLink string SwitchProtocolTarget codersdk.WorkspaceAgentPortShareProtocol + AdditionalInfo string RenderDescriptionMarkdown bool } diff --git a/site/static/error.html b/site/static/error.html index ddb4056a1f44e..4337c3a4d6902 100644 --- a/site/static/error.html +++ b/site/static/error.html @@ -170,7 +170,11 @@

{{- if .Error.RenderDescriptionMarkdown }} {{ .ErrorDescriptionHTML }} {{ else }}

{{ .Error.Description }}

- {{ end }} {{- if .Error.Warnings }} + {{ end }} + {{- if .Error.AdditionalInfo }} +

{{ .Error.AdditionalInfo }}

+ {{ end }} + {{- if .Error.Warnings }}
Warnings

{{ end }} {{- if .Error.SwitchProtocolLink }} - Switch to HTTPX + Switch to {{ .ErrorSwitchProtocolTarget }} {{ end }} Back to site From d8620eaf55d46bba692f2987a8574c579e87aeab Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Tue, 23 Apr 2024 21:33:30 +0000 Subject: [PATCH 06/12] it works --- Makefile | 3 ++- coderd/tailnet.go | 31 ++++++++++++++-------- coderd/workspaceapps/appurl/appurl.go | 38 +++++++++++++++------------ coderd/workspaceapps/proxy.go | 13 +++------ site/site.go | 2 +- site/static/error.html | 4 +-- 6 files changed, 49 insertions(+), 42 deletions(-) diff --git a/Makefile b/Makefile index 5eeba4bb06041..dd31c10fff810 100644 --- a/Makefile +++ b/Makefile @@ -200,7 +200,8 @@ endef # calling this manually. $(CODER_ALL_BINARIES): go.mod go.sum \ $(GO_SRC_FILES) \ - $(shell find ./examples/templates) + $(shell find ./examples/templates) \ + site/static/error.html $(get-mode-os-arch-ext) if [[ "$$os" != "windows" ]] && [[ "$$ext" != "" ]]; then diff --git a/coderd/tailnet.go b/coderd/tailnet.go index 9a3b421ad9ccd..f7ebc453efe1c 100644 --- a/coderd/tailnet.go +++ b/coderd/tailnet.go @@ -26,7 +26,6 @@ import ( "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/coderd/workspaceapps" "github.com/coder/coder/v2/coderd/workspaceapps/appurl" - "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/site" "github.com/coder/coder/v2/tailnet" @@ -345,7 +344,7 @@ type ServerTailnet struct { totalConns *prometheus.CounterVec } -func (s *ServerTailnet) ReverseProxy(targetURL, dashboardURL *url.URL, agentID uuid.UUID, app appurl.ApplicationURL) *httputil.ReverseProxy { +func (s *ServerTailnet) ReverseProxy(targetURL, dashboardURL *url.URL, agentID uuid.UUID, app appurl.ApplicationURL, wildcardHostname string) *httputil.ReverseProxy { // Rewrite the targetURL's Host to point to the agent's IP. This is // necessary because due to TCP connection caching, each agent needs to be // addressed invidivually. Otherwise, all connections get dialed as @@ -356,31 +355,41 @@ func (s *ServerTailnet) ReverseProxy(targetURL, dashboardURL *url.URL, agentID u proxy := httputil.NewSingleHostReverseProxy(&tgt) proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, theErr error) { - desc := "Failed to proxy request to application: " + theErr.Error() - additional := "" + var ( + desc = "Failed to proxy request to application: " + theErr.Error() + additional = "" + switchLink = "" + switchTarget = "" + ) if strings.Contains(theErr.Error(), "tls:") { // If the error is due to an HTTP/HTTPS mismatch, we can provide a // more helpful error message with redirect buttons. + switchURL := url.URL{ + Scheme: dashboardURL.Scheme, + } if app.IsPort() { - if app.Protocol() == codersdk.WorkspaceAgentPortShareProtocolHTTPS { - app.ChangePortProtocol(codersdk.WorkspaceAgentPortShareProtocolHTTP) + if app.Protocol() == "https" { + app.ChangePortProtocol("http") } else { - app.ChangePortProtocol(codersdk.WorkspaceAgentPortShareProtocolHTTPS) + app.ChangePortProtocol("https") } - additional += fmt.Sprintf("This error seems to be due to an app protocol mismatch, please try switching to %s.", app.Protocol()) + switchURL.Host = fmt.Sprintf("%s%s", app.String(), strings.TrimPrefix(wildcardHostname, "*")) + switchLink = switchURL.String() + switchTarget = app.Protocol() + additional += fmt.Sprintf("This error seems to be due to an app protocol mismatch, try switching to %s.", strings.ToUpper(app.Protocol())) } } site.RenderStaticErrorPage(w, r, site.ErrorPageData{ Status: http.StatusBadGateway, - Title: "Bad Gateway Dood", + Title: "Bad Gateway", Description: desc, RetryEnabled: true, DashboardURL: dashboardURL.String(), - SwitchProtocolLink: app.String(), - SwitchProtocolTarget: string(app.Protocol()), + SwitchProtocolLink: switchLink, + SwitchProtocolTarget: strings.ToUpper(switchTarget), AdditionalInfo: additional, }) } diff --git a/coderd/workspaceapps/appurl/appurl.go b/coderd/workspaceapps/appurl/appurl.go index cd2ead5849386..86b9bcf985170 100644 --- a/coderd/workspaceapps/appurl/appurl.go +++ b/coderd/workspaceapps/appurl/appurl.go @@ -8,7 +8,6 @@ import ( "strconv" "strings" - "github.com/coder/coder/v2/codersdk" "golang.org/x/xerrors" ) @@ -90,6 +89,7 @@ func (a ApplicationURL) IsPort() bool { if strings.HasSuffix(a.AppSlugOrPort, "s") { trimmed := strings.TrimSuffix(a.AppSlugOrPort, "s") _, err := strconv.ParseInt(trimmed, 10, 64) + //nolint:gosimple if err != nil { return false } @@ -99,41 +99,45 @@ func (a ApplicationURL) IsPort() bool { // check if port at all _, err := strconv.ParseInt(a.AppSlugOrPort, 10, 64) + //nolint:gosimple if err != nil { return false } return true - } -func (a ApplicationURL) Protocol() codersdk.WorkspaceAgentPortShareProtocol { +func (a ApplicationURL) Protocol() string { if strings.HasSuffix(a.AppSlugOrPort, "s") { trimmed := strings.TrimSuffix(a.AppSlugOrPort, "s") _, err := strconv.ParseInt(trimmed, 10, 64) if err == nil { - return codersdk.WorkspaceAgentPortShareProtocolHTTPS + return "https" } } - return codersdk.WorkspaceAgentPortShareProtocolHTTP + return "http" } -func (a ApplicationURL) ChangePortProtocol(target codersdk.WorkspaceAgentPortShareProtocol) error { - if target == codersdk.WorkspaceAgentPortShareProtocolHTTP { - if strings.HasSuffix(a.AppSlugOrPort, "s") { - trimmed := strings.TrimSuffix(a.AppSlugOrPort, "s") - _, err := strconv.ParseInt(trimmed, 10, 64) - if err != nil { - return xerrors.Errorf("invalid port: %s", a.AppSlugOrPort) - } +func (a *ApplicationURL) ChangePortProtocol(target string) { + if target == "http" { + if a.Protocol() == "http" { + return + } + trimmed := strings.TrimSuffix(a.AppSlugOrPort, "s") + _, err := strconv.ParseInt(trimmed, 10, 64) + if err == nil { a.AppSlugOrPort = trimmed } + } else { + if a.Protocol() == "https" { + return + } + _, err := strconv.ParseInt(a.AppSlugOrPort, 10, 64) + if err == nil { + a.AppSlugOrPort = fmt.Sprintf("%s%s", a.AppSlugOrPort, "s") + } } - - a.AppSlugOrPort = fmt.Sprintf("%s%s", a.AppSlugOrPort, "s") - - return nil } // ParseSubdomainAppURL parses an ApplicationURL from the given subdomain. If diff --git a/coderd/workspaceapps/proxy.go b/coderd/workspaceapps/proxy.go index 306bde7cb06ff..4797385c4def8 100644 --- a/coderd/workspaceapps/proxy.go +++ b/coderd/workspaceapps/proxy.go @@ -66,7 +66,7 @@ var nonCanonicalHeaders = map[string]string{ type AgentProvider interface { // ReverseProxy returns an httputil.ReverseProxy for proxying HTTP requests // to the specified agent. - ReverseProxy(targetURL, dashboardURL *url.URL, agentID uuid.UUID, app appurl.ApplicationURL) *httputil.ReverseProxy + ReverseProxy(targetURL, dashboardURL *url.URL, agentID uuid.UUID, app appurl.ApplicationURL, wildcardHost string) *httputil.ReverseProxy // AgentConn returns a new connection to the specified agent. AgentConn(ctx context.Context, agentID uuid.UUID) (_ *workspacesdk.AgentConn, release func(), _ error) @@ -545,16 +545,9 @@ func (s *Server) proxyWorkspaceApp(rw http.ResponseWriter, r *http.Request, appT r.URL.Path = path appURL.RawQuery = "" + appURL.Scheme = app.Protocol() - appURL.Scheme = "http" - if strings.HasSuffix(app.AppSlugOrPort, "s") { - _, err = strconv.ParseInt(strings.TrimSuffix(app.AppSlugOrPort, "s"), 10, 64) - if err == nil { - appURL.Scheme = "https" - } - } - - proxy := s.AgentProvider.ReverseProxy(appURL, s.DashboardURL, appToken.AgentID, app) + proxy := s.AgentProvider.ReverseProxy(appURL, s.DashboardURL, appToken.AgentID, app, s.Hostname) proxy.ModifyResponse = func(r *http.Response) error { r.Header.Del(httpmw.AccessControlAllowOriginHeader) diff --git a/site/site.go b/site/site.go index e8f55c01a05ba..baf69710f29d1 100644 --- a/site/site.go +++ b/site/site.go @@ -793,7 +793,7 @@ type ErrorPageData struct { DashboardURL string Warnings []string SwitchProtocolLink string - SwitchProtocolTarget codersdk.WorkspaceAgentPortShareProtocol + SwitchProtocolTarget string AdditionalInfo string RenderDescriptionMarkdown bool diff --git a/site/static/error.html b/site/static/error.html index 4337c3a4d6902..a17abfbf89baa 100644 --- a/site/static/error.html +++ b/site/static/error.html @@ -33,7 +33,7 @@ .container { --side-padding: 24px; width: 100%; - max-width: calc(320px + var(--side-padding) * 2); + max-width: calc(460px + var(--side-padding) * 2); padding: 0 var(--side-padding); text-align: center; } @@ -203,7 +203,7 @@

Warnings

{{ end }} {{- if .Error.SwitchProtocolLink }} - Switch to {{ .ErrorSwitchProtocolTarget }} + Switch to {{ .Error.SwitchProtocolTarget }} {{ end }} Back to site From 529202d91d6f222a06aaabd7fbb205372b6269b7 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Wed, 24 Apr 2024 20:05:26 +0000 Subject: [PATCH 07/12] tests --- coderd/tailnet_test.go | 17 +++++++++-------- coderd/workspaceapps/appurl/appurl.go | 4 +++- coderd/workspaceapps/appurl/appurl_test.go | 10 ++++++++++ 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/coderd/tailnet_test.go b/coderd/tailnet_test.go index 0a78a8349c0df..d4dac9b94ca9d 100644 --- a/coderd/tailnet_test.go +++ b/coderd/tailnet_test.go @@ -26,6 +26,7 @@ import ( "github.com/coder/coder/v2/agent/agenttest" "github.com/coder/coder/v2/agent/proto" "github.com/coder/coder/v2/coderd" + "github.com/coder/coder/v2/coderd/workspaceapps/appurl" "github.com/coder/coder/v2/codersdk/agentsdk" "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/tailnet" @@ -81,7 +82,7 @@ func TestServerTailnet_ReverseProxy_ProxyEnv(t *testing.T) { u, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", workspacesdk.AgentHTTPAPIServerPort)) require.NoError(t, err) - rp := serverTailnet.ReverseProxy(u, u, a.id) + rp := serverTailnet.ReverseProxy(u, u, a.id, appurl.ApplicationURL{}, "") rw := httptest.NewRecorder() req := httptest.NewRequest( @@ -112,7 +113,7 @@ func TestServerTailnet_ReverseProxy(t *testing.T) { u, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", workspacesdk.AgentHTTPAPIServerPort)) require.NoError(t, err) - rp := serverTailnet.ReverseProxy(u, u, a.id) + rp := serverTailnet.ReverseProxy(u, u, a.id, appurl.ApplicationURL{}, "") rw := httptest.NewRecorder() req := httptest.NewRequest( @@ -143,7 +144,7 @@ func TestServerTailnet_ReverseProxy(t *testing.T) { u, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", workspacesdk.AgentHTTPAPIServerPort)) require.NoError(t, err) - rp := serverTailnet.ReverseProxy(u, u, a.id) + rp := serverTailnet.ReverseProxy(u, u, a.id, appurl.ApplicationURL{}, "") rw := httptest.NewRecorder() req := httptest.NewRequest( @@ -177,7 +178,7 @@ func TestServerTailnet_ReverseProxy(t *testing.T) { u, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", workspacesdk.AgentHTTPAPIServerPort)) require.NoError(t, err) - rp := serverTailnet.ReverseProxy(u, u, a.id) + rp := serverTailnet.ReverseProxy(u, u, a.id, appurl.ApplicationURL{}, "") req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) require.NoError(t, err) @@ -222,7 +223,7 @@ func TestServerTailnet_ReverseProxy(t *testing.T) { u, err := url.Parse("http://127.0.0.1" + port) require.NoError(t, err) - rp := serverTailnet.ReverseProxy(u, u, a.id) + rp := serverTailnet.ReverseProxy(u, u, a.id, appurl.ApplicationURL{}, "") for i := 0; i < 5; i++ { rw := httptest.NewRecorder() @@ -279,7 +280,7 @@ func TestServerTailnet_ReverseProxy(t *testing.T) { require.NoError(t, err) for i, ag := range agents { - rp := serverTailnet.ReverseProxy(u, u, ag.id) + rp := serverTailnet.ReverseProxy(u, u, ag.id, appurl.ApplicationURL{}, "") rw := httptest.NewRecorder() req := httptest.NewRequest( @@ -317,7 +318,7 @@ func TestServerTailnet_ReverseProxy(t *testing.T) { uri, err := url.Parse(s.URL) require.NoError(t, err) - rp := serverTailnet.ReverseProxy(uri, uri, a.id) + rp := serverTailnet.ReverseProxy(uri, uri, a.id, appurl.ApplicationURL{}, "") rw := httptest.NewRecorder() req := httptest.NewRequest( @@ -347,7 +348,7 @@ func TestServerTailnet_ReverseProxy(t *testing.T) { u, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", workspacesdk.AgentHTTPAPIServerPort)) require.NoError(t, err) - rp := serverTailnet.ReverseProxy(u, u, a.id) + rp := serverTailnet.ReverseProxy(u, u, a.id, appurl.ApplicationURL{}, "") rw := httptest.NewRecorder() req := httptest.NewRequest( diff --git a/coderd/workspaceapps/appurl/appurl.go b/coderd/workspaceapps/appurl/appurl.go index 86b9bcf985170..37d9a20032194 100644 --- a/coderd/workspaceapps/appurl/appurl.go +++ b/coderd/workspaceapps/appurl/appurl.go @@ -129,7 +129,9 @@ func (a *ApplicationURL) ChangePortProtocol(target string) { if err == nil { a.AppSlugOrPort = trimmed } - } else { + } + + if target == "https" { if a.Protocol() == "https" { return } diff --git a/coderd/workspaceapps/appurl/appurl_test.go b/coderd/workspaceapps/appurl/appurl_test.go index 98a34c60037d7..8353768de1d33 100644 --- a/coderd/workspaceapps/appurl/appurl_test.go +++ b/coderd/workspaceapps/appurl/appurl_test.go @@ -124,6 +124,16 @@ func TestParseSubdomainAppURL(t *testing.T) { Username: "user", }, }, + { + Name: "Port--Agent--Workspace--User", + Subdomain: "8080s--agent--workspace--user", + Expected: appurl.ApplicationURL{ + AppSlugOrPort: "8080s", + AgentName: "agent", + WorkspaceName: "workspace", + Username: "user", + }, + }, { Name: "HyphenatedNames", Subdomain: "app-slug--agent-name--workspace-name--user-name", From f2edf05c33a259a9e95592b018d3777f9cef39a2 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Wed, 24 Apr 2024 20:13:30 +0000 Subject: [PATCH 08/12] fmt --- site/static/error.html | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/site/static/error.html b/site/static/error.html index a17abfbf89baa..59b2252866a0e 100644 --- a/site/static/error.html +++ b/site/static/error.html @@ -170,11 +170,10 @@

{{- if .Error.RenderDescriptionMarkdown }} {{ .ErrorDescriptionHTML }} {{ else }}

{{ .Error.Description }}

- {{ end }} - {{- if .Error.AdditionalInfo }} -

{{ .Error.AdditionalInfo }}

- {{ end }} - {{- if .Error.Warnings }} + {{ end }} {{- if .Error.AdditionalInfo }} +
+

{{ .Error.AdditionalInfo }}

+ {{ end }} {{- if .Error.Warnings }}
Warnings

{{- if .Error.RetryEnabled }} - {{ end }} - {{- if .Error.SwitchProtocolLink }} - Switch to {{ .Error.SwitchProtocolTarget }} + {{ end }} {{- if .Error.SwitchProtocolLink }} + Switch to {{ .Error.SwitchProtocolTarget }} {{ end }} Back to site
From c623e5b90ccacc608e1350d37d5cea9c0e3c329c Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Thu, 25 Apr 2024 15:43:44 +0000 Subject: [PATCH 09/12] tests --- coderd/workspaceapps/proxy.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/coderd/workspaceapps/proxy.go b/coderd/workspaceapps/proxy.go index 4797385c4def8..e5c6cbd09a949 100644 --- a/coderd/workspaceapps/proxy.go +++ b/coderd/workspaceapps/proxy.go @@ -545,7 +545,9 @@ func (s *Server) proxyWorkspaceApp(rw http.ResponseWriter, r *http.Request, appT r.URL.Path = path appURL.RawQuery = "" - appURL.Scheme = app.Protocol() + if app.IsPort() { + appURL.Scheme = app.Protocol() + } proxy := s.AgentProvider.ReverseProxy(appURL, s.DashboardURL, appToken.AgentID, app, s.Hostname) From e291e8c724b30edb60c25a5096e0321f7efaf8fe Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Thu, 25 Apr 2024 16:39:41 +0000 Subject: [PATCH 10/12] rename --- coderd/tailnet.go | 6 +++--- coderd/workspaceapps/appurl/appurl.go | 6 +++--- coderd/workspaceapps/proxy.go | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/coderd/tailnet.go b/coderd/tailnet.go index f7ebc453efe1c..d2493c6283659 100644 --- a/coderd/tailnet.go +++ b/coderd/tailnet.go @@ -369,7 +369,7 @@ func (s *ServerTailnet) ReverseProxy(targetURL, dashboardURL *url.URL, agentID u Scheme: dashboardURL.Scheme, } if app.IsPort() { - if app.Protocol() == "https" { + if app.PortProtocol() == "https" { app.ChangePortProtocol("http") } else { app.ChangePortProtocol("https") @@ -377,8 +377,8 @@ func (s *ServerTailnet) ReverseProxy(targetURL, dashboardURL *url.URL, agentID u switchURL.Host = fmt.Sprintf("%s%s", app.String(), strings.TrimPrefix(wildcardHostname, "*")) switchLink = switchURL.String() - switchTarget = app.Protocol() - additional += fmt.Sprintf("This error seems to be due to an app protocol mismatch, try switching to %s.", strings.ToUpper(app.Protocol())) + switchTarget = app.PortProtocol() + additional += fmt.Sprintf("This error seems to be due to an app protocol mismatch, try switching to %s.", strings.ToUpper(app.PortProtocol())) } } diff --git a/coderd/workspaceapps/appurl/appurl.go b/coderd/workspaceapps/appurl/appurl.go index 37d9a20032194..1c4f2d29f1d7f 100644 --- a/coderd/workspaceapps/appurl/appurl.go +++ b/coderd/workspaceapps/appurl/appurl.go @@ -107,7 +107,7 @@ func (a ApplicationURL) IsPort() bool { return true } -func (a ApplicationURL) Protocol() string { +func (a ApplicationURL) PortProtocol() string { if strings.HasSuffix(a.AppSlugOrPort, "s") { trimmed := strings.TrimSuffix(a.AppSlugOrPort, "s") _, err := strconv.ParseInt(trimmed, 10, 64) @@ -121,7 +121,7 @@ func (a ApplicationURL) Protocol() string { func (a *ApplicationURL) ChangePortProtocol(target string) { if target == "http" { - if a.Protocol() == "http" { + if a.PortProtocol() == "http" { return } trimmed := strings.TrimSuffix(a.AppSlugOrPort, "s") @@ -132,7 +132,7 @@ func (a *ApplicationURL) ChangePortProtocol(target string) { } if target == "https" { - if a.Protocol() == "https" { + if a.PortProtocol() == "https" { return } _, err := strconv.ParseInt(a.AppSlugOrPort, 10, 64) diff --git a/coderd/workspaceapps/proxy.go b/coderd/workspaceapps/proxy.go index e5c6cbd09a949..83d4354247223 100644 --- a/coderd/workspaceapps/proxy.go +++ b/coderd/workspaceapps/proxy.go @@ -546,7 +546,7 @@ func (s *Server) proxyWorkspaceApp(rw http.ResponseWriter, r *http.Request, appT r.URL.Path = path appURL.RawQuery = "" if app.IsPort() { - appURL.Scheme = app.Protocol() + appURL.Scheme = app.PortProtocol() } proxy := s.AgentProvider.ReverseProxy(appURL, s.DashboardURL, appToken.AgentID, app, s.Hostname) From bbbd3c2e76a3e17438cb001184d8322e07f1b538 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Fri, 26 Apr 2024 14:42:35 +0000 Subject: [PATCH 11/12] pr comments --- coderd/tailnet.go | 25 +++--- coderd/workspaceapps/appurl/appurl.go | 111 ++++++++++++++++---------- coderd/workspaceapps/proxy.go | 5 +- site/site.go | 4 +- site/static/error.html | 10 +-- 5 files changed, 93 insertions(+), 62 deletions(-) diff --git a/coderd/tailnet.go b/coderd/tailnet.go index d2493c6283659..40feb31773aa8 100644 --- a/coderd/tailnet.go +++ b/coderd/tailnet.go @@ -4,6 +4,7 @@ import ( "bufio" "context" "crypto/tls" + "errors" "fmt" "net" "net/http" @@ -362,23 +363,27 @@ func (s *ServerTailnet) ReverseProxy(targetURL, dashboardURL *url.URL, agentID u switchTarget = "" ) - if strings.Contains(theErr.Error(), "tls:") { + var tlsError tls.RecordHeaderError + if (errors.As(theErr, &tlsError) && tlsError.Msg == "first record does not look like a TLS handshake") || + errors.Is(theErr, http.ErrSchemeMismatch) { // If the error is due to an HTTP/HTTPS mismatch, we can provide a // more helpful error message with redirect buttons. switchURL := url.URL{ Scheme: dashboardURL.Scheme, } - if app.IsPort() { - if app.PortProtocol() == "https" { - app.ChangePortProtocol("http") - } else { - app.ChangePortProtocol("https") + _, protocol, isPort := app.PortInfo() + if isPort { + if protocol == "https" { + app = app.ChangePortProtocol("http") + } + if protocol == "http" { + app = app.ChangePortProtocol("https") } switchURL.Host = fmt.Sprintf("%s%s", app.String(), strings.TrimPrefix(wildcardHostname, "*")) switchLink = switchURL.String() - switchTarget = app.PortProtocol() - additional += fmt.Sprintf("This error seems to be due to an app protocol mismatch, try switching to %s.", strings.ToUpper(app.PortProtocol())) + switchTarget = protocol + additional += fmt.Sprintf("This error seems to be due to an app protocol mismatch, try switching to %s.", strings.ToUpper(protocol)) } } @@ -388,9 +393,9 @@ func (s *ServerTailnet) ReverseProxy(targetURL, dashboardURL *url.URL, agentID u Description: desc, RetryEnabled: true, DashboardURL: dashboardURL.String(), - SwitchProtocolLink: switchLink, - SwitchProtocolTarget: strings.ToUpper(switchTarget), AdditionalInfo: additional, + AdditionalButtonLink: switchLink, + AdditionalButtonText: fmt.Sprintf("Switch to %s", strings.ToUpper(switchTarget)), }) } proxy.Director = s.director(agentID, proxy.Director) diff --git a/coderd/workspaceapps/appurl/appurl.go b/coderd/workspaceapps/appurl/appurl.go index 1c4f2d29f1d7f..75dbda3fd8811 100644 --- a/coderd/workspaceapps/appurl/appurl.go +++ b/coderd/workspaceapps/appurl/appurl.go @@ -84,62 +84,87 @@ func (a ApplicationURL) Path() string { return fmt.Sprintf("/@%s/%s.%s/apps/%s", a.Username, a.WorkspaceName, a.AgentName, a.AppSlugOrPort) } -func (a ApplicationURL) IsPort() bool { - // check if https port - if strings.HasSuffix(a.AppSlugOrPort, "s") { - trimmed := strings.TrimSuffix(a.AppSlugOrPort, "s") - _, err := strconv.ParseInt(trimmed, 10, 64) - //nolint:gosimple - if err != nil { - return false - } - - return true - } - - // check if port at all - _, err := strconv.ParseInt(a.AppSlugOrPort, 10, 64) - //nolint:gosimple - if err != nil { - return false - } - - return true -} +// func (a ApplicationURL) IsPort() bool { +// // check if https port +// if strings.HasSuffix(a.AppSlugOrPort, "s") { +// trimmed := strings.TrimSuffix(a.AppSlugOrPort, "s") +// _, err := strconv.ParseInt(trimmed, 10, 64) +// //nolint:gosimple +// if err != nil { +// return false +// } + +// return true +// } + +// // check if port at all +// _, err := strconv.ParseInt(a.AppSlugOrPort, 10, 64) +// //nolint:gosimple +// if err != nil { +// return false +// } + +// return true +// } + +// func (a ApplicationURL) PortProtocol() string { +// if strings.HasSuffix(a.AppSlugOrPort, "s") { +// trimmed := strings.TrimSuffix(a.AppSlugOrPort, "s") +// _, err := strconv.ParseInt(trimmed, 10, 64) +// if err == nil { +// return "https" +// } +// } + +// return "http" +// } + +func (a ApplicationURL) PortInfo() (uint, string, bool) { + var ( + port uint64 + protocol string + isPort bool + err error + ) -func (a ApplicationURL) PortProtocol() string { if strings.HasSuffix(a.AppSlugOrPort, "s") { trimmed := strings.TrimSuffix(a.AppSlugOrPort, "s") - _, err := strconv.ParseInt(trimmed, 10, 64) + port, err = strconv.ParseUint(trimmed, 10, 16) + if err == nil { + protocol = "https" + isPort = true + } + } else { + port, err = strconv.ParseUint(a.AppSlugOrPort, 10, 16) if err == nil { - return "https" + protocol = "http" + isPort = true } } - return "http" + return uint(port), protocol, isPort } -func (a *ApplicationURL) ChangePortProtocol(target string) { - if target == "http" { - if a.PortProtocol() == "http" { - return - } - trimmed := strings.TrimSuffix(a.AppSlugOrPort, "s") - _, err := strconv.ParseInt(trimmed, 10, 64) - if err == nil { - a.AppSlugOrPort = trimmed - } +func (a *ApplicationURL) ChangePortProtocol(target string) ApplicationURL { + newAppURL := *a + port, protocol, isPort := a.PortInfo() + if !isPort { + return newAppURL + } + + if target == protocol { + return newAppURL } if target == "https" { - if a.PortProtocol() == "https" { - return - } - _, err := strconv.ParseInt(a.AppSlugOrPort, 10, 64) - if err == nil { - a.AppSlugOrPort = fmt.Sprintf("%s%s", a.AppSlugOrPort, "s") - } + newAppURL.AppSlugOrPort = fmt.Sprintf("%ds", port) } + + if target == "http" { + newAppURL.AppSlugOrPort = fmt.Sprintf("%d", port) + } + + return newAppURL } // ParseSubdomainAppURL parses an ApplicationURL from the given subdomain. If diff --git a/coderd/workspaceapps/proxy.go b/coderd/workspaceapps/proxy.go index 83d4354247223..7bf470a3cc416 100644 --- a/coderd/workspaceapps/proxy.go +++ b/coderd/workspaceapps/proxy.go @@ -545,8 +545,9 @@ func (s *Server) proxyWorkspaceApp(rw http.ResponseWriter, r *http.Request, appT r.URL.Path = path appURL.RawQuery = "" - if app.IsPort() { - appURL.Scheme = app.PortProtocol() + _, protocol, isPort := app.PortInfo() + if isPort { + appURL.Scheme = protocol } proxy := s.AgentProvider.ReverseProxy(appURL, s.DashboardURL, appToken.AgentID, app, s.Hostname) diff --git a/site/site.go b/site/site.go index baf69710f29d1..1d9d31d5fc21b 100644 --- a/site/site.go +++ b/site/site.go @@ -792,9 +792,9 @@ type ErrorPageData struct { RetryEnabled bool DashboardURL string Warnings []string - SwitchProtocolLink string - SwitchProtocolTarget string AdditionalInfo string + AdditionalButtonLink string + AdditionalButtonText string RenderDescriptionMarkdown bool } diff --git a/site/static/error.html b/site/static/error.html index 59b2252866a0e..9c4cdfca42ed1 100644 --- a/site/static/error.html +++ b/site/static/error.html @@ -198,12 +198,12 @@

Warnings

{{ end }}
- {{- if .Error.RetryEnabled }} - - {{ end }} {{- if .Error.SwitchProtocolLink }} - Switch to {{ .Error.SwitchProtocolTarget }}Switch to {{ .Error.AdditionalButtonText }} + {{ end }} {{- if .Error.RetryEnabled }} + {{ end }} Back to site
From 8b08a162a44c0c80179586c12651652b00367217 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Fri, 26 Apr 2024 15:12:49 +0000 Subject: [PATCH 12/12] clean up --- coderd/tailnet.go | 27 ++++++++++---------- coderd/workspaceapps/appurl/appurl.go | 36 +-------------------------- site/static/error.html | 8 +++--- 3 files changed, 18 insertions(+), 53 deletions(-) diff --git a/coderd/tailnet.go b/coderd/tailnet.go index 40feb31773aa8..e995f92fe6d61 100644 --- a/coderd/tailnet.go +++ b/coderd/tailnet.go @@ -357,10 +357,10 @@ func (s *ServerTailnet) ReverseProxy(targetURL, dashboardURL *url.URL, agentID u proxy := httputil.NewSingleHostReverseProxy(&tgt) proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, theErr error) { var ( - desc = "Failed to proxy request to application: " + theErr.Error() - additional = "" - switchLink = "" - switchTarget = "" + desc = "Failed to proxy request to application: " + theErr.Error() + additionalInfo = "" + additionalButtonLink = "" + additionalButtonText = "" ) var tlsError tls.RecordHeaderError @@ -373,17 +373,16 @@ func (s *ServerTailnet) ReverseProxy(targetURL, dashboardURL *url.URL, agentID u } _, protocol, isPort := app.PortInfo() if isPort { + targetProtocol := "https" if protocol == "https" { - app = app.ChangePortProtocol("http") - } - if protocol == "http" { - app = app.ChangePortProtocol("https") + targetProtocol = "http" } + app = app.ChangePortProtocol(targetProtocol) switchURL.Host = fmt.Sprintf("%s%s", app.String(), strings.TrimPrefix(wildcardHostname, "*")) - switchLink = switchURL.String() - switchTarget = protocol - additional += fmt.Sprintf("This error seems to be due to an app protocol mismatch, try switching to %s.", strings.ToUpper(protocol)) + additionalButtonLink = switchURL.String() + additionalButtonText = fmt.Sprintf("Switch to %s", strings.ToUpper(targetProtocol)) + additionalInfo += fmt.Sprintf("This error seems to be due to an app protocol mismatch, try switching to %s.", strings.ToUpper(targetProtocol)) } } @@ -393,9 +392,9 @@ func (s *ServerTailnet) ReverseProxy(targetURL, dashboardURL *url.URL, agentID u Description: desc, RetryEnabled: true, DashboardURL: dashboardURL.String(), - AdditionalInfo: additional, - AdditionalButtonLink: switchLink, - AdditionalButtonText: fmt.Sprintf("Switch to %s", strings.ToUpper(switchTarget)), + AdditionalInfo: additionalInfo, + AdditionalButtonLink: additionalButtonLink, + AdditionalButtonText: additionalButtonText, }) } proxy.Director = s.director(agentID, proxy.Director) diff --git a/coderd/workspaceapps/appurl/appurl.go b/coderd/workspaceapps/appurl/appurl.go index 75dbda3fd8811..31ec677354b79 100644 --- a/coderd/workspaceapps/appurl/appurl.go +++ b/coderd/workspaceapps/appurl/appurl.go @@ -84,41 +84,7 @@ func (a ApplicationURL) Path() string { return fmt.Sprintf("/@%s/%s.%s/apps/%s", a.Username, a.WorkspaceName, a.AgentName, a.AppSlugOrPort) } -// func (a ApplicationURL) IsPort() bool { -// // check if https port -// if strings.HasSuffix(a.AppSlugOrPort, "s") { -// trimmed := strings.TrimSuffix(a.AppSlugOrPort, "s") -// _, err := strconv.ParseInt(trimmed, 10, 64) -// //nolint:gosimple -// if err != nil { -// return false -// } - -// return true -// } - -// // check if port at all -// _, err := strconv.ParseInt(a.AppSlugOrPort, 10, 64) -// //nolint:gosimple -// if err != nil { -// return false -// } - -// return true -// } - -// func (a ApplicationURL) PortProtocol() string { -// if strings.HasSuffix(a.AppSlugOrPort, "s") { -// trimmed := strings.TrimSuffix(a.AppSlugOrPort, "s") -// _, err := strconv.ParseInt(trimmed, 10, 64) -// if err == nil { -// return "https" -// } -// } - -// return "http" -// } - +// PortInfo returns the port, protocol, and whether the AppSlugOrPort is a port or not. func (a ApplicationURL) PortInfo() (uint, string, bool) { var ( port uint64 diff --git a/site/static/error.html b/site/static/error.html index 9c4cdfca42ed1..d0eee1bb4a402 100644 --- a/site/static/error.html +++ b/site/static/error.html @@ -33,7 +33,7 @@ .container { --side-padding: 24px; width: 100%; - max-width: calc(460px + var(--side-padding) * 2); + max-width: calc(500px + var(--side-padding) * 2); padding: 0 var(--side-padding); text-align: center; } @@ -198,9 +198,9 @@

Warnings

{{ end }}
- {{- if and .Error.AdditionalButtonText .Error.AdditionButtonLink }} - Switch to {{ .Error.AdditionalButtonText }}{{ .Error.AdditionalButtonText }} {{ end }} {{- if .Error.RetryEnabled }}