feat: add configurable DNS match domain for tailnet connections (#17336)

Use the hostname suffix to set the DNS match domain when creating a Tailnet as part of the vpn `Tunnel`.

part of: #16828
This commit is contained in:
Spike Curtis
2025-04-11 15:00:48 +04:00
committed by GitHub
parent 69aa365169
commit 9e2af3e127
6 changed files with 65 additions and 34 deletions

View File

@ -36,7 +36,10 @@ const lostTimeout = 15 * time.Minute
// CoderDNSSuffix is the default DNS suffix that we append to Coder DNS
// records.
const CoderDNSSuffix = "coder."
const (
CoderDNSSuffix = "coder"
CoderDNSSuffixFQDN = dnsname.FQDN(CoderDNSSuffix + ".")
)
// engineConfigurable is the subset of wgengine.Engine that we use for configuration.
//
@ -71,18 +74,24 @@ type configMaps struct {
engine engineConfigurable
static netmap.NetworkMap
hosts map[dnsname.FQDN][]netip.Addr
peers map[uuid.UUID]*peerLifecycle
addresses []netip.Prefix
derpMap *tailcfg.DERPMap
logger slog.Logger
blockEndpoints bool
matchDomain dnsname.FQDN
// for testing
clock quartz.Clock
}
func newConfigMaps(logger slog.Logger, engine engineConfigurable, nodeID tailcfg.NodeID, nodeKey key.NodePrivate, discoKey key.DiscoPublic) *configMaps {
func newConfigMaps(
logger slog.Logger, engine engineConfigurable,
nodeID tailcfg.NodeID, nodeKey key.NodePrivate, discoKey key.DiscoPublic,
matchDomain dnsname.FQDN,
) *configMaps {
pubKey := nodeKey.Public()
c := &configMaps{
phased: phased{Cond: *(sync.NewCond(&sync.Mutex{}))},
@ -126,6 +135,7 @@ func newConfigMaps(logger slog.Logger, engine engineConfigurable, nodeID tailcfg
}},
},
peers: make(map[uuid.UUID]*peerLifecycle),
matchDomain: matchDomain,
clock: quartz.NewReal(),
}
go c.configLoop()
@ -338,7 +348,7 @@ func (c *configMaps) reconfig(nm *netmap.NetworkMap, hosts map[dnsname.FQDN][]ne
dnsCfg.Hosts = hosts
dnsCfg.OnlyIPv6 = true
dnsCfg.Routes = map[dnsname.FQDN][]*dnstype.Resolver{
CoderDNSSuffix: nil,
c.matchDomain: nil,
}
}
cfg, err := nmcfg.WGCfg(nm, Logger(c.logger.Named("net.wgconfig")), netmap.AllowSingleHosts, "")

View File

@ -34,7 +34,7 @@ func TestConfigMaps_setAddresses_different(t *testing.T) {
nodePrivateKey := key.NewNode()
nodeID := tailcfg.NodeID(5)
discoKey := key.NewDisco()
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public())
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public(), CoderDNSSuffixFQDN)
defer uut.close()
addrs := []netip.Prefix{netip.MustParsePrefix("192.168.0.200/32")}
@ -93,7 +93,7 @@ func TestConfigMaps_setAddresses_same(t *testing.T) {
nodeID := tailcfg.NodeID(5)
discoKey := key.NewDisco()
addrs := []netip.Prefix{netip.MustParsePrefix("192.168.0.200/32")}
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public())
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public(), CoderDNSSuffixFQDN)
defer uut.close()
// Given: addresses already set
@ -123,7 +123,7 @@ func TestConfigMaps_updatePeers_new(t *testing.T) {
nodePrivateKey := key.NewNode()
nodeID := tailcfg.NodeID(5)
discoKey := key.NewDisco()
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public())
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public(), CoderDNSSuffixFQDN)
defer uut.close()
p1ID := uuid.UUID{1}
@ -193,7 +193,7 @@ func TestConfigMaps_updatePeers_new_waitForHandshake_neverConfigures(t *testing.
nodePrivateKey := key.NewNode()
nodeID := tailcfg.NodeID(5)
discoKey := key.NewDisco()
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public())
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public(), CoderDNSSuffixFQDN)
defer uut.close()
mClock := quartz.NewMock(t)
uut.clock = mClock
@ -237,7 +237,7 @@ func TestConfigMaps_updatePeers_new_waitForHandshake_outOfOrder(t *testing.T) {
nodePrivateKey := key.NewNode()
nodeID := tailcfg.NodeID(5)
discoKey := key.NewDisco()
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public())
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public(), CoderDNSSuffixFQDN)
defer uut.close()
mClock := quartz.NewMock(t)
uut.clock = mClock
@ -308,7 +308,7 @@ func TestConfigMaps_updatePeers_new_waitForHandshake(t *testing.T) {
nodePrivateKey := key.NewNode()
nodeID := tailcfg.NodeID(5)
discoKey := key.NewDisco()
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public())
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public(), CoderDNSSuffixFQDN)
defer uut.close()
mClock := quartz.NewMock(t)
uut.clock = mClock
@ -379,7 +379,7 @@ func TestConfigMaps_updatePeers_new_waitForHandshake_timeout(t *testing.T) {
nodePrivateKey := key.NewNode()
nodeID := tailcfg.NodeID(5)
discoKey := key.NewDisco()
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public())
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public(), CoderDNSSuffixFQDN)
defer uut.close()
mClock := quartz.NewMock(t)
uut.clock = mClock
@ -437,7 +437,7 @@ func TestConfigMaps_updatePeers_same(t *testing.T) {
nodePrivateKey := key.NewNode()
nodeID := tailcfg.NodeID(5)
discoKey := key.NewDisco()
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public())
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public(), CoderDNSSuffixFQDN)
defer uut.close()
// Then: we don't configure
@ -496,7 +496,7 @@ func TestConfigMaps_updatePeers_disconnect(t *testing.T) {
nodePrivateKey := key.NewNode()
nodeID := tailcfg.NodeID(5)
discoKey := key.NewDisco()
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public())
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public(), CoderDNSSuffixFQDN)
defer uut.close()
p1ID := uuid.UUID{1}
@ -564,7 +564,7 @@ func TestConfigMaps_updatePeers_lost(t *testing.T) {
nodePrivateKey := key.NewNode()
nodeID := tailcfg.NodeID(5)
discoKey := key.NewDisco()
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public())
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public(), CoderDNSSuffixFQDN)
defer uut.close()
mClock := quartz.NewMock(t)
start := mClock.Now()
@ -649,7 +649,7 @@ func TestConfigMaps_updatePeers_lost_and_found(t *testing.T) {
nodePrivateKey := key.NewNode()
nodeID := tailcfg.NodeID(5)
discoKey := key.NewDisco()
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public())
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public(), CoderDNSSuffixFQDN)
defer uut.close()
mClock := quartz.NewMock(t)
start := mClock.Now()
@ -734,7 +734,7 @@ func TestConfigMaps_setAllPeersLost(t *testing.T) {
nodePrivateKey := key.NewNode()
nodeID := tailcfg.NodeID(5)
discoKey := key.NewDisco()
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public())
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public(), CoderDNSSuffixFQDN)
defer uut.close()
mClock := quartz.NewMock(t)
start := mClock.Now()
@ -820,7 +820,7 @@ func TestConfigMaps_setBlockEndpoints_different(t *testing.T) {
nodePrivateKey := key.NewNode()
nodeID := tailcfg.NodeID(5)
discoKey := key.NewDisco()
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public())
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public(), CoderDNSSuffixFQDN)
defer uut.close()
p1ID := uuid.MustParse("10000000-0000-0000-0000-000000000000")
@ -864,7 +864,7 @@ func TestConfigMaps_setBlockEndpoints_same(t *testing.T) {
nodePrivateKey := key.NewNode()
nodeID := tailcfg.NodeID(5)
discoKey := key.NewDisco()
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public())
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public(), CoderDNSSuffixFQDN)
defer uut.close()
p1ID := uuid.MustParse("10000000-0000-0000-0000-000000000000")
@ -907,7 +907,7 @@ func TestConfigMaps_setDERPMap_different(t *testing.T) {
nodePrivateKey := key.NewNode()
nodeID := tailcfg.NodeID(5)
discoKey := key.NewDisco()
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public())
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public(), CoderDNSSuffixFQDN)
defer uut.close()
derpMap := &tailcfg.DERPMap{
@ -948,7 +948,7 @@ func TestConfigMaps_setDERPMap_same(t *testing.T) {
nodePrivateKey := key.NewNode()
nodeID := tailcfg.NodeID(5)
discoKey := key.NewDisco()
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public())
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public(), CoderDNSSuffixFQDN)
defer uut.close()
// Given: DERP Map already set
@ -1017,7 +1017,7 @@ func TestConfigMaps_fillPeerDiagnostics(t *testing.T) {
nodePrivateKey := key.NewNode()
nodeID := tailcfg.NodeID(5)
discoKey := key.NewDisco()
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public())
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public(), CoderDNSSuffixFQDN)
defer uut.close()
// Given: DERP Map and peer already set
@ -1125,7 +1125,7 @@ func TestConfigMaps_updatePeers_nonexist(t *testing.T) {
nodePrivateKey := key.NewNode()
nodeID := tailcfg.NodeID(5)
discoKey := key.NewDisco()
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public())
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public(), CoderDNSSuffixFQDN)
defer uut.close()
// Then: we don't configure
@ -1166,7 +1166,8 @@ func TestConfigMaps_addRemoveHosts(t *testing.T) {
nodePrivateKey := key.NewNode()
nodeID := tailcfg.NodeID(5)
discoKey := key.NewDisco()
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public())
suffix := dnsname.FQDN("test.")
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public(), suffix)
defer uut.close()
addr1 := CoderServicePrefix.AddrFromUUID(uuid.New())
@ -1190,8 +1191,10 @@ func TestConfigMaps_addRemoveHosts(t *testing.T) {
req := testutil.RequireRecvCtx(ctx, t, fEng.reconfig)
require.Equal(t, req.dnsCfg, &dns.Config{
Routes: map[dnsname.FQDN][]*dnstype.Resolver{
CoderDNSSuffix: nil,
suffix: nil,
},
// Note that host names and Routes are independent --- so we faithfully reproduce the hosts, even though
// they don't match the route.
Hosts: map[dnsname.FQDN][]netip.Addr{
"agent.myws.me.coder.": {
addr1,
@ -1219,7 +1222,7 @@ func TestConfigMaps_addRemoveHosts(t *testing.T) {
req = testutil.RequireRecvCtx(ctx, t, fEng.reconfig)
require.Equal(t, req.dnsCfg, &dns.Config{
Routes: map[dnsname.FQDN][]*dnstype.Resolver{
CoderDNSSuffix: nil,
suffix: nil,
},
Hosts: map[dnsname.FQDN][]netip.Addr{
"newagent.myws.me.coder.": {

View File

@ -120,6 +120,9 @@ type Options struct {
// WireguardMonitor is optional, and is passed to the underlying wireguard
// engine.
WireguardMonitor *netmon.Monitor
// DNSMatchDomain is the DNS suffix to use as a match domain. Only relevant for TUN connections that configure the
// OS DNS resolver.
DNSMatchDomain string
}
// TelemetrySink allows tailnet.Conn to send network telemetry to the Coder
@ -267,12 +270,21 @@ func NewConn(options *Options) (conn *Conn, err error) {
netStack.ProcessLocalIPs = true
}
if options.DNSMatchDomain == "" {
options.DNSMatchDomain = CoderDNSSuffix
}
matchDomain, err := dnsname.ToFQDN(options.DNSMatchDomain + ".")
if err != nil {
return nil, xerrors.Errorf("convert hostname suffix (%s) to fully-qualified domain: %w",
options.DNSMatchDomain, err)
}
cfgMaps := newConfigMaps(
options.Logger,
wireguardEngine,
nodeID,
nodePrivateKey,
magicConn.DiscoPublicKey(),
matchDomain,
)
cfgMaps.setAddresses(options.Addresses)
if options.DERPMap != nil {

View File

@ -1309,7 +1309,7 @@ func NewTunnelAllWorkspaceUpdatesController(
t := &TunnelAllWorkspaceUpdatesController{
logger: logger,
coordCtrl: c,
dnsNameOptions: DNSNameOptions{"coder"},
dnsNameOptions: DNSNameOptions{CoderDNSSuffix},
}
for _, opt := range opts {
opt(t)

View File

@ -1637,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.DNSNameOptions{Suffix: "coder"}),
tailnet.WithDNS(fDNS, "testy", tailnet.DNSNameOptions{Suffix: tailnet.CoderDNSSuffix}),
tailnet.WithHandler(fUH),
)
@ -1664,7 +1664,8 @@ 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"))
expectedCoderConnectFQDN, err := dnsname.ToFQDN(
fmt.Sprintf(tailnet.IsCoderConnectEnabledFmtString, tailnet.CoderDNSSuffix))
require.NoError(t, err)
// DNS for w1a1
@ -1785,7 +1786,7 @@ func TestTunnelAllWorkspaceUpdatesController_DNSError(t *testing.T) {
fConn := &fakeCoordinatee{}
tsc := tailnet.NewTunnelSrcCoordController(logger, fConn)
uut := tailnet.NewTunnelAllWorkspaceUpdatesController(logger, tsc,
tailnet.WithDNS(fDNS, "testy", tailnet.DNSNameOptions{Suffix: "coder"}),
tailnet.WithDNS(fDNS, "testy", tailnet.DNSNameOptions{Suffix: tailnet.CoderDNSSuffix}),
)
updateC := newFakeWorkspaceUpdateClient(ctx, t)
@ -1806,7 +1807,8 @@ 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"))
expectedCoderConnectFQDN, err := dnsname.ToFQDN(
fmt.Sprintf(tailnet.IsCoderConnectEnabledFmtString, tailnet.CoderDNSSuffix))
require.NoError(t, err)
// DNS for w1a1

View File

@ -7,6 +7,7 @@ import (
"net/url"
"golang.org/x/xerrors"
"tailscale.com/net/dns"
"tailscale.com/net/netmon"
"tailscale.com/wgengine/router"
@ -108,9 +109,11 @@ func (*client) NewConn(initCtx context.Context, serverURL *url.URL, token string
return nil, xerrors.Errorf("get connection info: %w", err)
}
// default to DNS suffix of "coder" if the server hasn't set it (might be too old).
dnsNameOptions := tailnet.DNSNameOptions{Suffix: "coder"}
dnsNameOptions := tailnet.DNSNameOptions{Suffix: tailnet.CoderDNSSuffix}
dnsMatch := tailnet.CoderDNSSuffix
if connInfo.HostnameSuffix != "" {
dnsNameOptions.Suffix = connInfo.HostnameSuffix
dnsMatch = connInfo.HostnameSuffix
}
headers.Set(codersdk.SessionTokenHeader, token)
@ -134,6 +137,7 @@ func (*client) NewConn(initCtx context.Context, serverURL *url.URL, token string
Router: options.Router,
TUNDev: options.TUNDevice,
WireguardMonitor: options.WireguardMonitor,
DNSMatchDomain: dnsMatch,
})
if err != nil {
return nil, xerrors.Errorf("create tailnet: %w", err)