mirror of
https://github.com/juanfont/headscale.git
synced 2026-10-06 14:50:07 +09:00
servertest: cover a node's DNS config following its NextDNS inputs
Profile via policy reload or tag change, device metadata via hostname; a hostname change today waits for the next policy response to reach DNS.
This commit is contained in:
committed by
Kristoffer Dalby
parent
60d42808b8
commit
59030f380d
@@ -3,6 +3,7 @@ package servertest_test
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -463,3 +464,95 @@ func TestSuggestExitNodeDefaultOnPeerCapMap(t *testing.T) {
|
||||
viewer.WaitForCondition(t, "peer suggest-exit-node gone after unapprove",
|
||||
10*time.Second, peerHasCap(false))
|
||||
}
|
||||
|
||||
// firstResolver returns the address of the first DNS resolver in nm.
|
||||
func firstResolver(nm *netmap.NetworkMap) string {
|
||||
if nm == nil || len(nm.DNS.Resolvers) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return nm.DNS.Resolvers[0].Addr
|
||||
}
|
||||
|
||||
// TestNodeAttrsNextDNS checks a node's DNS config follows each of its
|
||||
// inputs: the NextDNS profile from nodeAttrs, whether reached through a
|
||||
// policy reload or a tag change, and the device metadata from its Hostinfo.
|
||||
func TestNodeAttrsNextDNS(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
setup := func(t *testing.T) (*servertest.TestServer, *servertest.TestClient) {
|
||||
t.Helper()
|
||||
|
||||
srv := servertest.NewServer(t, servertest.WithDNSResolvers("https://dns.nextdns.io/base"))
|
||||
user := srv.CreateUser(t, "nd-user")
|
||||
c := servertest.NewClient(t, srv, "nd-node", servertest.WithUser(user))
|
||||
|
||||
c.WaitForCondition(t, "base resolver", 10*time.Second,
|
||||
func(nm *netmap.NetworkMap) bool {
|
||||
return strings.HasPrefix(firstResolver(nm), "https://dns.nextdns.io/base?")
|
||||
})
|
||||
|
||||
return srv, c
|
||||
}
|
||||
|
||||
waitResolver := func(t *testing.T, c *servertest.TestClient, prefix string) {
|
||||
t.Helper()
|
||||
|
||||
c.WaitForCondition(t, "resolver "+prefix, 10*time.Second,
|
||||
func(nm *netmap.NetworkMap) bool {
|
||||
return strings.HasPrefix(firstResolver(nm), prefix)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("policy_reload", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
srv, c := setup(t)
|
||||
|
||||
reloadPolicy(t, srv, `{
|
||||
"acls": [{"action": "accept", "src": ["*"], "dst": ["*:*"]}],
|
||||
"nodeAttrs": [{"target": ["nd-user@"], "attr": ["nextdns:userprof"]}]
|
||||
}`)
|
||||
|
||||
waitResolver(t, c, "https://dns.nextdns.io/userprof?")
|
||||
})
|
||||
|
||||
t.Run("tag_change", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
srv, c := setup(t)
|
||||
|
||||
reloadPolicy(t, srv, `{
|
||||
"tagOwners": {"tag:dns": ["nd-user@"]},
|
||||
"acls": [{"action": "accept", "src": ["*"], "dst": ["*:*"]}],
|
||||
"nodeAttrs": [{"target": ["tag:dns"], "attr": ["nextdns:tagprof"]}]
|
||||
}`)
|
||||
|
||||
_, tagChange, err := srv.State().SetNodeTags(findNodeID(t, srv, "nd-node"), []string{"tag:dns"})
|
||||
require.NoError(t, err)
|
||||
srv.App.Change(tagChange)
|
||||
|
||||
waitResolver(t, c, "https://dns.nextdns.io/tagprof?")
|
||||
})
|
||||
|
||||
t.Run("hostname_change", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, c := setup(t)
|
||||
|
||||
c.Direct().SetHostinfo(&tailcfg.Hostinfo{
|
||||
BackendLogID: "servertest-nd-node",
|
||||
Hostname: "nd-renamed",
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_ = c.Direct().SendUpdate(ctx)
|
||||
|
||||
c.WaitForCondition(t, "renamed device_name", 10*time.Second,
|
||||
func(nm *netmap.NetworkMap) bool {
|
||||
return strings.Contains(firstResolver(nm), "device_name=nd-renamed")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
"tailscale.com/net/memnet"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/dnstype"
|
||||
)
|
||||
|
||||
// TestServer is an in-process Headscale control server suitable for
|
||||
@@ -46,6 +47,7 @@ type serverConfig struct {
|
||||
taildropEnabled bool
|
||||
realListener bool
|
||||
magicDNSDomain string
|
||||
dnsResolvers []string
|
||||
}
|
||||
|
||||
func defaultServerConfig() *serverConfig {
|
||||
@@ -108,6 +110,12 @@ func WithMagicDNS(domain string) ServerOption {
|
||||
return func(c *serverConfig) { c.magicDNSDomain = domain }
|
||||
}
|
||||
|
||||
// WithDNSResolvers sets the global DNS resolvers, so map responses carry a
|
||||
// [tailcfg.DNSConfig].
|
||||
func WithDNSResolvers(addrs ...string) ServerOption {
|
||||
return func(c *serverConfig) { c.dnsResolvers = addrs }
|
||||
}
|
||||
|
||||
// NewServer creates and starts a Headscale test server.
|
||||
// The server is fully functional and accepts real Tailscale control
|
||||
// protocol connections over Noise.
|
||||
@@ -154,12 +162,18 @@ func NewServer(tb testing.TB, opts ...ServerOption) *TestServer {
|
||||
},
|
||||
}
|
||||
|
||||
if sc.magicDNSDomain != "" || len(sc.dnsResolvers) > 0 {
|
||||
cfg.TailcfgDNSConfig = &tailcfg.DNSConfig{}
|
||||
}
|
||||
|
||||
if sc.magicDNSDomain != "" {
|
||||
cfg.BaseDomain = sc.magicDNSDomain
|
||||
cfg.TailcfgDNSConfig = &tailcfg.DNSConfig{
|
||||
Proxied: true,
|
||||
Domains: []string{sc.magicDNSDomain},
|
||||
}
|
||||
cfg.TailcfgDNSConfig.Proxied = true
|
||||
cfg.TailcfgDNSConfig.Domains = []string{sc.magicDNSDomain}
|
||||
}
|
||||
|
||||
for _, addr := range sc.dnsResolvers {
|
||||
cfg.TailcfgDNSConfig.Resolvers = append(cfg.TailcfgDNSConfig.Resolvers, &dnstype.Resolver{Addr: addr})
|
||||
}
|
||||
|
||||
app, err := hscontrol.NewHeadscale(&cfg)
|
||||
|
||||
Reference in New Issue
Block a user