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")
 | |
| 	}
 | |
| }
 |