mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
feat: allow http and https listening simultaneously (#5365)
This commit is contained in:
@ -55,7 +55,7 @@ func TestServer(t *testing.T) {
|
||||
|
||||
root, cfg := clitest.New(t,
|
||||
"server",
|
||||
"--address", ":0",
|
||||
"--http-address", ":0",
|
||||
"--access-url", "http://example.com",
|
||||
"--postgres-url", connectionURL,
|
||||
"--cache-dir", t.TempDir(),
|
||||
@ -89,7 +89,7 @@ func TestServer(t *testing.T) {
|
||||
|
||||
root, cfg := clitest.New(t,
|
||||
"server",
|
||||
"--address", ":0",
|
||||
"--http-address", ":0",
|
||||
"--access-url", "http://example.com",
|
||||
"--cache-dir", t.TempDir(),
|
||||
)
|
||||
@ -129,7 +129,7 @@ func TestServer(t *testing.T) {
|
||||
root, cfg := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--address", ":0",
|
||||
"--http-address", ":0",
|
||||
"--access-url", "http://localhost:3000/",
|
||||
"--cache-dir", t.TempDir(),
|
||||
)
|
||||
@ -161,7 +161,7 @@ func TestServer(t *testing.T) {
|
||||
root, cfg := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--address", ":0",
|
||||
"--http-address", ":0",
|
||||
"--access-url", "https://foobarbaz.mydomain",
|
||||
"--cache-dir", t.TempDir(),
|
||||
)
|
||||
@ -191,7 +191,7 @@ func TestServer(t *testing.T) {
|
||||
root, cfg := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--address", ":0",
|
||||
"--http-address", ":0",
|
||||
"--access-url", "https://google.com",
|
||||
"--cache-dir", t.TempDir(),
|
||||
)
|
||||
@ -220,7 +220,7 @@ func TestServer(t *testing.T) {
|
||||
root, _ := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--address", ":0",
|
||||
"--http-address", ":0",
|
||||
"--access-url", "google.com",
|
||||
"--cache-dir", t.TempDir(),
|
||||
)
|
||||
@ -236,9 +236,10 @@ func TestServer(t *testing.T) {
|
||||
root, _ := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--address", ":0",
|
||||
"--http-address", "",
|
||||
"--access-url", "http://example.com",
|
||||
"--tls-enable",
|
||||
"--tls-address", ":0",
|
||||
"--tls-min-version", "tls9",
|
||||
"--cache-dir", t.TempDir(),
|
||||
)
|
||||
@ -253,9 +254,10 @@ func TestServer(t *testing.T) {
|
||||
root, _ := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--address", ":0",
|
||||
"--http-address", "",
|
||||
"--access-url", "http://example.com",
|
||||
"--tls-enable",
|
||||
"--tls-address", ":0",
|
||||
"--tls-client-auth", "something",
|
||||
"--cache-dir", t.TempDir(),
|
||||
)
|
||||
@ -310,7 +312,7 @@ func TestServer(t *testing.T) {
|
||||
args := []string{
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--address", ":0",
|
||||
"--http-address", ":0",
|
||||
"--access-url", "http://example.com",
|
||||
"--cache-dir", t.TempDir(),
|
||||
}
|
||||
@ -331,9 +333,10 @@ func TestServer(t *testing.T) {
|
||||
root, cfg := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--address", ":0",
|
||||
"--http-address", "",
|
||||
"--access-url", "http://example.com",
|
||||
"--tls-enable",
|
||||
"--tls-address", ":0",
|
||||
"--tls-cert-file", certPath,
|
||||
"--tls-key-file", keyPath,
|
||||
"--cache-dir", t.TempDir(),
|
||||
@ -371,9 +374,10 @@ func TestServer(t *testing.T) {
|
||||
root, cfg := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--address", ":0",
|
||||
"--http-address", "",
|
||||
"--access-url", "http://example.com",
|
||||
"--tls-enable",
|
||||
"--tls-address", ":0",
|
||||
"--tls-cert-file", cert1Path,
|
||||
"--tls-key-file", key1Path,
|
||||
"--tls-cert-file", cert2Path,
|
||||
@ -443,6 +447,334 @@ func TestServer(t *testing.T) {
|
||||
cancelFunc()
|
||||
require.NoError(t, <-errC)
|
||||
})
|
||||
|
||||
t.Run("TLSAndHTTP", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
defer cancelFunc()
|
||||
|
||||
certPath, keyPath := generateTLSCertificate(t)
|
||||
root, _ := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--http-address", ":0",
|
||||
"--access-url", "https://example.com",
|
||||
"--tls-enable",
|
||||
"--tls-redirect-http-to-https=false",
|
||||
"--tls-address", ":0",
|
||||
"--tls-cert-file", certPath,
|
||||
"--tls-key-file", keyPath,
|
||||
"--cache-dir", t.TempDir(),
|
||||
)
|
||||
pty := ptytest.New(t)
|
||||
root.SetOutput(pty.Output())
|
||||
root.SetErr(pty.Output())
|
||||
|
||||
errC := make(chan error, 1)
|
||||
go func() {
|
||||
errC <- root.ExecuteContext(ctx)
|
||||
}()
|
||||
|
||||
// We can't use waitAccessURL as it will only return the HTTP URL.
|
||||
const httpLinePrefix = "Started HTTP listener at "
|
||||
pty.ExpectMatch(httpLinePrefix)
|
||||
httpLine := pty.ReadLine()
|
||||
httpAddr := strings.TrimSpace(strings.TrimPrefix(httpLine, httpLinePrefix))
|
||||
require.NotEmpty(t, httpAddr)
|
||||
const tlsLinePrefix = "Started TLS/HTTPS listener at "
|
||||
pty.ExpectMatch(tlsLinePrefix)
|
||||
tlsLine := pty.ReadLine()
|
||||
tlsAddr := strings.TrimSpace(strings.TrimPrefix(tlsLine, tlsLinePrefix))
|
||||
require.NotEmpty(t, tlsAddr)
|
||||
|
||||
// Verify HTTP
|
||||
httpURL, err := url.Parse(httpAddr)
|
||||
require.NoError(t, err)
|
||||
client := codersdk.New(httpURL)
|
||||
client.HTTPClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
_, err = client.HasFirstUser(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify TLS
|
||||
tlsURL, err := url.Parse(tlsAddr)
|
||||
require.NoError(t, err)
|
||||
client = codersdk.New(tlsURL)
|
||||
client.HTTPClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
client.HTTPClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
//nolint:gosec
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err = client.HasFirstUser(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
cancelFunc()
|
||||
require.NoError(t, <-errC)
|
||||
})
|
||||
|
||||
t.Run("TLSRedirect", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
httpListener bool
|
||||
tlsListener bool
|
||||
accessURL string
|
||||
// Empty string means no redirect.
|
||||
expectRedirect string
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
httpListener: true,
|
||||
tlsListener: true,
|
||||
accessURL: "https://example.com",
|
||||
expectRedirect: "https://example.com",
|
||||
},
|
||||
{
|
||||
name: "NoTLSListener",
|
||||
httpListener: true,
|
||||
tlsListener: false,
|
||||
accessURL: "https://example.com",
|
||||
expectRedirect: "",
|
||||
},
|
||||
{
|
||||
name: "NoHTTPListener",
|
||||
httpListener: false,
|
||||
tlsListener: true,
|
||||
accessURL: "https://example.com",
|
||||
expectRedirect: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
c := c
|
||||
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
defer cancelFunc()
|
||||
|
||||
certPath, keyPath := generateTLSCertificate(t)
|
||||
flags := []string{
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--cache-dir", t.TempDir(),
|
||||
}
|
||||
if c.httpListener {
|
||||
flags = append(flags, "--http-address", ":0")
|
||||
}
|
||||
if c.tlsListener {
|
||||
flags = append(flags,
|
||||
"--tls-enable",
|
||||
"--tls-address", ":0",
|
||||
"--tls-cert-file", certPath,
|
||||
"--tls-key-file", keyPath,
|
||||
)
|
||||
}
|
||||
if c.accessURL != "" {
|
||||
flags = append(flags, "--access-url", c.accessURL)
|
||||
}
|
||||
|
||||
root, _ := clitest.New(t, flags...)
|
||||
pty := ptytest.New(t)
|
||||
root.SetOutput(pty.Output())
|
||||
root.SetErr(pty.Output())
|
||||
|
||||
errC := make(chan error, 1)
|
||||
go func() {
|
||||
errC <- root.ExecuteContext(ctx)
|
||||
}()
|
||||
|
||||
var (
|
||||
httpAddr string
|
||||
tlsAddr string
|
||||
)
|
||||
// We can't use waitAccessURL as it will only return the HTTP URL.
|
||||
if c.httpListener {
|
||||
const httpLinePrefix = "Started HTTP listener at "
|
||||
pty.ExpectMatch(httpLinePrefix)
|
||||
httpLine := pty.ReadLine()
|
||||
httpAddr = strings.TrimSpace(strings.TrimPrefix(httpLine, httpLinePrefix))
|
||||
require.NotEmpty(t, httpAddr)
|
||||
}
|
||||
if c.tlsListener {
|
||||
const tlsLinePrefix = "Started TLS/HTTPS listener at "
|
||||
pty.ExpectMatch(tlsLinePrefix)
|
||||
tlsLine := pty.ReadLine()
|
||||
tlsAddr = strings.TrimSpace(strings.TrimPrefix(tlsLine, tlsLinePrefix))
|
||||
require.NotEmpty(t, tlsAddr)
|
||||
}
|
||||
|
||||
// Verify HTTP redirects (or not)
|
||||
if c.httpListener {
|
||||
httpURL, err := url.Parse(httpAddr)
|
||||
require.NoError(t, err)
|
||||
client := codersdk.New(httpURL)
|
||||
client.HTTPClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
resp, err := client.Request(ctx, http.MethodGet, "/api/v2/buildinfo", nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
if c.expectRedirect == "" {
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
} else {
|
||||
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
|
||||
require.Equal(t, c.expectRedirect, resp.Header.Get("Location"))
|
||||
}
|
||||
}
|
||||
|
||||
// Verify TLS
|
||||
if c.tlsListener {
|
||||
tlsURL, err := url.Parse(tlsAddr)
|
||||
require.NoError(t, err)
|
||||
client := codersdk.New(tlsURL)
|
||||
client.HTTPClient = &http.Client{
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
//nolint:gosec
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err = client.HasFirstUser(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
cancelFunc()
|
||||
require.NoError(t, <-errC)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("NoAddress", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
defer cancelFunc()
|
||||
|
||||
root, _ := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--http-address", "",
|
||||
"--tls-enable=false",
|
||||
"--tls-address", "",
|
||||
)
|
||||
err := root.ExecuteContext(ctx)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "either HTTP or TLS must be enabled")
|
||||
})
|
||||
|
||||
t.Run("NoTLSAddress", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
defer cancelFunc()
|
||||
|
||||
root, _ := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--tls-enable=true",
|
||||
"--tls-address", "",
|
||||
)
|
||||
err := root.ExecuteContext(ctx)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "TLS address must be set if TLS is enabled")
|
||||
})
|
||||
|
||||
// DeprecatedAddress is a test for the deprecated --address flag. If
|
||||
// specified, --http-address and --tls-address are both ignored, a warning
|
||||
// is printed, and the server will either be HTTP-only or TLS-only depending
|
||||
// on if --tls-enable is set.
|
||||
t.Run("DeprecatedAddress", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("HTTP", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
defer cancelFunc()
|
||||
|
||||
root, cfg := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--address", ":0",
|
||||
"--access-url", "http://example.com",
|
||||
"--cache-dir", t.TempDir(),
|
||||
)
|
||||
pty := ptytest.New(t)
|
||||
root.SetOutput(pty.Output())
|
||||
root.SetErr(pty.Output())
|
||||
errC := make(chan error, 1)
|
||||
go func() {
|
||||
errC <- root.ExecuteContext(ctx)
|
||||
}()
|
||||
|
||||
pty.ExpectMatch("--address and -a are deprecated")
|
||||
|
||||
accessURL := waitAccessURL(t, cfg)
|
||||
require.Equal(t, "http", accessURL.Scheme)
|
||||
client := codersdk.New(accessURL)
|
||||
_, err := client.HasFirstUser(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
cancelFunc()
|
||||
require.NoError(t, <-errC)
|
||||
})
|
||||
|
||||
t.Run("TLS", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
defer cancelFunc()
|
||||
|
||||
certPath, keyPath := generateTLSCertificate(t)
|
||||
root, cfg := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--address", ":0",
|
||||
"--access-url", "http://example.com",
|
||||
"--tls-enable",
|
||||
"--tls-cert-file", certPath,
|
||||
"--tls-key-file", keyPath,
|
||||
"--cache-dir", t.TempDir(),
|
||||
)
|
||||
pty := ptytest.New(t)
|
||||
root.SetOutput(pty.Output())
|
||||
root.SetErr(pty.Output())
|
||||
errC := make(chan error, 1)
|
||||
go func() {
|
||||
errC <- root.ExecuteContext(ctx)
|
||||
}()
|
||||
|
||||
pty.ExpectMatch("--address and -a are deprecated")
|
||||
|
||||
accessURL := waitAccessURL(t, cfg)
|
||||
require.Equal(t, "https", accessURL.Scheme)
|
||||
client := codersdk.New(accessURL)
|
||||
client.HTTPClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
//nolint:gosec
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err := client.HasFirstUser(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
cancelFunc()
|
||||
require.NoError(t, <-errC)
|
||||
})
|
||||
})
|
||||
|
||||
// This cannot be ran in parallel because it uses a signal.
|
||||
//nolint:paralleltest
|
||||
t.Run("Shutdown", func(t *testing.T) {
|
||||
@ -456,7 +788,7 @@ func TestServer(t *testing.T) {
|
||||
root, cfg := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--address", ":0",
|
||||
"--http-address", ":0",
|
||||
"--access-url", "http://example.com",
|
||||
"--provisioner-daemons", "1",
|
||||
"--cache-dir", t.TempDir(),
|
||||
@ -483,7 +815,7 @@ func TestServer(t *testing.T) {
|
||||
root, _ := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--address", ":0",
|
||||
"--http-address", ":0",
|
||||
"--access-url", "http://example.com",
|
||||
"--trace=true",
|
||||
"--cache-dir", t.TempDir(),
|
||||
@ -521,7 +853,7 @@ func TestServer(t *testing.T) {
|
||||
root, _ := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--address", ":0",
|
||||
"--http-address", ":0",
|
||||
"--access-url", "http://example.com",
|
||||
"--telemetry",
|
||||
"--telemetry-url", server.URL,
|
||||
@ -552,7 +884,7 @@ func TestServer(t *testing.T) {
|
||||
root, cfg := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--address", ":0",
|
||||
"--http-address", ":0",
|
||||
"--access-url", "http://example.com",
|
||||
"--provisioner-daemons", "1",
|
||||
"--prometheus-enable",
|
||||
@ -605,7 +937,7 @@ func TestServer(t *testing.T) {
|
||||
root, cfg := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--address", ":0",
|
||||
"--http-address", ":0",
|
||||
"--access-url", "http://example.com",
|
||||
"--oauth2-github-allow-everyone",
|
||||
"--oauth2-github-client-id", "fake",
|
||||
@ -646,7 +978,7 @@ func TestServer(t *testing.T) {
|
||||
root, cfg := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--address", ":0",
|
||||
"--http-address", ":0",
|
||||
"--access-url", "http://example.com",
|
||||
)
|
||||
serverErr := make(chan error, 1)
|
||||
@ -674,7 +1006,7 @@ func TestServer(t *testing.T) {
|
||||
root, cfg := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--address", ":0",
|
||||
"--http-address", ":0",
|
||||
"--access-url", "http://example.com",
|
||||
"--api-rate-limit", val,
|
||||
)
|
||||
@ -702,7 +1034,7 @@ func TestServer(t *testing.T) {
|
||||
root, cfg := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--address", ":0",
|
||||
"--http-address", ":0",
|
||||
"--access-url", "http://example.com",
|
||||
"--api-rate-limit", "-1",
|
||||
)
|
||||
|
Reference in New Issue
Block a user