From 72255c8bfbc90f92ae3407d7f6ec82c3822692a7 Mon Sep 17 00:00:00 2001 From: David Wahler Date: Fri, 10 Jun 2022 16:16:41 +0000 Subject: [PATCH 1/4] feat: Warn user if access URL is localhost --- cli/server.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/cli/server.go b/cli/server.go index 2fbc645edb1b9..e93a6cab5ae73 100644 --- a/cli/server.go +++ b/cli/server.go @@ -203,6 +203,28 @@ func server() *cobra.Command { _, _ = fmt.Fprintln(cmd.ErrOrStderr()) } + // Warn the user if the access URL appears to be a loopback address. + isLocal, err := isLocalURL(cmd.Context(), accessURL) + if isLocal || err != nil { + var reason string + if isLocal { + reason = "appears to be a loopback address" + } else { + reason = "could not be resolved" + } + _, _ = fmt.Fprintf(cmd.ErrOrStderr(), cliui.Styles.Wrap.Render( + cliui.Styles.Warn.Render("Warning:")+" The current access URL:")+"\n\n") + _, _ = fmt.Fprintf(cmd.ErrOrStderr(), " "+cliui.Styles.Field.Render(accessURL)+"\n\n") + _, _ = fmt.Fprintf(cmd.ErrOrStderr(), cliui.Styles.Wrap.Render( + reason+". Provisioned workspaces are unlikely to be able to "+ + "connect to Coder. Please consider changing your "+ + "access URL using the --access-url option, or directly "+ + "specifying access URLs on templates.", + )+"\n\n") + _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "For more information, see "+ + "https://github.com/coder/coder/issues/1528\n\n") + } + validator, err := idtoken.NewValidator(cmd.Context(), option.WithoutAuthentication()) if err != nil { return err @@ -803,3 +825,24 @@ func serveHandler(ctx context.Context, logger slog.Logger, handler http.Handler, return func() { _ = srv.Close() } } + +// isLocalURL returns true if the hostname of the provided URL appears to +// resolve to a loopback address. +func isLocalURL(ctx context.Context, urlString string) (bool, error) { + url, err := url.Parse(urlString) + if err != nil { + return false, err + } + resolver := &net.Resolver{} + ips, err := resolver.LookupIPAddr(ctx, url.Hostname()) + if err != nil { + return false, err + } + + for _, ip := range ips { + if ip.IP.IsLoopback() { + return true, nil + } + } + return false, nil +} From 70f76e9669550dbf2b316ad6c82aa7ffdfbd74d9 Mon Sep 17 00:00:00 2001 From: David Wahler Date: Fri, 10 Jun 2022 16:22:05 +0000 Subject: [PATCH 2/4] fix linter --- cli/server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/server.go b/cli/server.go index e93a6cab5ae73..f729a9428608b 100644 --- a/cli/server.go +++ b/cli/server.go @@ -829,12 +829,12 @@ func serveHandler(ctx context.Context, logger slog.Logger, handler http.Handler, // isLocalURL returns true if the hostname of the provided URL appears to // resolve to a loopback address. func isLocalURL(ctx context.Context, urlString string) (bool, error) { - url, err := url.Parse(urlString) + parsedURL, err := url.Parse(urlString) if err != nil { return false, err } resolver := &net.Resolver{} - ips, err := resolver.LookupIPAddr(ctx, url.Hostname()) + ips, err := resolver.LookupIPAddr(ctx, parsedURL.Hostname()) if err != nil { return false, err } From 90a3954b5fc62de263d80d6908ec8761e3db2322 Mon Sep 17 00:00:00 2001 From: David Wahler Date: Fri, 10 Jun 2022 16:53:39 +0000 Subject: [PATCH 3/4] add tests for localhost access URL --- cli/server_test.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/cli/server_test.go b/cli/server_test.go index c9afbf2677158..b197219306d92 100644 --- a/cli/server_test.go +++ b/cli/server_test.go @@ -118,6 +118,9 @@ func TestServer(t *testing.T) { } else { t.Error("expected password line output; got no match") } + + // Verify that we warned the user about the default access URL possibly not being what they want. + assert.Contains(t, buf.String(), "coder/coder/issues/1528") }) // Duplicated test from "Development" above to test setting email/password via env. @@ -163,6 +166,32 @@ func TestServer(t *testing.T) { assert.Contains(t, buf.String(), fmt.Sprintf("password: %s", wantPassword), "expected output %q; got no match", wantPassword) }) + t.Run("NoWarningWithRemoteAccessURL", func(t *testing.T) { + t.Parallel() + ctx, cancelFunc := context.WithCancel(context.Background()) + defer cancelFunc() + + root, cfg := clitest.New(t, "server", "--dev", "--tunnel=false", "--address", ":0", "--access-url", "http://1.2.3.4:3000/") + var buf strings.Builder + errC := make(chan error) + root.SetOutput(&buf) + go func() { + errC <- root.ExecuteContext(ctx) + }() + + // Just wait for startup + require.Eventually(t, func() bool { + var err error + _, err = cfg.URL().Read() + return err == nil + }, 15*time.Second, 25*time.Millisecond) + + assert.NotContains(t, buf.String(), "coder/coder/issues/1528") + + cancelFunc() + require.ErrorIs(t, <-errC, context.Canceled) + }) + t.Run("TLSBadVersion", func(t *testing.T) { t.Parallel() ctx, cancelFunc := context.WithCancel(context.Background()) From 4d6aa886812cf8d5c75733e3f56c2db4f736368e Mon Sep 17 00:00:00 2001 From: David Wahler Date: Fri, 10 Jun 2022 18:14:26 +0000 Subject: [PATCH 4/4] fix test race --- cli/server_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/server_test.go b/cli/server_test.go index b197219306d92..7af01138e8c2c 100644 --- a/cli/server_test.go +++ b/cli/server_test.go @@ -186,10 +186,10 @@ func TestServer(t *testing.T) { return err == nil }, 15*time.Second, 25*time.Millisecond) - assert.NotContains(t, buf.String(), "coder/coder/issues/1528") - cancelFunc() require.ErrorIs(t, <-errC, context.Canceled) + + assert.NotContains(t, buf.String(), "coder/coder/issues/1528") }) t.Run("TLSBadVersion", func(t *testing.T) {