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

Commit8b17bf9

Browse files
authored
fix: prepend scheme to access url (#3317)
- Problems can arise spawning workspaces if a schemeless URL is passed as the access URL. If an access url is detected to not have an "http" or "https" scheme then it is prepended with "https". If the hostname is detected to be a loopback device then "http" is preferred.
1 parentf82df1b commit8b17bf9

File tree

2 files changed

+130
-34
lines changed

2 files changed

+130
-34
lines changed

‎cli/server.go

Lines changed: 58 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -217,22 +217,23 @@ func server() *cobra.Command {
217217
accessURL=devTunnel.URL
218218
}
219219

220+
accessURLParsed,err:=parseURL(ctx,accessURL)
221+
iferr!=nil {
222+
returnxerrors.Errorf("parse URL: %w",err)
223+
}
224+
220225
// Warn the user if the access URL appears to be a loopback address.
221-
isLocal,err:=isLocalURL(ctx,accessURL)
226+
isLocal,err:=isLocalURL(ctx,accessURLParsed)
222227
ifisLocal||err!=nil {
223228
reason:="could not be resolved"
224229
ifisLocal {
225230
reason="isn't externally reachable"
226231
}
227-
cmd.Printf("%s The access URL %s %s, this may cause unexpected problems when creating workspaces. Generate a unique *.try.coder.app URL with:\n",cliui.Styles.Warn.Render("Warning:"),cliui.Styles.Field.Render(accessURL),reason)
232+
cmd.Printf("%s The access URL %s %s, this may cause unexpected problems when creating workspaces. Generate a unique *.try.coder.app URL with:\n",cliui.Styles.Warn.Render("Warning:"),cliui.Styles.Field.Render(accessURLParsed.String()),reason)
228233
cmd.Println(cliui.Styles.Code.Render(strings.Join(os.Args," ")+" --tunnel"))
229234
}
230-
cmd.Printf("View the Web UI: %s\n",accessURL)
231235

232-
accessURLParsed,err:=url.Parse(accessURL)
233-
iferr!=nil {
234-
returnxerrors.Errorf("parse access url %q: %w",accessURL,err)
235-
}
236+
cmd.Printf("View the Web UI: %s\n",accessURLParsed.String())
236237

237238
// Used for zero-trust instance identity with Google Cloud.
238239
googleTokenValidator,err:=idtoken.NewValidator(ctx,option.WithoutAuthentication())
@@ -324,7 +325,7 @@ func server() *cobra.Command {
324325
}
325326

326327
// Parse the raw telemetry URL!
327-
telemetryURL,err:=url.Parse(telemetryURL)
328+
telemetryURL,err:=parseURL(ctx,telemetryURL)
328329
iferr!=nil {
329330
returnxerrors.Errorf("parse telemetry url: %w",err)
330331
}
@@ -440,7 +441,7 @@ func server() *cobra.Command {
440441
if!hasFirstUser&&err==nil {
441442
cmd.Println()
442443
cmd.Println("Get started by creating the first user (in a new terminal):")
443-
cmd.Println(cliui.Styles.Code.Render("coder login "+accessURL))
444+
cmd.Println(cliui.Styles.Code.Render("coder login "+accessURLParsed.String()))
444445
}
445446

446447
cmd.Println("\n==> Logs will stream in below (press ctrl+c to gracefully exit):")
@@ -672,6 +673,54 @@ func server() *cobra.Command {
672673
returnroot
673674
}
674675

676+
// parseURL parses a string into a URL. It works around some technically correct
677+
// but undesired behavior of url.Parse by prepending a scheme if one does not
678+
// exist so that the URL does not get parsed improprely.
679+
funcparseURL(ctx context.Context,ustring) (*url.URL,error) {
680+
var (
681+
hasScheme=strings.HasPrefix(u,"http:")||strings.HasPrefix(u,"https:")
682+
)
683+
684+
if!hasScheme {
685+
// Append a scheme if it doesn't have one. Otherwise the hostname
686+
// will likely get parsed as the scheme and cause methods like Hostname()
687+
// to return an empty string, largely obviating the purpose of this
688+
// function.
689+
u="https://"+u
690+
}
691+
692+
parsed,err:=url.Parse(u)
693+
iferr!=nil {
694+
returnnil,err
695+
}
696+
697+
// If the specified url is a loopback device and no scheme has been
698+
// specified, prefer http over https. It's unlikely anyone intends to use
699+
// https on a loopback and if they do they can specify a scheme.
700+
iflocal,_:=isLocalURL(ctx,parsed);local&&!hasScheme {
701+
parsed.Scheme="http"
702+
}
703+
704+
returnparsed,nil
705+
}
706+
707+
// isLocalURL returns true if the hostname of the provided URL appears to
708+
// resolve to a loopback address.
709+
funcisLocalURL(ctx context.Context,u*url.URL) (bool,error) {
710+
resolver:=&net.Resolver{}
711+
ips,err:=resolver.LookupIPAddr(ctx,u.Hostname())
712+
iferr!=nil {
713+
returnfalse,err
714+
}
715+
716+
for_,ip:=rangeips {
717+
ifip.IP.IsLoopback() {
718+
returntrue,nil
719+
}
720+
}
721+
returnfalse,nil
722+
}
723+
675724
funcshutdownWithTimeout(sinterface{Shutdown(context.Context)error },timeout time.Duration)error {
676725
ctx,cancel:=context.WithTimeout(context.Background(),timeout)
677726
defercancel()
@@ -922,27 +971,6 @@ func serveHandler(ctx context.Context, logger slog.Logger, handler http.Handler,
922971
returnfunc() {_=srv.Close() }
923972
}
924973

925-
// isLocalURL returns true if the hostname of the provided URL appears to
926-
// resolve to a loopback address.
927-
funcisLocalURL(ctx context.Context,urlStringstring) (bool,error) {
928-
parsedURL,err:=url.Parse(urlString)
929-
iferr!=nil {
930-
returnfalse,err
931-
}
932-
resolver:=&net.Resolver{}
933-
ips,err:=resolver.LookupIPAddr(ctx,parsedURL.Hostname())
934-
iferr!=nil {
935-
returnfalse,err
936-
}
937-
938-
for_,ip:=rangeips {
939-
ifip.IP.IsLoopback() {
940-
returntrue,nil
941-
}
942-
}
943-
returnfalse,nil
944-
}
945-
946974
// embeddedPostgresURL returns the URL for the embedded PostgreSQL deployment.
947975
funcembeddedPostgresURL(cfg config.Root) (string,error) {
948976
pgPassword,err:=cfg.PostgresPassword().Read()

‎cli/server_test.go

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"time"
2222

2323
"github.com/go-chi/chi"
24-
"github.com/stretchr/testify/assert"
2524
"github.com/stretchr/testify/require"
2625
"go.uber.org/goleak"
2726

@@ -113,7 +112,10 @@ func TestServer(t *testing.T) {
113112
pty.ExpectMatch("psql")
114113
})
115114

116-
t.Run("NoWarningWithRemoteAccessURL",func(t*testing.T) {
115+
// Validate that an http scheme is prepended to a loopback
116+
// access URL and that a warning is printed that it may not be externally
117+
// reachable.
118+
t.Run("NoSchemeLocalAccessURL",func(t*testing.T) {
117119
t.Parallel()
118120
ctx,cancelFunc:=context.WithCancel(context.Background())
119121
defercancelFunc()
@@ -122,7 +124,7 @@ func TestServer(t *testing.T) {
122124
"server",
123125
"--in-memory",
124126
"--address",":0",
125-
"--access-url","http://1.2.3.4:3000/",
127+
"--access-url","localhost:3000/",
126128
"--cache-dir",t.TempDir(),
127129
)
128130
buf:=newThreadSafeBuffer()
@@ -141,8 +143,74 @@ func TestServer(t *testing.T) {
141143

142144
cancelFunc()
143145
require.ErrorIs(t,<-errC,context.Canceled)
146+
require.Contains(t,buf.String(),"this may cause unexpected problems when creating workspaces")
147+
require.Contains(t,buf.String(),"View the Web UI: http://localhost:3000/\n")
148+
})
149+
150+
// Validate that an https scheme is prepended to a remote access URL
151+
// and that a warning is printed for a host that cannot be resolved.
152+
t.Run("NoSchemeRemoteAccessURL",func(t*testing.T) {
153+
t.Parallel()
154+
ctx,cancelFunc:=context.WithCancel(context.Background())
155+
defercancelFunc()
144156

145-
assert.NotContains(t,buf.String(),"Workspaces must be able to reach Coder from this URL")
157+
root,cfg:=clitest.New(t,
158+
"server",
159+
"--in-memory",
160+
"--address",":0",
161+
"--access-url","foobarbaz.mydomain",
162+
"--cache-dir",t.TempDir(),
163+
)
164+
buf:=newThreadSafeBuffer()
165+
root.SetOutput(buf)
166+
errC:=make(chanerror,1)
167+
gofunc() {
168+
errC<-root.ExecuteContext(ctx)
169+
}()
170+
171+
// Just wait for startup
172+
require.Eventually(t,func()bool {
173+
varerrerror
174+
_,err=cfg.URL().Read()
175+
returnerr==nil
176+
},15*time.Second,25*time.Millisecond)
177+
178+
cancelFunc()
179+
require.ErrorIs(t,<-errC,context.Canceled)
180+
require.Contains(t,buf.String(),"this may cause unexpected problems when creating workspaces")
181+
require.Contains(t,buf.String(),"View the Web UI: https://foobarbaz.mydomain\n")
182+
})
183+
184+
t.Run("NoWarningWithRemoteAccessURL",func(t*testing.T) {
185+
t.Parallel()
186+
ctx,cancelFunc:=context.WithCancel(context.Background())
187+
defercancelFunc()
188+
189+
root,cfg:=clitest.New(t,
190+
"server",
191+
"--in-memory",
192+
"--address",":0",
193+
"--access-url","https://google.com",
194+
"--cache-dir",t.TempDir(),
195+
)
196+
buf:=newThreadSafeBuffer()
197+
root.SetOutput(buf)
198+
errC:=make(chanerror,1)
199+
gofunc() {
200+
errC<-root.ExecuteContext(ctx)
201+
}()
202+
203+
// Just wait for startup
204+
require.Eventually(t,func()bool {
205+
varerrerror
206+
_,err=cfg.URL().Read()
207+
returnerr==nil
208+
},15*time.Second,25*time.Millisecond)
209+
210+
cancelFunc()
211+
require.ErrorIs(t,<-errC,context.Canceled)
212+
require.NotContains(t,buf.String(),"this may cause unexpected problems when creating workspaces")
213+
require.Contains(t,buf.String(),"View the Web UI: https://google.com\n")
146214
})
147215

148216
t.Run("TLSBadVersion",func(t*testing.T) {

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp