feat: add server flag to force DERP to use always websockets (#9238)

This commit is contained in:
Dean Sheather
2023-08-24 10:22:31 -07:00
committed by GitHub
parent 9cb913fb1a
commit 64df076328
28 changed files with 280 additions and 68 deletions

12
coderd/apidoc/docs.go generated
View File

@ -6416,6 +6416,9 @@ const docTemplate = `{
"$ref": "#/definitions/codersdk.WorkspaceApp"
}
},
"derp_force_websockets": {
"type": "boolean"
},
"derpmap": {
"$ref": "#/definitions/tailcfg.DERPMap"
},
@ -7781,6 +7784,9 @@ const docTemplate = `{
"block_direct": {
"type": "boolean"
},
"force_websockets": {
"type": "boolean"
},
"path": {
"type": "string"
},
@ -10710,6 +10716,9 @@ const docTemplate = `{
"codersdk.WorkspaceAgentConnectionInfo": {
"type": "object",
"properties": {
"derp_force_websockets": {
"type": "boolean"
},
"derp_map": {
"$ref": "#/definitions/tailcfg.DERPMap"
},
@ -11973,6 +11982,9 @@ const docTemplate = `{
"app_security_key": {
"type": "string"
},
"derp_force_websockets": {
"type": "boolean"
},
"derp_map": {
"$ref": "#/definitions/tailcfg.DERPMap"
},

View File

@ -5656,6 +5656,9 @@
"$ref": "#/definitions/codersdk.WorkspaceApp"
}
},
"derp_force_websockets": {
"type": "boolean"
},
"derpmap": {
"$ref": "#/definitions/tailcfg.DERPMap"
},
@ -6936,6 +6939,9 @@
"block_direct": {
"type": "boolean"
},
"force_websockets": {
"type": "boolean"
},
"path": {
"type": "string"
},
@ -9712,6 +9718,9 @@
"codersdk.WorkspaceAgentConnectionInfo": {
"type": "object",
"properties": {
"derp_force_websockets": {
"type": "boolean"
},
"derp_map": {
"$ref": "#/definitions/tailcfg.DERPMap"
},
@ -10934,6 +10943,9 @@
"app_security_key": {
"type": "string"
},
"derp_force_websockets": {
"type": "boolean"
},
"derp_map": {
"$ref": "#/definitions/tailcfg.DERPMap"
},

View File

@ -405,6 +405,7 @@ func New(options *Options) *API {
options.Logger,
options.DERPServer,
api.DERPMap,
options.DeploymentValues.DERP.Config.ForceWebSockets.Value(),
func(context.Context) (tailnet.MultiAgentConn, error) {
return (*api.TailnetCoordinator.Load()).ServeMultiAgent(uuid.New()), nil
},

View File

@ -7,9 +7,13 @@ import (
"net/http"
"net/netip"
"strconv"
"strings"
"sync"
"sync/atomic"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/goleak"
@ -18,8 +22,13 @@ import (
"cdr.dev/slog"
"cdr.dev/slog/sloggers/slogtest"
"github.com/coder/coder/v2/agent"
"github.com/coder/coder/v2/buildinfo"
"github.com/coder/coder/v2/coderd"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/agentsdk"
"github.com/coder/coder/v2/provisioner/echo"
"github.com/coder/coder/v2/tailnet"
"github.com/coder/coder/v2/testutil"
)
@ -119,6 +128,91 @@ func TestDERP(t *testing.T) {
w2.Close()
}
func TestDERPForceWebSockets(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.DERP.Config.ForceWebSockets = true
dv.DERP.Config.BlockDirect = true // to ensure the test always uses DERP
// Manually create a server so we can influence the HTTP handler.
options := &coderdtest.Options{
DeploymentValues: dv,
}
setHandler, cancelFunc, serverURL, newOptions := coderdtest.NewOptions(t, options)
coderAPI := coderd.New(newOptions)
t.Cleanup(func() {
cancelFunc()
_ = coderAPI.Close()
})
// Set the HTTP handler to a custom one that ensures all /derp calls are
// WebSockets and not `Upgrade: derp`.
var upgradeCount int64
setHandler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/derp") {
up := r.Header.Get("Upgrade")
if up != "" && up != "websocket" {
t.Errorf("expected Upgrade: websocket, got %q", up)
} else {
atomic.AddInt64(&upgradeCount, 1)
}
}
coderAPI.RootHandler.ServeHTTP(rw, r)
}))
// Start a provisioner daemon.
provisionerCloser := coderdtest.NewProvisionerDaemon(t, coderAPI)
t.Cleanup(func() {
_ = provisionerCloser.Close()
})
client := codersdk.New(serverURL)
t.Cleanup(func() {
client.HTTPClient.CloseIdleConnections()
})
user := coderdtest.CreateFirstUser(t, client)
gen, err := client.WorkspaceAgentConnectionInfoGeneric(context.Background())
require.NoError(t, err)
t.Log(spew.Sdump(gen))
authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
})
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
agentClient := agentsdk.New(client.URL)
agentClient.SetSessionToken(authToken)
agentCloser := agent.New(agent.Options{
Client: agentClient,
Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelDebug),
})
defer func() {
_ = agentCloser.Close()
}()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, nil)
require.NoError(t, err)
defer func() {
_ = conn.Close()
}()
conn.AwaitReachable(ctx)
require.GreaterOrEqual(t, atomic.LoadInt64(&upgradeCount), int64(1), "expected at least one /derp call")
}
func TestDERPLatencyCheck(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)

View File

@ -326,7 +326,7 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can
stunAddresses []string
dvStunAddresses = options.DeploymentValues.DERP.Server.STUNAddresses.Value()
)
if len(dvStunAddresses) == 0 || (len(dvStunAddresses) == 1 && dvStunAddresses[0] == "stun.l.google.com:19302") {
if len(dvStunAddresses) == 0 || dvStunAddresses[0] == "stun.l.google.com:19302" {
stunAddr, stunCleanup := stuntest.ServeWithPacketListener(t, nettype.Std{})
stunAddr.IP = net.ParseIP("127.0.0.1")
t.Cleanup(stunCleanup)

View File

@ -45,6 +45,7 @@ func NewServerTailnet(
logger slog.Logger,
derpServer *derp.Server,
derpMapFn func() *tailcfg.DERPMap,
derpForceWebSockets bool,
getMultiAgent func(context.Context) (tailnet.MultiAgentConn, error),
cache *wsconncache.Cache,
traceProvider trace.TracerProvider,
@ -52,9 +53,10 @@ func NewServerTailnet(
logger = logger.Named("servertailnet")
originalDerpMap := derpMapFn()
conn, err := tailnet.NewConn(&tailnet.Options{
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
DERPMap: originalDerpMap,
Logger: logger,
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
DERPMap: originalDerpMap,
DERPForceWebSockets: derpForceWebSockets,
Logger: logger,
})
if err != nil {
return nil, xerrors.Errorf("create tailnet conn: %w", err)

View File

@ -232,6 +232,7 @@ func setupAgent(t *testing.T, agentAddresses []netip.Prefix) (uuid.UUID, agent.A
logger,
derpServer,
func() *tailcfg.DERPMap { return manifest.DERPMap },
false,
func(context.Context) (tailnet.MultiAgentConn, error) { return coord.ServeMultiAgent(uuid.New()), nil },
cache,
trace.NewNoopTracerProvider(),

View File

@ -167,6 +167,7 @@ func (api *API) workspaceAgentManifest(rw http.ResponseWriter, r *http.Request)
AgentID: apiAgent.ID,
Apps: convertApps(dbApps),
DERPMap: api.DERPMap(),
DERPForceWebSockets: api.DeploymentValues.DERP.Config.ForceWebSockets.Value(),
GitAuthConfigs: len(api.GitAuthConfigs),
EnvironmentVariables: apiAgent.EnvironmentVariables,
StartupScript: apiAgent.StartupScript,
@ -733,10 +734,11 @@ func (api *API) _dialWorkspaceAgentTailnet(agentID uuid.UUID) (*codersdk.Workspa
derpMap := api.DERPMap()
conn, err := tailnet.NewConn(&tailnet.Options{
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
DERPMap: api.DERPMap(),
Logger: api.Logger.Named("net.tailnet"),
BlockEndpoints: api.DeploymentValues.DERP.Config.BlockDirect.Value(),
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
DERPMap: api.DERPMap(),
DERPForceWebSockets: api.DeploymentValues.DERP.Config.ForceWebSockets.Value(),
Logger: api.Logger.Named("net.tailnet"),
BlockEndpoints: api.DeploymentValues.DERP.Config.BlockDirect.Value(),
})
if err != nil {
_ = clientConn.Close()
@ -831,6 +833,7 @@ func (api *API) workspaceAgentConnection(rw http.ResponseWriter, r *http.Request
httpapi.Write(ctx, rw, http.StatusOK, codersdk.WorkspaceAgentConnectionInfo{
DERPMap: api.DERPMap(),
DERPForceWebSockets: api.DeploymentValues.DERP.Config.ForceWebSockets.Value(),
DisableDirectConnections: api.DeploymentValues.DERP.Config.BlockDirect.Value(),
})
}
@ -851,6 +854,7 @@ func (api *API) workspaceAgentConnectionGeneric(rw http.ResponseWriter, r *http.
httpapi.Write(ctx, rw, http.StatusOK, codersdk.WorkspaceAgentConnectionInfo{
DERPMap: api.DERPMap(),
DERPForceWebSockets: api.DeploymentValues.DERP.Config.ForceWebSockets.Value(),
DisableDirectConnections: api.DeploymentValues.DERP.Config.BlockDirect.Value(),
})
}

View File

@ -179,9 +179,10 @@ func setupAgent(t *testing.T, manifest agentsdk.Manifest, ptyTimeout time.Durati
_ = closer.Close()
})
conn, err := tailnet.NewConn(&tailnet.Options{
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
DERPMap: manifest.DERPMap,
Logger: slogtest.Make(t, nil).Named("tailnet").Leveled(slog.LevelDebug),
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
DERPMap: manifest.DERPMap,
DERPForceWebSockets: manifest.DERPForceWebSockets,
Logger: slogtest.Make(t, nil).Named("tailnet").Leveled(slog.LevelDebug),
})
require.NoError(t, err)
clientConn, serverConn := net.Pipe()