feat: Add local configuration option for DERP mapping (#3996)

This allows entirely airgapped geodistributed deployments of Coder!
This commit is contained in:
Kyle Carberry
2022-09-11 16:45:49 -05:00
committed by GitHub
parent 6e20f9c729
commit 5b5bc1da56
5 changed files with 81 additions and 30 deletions

View File

@@ -81,6 +81,7 @@ func Server(newAPI func(*coderd.Options) *coderd.API) *cobra.Command {
derpServerRegionName string
derpServerSTUNAddrs []string
derpConfigURL string
derpConfigPath string
promEnabled bool
promAddress string
pprofEnabled bool
@@ -345,7 +346,7 @@ func Server(newAPI func(*coderd.Options) *coderd.API) *cobra.Command {
if !derpServerEnabled {
defaultRegion = nil
}
derpMap, err := tailnet.NewDERPMap(ctx, defaultRegion, derpServerSTUNAddrs, derpConfigURL)
derpMap, err := tailnet.NewDERPMap(ctx, defaultRegion, derpServerSTUNAddrs, derpConfigURL, derpConfigPath)
if err != nil {
return xerrors.Errorf("create derp map: %w", err)
}
@@ -753,6 +754,8 @@ func Server(newAPI func(*coderd.Options) *coderd.API) *cobra.Command {
cliflag.StringVarP(root.Flags(), &address, "address", "a", "CODER_ADDRESS", "127.0.0.1:3000", "The address to serve the API and dashboard.")
cliflag.StringVarP(root.Flags(), &derpConfigURL, "derp-config-url", "", "CODER_DERP_CONFIG_URL", "",
"Specifies a URL to periodically fetch a DERP map. See: https://tailscale.com/kb/1118/custom-derp-servers/")
cliflag.StringVarP(root.Flags(), &derpConfigPath, "derp-config-path", "", "CODER_DERP_CONFIG_PATH", "",
"Specifies a path to read a DERP map from. See: https://tailscale.com/kb/1118/custom-derp-servers/")
cliflag.BoolVarP(root.Flags(), &derpServerEnabled, "derp-server-enable", "", "CODER_DERP_SERVER_ENABLE", true, "Specifies whether to enable or disable the embedded DERP server.")
cliflag.IntVarP(root.Flags(), &derpServerRegionID, "derp-server-region-id", "", "CODER_DERP_SERVER_REGION_ID", 999, "Specifies the region ID to use for the embedded DERP server.")
cliflag.StringVarP(root.Flags(), &derpServerRegionCode, "derp-server-region-code", "", "CODER_DERP_SERVER_REGION_CODE", "coder", "Specifies the region code that is displayed in the Coder UI for the embedded DERP server.")
@@ -763,6 +766,7 @@ func Server(newAPI func(*coderd.Options) *coderd.API) *cobra.Command {
// Mark hidden while this feature is in testing!
_ = root.Flags().MarkHidden("derp-config-url")
_ = root.Flags().MarkHidden("derp-config-path")
_ = root.Flags().MarkHidden("derp-server-enable")
_ = root.Flags().MarkHidden("derp-server-region-id")
_ = root.Flags().MarkHidden("derp-server-region-code")

View File

@@ -11,6 +11,7 @@ import (
tsspeedtest "tailscale.com/net/speedtest"
"cdr.dev/slog"
"cdr.dev/slog/sloggers/sloghuman"
"github.com/coder/coder/agent"
"github.com/coder/coder/cli/cliflag"
"github.com/coder/coder/cli/cliui"
@@ -51,36 +52,43 @@ func speedtest() *cobra.Command {
if err != nil {
return xerrors.Errorf("await agent: %w", err)
}
conn, err := client.DialWorkspaceAgentTailnet(ctx, slog.Logger{}, workspaceAgent.ID)
logger := slog.Make(sloghuman.Sink(cmd.ErrOrStderr()))
if cliflag.IsSetBool(cmd, varVerbose) {
logger = logger.Leveled(slog.LevelDebug)
}
conn, err := client.DialWorkspaceAgentTailnet(ctx, logger, workspaceAgent.ID)
if err != nil {
return err
}
defer conn.Close()
if direct {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
}
dur, err := conn.Ping()
if err != nil {
continue
}
tc, _ := conn.(*agent.TailnetConn)
status := tc.Status()
if len(status.Peers()) != 1 {
continue
}
peer := status.Peer[status.Peers()[0]]
if peer.CurAddr == "" {
cmd.Printf("Waiting for a direct connection... (%dms via %s)\n", dur.Milliseconds(), peer.Relay)
continue
}
break
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
}
dur, err := conn.Ping()
if err != nil {
continue
}
tc, _ := conn.(*agent.TailnetConn)
status := tc.Status()
if len(status.Peers()) != 1 {
continue
}
peer := status.Peer[status.Peers()[0]]
if peer.CurAddr == "" && direct {
cmd.Printf("Waiting for a direct connection... (%dms via %s)\n", dur.Milliseconds(), peer.Relay)
continue
}
via := peer.Relay
if via == "" {
via = "direct"
}
cmd.Printf("%dms via %s\n", dur.Milliseconds(), via)
break
}
dir := tsspeedtest.Download
if reverse {

View File

@@ -268,6 +268,7 @@ func (c *Conn) SetNodeCallback(callback func(node *Node)) {
case <-c.closed:
return
case node := <-queue:
c.logger.Debug(context.Background(), "send node callback", slog.F("node", node))
callback(node)
}
}
@@ -299,6 +300,8 @@ func (c *Conn) SetNodeCallback(callback func(node *Node)) {
func (c *Conn) SetDERPMap(derpMap *tailcfg.DERPMap) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.netMap.DERPMap = derpMap
c.logger.Debug(context.Background(), "updating derp map", slog.F("derp_map", derpMap))
c.wireguardEngine.SetDERPMap(derpMap)
}
@@ -340,6 +343,9 @@ func (c *Conn) UpdateNodes(nodes []*Node) error {
existingNode, ok := peerMap[node.ID]
if ok {
peerNode.Created = existingNode.Created
c.logger.Debug(context.Background(), "updating peer", slog.F("peer", peerNode))
} else {
c.logger.Debug(context.Background(), "adding peer", slog.F("peer", peerNode))
}
peerMap[node.ID] = peerNode
}

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"net"
"net/http"
"os"
"strconv"
"golang.org/x/xerrors"
@@ -14,7 +15,10 @@ import (
// NewDERPMap constructs a DERPMap from a set of STUN addresses and optionally a remote
// URL to fetch a mapping from e.g. https://controlplane.tailscale.com/derpmap/default.
func NewDERPMap(ctx context.Context, region *tailcfg.DERPRegion, stunAddrs []string, remoteURL string) (*tailcfg.DERPMap, error) {
func NewDERPMap(ctx context.Context, region *tailcfg.DERPRegion, stunAddrs []string, remoteURL, localPath string) (*tailcfg.DERPMap, error) {
if remoteURL != "" && localPath != "" {
return nil, xerrors.New("a remote URL or local path must be specified, not both")
}
if region != nil {
for index, stunAddr := range stunAddrs {
host, rawPort, err := net.SplitHostPort(stunAddr)
@@ -53,6 +57,16 @@ func NewDERPMap(ctx context.Context, region *tailcfg.DERPRegion, stunAddrs []str
return nil, xerrors.Errorf("fetch derpmap: %w", err)
}
}
if localPath != "" {
content, err := os.ReadFile(localPath)
if err != nil {
return nil, xerrors.Errorf("read derpmap from %q: %w", localPath, err)
}
err = json.Unmarshal(content, &derpMap)
if err != nil {
return nil, xerrors.Errorf("unmarshal derpmap: %w", err)
}
}
if region != nil {
_, conflicts := derpMap.Regions[region.RegionID]
if conflicts {

View File

@@ -5,6 +5,8 @@ import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
@@ -20,7 +22,7 @@ func TestNewDERPMap(t *testing.T) {
derpMap, err := tailnet.NewDERPMap(context.Background(), &tailcfg.DERPRegion{
RegionID: 1,
Nodes: []*tailcfg.DERPNode{{}},
}, []string{"stun.google.com:2345"}, "")
}, []string{"stun.google.com:2345"}, "", "")
require.NoError(t, err)
require.Len(t, derpMap.Regions[1].Nodes, 2)
})
@@ -37,7 +39,7 @@ func TestNewDERPMap(t *testing.T) {
t.Cleanup(server.Close)
derpMap, err := tailnet.NewDERPMap(context.Background(), &tailcfg.DERPRegion{
RegionID: 2,
}, []string{}, server.URL)
}, []string{}, server.URL, "")
require.NoError(t, err)
require.Len(t, derpMap.Regions, 2)
})
@@ -54,7 +56,24 @@ func TestNewDERPMap(t *testing.T) {
t.Cleanup(server.Close)
_, err := tailnet.NewDERPMap(context.Background(), &tailcfg.DERPRegion{
RegionID: 1,
}, []string{}, server.URL)
}, []string{}, server.URL, "")
require.Error(t, err)
})
t.Run("LocalPath", func(t *testing.T) {
t.Parallel()
localPath := filepath.Join(t.TempDir(), "derp.json")
content, err := json.Marshal(&tailcfg.DERPMap{
Regions: map[int]*tailcfg.DERPRegion{
1: {},
},
})
require.NoError(t, err)
err = os.WriteFile(localPath, content, 0600)
require.NoError(t, err)
derpMap, err := tailnet.NewDERPMap(context.Background(), &tailcfg.DERPRegion{
RegionID: 2,
}, []string{}, "", localPath)
require.NoError(t, err)
require.Len(t, derpMap.Regions, 2)
})
}