mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-10-28 10:51:44 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			228 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			228 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package integration
 | 
						|
 | 
						|
import (
 | 
						|
	"encoding/json"
 | 
						|
	"fmt"
 | 
						|
	"strings"
 | 
						|
	"testing"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/juanfont/headscale/integration/hsic"
 | 
						|
	"github.com/juanfont/headscale/integration/tsic"
 | 
						|
	"github.com/stretchr/testify/assert"
 | 
						|
	"tailscale.com/tailcfg"
 | 
						|
)
 | 
						|
 | 
						|
func TestResolveMagicDNS(t *testing.T) {
 | 
						|
	IntegrationSkip(t)
 | 
						|
 | 
						|
	spec := ScenarioSpec{
 | 
						|
		NodesPerUser: len(MustTestVersions),
 | 
						|
		Users:        []string{"user1", "user2"},
 | 
						|
	}
 | 
						|
 | 
						|
	scenario, err := NewScenario(spec)
 | 
						|
	assertNoErr(t, err)
 | 
						|
	defer scenario.ShutdownAssertNoPanics(t)
 | 
						|
 | 
						|
	err = scenario.CreateHeadscaleEnv([]tsic.Option{}, hsic.WithTestName("magicdns"))
 | 
						|
	assertNoErrHeadscaleEnv(t, err)
 | 
						|
 | 
						|
	allClients, err := scenario.ListTailscaleClients()
 | 
						|
	assertNoErrListClients(t, err)
 | 
						|
 | 
						|
	err = scenario.WaitForTailscaleSync()
 | 
						|
	assertNoErrSync(t, err)
 | 
						|
 | 
						|
	// assertClientsState(t, allClients)
 | 
						|
 | 
						|
	// Poor mans cache
 | 
						|
	_, err = scenario.ListTailscaleClientsFQDNs()
 | 
						|
	assertNoErrListFQDN(t, err)
 | 
						|
 | 
						|
	_, err = scenario.ListTailscaleClientsIPs()
 | 
						|
	assertNoErrListClientIPs(t, err)
 | 
						|
 | 
						|
	for _, client := range allClients {
 | 
						|
		for _, peer := range allClients {
 | 
						|
			// It is safe to ignore this error as we handled it when caching it
 | 
						|
			peerFQDN, _ := peer.FQDN()
 | 
						|
 | 
						|
			assert.Equal(t, peer.Hostname()+".headscale.net.", peerFQDN)
 | 
						|
 | 
						|
			assert.EventuallyWithT(t, func(ct *assert.CollectT) {
 | 
						|
				command := []string{
 | 
						|
					"tailscale",
 | 
						|
					"ip", peerFQDN,
 | 
						|
				}
 | 
						|
				result, _, err := client.Execute(command)
 | 
						|
				assert.NoError(ct, err, "Failed to execute resolve/ip command %s from %s", peerFQDN, client.Hostname())
 | 
						|
 | 
						|
				ips, err := peer.IPs()
 | 
						|
				assert.NoError(ct, err, "Failed to get IPs for %s", peer.Hostname())
 | 
						|
 | 
						|
				for _, ip := range ips {
 | 
						|
					assert.Contains(ct, result, ip.String(), "IP %s should be found in DNS resolution result from %s to %s", ip.String(), client.Hostname(), peer.Hostname())
 | 
						|
				}
 | 
						|
			}, 30*time.Second, 2*time.Second)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestResolveMagicDNSExtraRecordsPath(t *testing.T) {
 | 
						|
	IntegrationSkip(t)
 | 
						|
 | 
						|
	spec := ScenarioSpec{
 | 
						|
		NodesPerUser: 1,
 | 
						|
		Users:        []string{"user1", "user2"},
 | 
						|
	}
 | 
						|
 | 
						|
	scenario, err := NewScenario(spec)
 | 
						|
	assertNoErr(t, err)
 | 
						|
	defer scenario.ShutdownAssertNoPanics(t)
 | 
						|
 | 
						|
	const erPath = "/tmp/extra_records.json"
 | 
						|
 | 
						|
	extraRecords := []tailcfg.DNSRecord{
 | 
						|
		{
 | 
						|
			Name:  "test.myvpn.example.com",
 | 
						|
			Type:  "A",
 | 
						|
			Value: "6.6.6.6",
 | 
						|
		},
 | 
						|
	}
 | 
						|
	b, _ := json.Marshal(extraRecords)
 | 
						|
 | 
						|
	err = scenario.CreateHeadscaleEnv([]tsic.Option{
 | 
						|
		tsic.WithDockerEntrypoint([]string{
 | 
						|
			"/bin/sh",
 | 
						|
			"-c",
 | 
						|
			"/bin/sleep 3 ; apk add python3 curl bind-tools ; update-ca-certificates ; tailscaled --tun=tsdev",
 | 
						|
		}),
 | 
						|
	},
 | 
						|
		hsic.WithTestName("extrarecords"),
 | 
						|
		hsic.WithConfigEnv(map[string]string{
 | 
						|
			// Disable global nameservers to make the test run offline.
 | 
						|
			"HEADSCALE_DNS_NAMESERVERS_GLOBAL": "",
 | 
						|
			"HEADSCALE_DNS_EXTRA_RECORDS_PATH": erPath,
 | 
						|
		}),
 | 
						|
		hsic.WithFileInContainer(erPath, b),
 | 
						|
		hsic.WithEmbeddedDERPServerOnly(),
 | 
						|
		hsic.WithTLS(),
 | 
						|
	)
 | 
						|
	assertNoErrHeadscaleEnv(t, err)
 | 
						|
 | 
						|
	allClients, err := scenario.ListTailscaleClients()
 | 
						|
	assertNoErrListClients(t, err)
 | 
						|
 | 
						|
	err = scenario.WaitForTailscaleSync()
 | 
						|
	assertNoErrSync(t, err)
 | 
						|
 | 
						|
	// assertClientsState(t, allClients)
 | 
						|
 | 
						|
	// Poor mans cache
 | 
						|
	_, err = scenario.ListTailscaleClientsFQDNs()
 | 
						|
	assertNoErrListFQDN(t, err)
 | 
						|
 | 
						|
	_, err = scenario.ListTailscaleClientsIPs()
 | 
						|
	assertNoErrListClientIPs(t, err)
 | 
						|
 | 
						|
	for _, client := range allClients {
 | 
						|
		assertCommandOutputContains(t, client, []string{"dig", "test.myvpn.example.com"}, "6.6.6.6")
 | 
						|
	}
 | 
						|
 | 
						|
	hs, err := scenario.Headscale()
 | 
						|
	assertNoErr(t, err)
 | 
						|
 | 
						|
	// Write the file directly into place from the docker API.
 | 
						|
	b0, _ := json.Marshal([]tailcfg.DNSRecord{
 | 
						|
		{
 | 
						|
			Name:  "docker.myvpn.example.com",
 | 
						|
			Type:  "A",
 | 
						|
			Value: "2.2.2.2",
 | 
						|
		},
 | 
						|
	})
 | 
						|
 | 
						|
	err = hs.WriteFile(erPath, b0)
 | 
						|
	assertNoErr(t, err)
 | 
						|
 | 
						|
	for _, client := range allClients {
 | 
						|
		assertCommandOutputContains(t, client, []string{"dig", "docker.myvpn.example.com"}, "2.2.2.2")
 | 
						|
	}
 | 
						|
 | 
						|
	// Write a new file and move it to the path to ensure the reload
 | 
						|
	// works when a file is moved atomically into place.
 | 
						|
	extraRecords = append(extraRecords, tailcfg.DNSRecord{
 | 
						|
		Name:  "otherrecord.myvpn.example.com",
 | 
						|
		Type:  "A",
 | 
						|
		Value: "7.7.7.7",
 | 
						|
	})
 | 
						|
	b2, _ := json.Marshal(extraRecords)
 | 
						|
 | 
						|
	err = hs.WriteFile(erPath+"2", b2)
 | 
						|
	assertNoErr(t, err)
 | 
						|
	_, err = hs.Execute([]string{"mv", erPath + "2", erPath})
 | 
						|
	assertNoErr(t, err)
 | 
						|
 | 
						|
	for _, client := range allClients {
 | 
						|
		assertCommandOutputContains(t, client, []string{"dig", "test.myvpn.example.com"}, "6.6.6.6")
 | 
						|
		assertCommandOutputContains(t, client, []string{"dig", "otherrecord.myvpn.example.com"}, "7.7.7.7")
 | 
						|
	}
 | 
						|
 | 
						|
	// Write a new file and copy it to the path to ensure the reload
 | 
						|
	// works when a file is copied into place.
 | 
						|
	b3, _ := json.Marshal([]tailcfg.DNSRecord{
 | 
						|
		{
 | 
						|
			Name:  "copy.myvpn.example.com",
 | 
						|
			Type:  "A",
 | 
						|
			Value: "8.8.8.8",
 | 
						|
		},
 | 
						|
	})
 | 
						|
 | 
						|
	err = hs.WriteFile(erPath+"3", b3)
 | 
						|
	assertNoErr(t, err)
 | 
						|
	_, err = hs.Execute([]string{"cp", erPath + "3", erPath})
 | 
						|
	assertNoErr(t, err)
 | 
						|
 | 
						|
	for _, client := range allClients {
 | 
						|
		assertCommandOutputContains(t, client, []string{"dig", "copy.myvpn.example.com"}, "8.8.8.8")
 | 
						|
	}
 | 
						|
 | 
						|
	// Write in place to ensure pipe like behaviour works
 | 
						|
	b4, _ := json.Marshal([]tailcfg.DNSRecord{
 | 
						|
		{
 | 
						|
			Name:  "docker.myvpn.example.com",
 | 
						|
			Type:  "A",
 | 
						|
			Value: "9.9.9.9",
 | 
						|
		},
 | 
						|
	})
 | 
						|
	command := []string{"echo", fmt.Sprintf("'%s'", string(b4)), ">", erPath}
 | 
						|
	_, err = hs.Execute([]string{"bash", "-c", strings.Join(command, " ")})
 | 
						|
	assertNoErr(t, err)
 | 
						|
 | 
						|
	for _, client := range allClients {
 | 
						|
		assertCommandOutputContains(t, client, []string{"dig", "docker.myvpn.example.com"}, "9.9.9.9")
 | 
						|
	}
 | 
						|
 | 
						|
	// Delete the file and create a new one to ensure it is picked up again.
 | 
						|
	_, err = hs.Execute([]string{"rm", erPath})
 | 
						|
	assertNoErr(t, err)
 | 
						|
 | 
						|
	// The same paths should still be available as it is not cleared on delete.
 | 
						|
	assert.EventuallyWithT(t, func(ct *assert.CollectT) {
 | 
						|
		for _, client := range allClients {
 | 
						|
			result, _, err := client.Execute([]string{"dig", "docker.myvpn.example.com"})
 | 
						|
			assert.NoError(ct, err)
 | 
						|
			assert.Contains(ct, result, "9.9.9.9")
 | 
						|
		}
 | 
						|
	}, 10*time.Second, 1*time.Second)
 | 
						|
 | 
						|
	// Write a new file, the backoff mechanism should make the filewatcher pick it up
 | 
						|
	// again.
 | 
						|
	err = hs.WriteFile(erPath, b3)
 | 
						|
	assertNoErr(t, err)
 | 
						|
 | 
						|
	for _, client := range allClients {
 | 
						|
		assertCommandOutputContains(t, client, []string{"dig", "copy.myvpn.example.com"}, "8.8.8.8")
 | 
						|
	}
 | 
						|
}
 |