From 0aac79f8fa2a853e5537882fcb50e0b7d54f77a9 Mon Sep 17 00:00:00 2001 From: ohdearaugustin Date: Mon, 13 Sep 2021 20:03:03 +0200 Subject: [PATCH 01/15] Dockerfile: add golang tag --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9499af22..20bb7dae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:latest AS build +FROM golang:1.17.1-bullseye AS build ENV GOPATH /go COPY go.mod go.sum /go/src/headscale/ From b28ebb5d20570dc87fc854c04fd3806f5334898a Mon Sep 17 00:00:00 2001 From: Tjerk Woudsma Date: Sat, 18 Sep 2021 12:34:04 +0200 Subject: [PATCH 02/15] fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c4ff7bc2..c3610e8b 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ Suggestions/PRs welcomed! ``` or docker: ```shell - docker run -v ./private.key:/private.key -v ./config.json:/config.json headscale/headscale:x.x.x headscale namespace create myfirstnamespace + docker run -v ./private.key:/private.key -v ./config.json:/config.json headscale/headscale:x.x.x headscale namespaces create myfirstnamespace ``` 5. Run the server From e154e7a0fb0d4de7ad668f3cce6009e9369bc8f9 Mon Sep 17 00:00:00 2001 From: Felix Kronlage-Dammers Date: Sun, 19 Sep 2021 12:07:17 +0200 Subject: [PATCH 03/15] fix typo, it is 'relayed' not 'relied' --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c4ff7bc2..1a60db18 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Headscale implements this coordination server. - [x] Base functionality (nodes can communicate with each other) - [x] Node registration through the web flow -- [x] Network changes are relied to the nodes +- [x] Network changes are relayed to the nodes - [x] Namespace support (~equivalent to multi-user in Tailscale.com) - [x] Routing (advertise & accept, including exit nodes) - [x] Node registration via pre-auth keys (including reusable keys, and ephemeral node support) From da209e89a7d09e7dc9d5e01e4dedadfdc7bb689a Mon Sep 17 00:00:00 2001 From: Juan Font Date: Mon, 20 Sep 2021 18:42:04 +0200 Subject: [PATCH 04/15] Update README.md Fixed typo (causing #110) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 99cc1319..8a04f117 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Suggestions/PRs welcomed! ``` or docker: ```shell - docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derb.yaml:/derb.yaml -p 127.0.0.1:8080:8080 headscale/headscale:x.x.x headscale serve + docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml -p 127.0.0.1:8080:8080 headscale/headscale:x.x.x headscale serve ``` 6. If you used tailscale.com before in your nodes, make sure you clear the tailscaled data folder From d68d201722d184db6ed5ace314836f415d383cc9 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Mon, 20 Sep 2021 19:23:18 +0100 Subject: [PATCH 05/15] Add version support to integration support This commit adds a list of tailscale versions to use in the integration test. An equal distribution of versions will be used across the clients. --- Dockerfile.tailscale | 4 ++- integration_test.go | 77 +++++++++++++++++++++++++++----------------- 2 files changed, 51 insertions(+), 30 deletions(-) diff --git a/Dockerfile.tailscale b/Dockerfile.tailscale index c6830f8a..2e1d119f 100644 --- a/Dockerfile.tailscale +++ b/Dockerfile.tailscale @@ -1,9 +1,11 @@ FROM ubuntu:latest +ARG TAILSCALE_VERSION=now + RUN apt-get update \ && apt-get install -y gnupg curl \ && curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/focal.gpg | apt-key add - \ && curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/focal.list | tee /etc/apt/sources.list.d/tailscale.list \ && apt-get update \ - && apt-get install -y tailscale \ + && apt-get install -y tailscale=${TAILSCALE_VERSION} \ && rm -rf /var/lib/apt/lists/* diff --git a/integration_test.go b/integration_test.go index 8cdc1918..ea35f6bd 100644 --- a/integration_test.go +++ b/integration_test.go @@ -1,3 +1,4 @@ +//go:build integration // +build integration package headscale @@ -23,14 +24,20 @@ import ( "inet.af/netaddr" ) -var integrationTmpDir string -var ih Headscale +var ( + integrationTmpDir string + ih Headscale +) -var pool dockertest.Pool -var network dockertest.Network -var headscale dockertest.Resource -var tailscaleCount int = 25 -var tailscales map[string]dockertest.Resource +var ( + pool dockertest.Pool + network dockertest.Network + headscale dockertest.Resource + tailscaleCount int = 25 + tailscales map[string]dockertest.Resource +) + +var tailscaleVersions = []string{"1.14.3", "1.12.3"} type IntegrationTestSuite struct { suite.Suite @@ -119,12 +126,12 @@ func saveLog(resource *dockertest.Resource, basePath string) error { fmt.Printf("Saving logs for %s to %s\n", resource.Container.Name, basePath) - err = ioutil.WriteFile(path.Join(basePath, resource.Container.Name+".stdout.log"), []byte(stdout.String()), 0644) + err = ioutil.WriteFile(path.Join(basePath, resource.Container.Name+".stdout.log"), []byte(stdout.String()), 0o644) if err != nil { return err } - err = ioutil.WriteFile(path.Join(basePath, resource.Container.Name+".stderr.log"), []byte(stdout.String()), 0644) + err = ioutil.WriteFile(path.Join(basePath, resource.Container.Name+".stderr.log"), []byte(stdout.String()), 0o644) if err != nil { return err } @@ -140,6 +147,32 @@ func dockerRestartPolicy(config *docker.HostConfig) { } } +func tailscaleContainer(identifier string, version string) (string, *dockertest.Resource) { + tailscaleBuildOptions := &dockertest.BuildOptions{ + Dockerfile: "Dockerfile.tailscale", + ContextDir: ".", + BuildArgs: []docker.BuildArg{ + { + Name: "TAILSCALE_VERSION", + Value: version, + }, + }, + } + hostname := fmt.Sprintf("tailscale-%s-%s", strings.Replace(version, ".", "-", -1), identifier) + tailscaleOptions := &dockertest.RunOptions{ + Name: hostname, + Networks: []*dockertest.Network{&network}, + Cmd: []string{"tailscaled", "--tun=userspace-networking", "--socks5-server=localhost:1055"}, + } + + pts, err := pool.BuildAndRunWithBuildOptions(tailscaleBuildOptions, tailscaleOptions, dockerRestartPolicy) + if err != nil { + log.Fatalf("Could not start resource: %s", err) + } + fmt.Printf("Created %s container\n", hostname) + return hostname, pts +} + func (s *IntegrationTestSuite) SetupSuite() { var err error h = Headscale{ @@ -164,11 +197,6 @@ func (s *IntegrationTestSuite) SetupSuite() { ContextDir: ".", } - tailscaleBuildOptions := &dockertest.BuildOptions{ - Dockerfile: "Dockerfile.tailscale", - ContextDir: ".", - } - currentPath, err := os.Getwd() if err != nil { log.Fatalf("Could not determine current path: %s", err) @@ -183,7 +211,7 @@ func (s *IntegrationTestSuite) SetupSuite() { Networks: []*dockertest.Network{&network}, Cmd: []string{"headscale", "serve"}, PortBindings: map[docker.Port][]docker.PortBinding{ - "8080/tcp": []docker.PortBinding{{HostPort: "8080"}}, + "8080/tcp": {{HostPort: "8080"}}, }, } @@ -198,19 +226,10 @@ func (s *IntegrationTestSuite) SetupSuite() { fmt.Println("Creating tailscale containers") tailscales = make(map[string]dockertest.Resource) for i := 0; i < tailscaleCount; i++ { - hostname := fmt.Sprintf("tailscale%d", i) - tailscaleOptions := &dockertest.RunOptions{ - Name: hostname, - Networks: []*dockertest.Network{&network}, - Cmd: []string{"tailscaled", "--tun=userspace-networking", "--socks5-server=localhost:1055"}, - } + version := tailscaleVersions[i%len(tailscaleVersions)] - if pts, err := pool.BuildAndRunWithBuildOptions(tailscaleBuildOptions, tailscaleOptions, dockerRestartPolicy); err == nil { - tailscales[hostname] = *pts - } else { - log.Fatalf("Could not start resource: %s", err) - } - fmt.Printf("Created %s container\n", hostname) + hostname, container := tailscaleContainer(fmt.Sprint(i), version) + tailscales[hostname] = *container } fmt.Println("Waiting for headscale to be ready") @@ -288,7 +307,7 @@ func (s *IntegrationTestSuite) TestListNodes() { lines := strings.Split(result, "\n") assert.Equal(s.T(), len(tailscales), len(lines)-2) - for hostname, _ := range tailscales { + for hostname := range tailscales { assert.Contains(s.T(), result, hostname) } } @@ -298,7 +317,7 @@ func (s *IntegrationTestSuite) TestGetIpAddresses() { ips, err := getIPs() assert.Nil(s.T(), err) - for hostname, _ := range tailscales { + for hostname := range tailscales { s.T().Run(hostname, func(t *testing.T) { ip := ips[hostname] From f905812afa9114bb17b1b848d33d6de85d1f30ff Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Mon, 20 Sep 2021 20:18:28 +0100 Subject: [PATCH 06/15] Test two namespaces This expands the tests to verify two namespaces instead of only one. It verifies some of the isolation, and is prework for shared nodes testing --- integration_test.go | 271 ++++++++++++++++++++++++-------------------- 1 file changed, 151 insertions(+), 120 deletions(-) diff --git a/integration_test.go b/integration_test.go index ea35f6bd..5ad8dede 100644 --- a/integration_test.go +++ b/integration_test.go @@ -30,30 +30,49 @@ var ( ) var ( - pool dockertest.Pool - network dockertest.Network - headscale dockertest.Resource - tailscaleCount int = 25 - tailscales map[string]dockertest.Resource + pool dockertest.Pool + network dockertest.Network + headscale dockertest.Resource ) var tailscaleVersions = []string{"1.14.3", "1.12.3"} +type TestNamespace struct { + count int + tailscales map[string]dockertest.Resource +} + type IntegrationTestSuite struct { suite.Suite stats *suite.SuiteInformation + + namespaces map[string]TestNamespace } func TestIntegrationTestSuite(t *testing.T) { s := new(IntegrationTestSuite) + + s.namespaces = map[string]TestNamespace{ + "main": { + count: 20, + tailscales: make(map[string]dockertest.Resource), + }, + "shared": { + count: 5, + tailscales: make(map[string]dockertest.Resource), + }, + } + suite.Run(t, s) // HandleStats, which allows us to check if we passed and save logs // is called after TearDown, so we cannot tear down containers before // we have potentially saved the logs. - for _, tailscale := range tailscales { - if err := pool.Purge(&tailscale); err != nil { - log.Printf("Could not purge resource: %s\n", err) + for _, scales := range s.namespaces { + for _, tailscale := range scales.tailscales { + if err := pool.Purge(&tailscale); err != nil { + log.Printf("Could not purge resource: %s\n", err) + } } } @@ -147,7 +166,7 @@ func dockerRestartPolicy(config *docker.HostConfig) { } } -func tailscaleContainer(identifier string, version string) (string, *dockertest.Resource) { +func tailscaleContainer(namespace, identifier, version string) (string, *dockertest.Resource) { tailscaleBuildOptions := &dockertest.BuildOptions{ Dockerfile: "Dockerfile.tailscale", ContextDir: ".", @@ -158,7 +177,7 @@ func tailscaleContainer(identifier string, version string) (string, *dockertest. }, }, } - hostname := fmt.Sprintf("tailscale-%s-%s", strings.Replace(version, ".", "-", -1), identifier) + hostname := fmt.Sprintf("%s-tailscale-%s-%s", namespace, strings.Replace(version, ".", "-", -1), identifier) tailscaleOptions := &dockertest.RunOptions{ Name: hostname, Networks: []*dockertest.Network{&network}, @@ -224,12 +243,13 @@ func (s *IntegrationTestSuite) SetupSuite() { fmt.Println("Created headscale container") fmt.Println("Creating tailscale containers") - tailscales = make(map[string]dockertest.Resource) - for i := 0; i < tailscaleCount; i++ { - version := tailscaleVersions[i%len(tailscaleVersions)] + for namespace, scales := range s.namespaces { + for i := 0; i < scales.count; i++ { + version := tailscaleVersions[i%len(tailscaleVersions)] - hostname, container := tailscaleContainer(fmt.Sprint(i), version) - tailscales[hostname] = *container + hostname, container := tailscaleContainer(namespace, fmt.Sprint(i), version) + scales.tailscales[hostname] = *container + } } fmt.Println("Waiting for headscale to be ready") @@ -250,35 +270,38 @@ func (s *IntegrationTestSuite) SetupSuite() { } fmt.Println("headscale container is ready") - fmt.Println("Creating headscale namespace") - result, err := executeCommand( - &headscale, - []string{"headscale", "namespaces", "create", "test"}, - ) - assert.Nil(s.T(), err) - - fmt.Println("Creating pre auth key") - authKey, err := executeCommand( - &headscale, - []string{"headscale", "-n", "test", "preauthkeys", "create", "--reusable", "--expiration", "24h"}, - ) - assert.Nil(s.T(), err) - - headscaleEndpoint := fmt.Sprintf("http://headscale:%s", headscale.GetPort("8080/tcp")) - - fmt.Printf("Joining tailscale containers to headscale at %s\n", headscaleEndpoint) - for hostname, tailscale := range tailscales { - command := []string{"tailscale", "up", "-login-server", headscaleEndpoint, "--authkey", strings.TrimSuffix(authKey, "\n"), "--hostname", hostname} - - fmt.Println("Join command:", command) - fmt.Printf("Running join command for %s\n", hostname) - result, err = executeCommand( - &tailscale, - command, + for namespace, scales := range s.namespaces { + fmt.Printf("Creating headscale namespace: %s\n", namespace) + result, err := executeCommand( + &headscale, + []string{"headscale", "namespaces", "create", namespace}, ) - fmt.Println("tailscale result: ", result) assert.Nil(s.T(), err) - fmt.Printf("%s joined\n", hostname) + fmt.Println("headscale create namespace result: ", result) + + fmt.Printf("Creating pre auth key for %s\n", namespace) + authKey, err := executeCommand( + &headscale, + []string{"headscale", "-n", namespace, "preauthkeys", "create", "--reusable", "--expiration", "24h"}, + ) + assert.Nil(s.T(), err) + + headscaleEndpoint := fmt.Sprintf("http://headscale:%s", headscale.GetPort("8080/tcp")) + + fmt.Printf("Joining tailscale containers to headscale at %s\n", headscaleEndpoint) + for hostname, tailscale := range scales.tailscales { + command := []string{"tailscale", "up", "-login-server", headscaleEndpoint, "--authkey", strings.TrimSuffix(authKey, "\n"), "--hostname", hostname} + + fmt.Println("Join command:", command) + fmt.Printf("Running join command for %s\n", hostname) + result, err := executeCommand( + &tailscale, + command, + ) + fmt.Println("tailscale result: ", result) + assert.Nil(s.T(), err) + fmt.Printf("%s joined\n", hostname) + } } // The nodes need a bit of time to get their updated maps from headscale @@ -294,109 +317,117 @@ func (s *IntegrationTestSuite) HandleStats(suiteName string, stats *suite.SuiteI } func (s *IntegrationTestSuite) TestListNodes() { - fmt.Println("Listing nodes") - result, err := executeCommand( - &headscale, - []string{"headscale", "-n", "test", "nodes", "list"}, - ) - assert.Nil(s.T(), err) + for namespace, scales := range s.namespaces { + fmt.Println("Listing nodes") + result, err := executeCommand( + &headscale, + []string{"headscale", "-n", namespace, "nodes", "list"}, + ) + assert.Nil(s.T(), err) - fmt.Printf("List nodes: \n%s\n", result) + fmt.Printf("List nodes: \n%s\n", result) - // Chck that the correct count of host is present in node list - lines := strings.Split(result, "\n") - assert.Equal(s.T(), len(tailscales), len(lines)-2) + // Chck that the correct count of host is present in node list + lines := strings.Split(result, "\n") + assert.Equal(s.T(), len(scales.tailscales), len(lines)-2) - for hostname := range tailscales { - assert.Contains(s.T(), result, hostname) + for hostname := range scales.tailscales { + assert.Contains(s.T(), result, hostname) + } } } func (s *IntegrationTestSuite) TestGetIpAddresses() { - ipPrefix := netaddr.MustParseIPPrefix("100.64.0.0/10") - ips, err := getIPs() - assert.Nil(s.T(), err) + for _, scales := range s.namespaces { + ipPrefix := netaddr.MustParseIPPrefix("100.64.0.0/10") + ips, err := getIPs(scales.tailscales) + assert.Nil(s.T(), err) - for hostname := range tailscales { - s.T().Run(hostname, func(t *testing.T) { - ip := ips[hostname] + for hostname := range scales.tailscales { + s.T().Run(hostname, func(t *testing.T) { + ip := ips[hostname] - fmt.Printf("IP for %s: %s\n", hostname, ip) + fmt.Printf("IP for %s: %s\n", hostname, ip) - // c.Assert(ip.Valid(), check.IsTrue) - assert.True(t, ip.Is4()) - assert.True(t, ipPrefix.Contains(ip)) + // c.Assert(ip.Valid(), check.IsTrue) + assert.True(t, ip.Is4()) + assert.True(t, ipPrefix.Contains(ip)) - ips[hostname] = ip - }) + ips[hostname] = ip + }) + } } } func (s *IntegrationTestSuite) TestStatus() { - ips, err := getIPs() - assert.Nil(s.T(), err) + for _, scales := range s.namespaces { + ips, err := getIPs(scales.tailscales) + assert.Nil(s.T(), err) - for hostname, tailscale := range tailscales { - s.T().Run(hostname, func(t *testing.T) { - command := []string{"tailscale", "status"} + for hostname, tailscale := range scales.tailscales { + s.T().Run(hostname, func(t *testing.T) { + command := []string{"tailscale", "status"} - fmt.Printf("Getting status for %s\n", hostname) - result, err := executeCommand( - &tailscale, - command, - ) - assert.Nil(t, err) - // fmt.Printf("Status for %s: %s", hostname, result) + fmt.Printf("Getting status for %s\n", hostname) + result, err := executeCommand( + &tailscale, + command, + ) + assert.Nil(t, err) + // fmt.Printf("Status for %s: %s", hostname, result) - // Check if we have as many nodes in status - // as we have IPs/tailscales - lines := strings.Split(result, "\n") - assert.Equal(t, len(ips), len(lines)-1) - assert.Equal(t, len(tailscales), len(lines)-1) + // Check if we have as many nodes in status + // as we have IPs/tailscales + lines := strings.Split(result, "\n") + assert.Equal(t, len(ips), len(lines)-1) + assert.Equal(t, len(scales.tailscales), len(lines)-1) - // Check that all hosts is present in all hosts status - for ipHostname, ip := range ips { - assert.Contains(t, result, ip.String()) - assert.Contains(t, result, ipHostname) - } - }) - } -} - -func (s *IntegrationTestSuite) TestPingAllPeers() { - ips, err := getIPs() - assert.Nil(s.T(), err) - - for hostname, tailscale := range tailscales { - for peername, ip := range ips { - s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) { - // We currently cant ping ourselves, so skip that. - if peername != hostname { - // We are only interested in "direct ping" which means what we - // might need a couple of more attempts before reaching the node. - command := []string{ - "tailscale", "ping", - "--timeout=1s", - "--c=20", - "--until-direct=true", - ip.String(), - } - - fmt.Printf("Pinging from %s (%s) to %s (%s)\n", hostname, ips[hostname], peername, ip) - result, err := executeCommand( - &tailscale, - command, - ) - assert.Nil(t, err) - fmt.Printf("Result for %s: %s\n", hostname, result) - assert.Contains(t, result, "pong") + // Check that all hosts is present in all hosts status + for ipHostname, ip := range ips { + assert.Contains(t, result, ip.String()) + assert.Contains(t, result, ipHostname) } }) } } } -func getIPs() (map[string]netaddr.IP, error) { +func (s *IntegrationTestSuite) TestPingAllPeers() { + for _, scales := range s.namespaces { + ips, err := getIPs(scales.tailscales) + assert.Nil(s.T(), err) + + for hostname, tailscale := range scales.tailscales { + for peername, ip := range ips { + s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) { + // We currently cant ping ourselves, so skip that. + if peername != hostname { + // We are only interested in "direct ping" which means what we + // might need a couple of more attempts before reaching the node. + command := []string{ + "tailscale", "ping", + "--timeout=1s", + "--c=20", + "--until-direct=true", + ip.String(), + } + + fmt.Printf("Pinging from %s (%s) to %s (%s)\n", hostname, ips[hostname], peername, ip) + result, err := executeCommand( + &tailscale, + command, + ) + assert.Nil(t, err) + fmt.Printf("Result for %s: %s\n", hostname, result) + assert.Contains(t, result, "pong") + } + }) + } + } + } +} + +func getIPs(tailscales map[string]dockertest.Resource) (map[string]netaddr.IP, error) { ips := make(map[string]netaddr.IP) for hostname, tailscale := range tailscales { command := []string{"tailscale", "ip"} From 994b4eef17348bf452764ba410964ae35c3becfb Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Mon, 20 Sep 2021 22:53:34 +0100 Subject: [PATCH 07/15] Use JSON output and proper datamodel for tailscale status This commit uses tailscale --json to check status, and unmarshal it into the proper ipn Status. --- integration_test.go | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/integration_test.go b/integration_test.go index 5ad8dede..47098c3c 100644 --- a/integration_test.go +++ b/integration_test.go @@ -6,6 +6,7 @@ package headscale import ( "bytes" "context" + "encoding/json" "fmt" "io/ioutil" "log" @@ -20,6 +21,7 @@ import ( "github.com/ory/dockertest/v3/docker" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" + "tailscale.com/ipn/ipnstate" "inet.af/netaddr" ) @@ -282,7 +284,7 @@ func (s *IntegrationTestSuite) SetupSuite() { fmt.Printf("Creating pre auth key for %s\n", namespace) authKey, err := executeCommand( &headscale, - []string{"headscale", "-n", namespace, "preauthkeys", "create", "--reusable", "--expiration", "24h"}, + []string{"headscale", "--namespace", namespace, "preauthkeys", "create", "--reusable", "--expiration", "24h"}, ) assert.Nil(s.T(), err) @@ -321,7 +323,7 @@ func (s *IntegrationTestSuite) TestListNodes() { fmt.Println("Listing nodes") result, err := executeCommand( &headscale, - []string{"headscale", "-n", namespace, "nodes", "list"}, + []string{"headscale", "--namespace", namespace, "nodes", "list"}, ) assert.Nil(s.T(), err) @@ -366,7 +368,7 @@ func (s *IntegrationTestSuite) TestStatus() { for hostname, tailscale := range scales.tailscales { s.T().Run(hostname, func(t *testing.T) { - command := []string{"tailscale", "status"} + command := []string{"tailscale", "status", "--json"} fmt.Printf("Getting status for %s\n", hostname) result, err := executeCommand( @@ -374,24 +376,41 @@ func (s *IntegrationTestSuite) TestStatus() { command, ) assert.Nil(t, err) - // fmt.Printf("Status for %s: %s", hostname, result) + var status ipnstate.Status + err = json.Unmarshal([]byte(result), &status) + assert.Nil(s.T(), err) + + // TODO(kradalby): Replace this check with peer length of SAME namespace // Check if we have as many nodes in status // as we have IPs/tailscales - lines := strings.Split(result, "\n") - assert.Equal(t, len(ips), len(lines)-1) - assert.Equal(t, len(scales.tailscales), len(lines)-1) + // lines := strings.Split(result, "\n") + // assert.Equal(t, len(ips), len(lines)-1) + // assert.Equal(t, len(scales.tailscales), len(lines)-1) + + peerIps := getIPsfromIPNstate(status) // Check that all hosts is present in all hosts status for ipHostname, ip := range ips { - assert.Contains(t, result, ip.String()) - assert.Contains(t, result, ipHostname) + if hostname != ipHostname { + assert.Contains(t, peerIps, ip) + } } }) } } } +func getIPsfromIPNstate(status ipnstate.Status) []netaddr.IP { + ips := make([]netaddr.IP, 0) + + for _, peer := range status.Peer { + ips = append(ips, peer.TailscaleIPs...) + } + + return ips +} + func (s *IntegrationTestSuite) TestPingAllPeers() { for _, scales := range s.namespaces { ips, err := getIPs(scales.tailscales) From f0bbc3c7d870a9bb9c85e3bf379da9a6737522d9 Mon Sep 17 00:00:00 2001 From: ohdearaugustin Date: Mon, 20 Sep 2021 23:53:52 +0200 Subject: [PATCH 08/15] Fix docker docu --- README.md | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 8a04f117..fd79a74a 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Headscale implements this coordination server. - [X] ACLs - [X] Support for alternative IP ranges in the tailnets (default Tailscale's 100.64.0.0/10) - [X] DNS (passing DNS servers to nodes) -- [X] Share nodes between ~~users~~ namespaces +- [X] Share nodes between ~~users~~ namespaces - [ ] MagicDNS / Smart DNS @@ -43,7 +43,7 @@ Suggestions/PRs welcomed! ```shell docker pull headscale/headscale:x.x.x ``` - + ```shell + docker pull headscale/headscale:x.x.x + ``` + 2. (Optional, you can also use SQLite) Get yourself a PostgreSQL DB running - ```shell - docker run --name headscale -e POSTGRES_DB=headscale -e \ - POSTGRES_USER=foo -e POSTGRES_PASSWORD=bar -p 5432:5432 -d postgres - ``` + ```shell + docker run --name headscale -e POSTGRES_DB=headscale -e \ + POSTGRES_USER=foo -e POSTGRES_PASSWORD=bar -p 5432:5432 -d postgres + ``` 3. Set some stuff up (headscale Wireguard keys & the config.json file) - ```shell - wg genkey > private.key - wg pubkey < private.key > public.key # not needed + ```shell + wg genkey > private.key + wg pubkey < private.key > public.key # not needed - # Postgres - cp config.json.postgres.example config.json - # or - # SQLite - cp config.json.sqlite.example config.json - ``` + # Postgres + cp config.json.postgres.example config.json + # or + # SQLite + cp config.json.sqlite.example config.json + ``` 4. Create a namespace (a namespace is a 'tailnet', a group of Tailscale nodes that can talk to each other) - ```shell - headscale namespaces create myfirstnamespace - ``` - or docker: - the db.sqlite mount is only needed if you use sqlite - ```shell - docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml -v $(pwd)/db.sqlite:/db.sqlite -p 127.0.0.1:8000:8000 headscale/headscale:x.x.x headscale create myfirstnamespace - ``` - or if your server is already running in docker: - ```shell - docker exec headscale create myfirstnamespace - ``` + ```shell + headscale namespaces create myfirstnamespace + ``` + or docker: + + the db.sqlite mount is only needed if you use sqlite + ```shell + docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml -v $(pwd)/db.sqlite:/db.sqlite -p 127.0.0.1:8000:8000 headscale/headscale:x.x.x headscale create myfirstnamespace + ``` + or if your server is already running in docker: + ```shell + docker exec headscale create myfirstnamespace + ``` 5. Run the server - ```shell - headscale serve - ``` - or docker: - the db.sqlite mount is only needed if you use sqlite - ```shell - docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml -v $(pwd)/db.sqlite:/db.sqlite -p 127.0.0.1:8000:8000 headscale/headscale:x.x.x headscale serve - ``` + ```shell + headscale serve + ``` + or docker: + + the db.sqlite mount is only needed if you use sqlite + ```shell + docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml -v $(pwd)/db.sqlite:/db.sqlite -p 127.0.0.1:8000:8000 headscale/headscale:x.x.x headscale serve + ``` 6. If you used tailscale.com before in your nodes, make sure you clear the tailscald data folder - ```shell - systemctl stop tailscaled - rm -fr /var/lib/tailscale - systemctl start tailscaled - ``` + ```shell + systemctl stop tailscaled + rm -fr /var/lib/tailscale + systemctl start tailscaled + ``` 7. Add your first machine - ```shell - tailscale up -login-server YOUR_HEADSCALE_URL - ``` + ```shell + tailscale up -login-server YOUR_HEADSCALE_URL + ``` 8. Navigate to the URL you will get with `tailscale up`, where you'll find your machine key. 9. In the server, register your machine to a namespace with the CLI - ```shell - headscale -n myfirstnamespace node register YOURMACHINEKEY - ``` - or docker: - ```shell - docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml headscale/headscale:x.x.x headscale -n myfirstnamespace node register YOURMACHINEKEY - ``` - or if your server is already running in docker: - ```shell - docker exec headscale -n myfistnamespace node register YOURMACHINEKEY - ``` + ```shell + headscale -n myfirstnamespace node register YOURMACHINEKEY + ``` + or docker: + ```shell + docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml headscale/headscale:x.x.x headscale -n myfirstnamespace node register YOURMACHINEKEY + ``` + or if your server is already running in docker: + ```shell + docker exec headscale -n myfistnamespace node register YOURMACHINEKEY + ``` Alternatively, you can use Auth Keys to register your machines: @@ -125,19 +127,19 @@ Alternatively, you can use Auth Keys to register your machines: ```shell headscale -n myfirstnamespace preauthkeys create --reusable --expiration 24h ``` - or docker: - ```shell - docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v$(pwd)/derp.yaml:/derp.yaml -v $(pwd)/db.sqlite:/db.sqlite headscale/headscale:x.x.x headscale -n myfirstnamespace preauthkeys create --reusable --expiration 24h - ``` - or if your server is already running in docker: - ```shell - docker exec headscale -n myfirstnamespace preauthkeys create --reusable --expiration 24h - ``` + or docker: + ```shell + docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v$(pwd)/derp.yaml:/derp.yaml -v $(pwd)/db.sqlite:/db.sqlite headscale/headscale:x.x.x headscale -n myfirstnamespace preauthkeys create --reusable --expiration 24h + ``` + or if your server is already running in docker: + ```shell + docker exec headscale -n myfirstnamespace preauthkeys create --reusable --expiration 24h + ``` 2. Use the authkey from your machine to register it - ```shell - tailscale up -login-server YOUR_HEADSCALE_URL --authkey YOURAUTHKEY - ``` + ```shell + tailscale up -login-server YOUR_HEADSCALE_URL --authkey YOURAUTHKEY + ``` If you create an authkey with the `--ephemeral` flag, that key will create ephemeral nodes. This implies that `--reusable` is true. From ff1ee4ca64b5f4fcbc0074d88b27f86e8fe7c7d0 Mon Sep 17 00:00:00 2001 From: ohdearaugustin Date: Tue, 21 Sep 2021 00:02:54 +0200 Subject: [PATCH 10/15] Fix README whitespace --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8a0bae24..df35fac6 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ Suggestions/PRs welcomed! ``` or if your server is already running in docker: ```shell - docker exec headscale create myfirstnamespace + docker exec headscale create myfirstnamespace ``` 5. Run the server From f653b00258e6851b8179225b3ecde544578c68cc Mon Sep 17 00:00:00 2001 From: ohdearaugustin Date: Tue, 21 Sep 2021 00:10:00 +0200 Subject: [PATCH 11/15] workflows/release: add docker full version tag --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7f715ca7..37c55a04 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,6 +49,7 @@ jobs: ${{ secrets.DOCKERHUB_USERNAME }}/headscale ghcr.io/${{ github.repository_owner }}/headscale tags: | + type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} type=sha From 024d6ee7c3445a233485bcd895c3d799d34dde54 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Mon, 20 Sep 2021 23:21:25 +0100 Subject: [PATCH 12/15] Initial work on shared node integration test This commit adds initial integration tests for shared nodes, it adds them and verifies that they are shared. It does not yet manage to ping the shared node because it does not seem to establish the connection. --- integration_test.go | 83 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/integration_test.go b/integration_test.go index 47098c3c..4b28b148 100644 --- a/integration_test.go +++ b/integration_test.go @@ -446,6 +446,89 @@ func (s *IntegrationTestSuite) TestPingAllPeers() { } } +func (s *IntegrationTestSuite) TestSharedNodes() { + main := s.namespaces["main"] + shared := s.namespaces["shared"] + + result, err := executeCommand( + &headscale, + []string{"headscale", "nodes", "list", "-o", "json", "--namespace", "shared"}, + ) + assert.Nil(s.T(), err) + + var machineList []Machine + err = json.Unmarshal([]byte(result), &machineList) + assert.Nil(s.T(), err) + + for _, machine := range machineList { + + result, err := executeCommand( + &headscale, + []string{"headscale", "nodes", "share", "--namespace", "shared", fmt.Sprint(machine.ID), "main"}, + ) + assert.Nil(s.T(), err) + + fmt.Println("Shared node with result: ", result) + } + + result, err = executeCommand( + &headscale, + []string{"headscale", "nodes", "list", "--namespace", "main"}, + ) + assert.Nil(s.T(), err) + fmt.Println("Nodelist after sharing", result) + + // Chck that the correct count of host is present in node list + lines := strings.Split(result, "\n") + assert.Equal(s.T(), len(main.tailscales)+len(shared.tailscales), len(lines)-2) + + for hostname := range main.tailscales { + assert.Contains(s.T(), result, hostname) + } + + for hostname := range shared.tailscales { + assert.Contains(s.T(), result, hostname) + } + + // TODO(kradalby): Figure out why these connections are not set up + // // TODO: See if we can have a more deterministic wait here. + // time.Sleep(100 * time.Second) + + // mainIps, err := getIPs(main.tailscales) + // assert.Nil(s.T(), err) + + // sharedIps, err := getIPs(shared.tailscales) + // assert.Nil(s.T(), err) + + // for hostname, tailscale := range main.tailscales { + // for peername, ip := range sharedIps { + // s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) { + // // We currently cant ping ourselves, so skip that. + // if peername != hostname { + // // We are only interested in "direct ping" which means what we + // // might need a couple of more attempts before reaching the node. + // command := []string{ + // "tailscale", "ping", + // "--timeout=1s", + // "--c=20", + // "--until-direct=true", + // ip.String(), + // } + + // fmt.Printf("Pinging from %s (%s) to %s (%s)\n", hostname, mainIps[hostname], peername, ip) + // result, err := executeCommand( + // &tailscale, + // command, + // ) + // assert.Nil(t, err) + // fmt.Printf("Result for %s: %s\n", hostname, result) + // assert.Contains(t, result, "pong") + // } + // }) + // } + // } +} + func getIPs(tailscales map[string]dockertest.Resource) (map[string]netaddr.IP, error) { ips := make(map[string]netaddr.IP) for hostname, tailscale := range tailscales { From 9615138202670d41865b3295dcbd449fb1c2dbd5 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 21 Sep 2021 11:10:26 +0100 Subject: [PATCH 13/15] Remove non working default --- Dockerfile.tailscale | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.tailscale b/Dockerfile.tailscale index 2e1d119f..df8cdcb1 100644 --- a/Dockerfile.tailscale +++ b/Dockerfile.tailscale @@ -1,6 +1,6 @@ FROM ubuntu:latest -ARG TAILSCALE_VERSION=now +ARG TAILSCALE_VERSION RUN apt-get update \ && apt-get install -y gnupg curl \ From fd941054835b388e8fdc6d2c37313b10311184f5 Mon Sep 17 00:00:00 2001 From: t56k Date: Thu, 23 Sep 2021 16:32:15 +1200 Subject: [PATCH 14/15] fix namespace instructions in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index df35fac6..3c6370be 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ Suggestions/PRs welcomed! the db.sqlite mount is only needed if you use sqlite ```shell - docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml -v $(pwd)/db.sqlite:/db.sqlite -p 127.0.0.1:8000:8000 headscale/headscale:x.x.x headscale create myfirstnamespace + docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml -v $(pwd)/db.sqlite:/db.sqlite -p 127.0.0.1:8000:8000 headscale/headscale:x.x.x headscale namespaces create myfirstnamespace ``` or if your server is already running in docker: ```shell From 11e04023969cebe221eff1d0a6731d903bffbd66 Mon Sep 17 00:00:00 2001 From: t56k Date: Thu, 23 Sep 2021 17:14:04 +1200 Subject: [PATCH 15/15] create a db file first --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3c6370be..a57900a0 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ Suggestions/PRs welcomed! the db.sqlite mount is only needed if you use sqlite ```shell + touch db.sqlite docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml -v $(pwd)/db.sqlite:/db.sqlite -p 127.0.0.1:8000:8000 headscale/headscale:x.x.x headscale namespaces create myfirstnamespace ``` or if your server is already running in docker: