Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: add switch http(s) button to error page#12942

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
f0ssel merged 12 commits intomainfromf0ssel/tls-error-page
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletionMakefile
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand Down
51 changes: 44 additions & 7 deletionscoderd/tailnet.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,11 +4,14 @@ import (
"bufio"
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
"net/http/httputil"
"net/netip"
"net/url"
"strings"
"sync"
"sync/atomic"
"time"
Expand All@@ -23,6 +26,7 @@ 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/workspacesdk"
"github.com/coder/coder/v2/site"
"github.com/coder/coder/v2/tailnet"
Expand DownExpand Up@@ -341,7 +345,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, 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
Expand All@@ -351,13 +355,46 @@ 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 (
desc = "Failed to proxy request to application: " + theErr.Error()
additionalInfo = ""
additionalButtonLink = ""
additionalButtonText = ""
)

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,
}
_, protocol, isPort := app.PortInfo()
if isPort {
targetProtocol := "https"
if protocol == "https" {
targetProtocol = "http"
}
app = app.ChangePortProtocol(targetProtocol)

switchURL.Host = fmt.Sprintf("%s%s", app.String(), strings.TrimPrefix(wildcardHostname, "*"))
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))
}
}

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(),
AdditionalInfo: additionalInfo,
AdditionalButtonLink: additionalButtonLink,
AdditionalButtonText: additionalButtonText,
})
}
proxy.Director = s.director(agentID, proxy.Director)
Expand Down
17 changes: 9 additions & 8 deletionscoderd/tailnet_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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"
Expand DownExpand Up@@ -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(
Expand DownExpand Up@@ -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(
Expand DownExpand Up@@ -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(
Expand DownExpand Up@@ -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)
Expand DownExpand Up@@ -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()
Expand DownExpand Up@@ -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(
Expand DownExpand Up@@ -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(
Expand DownExpand Up@@ -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(
Expand Down
50 changes: 50 additions & 0 deletionscoderd/workspaceapps/appurl/appurl.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ import (
"net"
"net/url"
"regexp"
"strconv"
"strings"

"golang.org/x/xerrors"
Expand DownExpand Up@@ -83,6 +84,55 @@ func (a ApplicationURL) Path() string {
return fmt.Sprintf("/@%s/%s.%s/apps/%s", a.Username, a.WorkspaceName, a.AgentName, a.AppSlugOrPort)
}

// PortInfo returns the port, protocol, and whether the AppSlugOrPort is a port or not.
func (a ApplicationURL) PortInfo() (uint, string, bool) {
var (
port uint64
protocol string
isPort bool
err error
)

if strings.HasSuffix(a.AppSlugOrPort, "s") {
trimmed := strings.TrimSuffix(a.AppSlugOrPort, "s")
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 {
protocol = "http"
isPort = true
}
}

return uint(port), protocol, isPort
}

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" {
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
// 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
Expand Down
10 changes: 10 additions & 0 deletionscoderd/workspaceapps/appurl/appurl_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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",
Expand Down
14 changes: 9 additions & 5 deletionscoderd/workspaceapps/proxy.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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, 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)
Expand DownExpand Up@@ -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.
Expand DownExpand Up@@ -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))
})
}
Expand DownExpand Up@@ -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.
Expand DownExpand Up@@ -545,8 +545,12 @@ func (s *Server) proxyWorkspaceApp(rw http.ResponseWriter, r *http.Request, appT

r.URL.Path = path
appURL.RawQuery = ""
_, protocol, isPort := app.PortInfo()
if isPort {
appURL.Scheme = protocol
}

proxy := s.AgentProvider.ReverseProxy(appURL, s.DashboardURL, appToken.AgentID)
proxy := s.AgentProvider.ReverseProxy(appURL, s.DashboardURL, appToken.AgentID, app, s.Hostname)

proxy.ModifyResponse = func(r *http.Response) error {
r.Header.Del(httpmw.AccessControlAllowOriginHeader)
Expand Down
15 changes: 9 additions & 6 deletionssite/site.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -786,12 +786,15 @@ 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
AdditionalInfo string
AdditionalButtonLink string
AdditionalButtonText string

RenderDescriptionMarkdown bool
}
Expand Down
11 changes: 9 additions & 2 deletionssite/static/error.html
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,7 +33,7 @@
.container {
--side-padding: 24px;
width: 100%;
max-width: calc(320px + var(--side-padding) * 2);
max-width: calc(500px + var(--side-padding) * 2);
padding: 0 var(--side-padding);
text-align: center;
}
Expand DownExpand Up@@ -170,6 +170,9 @@ <h1>
{{- if .Error.RenderDescriptionMarkdown }} {{ .ErrorDescriptionHTML }} {{
else }}
<p>{{ .Error.Description }}</p>
{{ end }} {{- if .Error.AdditionalInfo }}
<br />
<p>{{ .Error.AdditionalInfo }}</p>
{{ end }} {{- if .Error.Warnings }}
<div class="warning">
<div class="warning-title">
Expand All@@ -195,7 +198,11 @@ <h3>Warnings</h3>
</div>
{{ end }}
<div class="button-group">
{{- if .Error.RetryEnabled }}
{{- if and .Error.AdditionalButtonText .Error.AdditionalButtonLink }}
<a href="{{ .Error.AdditionalButtonLink }}"
>{{ .Error.AdditionalButtonText }}</a
>
{{ end }} {{- if .Error.RetryEnabled }}
<button onclick="window.location.reload()">Retry</button>
{{ end }}
<a href="{{ .Error.DashboardURL }}">Back to site</a>
Expand Down

[8]ページ先頭

©2009-2025 Movatter.jp