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

Commit700a82f

Browse files
committed
feat: set DNS hostnames in workspace updates controller
1 parent08216aa commit700a82f

File tree

2 files changed

+195
-20
lines changed

2 files changed

+195
-20
lines changed

‎tailnet/controllers.go

Lines changed: 72 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"io"
77
"maps"
88
"math"
9+
"net/netip"
910
"strings"
1011
"sync"
1112
"time"
@@ -15,6 +16,7 @@ import (
1516
"storj.io/drpc"
1617
"storj.io/drpc/drpcerr"
1718
"tailscale.com/tailcfg"
19+
"tailscale.com/util/dnsname"
1820

1921
"cdr.dev/slog"
2022
"github.com/coder/coder/v2/codersdk"
@@ -104,6 +106,12 @@ type WorkspaceUpdatesController interface {
104106
New(WorkspaceUpdatesClient)CloserWaiter
105107
}
106108

109+
// DNSHostsSetter is something that you can set a mapping of DNS names to IPs on. It's the subset
110+
// of the tailnet.Conn that we use to configure DNS records.
111+
typeDNSHostsSetterinterface {
112+
SetDNSHosts(hostsmap[dnsname.FQDN][]netip.Addr)error
113+
}
114+
107115
// ControlProtocolClients represents an abstract interface to the tailnet control plane via a set
108116
// of protocol clients. The Closer should close all the clients (e.g. by closing the underlying
109117
// connection).
@@ -835,8 +843,9 @@ func (r *basicResumeTokenRefresher) refresh() {
835843
}
836844

837845
typetunnelAllWorkspaceUpdatesControllerstruct {
838-
coordCtrl*TunnelSrcCoordController
839-
logger slog.Logger
846+
coordCtrl*TunnelSrcCoordController
847+
dnsHostSetterDNSHostsSetter
848+
logger slog.Logger
840849
}
841850

842851
typeworkspacestruct {
@@ -845,30 +854,48 @@ type workspace struct {
845854
agentsmap[uuid.UUID]agent
846855
}
847856

857+
// addAllDNSNames adds names for all of its agents to the given map of names
858+
func (wworkspace)addAllDNSNames(namesmap[dnsname.FQDN][]netip.Addr)error {
859+
for_,a:=rangew.agents {
860+
// TODO: technically, DNS labels cannot start with numbers, but the rules are often not
861+
// strictly enforced.
862+
// TODO: support <agent>.<workspace>.<username>.coder
863+
fqdn,err:=dnsname.ToFQDN(fmt.Sprintf("%s.%s.me.coder.",a.name,w.name))
864+
iferr!=nil {
865+
returnerr
866+
}
867+
names[fqdn]= []netip.Addr{CoderServicePrefix.AddrFromUUID(a.id)}
868+
}
869+
// TODO: Possibly support <workspace>.coder. alias if there is only one agent
870+
returnnil
871+
}
872+
848873
typeagentstruct {
849874
id uuid.UUID
850875
namestring
851876
}
852877

853878
func (t*tunnelAllWorkspaceUpdatesController)New(clientWorkspaceUpdatesClient)CloserWaiter {
854879
updater:=&tunnelUpdater{
855-
client:client,
856-
errChan:make(chanerror,1),
857-
logger:t.logger,
858-
coordCtrl:t.coordCtrl,
859-
recvLoopDone:make(chanstruct{}),
860-
workspaces:make(map[uuid.UUID]*workspace),
880+
client:client,
881+
errChan:make(chanerror,1),
882+
logger:t.logger,
883+
coordCtrl:t.coordCtrl,
884+
dnsHostsSetter:t.dnsHostSetter,
885+
recvLoopDone:make(chanstruct{}),
886+
workspaces:make(map[uuid.UUID]*workspace),
861887
}
862888
goupdater.recvLoop()
863889
returnupdater
864890
}
865891

866892
typetunnelUpdaterstruct {
867-
errChanchanerror
868-
logger slog.Logger
869-
clientWorkspaceUpdatesClient
870-
coordCtrl*TunnelSrcCoordController
871-
recvLoopDonechanstruct{}
893+
errChanchanerror
894+
logger slog.Logger
895+
clientWorkspaceUpdatesClient
896+
coordCtrl*TunnelSrcCoordController
897+
dnsHostsSetterDNSHostsSetter
898+
recvLoopDonechanstruct{}
872899

873900
// don't need the mutex since only manipulated by the recvLoop
874901
workspacesmap[uuid.UUID]*workspace
@@ -991,6 +1018,16 @@ func (t *tunnelUpdater) handleUpdate(update *proto.WorkspaceUpdate) error {
9911018
}
9921019
allAgents:=t.allAgentIDs()
9931020
t.coordCtrl.SyncDestinations(allAgents)
1021+
ift.dnsHostsSetter!=nil {
1022+
t.logger.Debug(context.Background(),"updating dns hosts")
1023+
dnsNames:=t.allDNSNames()
1024+
err:=t.dnsHostsSetter.SetDNSHosts(dnsNames)
1025+
iferr!=nil {
1026+
returnxerrors.Errorf("failed to set DNS hosts: %w",err)
1027+
}
1028+
}else {
1029+
t.logger.Debug(context.Background(),"skipping setting DNS names because we have no setter")
1030+
}
9941031
returnnil
9951032
}
9961033

@@ -1035,10 +1072,30 @@ func (t *tunnelUpdater) allAgentIDs() []uuid.UUID {
10351072
returnout
10361073
}
10371074

1075+
func (t*tunnelUpdater)allDNSNames()map[dnsname.FQDN][]netip.Addr {
1076+
names:=make(map[dnsname.FQDN][]netip.Addr)
1077+
for_,w:=ranget.workspaces {
1078+
err:=w.addAllDNSNames(names)
1079+
iferr!=nil {
1080+
// This should never happen in production, because converting the FQDN only fails
1081+
// if names are too long, and we put strict length limits on agent, workspace, and user
1082+
// names.
1083+
t.logger.Critical(context.Background(),
1084+
"failed to include DNS name(s)",
1085+
slog.F("workspace_id",w.id),
1086+
slog.Error(err))
1087+
}
1088+
}
1089+
returnnames
1090+
}
1091+
1092+
// NewTunnelAllWorkspaceUpdatesController creates a WorkspaceUpdatesController that creates tunnels
1093+
// (via the TunnelSrcCoordController) to all agents received over the WorkspaceUpdates RPC. If a
1094+
// DNSHostSetter is provided, it also programs DNS hosts based on the agent and workspace names.
10381095
funcNewTunnelAllWorkspaceUpdatesController(
1039-
logger slog.Logger,c*TunnelSrcCoordController,
1096+
logger slog.Logger,c*TunnelSrcCoordController,dDNSHostsSetter,
10401097
)WorkspaceUpdatesController {
1041-
return&tunnelAllWorkspaceUpdatesController{logger:logger,coordCtrl:c}
1098+
return&tunnelAllWorkspaceUpdatesController{logger:logger,coordCtrl:c,dnsHostSetter:d}
10421099
}
10431100

10441101
// NewController creates a new Controller without running it

‎tailnet/controllers_test.go

Lines changed: 123 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"io"
77
"net"
8+
"net/netip"
89
"slices"
910
"sync"
1011
"sync/atomic"
@@ -23,6 +24,7 @@ import (
2324
"storj.io/drpc/drpcerr"
2425
"tailscale.com/tailcfg"
2526
"tailscale.com/types/key"
27+
"tailscale.com/util/dnsname"
2628

2729
"cdr.dev/slog"
2830
"cdr.dev/slog/sloggers/slogtest"
@@ -1344,14 +1346,56 @@ func testUUID(b ...byte) uuid.UUID {
13441346
returno
13451347
}
13461348

1349+
typefakeDNSSetterstruct {
1350+
ctx context.Context
1351+
t testing.TB
1352+
callschan*setDNSCall
1353+
}
1354+
1355+
typesetDNSCallstruct {
1356+
hostsmap[dnsname.FQDN][]netip.Addr
1357+
errchan<-error
1358+
}
1359+
1360+
funcnewFakeDNSSetter(ctx context.Context,t testing.TB)*fakeDNSSetter {
1361+
return&fakeDNSSetter{
1362+
ctx:ctx,
1363+
t:t,
1364+
calls:make(chan*setDNSCall),
1365+
}
1366+
}
1367+
1368+
func (f*fakeDNSSetter)SetDNSHosts(hostsmap[dnsname.FQDN][]netip.Addr)error {
1369+
f.t.Helper()
1370+
errs:=make(chanerror)
1371+
call:=&setDNSCall{
1372+
hosts:hosts,
1373+
err:errs,
1374+
}
1375+
select {
1376+
case<-f.ctx.Done():
1377+
f.t.Error("timed out waiting to send SetDNSHosts() call")
1378+
returnf.ctx.Err()
1379+
casef.calls<-call:
1380+
// OK
1381+
}
1382+
select {
1383+
case<-f.ctx.Done():
1384+
f.t.Error("timed out waiting for SetDNSHosts() call response")
1385+
returnf.ctx.Err()
1386+
caseerr:=<-errs:
1387+
returnerr
1388+
}
1389+
}
1390+
13471391
funcsetupConnectedAllWorkspaceUpdatesController(
1348-
ctx context.Context,t testing.TB,logger slog.Logger,
1392+
ctx context.Context,t testing.TB,logger slog.Logger,dnsSetter tailnet.DNSHostsSetter,
13491393
) (
13501394
*fakeCoordinatorClient,*fakeWorkspaceUpdateClient,
13511395
) {
13521396
fConn:=&fakeCoordinatee{}
13531397
tsc:=tailnet.NewTunnelSrcCoordController(logger,fConn)
1354-
uut:=tailnet.NewTunnelAllWorkspaceUpdatesController(logger,tsc)
1398+
uut:=tailnet.NewTunnelAllWorkspaceUpdatesController(logger,tsc,dnsSetter)
13551399

13561400
// connect up a coordinator client, to track adding and removing tunnels
13571401
coordC:=newFakeCoordinatorClient(ctx,t)
@@ -1385,7 +1429,8 @@ func TestTunnelAllWorkspaceUpdatesController_Initial(t *testing.T) {
13851429
ctx:=testutil.Context(t,testutil.WaitShort)
13861430
logger:=slogtest.Make(t,nil).Leveled(slog.LevelDebug)
13871431

1388-
coordC,updateC:=setupConnectedAllWorkspaceUpdatesController(ctx,t,logger)
1432+
fDNS:=newFakeDNSSetter(ctx,t)
1433+
coordC,updateC:=setupConnectedAllWorkspaceUpdatesController(ctx,t,logger,fDNS)
13891434

13901435
// Initial update contains 2 workspaces with 1 & 2 agents, respectively
13911436
w1ID:=testUUID(1)
@@ -1418,14 +1463,25 @@ func TestTunnelAllWorkspaceUpdatesController_Initial(t *testing.T) {
14181463
require.Contains(t,adds,w1a1ID)
14191464
require.Contains(t,adds,w2a1ID)
14201465
require.Contains(t,adds,w2a2ID)
1466+
1467+
// Also triggers setting DNS hosts
1468+
expectedDNS:=map[dnsname.FQDN][]netip.Addr{
1469+
"w1a1.w1.me.coder.": {netip.MustParseAddr("fd60:627a:a42b:0101::")},
1470+
"w2a1.w2.me.coder.": {netip.MustParseAddr("fd60:627a:a42b:0201::")},
1471+
"w2a2.w2.me.coder.": {netip.MustParseAddr("fd60:627a:a42b:0202::")},
1472+
}
1473+
dnsCall:=testutil.RequireRecvCtx(ctx,t,fDNS.calls)
1474+
require.Equal(t,expectedDNS,dnsCall.hosts)
1475+
testutil.RequireSendCtx(ctx,t,dnsCall.err,nil)
14211476
}
14221477

14231478
funcTestTunnelAllWorkspaceUpdatesController_DeleteAgent(t*testing.T) {
14241479
t.Parallel()
14251480
ctx:=testutil.Context(t,testutil.WaitShort)
14261481
logger:=slogtest.Make(t,nil).Leveled(slog.LevelDebug)
14271482

1428-
coordC,updateC:=setupConnectedAllWorkspaceUpdatesController(ctx,t,logger)
1483+
fDNS:=newFakeDNSSetter(ctx,t)
1484+
coordC,updateC:=setupConnectedAllWorkspaceUpdatesController(ctx,t,logger,fDNS)
14291485

14301486
w1ID:=testUUID(1)
14311487
w1a1ID:=testUUID(1,1)
@@ -1447,6 +1503,14 @@ func TestTunnelAllWorkspaceUpdatesController_DeleteAgent(t *testing.T) {
14471503
require.Equal(t,w1a1ID[:],coordCall.req.GetAddTunnel().GetId())
14481504
testutil.RequireSendCtx(ctx,t,coordCall.err,nil)
14491505

1506+
// DNS for w1a1
1507+
expectedDNS:=map[dnsname.FQDN][]netip.Addr{
1508+
"w1a1.w1.me.coder.": {netip.MustParseAddr("fd60:627a:a42b:0101::")},
1509+
}
1510+
dnsCall:=testutil.RequireRecvCtx(ctx,t,fDNS.calls)
1511+
require.Equal(t,expectedDNS,dnsCall.hosts)
1512+
testutil.RequireSendCtx(ctx,t,dnsCall.err,nil)
1513+
14501514
// Send update that removes w1a1 and adds w1a2
14511515
agentUpdate:=&proto.WorkspaceUpdate{
14521516
UpsertedAgents: []*proto.Agent{
@@ -1468,6 +1532,60 @@ func TestTunnelAllWorkspaceUpdatesController_DeleteAgent(t *testing.T) {
14681532
coordCall=testutil.RequireRecvCtx(ctx,t,coordC.reqs)
14691533
require.Equal(t,w1a1ID[:],coordCall.req.GetRemoveTunnel().GetId())
14701534
testutil.RequireSendCtx(ctx,t,coordCall.err,nil)
1535+
1536+
// DNS contains only w1a2
1537+
expectedDNS=map[dnsname.FQDN][]netip.Addr{
1538+
"w1a2.w1.me.coder.": {netip.MustParseAddr("fd60:627a:a42b:0102::")},
1539+
}
1540+
dnsCall=testutil.RequireRecvCtx(ctx,t,fDNS.calls)
1541+
require.Equal(t,expectedDNS,dnsCall.hosts)
1542+
testutil.RequireSendCtx(ctx,t,dnsCall.err,nil)
1543+
}
1544+
1545+
funcTestTunnelAllWorkspaceUpdatesController_DNSError(t*testing.T) {
1546+
t.Parallel()
1547+
ctx:=testutil.Context(t,testutil.WaitShort)
1548+
dnsError:=xerrors.New("a bad thing happened")
1549+
logger:=slogtest.Make(t,
1550+
&slogtest.Options{IgnoredErrorIs: []error{dnsError}}).
1551+
Leveled(slog.LevelDebug)
1552+
1553+
fDNS:=newFakeDNSSetter(ctx,t)
1554+
fConn:=&fakeCoordinatee{}
1555+
tsc:=tailnet.NewTunnelSrcCoordController(logger,fConn)
1556+
uut:=tailnet.NewTunnelAllWorkspaceUpdatesController(logger,tsc,fDNS)
1557+
1558+
updateC:=newFakeWorkspaceUpdateClient(ctx,t)
1559+
updateCW:=uut.New(updateC)
1560+
1561+
w1ID:=testUUID(1)
1562+
w1a1ID:=testUUID(1,1)
1563+
initUp:=&proto.WorkspaceUpdate{
1564+
UpsertedWorkspaces: []*proto.Workspace{
1565+
{Id:w1ID[:],Name:"w1"},
1566+
},
1567+
UpsertedAgents: []*proto.Agent{
1568+
{Id:w1a1ID[:],Name:"w1a1",WorkspaceId:w1ID[:]},
1569+
},
1570+
}
1571+
upRecvCall:=testutil.RequireRecvCtx(ctx,t,updateC.recv)
1572+
testutil.RequireSendCtx(ctx,t,upRecvCall.resp,initUp)
1573+
1574+
// DNS for w1a1
1575+
expectedDNS:=map[dnsname.FQDN][]netip.Addr{
1576+
"w1a1.w1.me.coder.": {netip.MustParseAddr("fd60:627a:a42b:0101::")},
1577+
}
1578+
dnsCall:=testutil.RequireRecvCtx(ctx,t,fDNS.calls)
1579+
require.Equal(t,expectedDNS,dnsCall.hosts)
1580+
testutil.RequireSendCtx(ctx,t,dnsCall.err,dnsError)
1581+
1582+
// should trigger a close on the client
1583+
closeCall:=testutil.RequireRecvCtx(ctx,t,updateC.close)
1584+
testutil.RequireSendCtx(ctx,t,closeCall,io.EOF)
1585+
1586+
// error should be our initial DNS error
1587+
err:=testutil.RequireRecvCtx(ctx,t,updateCW.Wait())
1588+
require.ErrorIs(t,err,dnsError)
14711589
}
14721590

14731591
funcTestTunnelAllWorkspaceUpdatesController_HandleErrors(t*testing.T) {
@@ -1562,7 +1680,7 @@ func TestTunnelAllWorkspaceUpdatesController_HandleErrors(t *testing.T) {
15621680

15631681
fConn:=&fakeCoordinatee{}
15641682
tsc:=tailnet.NewTunnelSrcCoordController(logger,fConn)
1565-
uut:=tailnet.NewTunnelAllWorkspaceUpdatesController(logger,tsc)
1683+
uut:=tailnet.NewTunnelAllWorkspaceUpdatesController(logger,tsc,nil)
15661684
updateC:=newFakeWorkspaceUpdateClient(ctx,t)
15671685
updateCW:=uut.New(updateC)
15681686

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp