From f7a5ce521e53445f740dd7d3ba78d557cf863242 Mon Sep 17 00:00:00 2001 From: Niels Bouma <9073152+nielscil@users.noreply.github.com> Date: Mon, 6 Jan 2025 13:20:08 +0100 Subject: [PATCH] intergration test nextdns --- hscontrol/mapper/mapper.go | 7 +- integration/nodeAttrs_test.go | 148 ++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 integration/nodeAttrs_test.go diff --git a/hscontrol/mapper/mapper.go b/hscontrol/mapper/mapper.go index 03b4cedd7a..b608a28e81 100644 --- a/hscontrol/mapper/mapper.go +++ b/hscontrol/mapper/mapper.go @@ -151,8 +151,11 @@ func addNextDNSMetadata(resolvers []*dnstype.Resolver, node *types.Node, nodeAtt } attrs := url.Values{ - "device_name": []string{node.Hostname}, - "device_model": []string{node.Hostinfo.OS}, + "device_name": []string{node.Hostname}, + } + + if node.Hostinfo != nil { + attrs.Add("device_model", node.Hostinfo.OS) } if len(node.IPs()) > 0 { diff --git a/integration/nodeAttrs_test.go b/integration/nodeAttrs_test.go new file mode 100644 index 0000000000..e1b45807bf --- /dev/null +++ b/integration/nodeAttrs_test.go @@ -0,0 +1,148 @@ +package integration + +import ( + "regexp" + "testing" + + "github.com/juanfont/headscale/hscontrol/policy" + "github.com/juanfont/headscale/integration/hsic" + "github.com/juanfont/headscale/integration/tsic" + "github.com/stretchr/testify/require" +) + +func TestNodeAttrsNextDNS(t *testing.T) { + IntegrationSkip(t) + + tests := []struct { + name string + policy policy.ACLPolicy + wantedResolverRegex map[string]string + }{ + { + name: "NextDNS attribute for all", + policy: policy.ACLPolicy{ + ACLs: []policy.ACL{ + { + Action: "accept", + Sources: []string{"*"}, + Destinations: []string{"*:*"}, + }, + }, + NodeAttributes: []policy.NodeAttributes{ + { + Targets: []string{"*"}, + Attributes: []string{"nextdns:fedcba"}, + }, + }, + }, + wantedResolverRegex: map[string]string{ + "user1": "https://dns\\.nextdns\\.io/fedcba\\?device_ip=.*?\\&device_model=.*?&device_name=.*", + "user2": "https://dns\\.nextdns\\.io/fedcba\\?device_ip=.*?\\&device_model=.*?&device_name=.*", + }, + }, + { + name: "NextDNS attribute for user 1", + policy: policy.ACLPolicy{ + ACLs: []policy.ACL{ + { + Action: "accept", + Sources: []string{"*"}, + Destinations: []string{"*:*"}, + }, + }, + NodeAttributes: []policy.NodeAttributes{ + { + Targets: []string{"user1"}, + Attributes: []string{"nextdns:fedcba"}, + }, + }, + }, + wantedResolverRegex: map[string]string{ + "user1": "https://dns\\.nextdns\\.io/fedcba\\?device_ip=.*?\\&device_model=.*?&device_name=.*", + "user2": "https://dns\\.nextdns\\.io/abcdef\\?device_ip=.*?\\&device_model=.*?&device_name=.*", + }, + }, + { + name: "NextDNS attribute for no deviceInfo", + policy: policy.ACLPolicy{ + ACLs: []policy.ACL{ + { + Action: "accept", + Sources: []string{"*"}, + Destinations: []string{"*:*"}, + }, + }, + NodeAttributes: []policy.NodeAttributes{ + { + Targets: []string{"*"}, + Attributes: []string{"nextdns:no-device-info"}, + }, + }, + }, + wantedResolverRegex: map[string]string{ + "user1": "https://dns\\.nextdns\\.io/abcdef", + "user2": "https://dns\\.nextdns\\.io/abcdef", + }, + }, + } + + spec := map[string]int{ + "user1": 2, + "user2": 2, + } + + for _, testcase := range tests { + t.Run(testcase.name, func(t *testing.T) { + scenario, err := NewScenario(dockertestMaxWait()) + require.NoError(t, err) + + scenario.CreateHeadscaleEnv(spec, + []tsic.Option{ + tsic.WithSSH(), + + // Alpine containers dont have ip6tables set up, which causes + // tailscaled to stop configuring the wgengine, causing it + // to not configure DNS. + tsic.WithNetfilter("off"), + tsic.WithDockerEntrypoint([]string{ + "/bin/sh", + "-c", + "/bin/sleep 3 ; apk add openssh ; adduser ssh-it-user ; update-ca-certificates ; tailscaled --tun=tsdev", + }), + tsic.WithDockerWorkdir("/"), + }, + hsic.WithACLPolicy(&testcase.policy), + hsic.WithConfigEnv(map[string]string{ + "HEADSCALE_DNS_NAMESERVERS_GLOBAL": "https://dns.nextdns.io/abcdef", + }), + ) + + require.NoError(t, err) + defer scenario.ShutdownAssertNoPanics(t) + + for user, expectedResolver := range testcase.wantedResolverRegex { + + expr, err := regexp.Compile(expectedResolver) + require.NoError(t, err) + + clients, err := scenario.ListTailscaleClients(user) + require.NoError(t, err) + + for _, client := range clients { + + output, _, err := client.Execute([]string{ + "tailscale", + "dns", + "status", + }) + + require.NoError(t, err) + + if !expr.MatchString(output) { + t.Logf("unexpected resolver expected: '%s', actual: '%s'", expectedResolver, output) + } + } + } + }) + } +}