mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: allow ports in wildcard url configuration (#11657)
* fix: allow ports in wildcard url configuration This just forwards the port to the ui that generates urls. Our existing parsing + regex already supported ports for subdomain app requests.
This commit is contained in:
@@ -963,6 +963,38 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("WildcardPortOK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Manually specifying a port should override the access url port on
|
||||
// the app host.
|
||||
appDetails := setupProxyTest(t, &DeploymentOptions{
|
||||
// Just throw both the wsproxy and primary to same url.
|
||||
AppHost: "*.test.coder.com:4444",
|
||||
PrimaryAppHost: "*.test.coder.com:4444",
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
u := appDetails.SubdomainAppURL(appDetails.Apps.Owner)
|
||||
t.Logf("url: %s", u)
|
||||
require.Equal(t, "4444", u.Port(), "port should be 4444")
|
||||
|
||||
// Assert the api response the UI uses has the port.
|
||||
apphost, err := appDetails.SDKClient.AppHost(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "*.test.coder.com:4444", apphost.Host, "apphost has port")
|
||||
|
||||
resp, err := requestWithRetries(ctx, t, appDetails.AppClient(t), http.MethodGet, u.String(), nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, proxyTestAppBody, string(body))
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("SuffixWildcardNotMatch", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ const (
|
||||
// DeploymentOptions are the options for creating a *Deployment with a
|
||||
// DeploymentFactory.
|
||||
type DeploymentOptions struct {
|
||||
PrimaryAppHost string
|
||||
AppHost string
|
||||
DisablePathApps bool
|
||||
DisableSubdomainApps bool
|
||||
@@ -407,7 +408,7 @@ func createWorkspaceWithApps(t *testing.T, client *codersdk.Client, orgID uuid.U
|
||||
Username: me.Username,
|
||||
}
|
||||
proxyURL := "http://" + appHost.String() + strings.ReplaceAll(primaryAppHost.Host, "*", "")
|
||||
require.Equal(t, proxyURL, manifest.VSCodePortProxyURI)
|
||||
require.Equal(t, manifest.VSCodePortProxyURI, proxyURL)
|
||||
}
|
||||
agentCloser := agent.New(agent.Options{
|
||||
Client: agentClient,
|
||||
|
||||
@@ -3,6 +3,7 @@ package appurl
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -20,6 +21,36 @@ var (
|
||||
validHostnameLabelRegex = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`)
|
||||
)
|
||||
|
||||
// SubdomainAppHost returns the URL of the apphost for subdomain based apps.
|
||||
// It will omit the scheme.
|
||||
//
|
||||
// Arguments:
|
||||
// apphost: Expected to contain a wildcard, example: "*.coder.com"
|
||||
// accessURL: The access url for the deployment.
|
||||
//
|
||||
// Returns:
|
||||
// 'apphost:port'
|
||||
//
|
||||
// For backwards compatibility and for "accessurl=localhost:0" purposes, we need
|
||||
// to use the port from the accessurl if the apphost doesn't have a port.
|
||||
// If the user specifies a port in the apphost, we will use that port instead.
|
||||
func SubdomainAppHost(apphost string, accessURL *url.URL) string {
|
||||
if apphost == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if apphost != "" && accessURL.Port() != "" {
|
||||
// This should always parse if we prepend a scheme. We should add
|
||||
// the access url port if the apphost doesn't have a port specified.
|
||||
appHostU, err := url.Parse(fmt.Sprintf("https://%s", apphost))
|
||||
if err != nil || (err == nil && appHostU.Port() == "") {
|
||||
apphost += fmt.Sprintf(":%s", accessURL.Port())
|
||||
}
|
||||
}
|
||||
|
||||
return apphost
|
||||
}
|
||||
|
||||
// ApplicationURL is a parsed application URL hostname.
|
||||
type ApplicationURL struct {
|
||||
Prefix string
|
||||
@@ -140,9 +171,7 @@ func CompileHostnamePattern(pattern string) (*regexp.Regexp, error) {
|
||||
if strings.Contains(pattern, "http:") || strings.Contains(pattern, "https:") {
|
||||
return nil, xerrors.Errorf("hostname pattern must not contain a scheme: %q", pattern)
|
||||
}
|
||||
if strings.Contains(pattern, ":") {
|
||||
return nil, xerrors.Errorf("hostname pattern must not contain a port: %q", pattern)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(pattern, ".") || strings.HasSuffix(pattern, ".") {
|
||||
return nil, xerrors.Errorf("hostname pattern must not start or end with a period: %q", pattern)
|
||||
}
|
||||
@@ -155,6 +184,16 @@ func CompileHostnamePattern(pattern string) (*regexp.Regexp, error) {
|
||||
if !strings.HasPrefix(pattern, "*") {
|
||||
return nil, xerrors.Errorf("hostname pattern must only contain an asterisk at the beginning: %q", pattern)
|
||||
}
|
||||
|
||||
// If there is a hostname:port, we only care about the hostname. For hostname
|
||||
// pattern reasons, we do not actually care what port the client is requesting.
|
||||
// Any port provided here is used for generating urls for the ui, not for
|
||||
// validation.
|
||||
hostname, _, err := net.SplitHostPort(pattern)
|
||||
if err == nil {
|
||||
pattern = hostname
|
||||
}
|
||||
|
||||
for i, label := range strings.Split(pattern, ".") {
|
||||
if i == 0 {
|
||||
// We have to allow the asterisk to be a valid hostname label, so
|
||||
|
||||
@@ -193,11 +193,6 @@ func TestCompileHostnamePattern(t *testing.T) {
|
||||
pattern: "https://*.hi.com",
|
||||
errorContains: "must not contain a scheme",
|
||||
},
|
||||
{
|
||||
name: "Invalid_ContainsPort",
|
||||
pattern: "*.hi.com:8080",
|
||||
errorContains: "must not contain a port",
|
||||
},
|
||||
{
|
||||
name: "Invalid_StartPeriod",
|
||||
pattern: ".hi.com",
|
||||
@@ -249,6 +244,13 @@ func TestCompileHostnamePattern(t *testing.T) {
|
||||
errorContains: "contains invalid label",
|
||||
},
|
||||
|
||||
{
|
||||
name: "Valid_ContainsPort",
|
||||
pattern: "*.hi.com:8080",
|
||||
// Although a port is provided, the regex already matches any port.
|
||||
// So it is ignored for validation purposes.
|
||||
expectedRegex: `([^.]+)\.hi\.com`,
|
||||
},
|
||||
{
|
||||
name: "Valid_Simple",
|
||||
pattern: "*.hi",
|
||||
|
||||
Reference in New Issue
Block a user