mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
feat: vpn uses WorkspaceHostnameSuffix for DNS names (#17335)
Use the hostname suffix to set DNS names as programmed into the DNS service and returned by the vpn `Tunnel`. part of: #16828
This commit is contained in:
+1
-3
@@ -357,9 +357,7 @@ func NewConn(options *Options) (conn *Conn, err error) {
|
||||
// A FQDN to be mapped to `tsaddr.CoderServiceIPv6`. This address can be used
|
||||
// when you want to know if Coder Connect is running, but are not trying to
|
||||
// connect to a specific known workspace.
|
||||
const IsCoderConnectEnabledFQDNString = "is.coder--connect--enabled--right--now.coder."
|
||||
|
||||
var IsCoderConnectEnabledFQDN, _ = dnsname.ToFQDN(IsCoderConnectEnabledFQDNString)
|
||||
const IsCoderConnectEnabledFmtString = "is.coder--connect--enabled--right--now.%s."
|
||||
|
||||
type ServicePrefix [6]byte
|
||||
|
||||
|
||||
+33
-16
@@ -864,11 +864,12 @@ func (r *basicResumeTokenRefresher) refresh() {
|
||||
}
|
||||
|
||||
type TunnelAllWorkspaceUpdatesController struct {
|
||||
coordCtrl *TunnelSrcCoordController
|
||||
dnsHostSetter DNSHostsSetter
|
||||
updateHandler UpdatesHandler
|
||||
ownerUsername string
|
||||
logger slog.Logger
|
||||
coordCtrl *TunnelSrcCoordController
|
||||
dnsHostSetter DNSHostsSetter
|
||||
dnsNameOptions DNSNameOptions
|
||||
updateHandler UpdatesHandler
|
||||
ownerUsername string
|
||||
logger slog.Logger
|
||||
|
||||
mu sync.Mutex
|
||||
updater *tunnelUpdater
|
||||
@@ -883,12 +884,16 @@ type Workspace struct {
|
||||
agents map[uuid.UUID]*Agent
|
||||
}
|
||||
|
||||
type DNSNameOptions struct {
|
||||
Suffix string
|
||||
}
|
||||
|
||||
// updateDNSNames updates the DNS names for all agents in the workspace.
|
||||
// DNS hosts must be all lowercase, or the resolver won't be able to find them.
|
||||
// Usernames are globally unique & case-insensitive.
|
||||
// Workspace names are unique per-user & case-insensitive.
|
||||
// Agent names are unique per-workspace & case-insensitive.
|
||||
func (w *Workspace) updateDNSNames() error {
|
||||
func (w *Workspace) updateDNSNames(options DNSNameOptions) error {
|
||||
wsName := strings.ToLower(w.Name)
|
||||
username := strings.ToLower(w.ownerUsername)
|
||||
for id, a := range w.agents {
|
||||
@@ -896,24 +901,22 @@ func (w *Workspace) updateDNSNames() error {
|
||||
names := make(map[dnsname.FQDN][]netip.Addr)
|
||||
// TODO: technically, DNS labels cannot start with numbers, but the rules are often not
|
||||
// strictly enforced.
|
||||
fqdn, err := dnsname.ToFQDN(fmt.Sprintf("%s.%s.me.coder.", agentName, wsName))
|
||||
fqdn, err := dnsname.ToFQDN(fmt.Sprintf("%s.%s.me.%s.", agentName, wsName, options.Suffix))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
names[fqdn] = []netip.Addr{CoderServicePrefix.AddrFromUUID(a.ID)}
|
||||
fqdn, err = dnsname.ToFQDN(fmt.Sprintf("%s.%s.%s.coder.", agentName, wsName, username))
|
||||
fqdn, err = dnsname.ToFQDN(fmt.Sprintf("%s.%s.%s.%s.", agentName, wsName, username, options.Suffix))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
names[fqdn] = []netip.Addr{CoderServicePrefix.AddrFromUUID(a.ID)}
|
||||
if len(w.agents) == 1 {
|
||||
fqdn, err := dnsname.ToFQDN(fmt.Sprintf("%s.coder.", wsName))
|
||||
fqdn, err = dnsname.ToFQDN(fmt.Sprintf("%s.%s.", wsName, options.Suffix))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, a := range w.agents {
|
||||
names[fqdn] = []netip.Addr{CoderServicePrefix.AddrFromUUID(a.ID)}
|
||||
}
|
||||
names[fqdn] = []netip.Addr{CoderServicePrefix.AddrFromUUID(a.ID)}
|
||||
}
|
||||
a.Hosts = names
|
||||
w.agents[id] = a
|
||||
@@ -950,6 +953,7 @@ func (t *TunnelAllWorkspaceUpdatesController) New(client WorkspaceUpdatesClient)
|
||||
logger: t.logger,
|
||||
coordCtrl: t.coordCtrl,
|
||||
dnsHostsSetter: t.dnsHostSetter,
|
||||
dnsNameOptions: t.dnsNameOptions,
|
||||
updateHandler: t.updateHandler,
|
||||
ownerUsername: t.ownerUsername,
|
||||
recvLoopDone: make(chan struct{}),
|
||||
@@ -996,6 +1000,7 @@ type tunnelUpdater struct {
|
||||
updateHandler UpdatesHandler
|
||||
ownerUsername string
|
||||
recvLoopDone chan struct{}
|
||||
dnsNameOptions DNSNameOptions
|
||||
|
||||
sync.Mutex
|
||||
workspaces map[uuid.UUID]*Workspace
|
||||
@@ -1250,7 +1255,7 @@ func (t *tunnelUpdater) allAgentIDsLocked() []uuid.UUID {
|
||||
func (t *tunnelUpdater) updateDNSNamesLocked() map[dnsname.FQDN][]netip.Addr {
|
||||
names := make(map[dnsname.FQDN][]netip.Addr)
|
||||
for _, w := range t.workspaces {
|
||||
err := w.updateDNSNames()
|
||||
err := w.updateDNSNames(t.dnsNameOptions)
|
||||
if err != nil {
|
||||
// This should never happen in production, because converting the FQDN only fails
|
||||
// if names are too long, and we put strict length limits on agent, workspace, and user
|
||||
@@ -1258,6 +1263,7 @@ func (t *tunnelUpdater) updateDNSNamesLocked() map[dnsname.FQDN][]netip.Addr {
|
||||
t.logger.Critical(context.Background(),
|
||||
"failed to include DNS name(s)",
|
||||
slog.F("workspace_id", w.ID),
|
||||
slog.F("suffix", t.dnsNameOptions.Suffix),
|
||||
slog.Error(err))
|
||||
}
|
||||
for _, a := range w.agents {
|
||||
@@ -1266,7 +1272,13 @@ func (t *tunnelUpdater) updateDNSNamesLocked() map[dnsname.FQDN][]netip.Addr {
|
||||
}
|
||||
}
|
||||
}
|
||||
names[IsCoderConnectEnabledFQDN] = []netip.Addr{tsaddr.CoderServiceIPv6()}
|
||||
isCoderConnectEnabledFQDN, err := dnsname.ToFQDN(fmt.Sprintf(IsCoderConnectEnabledFmtString, t.dnsNameOptions.Suffix))
|
||||
if err != nil {
|
||||
t.logger.Critical(context.Background(),
|
||||
"failed to include Coder Connect enabled DNS name", slog.F("suffix", t.dnsNameOptions.Suffix))
|
||||
} else {
|
||||
names[isCoderConnectEnabledFQDN] = []netip.Addr{tsaddr.CoderServiceIPv6()}
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
@@ -1274,10 +1286,11 @@ type TunnelAllOption func(t *TunnelAllWorkspaceUpdatesController)
|
||||
|
||||
// WithDNS configures the tunnelAllWorkspaceUpdatesController to set DNS names for all workspaces
|
||||
// and agents it learns about.
|
||||
func WithDNS(d DNSHostsSetter, ownerUsername string) TunnelAllOption {
|
||||
func WithDNS(d DNSHostsSetter, ownerUsername string, options DNSNameOptions) TunnelAllOption {
|
||||
return func(t *TunnelAllWorkspaceUpdatesController) {
|
||||
t.dnsHostSetter = d
|
||||
t.ownerUsername = ownerUsername
|
||||
t.dnsNameOptions = options
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1293,7 +1306,11 @@ func WithHandler(h UpdatesHandler) TunnelAllOption {
|
||||
func NewTunnelAllWorkspaceUpdatesController(
|
||||
logger slog.Logger, c *TunnelSrcCoordController, opts ...TunnelAllOption,
|
||||
) *TunnelAllWorkspaceUpdatesController {
|
||||
t := &TunnelAllWorkspaceUpdatesController{logger: logger, coordCtrl: c}
|
||||
t := &TunnelAllWorkspaceUpdatesController{
|
||||
logger: logger,
|
||||
coordCtrl: c,
|
||||
dnsNameOptions: DNSNameOptions{"coder"},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(t)
|
||||
}
|
||||
|
||||
+40
-31
@@ -1522,7 +1522,7 @@ func TestTunnelAllWorkspaceUpdatesController_Initial(t *testing.T) {
|
||||
fUH := newFakeUpdateHandler(ctx, t)
|
||||
fDNS := newFakeDNSSetter(ctx, t)
|
||||
coordC, updateC, updateCtrl := setupConnectedAllWorkspaceUpdatesController(ctx, t, logger,
|
||||
tailnet.WithDNS(fDNS, "testy"),
|
||||
tailnet.WithDNS(fDNS, "testy", tailnet.DNSNameOptions{Suffix: "mctest"}),
|
||||
tailnet.WithHandler(fUH),
|
||||
)
|
||||
|
||||
@@ -1562,16 +1562,19 @@ func TestTunnelAllWorkspaceUpdatesController_Initial(t *testing.T) {
|
||||
w2a1IP := netip.MustParseAddr("fd60:627a:a42b:0201::")
|
||||
w2a2IP := netip.MustParseAddr("fd60:627a:a42b:0202::")
|
||||
|
||||
expectedCoderConnectFQDN, err := dnsname.ToFQDN(fmt.Sprintf(tailnet.IsCoderConnectEnabledFmtString, "mctest"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Also triggers setting DNS hosts
|
||||
expectedDNS := map[dnsname.FQDN][]netip.Addr{
|
||||
"w1a1.w1.me.coder.": {ws1a1IP},
|
||||
"w2a1.w2.me.coder.": {w2a1IP},
|
||||
"w2a2.w2.me.coder.": {w2a2IP},
|
||||
"w1a1.w1.testy.coder.": {ws1a1IP},
|
||||
"w2a1.w2.testy.coder.": {w2a1IP},
|
||||
"w2a2.w2.testy.coder.": {w2a2IP},
|
||||
"w1.coder.": {ws1a1IP},
|
||||
tailnet.IsCoderConnectEnabledFQDNString: {tsaddr.CoderServiceIPv6()},
|
||||
"w1a1.w1.me.mctest.": {ws1a1IP},
|
||||
"w2a1.w2.me.mctest.": {w2a1IP},
|
||||
"w2a2.w2.me.mctest.": {w2a2IP},
|
||||
"w1a1.w1.testy.mctest.": {ws1a1IP},
|
||||
"w2a1.w2.testy.mctest.": {w2a1IP},
|
||||
"w2a2.w2.testy.mctest.": {w2a2IP},
|
||||
"w1.mctest.": {ws1a1IP},
|
||||
expectedCoderConnectFQDN: {tsaddr.CoderServiceIPv6()},
|
||||
}
|
||||
dnsCall := testutil.RequireRecvCtx(ctx, t, fDNS.calls)
|
||||
require.Equal(t, expectedDNS, dnsCall.hosts)
|
||||
@@ -1586,23 +1589,23 @@ func TestTunnelAllWorkspaceUpdatesController_Initial(t *testing.T) {
|
||||
{
|
||||
ID: w1a1ID, Name: "w1a1", WorkspaceID: w1ID,
|
||||
Hosts: map[dnsname.FQDN][]netip.Addr{
|
||||
"w1.coder.": {ws1a1IP},
|
||||
"w1a1.w1.me.coder.": {ws1a1IP},
|
||||
"w1a1.w1.testy.coder.": {ws1a1IP},
|
||||
"w1.mctest.": {ws1a1IP},
|
||||
"w1a1.w1.me.mctest.": {ws1a1IP},
|
||||
"w1a1.w1.testy.mctest.": {ws1a1IP},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: w2a1ID, Name: "w2a1", WorkspaceID: w2ID,
|
||||
Hosts: map[dnsname.FQDN][]netip.Addr{
|
||||
"w2a1.w2.me.coder.": {w2a1IP},
|
||||
"w2a1.w2.testy.coder.": {w2a1IP},
|
||||
"w2a1.w2.me.mctest.": {w2a1IP},
|
||||
"w2a1.w2.testy.mctest.": {w2a1IP},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: w2a2ID, Name: "w2a2", WorkspaceID: w2ID,
|
||||
Hosts: map[dnsname.FQDN][]netip.Addr{
|
||||
"w2a2.w2.me.coder.": {w2a2IP},
|
||||
"w2a2.w2.testy.coder.": {w2a2IP},
|
||||
"w2a2.w2.me.mctest.": {w2a2IP},
|
||||
"w2a2.w2.testy.mctest.": {w2a2IP},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1634,7 +1637,7 @@ func TestTunnelAllWorkspaceUpdatesController_DeleteAgent(t *testing.T) {
|
||||
fUH := newFakeUpdateHandler(ctx, t)
|
||||
fDNS := newFakeDNSSetter(ctx, t)
|
||||
coordC, updateC, updateCtrl := setupConnectedAllWorkspaceUpdatesController(ctx, t, logger,
|
||||
tailnet.WithDNS(fDNS, "testy"),
|
||||
tailnet.WithDNS(fDNS, "testy", tailnet.DNSNameOptions{Suffix: "coder"}),
|
||||
tailnet.WithHandler(fUH),
|
||||
)
|
||||
|
||||
@@ -1661,12 +1664,15 @@ func TestTunnelAllWorkspaceUpdatesController_DeleteAgent(t *testing.T) {
|
||||
require.Equal(t, w1a1ID[:], coordCall.req.GetAddTunnel().GetId())
|
||||
testutil.RequireSendCtx(ctx, t, coordCall.err, nil)
|
||||
|
||||
expectedCoderConnectFQDN, err := dnsname.ToFQDN(fmt.Sprintf(tailnet.IsCoderConnectEnabledFmtString, "coder"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// DNS for w1a1
|
||||
expectedDNS := map[dnsname.FQDN][]netip.Addr{
|
||||
"w1a1.w1.testy.coder.": {ws1a1IP},
|
||||
"w1a1.w1.me.coder.": {ws1a1IP},
|
||||
"w1.coder.": {ws1a1IP},
|
||||
tailnet.IsCoderConnectEnabledFQDNString: {tsaddr.CoderServiceIPv6()},
|
||||
"w1a1.w1.testy.coder.": {ws1a1IP},
|
||||
"w1a1.w1.me.coder.": {ws1a1IP},
|
||||
"w1.coder.": {ws1a1IP},
|
||||
expectedCoderConnectFQDN: {tsaddr.CoderServiceIPv6()},
|
||||
}
|
||||
dnsCall := testutil.RequireRecvCtx(ctx, t, fDNS.calls)
|
||||
require.Equal(t, expectedDNS, dnsCall.hosts)
|
||||
@@ -1719,10 +1725,10 @@ func TestTunnelAllWorkspaceUpdatesController_DeleteAgent(t *testing.T) {
|
||||
|
||||
// DNS contains only w1a2
|
||||
expectedDNS = map[dnsname.FQDN][]netip.Addr{
|
||||
"w1a2.w1.testy.coder.": {ws1a2IP},
|
||||
"w1a2.w1.me.coder.": {ws1a2IP},
|
||||
"w1.coder.": {ws1a2IP},
|
||||
tailnet.IsCoderConnectEnabledFQDNString: {tsaddr.CoderServiceIPv6()},
|
||||
"w1a2.w1.testy.coder.": {ws1a2IP},
|
||||
"w1a2.w1.me.coder.": {ws1a2IP},
|
||||
"w1.coder.": {ws1a2IP},
|
||||
expectedCoderConnectFQDN: {tsaddr.CoderServiceIPv6()},
|
||||
}
|
||||
dnsCall = testutil.RequireRecvCtx(ctx, t, fDNS.calls)
|
||||
require.Equal(t, expectedDNS, dnsCall.hosts)
|
||||
@@ -1779,7 +1785,7 @@ func TestTunnelAllWorkspaceUpdatesController_DNSError(t *testing.T) {
|
||||
fConn := &fakeCoordinatee{}
|
||||
tsc := tailnet.NewTunnelSrcCoordController(logger, fConn)
|
||||
uut := tailnet.NewTunnelAllWorkspaceUpdatesController(logger, tsc,
|
||||
tailnet.WithDNS(fDNS, "testy"),
|
||||
tailnet.WithDNS(fDNS, "testy", tailnet.DNSNameOptions{Suffix: "coder"}),
|
||||
)
|
||||
|
||||
updateC := newFakeWorkspaceUpdateClient(ctx, t)
|
||||
@@ -1800,12 +1806,15 @@ func TestTunnelAllWorkspaceUpdatesController_DNSError(t *testing.T) {
|
||||
upRecvCall := testutil.RequireRecvCtx(ctx, t, updateC.recv)
|
||||
testutil.RequireSendCtx(ctx, t, upRecvCall.resp, initUp)
|
||||
|
||||
expectedCoderConnectFQDN, err := dnsname.ToFQDN(fmt.Sprintf(tailnet.IsCoderConnectEnabledFmtString, "coder"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// DNS for w1a1
|
||||
expectedDNS := map[dnsname.FQDN][]netip.Addr{
|
||||
"w1a1.w1.me.coder.": {ws1a1IP},
|
||||
"w1a1.w1.testy.coder.": {ws1a1IP},
|
||||
"w1.coder.": {ws1a1IP},
|
||||
tailnet.IsCoderConnectEnabledFQDNString: {tsaddr.CoderServiceIPv6()},
|
||||
"w1a1.w1.me.coder.": {ws1a1IP},
|
||||
"w1a1.w1.testy.coder.": {ws1a1IP},
|
||||
"w1.coder.": {ws1a1IP},
|
||||
expectedCoderConnectFQDN: {tsaddr.CoderServiceIPv6()},
|
||||
}
|
||||
dnsCall := testutil.RequireRecvCtx(ctx, t, fDNS.calls)
|
||||
require.Equal(t, expectedDNS, dnsCall.hosts)
|
||||
@@ -1816,7 +1825,7 @@ func TestTunnelAllWorkspaceUpdatesController_DNSError(t *testing.T) {
|
||||
testutil.RequireSendCtx(ctx, t, closeCall, io.EOF)
|
||||
|
||||
// error should be our initial DNS error
|
||||
err := testutil.RequireRecvCtx(ctx, t, updateCW.Wait())
|
||||
err = testutil.RequireRecvCtx(ctx, t, updateCW.Wait())
|
||||
require.ErrorIs(t, err, dnsError)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user