mirror of
https://github.com/juanfont/headscale.git
synced 2025-08-17 19:07:31 +00:00
Compare commits
1 Commits
update-tai
...
v0.20.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ca125330fc |
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -63,7 +63,7 @@ jobs:
|
||||
|
||||
- name: Prettify code
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
uses: creyD/prettier_action@v4.3
|
||||
uses: creyD/prettier_action@v4.0
|
||||
with:
|
||||
prettier_options: >-
|
||||
--check **/*.{ts,js,md,yaml,yml,sass,css,scss,html}
|
||||
|
@@ -1,57 +0,0 @@
|
||||
# DO NOT EDIT, generated with cmd/gh-action-integration-generator/main.go
|
||||
# To regenerate, run "go generate" in cmd/gh-action-integration-generator/
|
||||
|
||||
name: Integration Test v2 - TestExpireNode
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v34
|
||||
with:
|
||||
files: |
|
||||
*.nix
|
||||
go.*
|
||||
**/*.go
|
||||
integration_test/
|
||||
config-example.yaml
|
||||
|
||||
- uses: cachix/install-nix-action@v18
|
||||
if: ${{ env.ACT }} || steps.changed-files.outputs.any_changed == 'true'
|
||||
|
||||
- name: Run general integration tests
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
run: |
|
||||
nix develop --command -- docker run \
|
||||
--tty --rm \
|
||||
--volume ~/.cache/hs-integration-go:/go \
|
||||
--name headscale-test-suite \
|
||||
--volume $PWD:$PWD -w $PWD/integration \
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock \
|
||||
--volume $PWD/control_logs:/tmp/control \
|
||||
golang:1 \
|
||||
go test ./... \
|
||||
-tags ts2019 \
|
||||
-failfast \
|
||||
-timeout 120m \
|
||||
-parallel 1 \
|
||||
-run "^TestExpireNode$"
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always() && steps.changed-files.outputs.any_changed == 'true'
|
||||
with:
|
||||
name: logs
|
||||
path: "control_logs/*.log"
|
@@ -9,8 +9,6 @@
|
||||
- Make OpenID Connect authenticated client expiry time configurable [#1191](https://github.com/juanfont/headscale/pull/1191)
|
||||
- defaults to 180 days like Tailscale SaaS
|
||||
- adds option to use the expiry time from the OpenID token for the node (see config-example.yaml)
|
||||
- Set ControlTime in Map info sent to nodes [#1195](https://github.com/juanfont/headscale/pull/1195)
|
||||
- Populate Tags field on Node updates sent [#1195](https://github.com/juanfont/headscale/pull/1195)
|
||||
|
||||
## 0.19.0 (2023-01-29)
|
||||
|
||||
|
@@ -81,11 +81,6 @@ one of the maintainers.
|
||||
|
||||
Please have a look at the documentation under [`docs/`](docs/).
|
||||
|
||||
## Talks
|
||||
|
||||
- Fosdem 2023 (video): [Headscale: How we are using integration testing to reimplement Tailscale](https://fosdem.org/2023/schedule/event/goheadscale/)
|
||||
- presented by Juan Font Alonso and Kristoffer Dalby
|
||||
|
||||
## Disclaimer
|
||||
|
||||
1. We have nothing to do with Tailscale, or Tailscale Inc.
|
||||
|
@@ -1,8 +1,6 @@
|
||||
package headscale
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
@@ -57,46 +55,16 @@ func (h *Headscale) generateMapResponse(
|
||||
peers,
|
||||
)
|
||||
|
||||
now := time.Now()
|
||||
|
||||
resp := tailcfg.MapResponse{
|
||||
KeepAlive: false,
|
||||
Node: node,
|
||||
|
||||
// TODO: Only send if updated
|
||||
DERPMap: h.DERPMap,
|
||||
|
||||
// TODO: Only send if updated
|
||||
Peers: nodePeers,
|
||||
|
||||
// TODO(kradalby): Implement:
|
||||
// https://github.com/tailscale/tailscale/blob/main/tailcfg/tailcfg.go#L1351-L1374
|
||||
// PeersChanged
|
||||
// PeersRemoved
|
||||
// PeersChangedPatch
|
||||
// PeerSeenChange
|
||||
// OnlineChange
|
||||
|
||||
// TODO: Only send if updated
|
||||
DNSConfig: dnsConfig,
|
||||
|
||||
// TODO: Only send if updated
|
||||
Domain: h.cfg.BaseDomain,
|
||||
|
||||
// Do not instruct clients to collect services, we do not
|
||||
// support or do anything with them
|
||||
CollectServices: "false",
|
||||
|
||||
// TODO: Only send if updated
|
||||
KeepAlive: false,
|
||||
Node: node,
|
||||
Peers: nodePeers,
|
||||
DNSConfig: dnsConfig,
|
||||
Domain: h.cfg.BaseDomain,
|
||||
PacketFilter: h.aclRules,
|
||||
|
||||
SSHPolicy: h.sshPolicy,
|
||||
DERPMap: h.DERPMap,
|
||||
UserProfiles: profiles,
|
||||
|
||||
// TODO: Only send if updated
|
||||
SSHPolicy: h.sshPolicy,
|
||||
|
||||
ControlTime: &now,
|
||||
|
||||
Debug: &tailcfg.Debug{
|
||||
DisableLogTail: !h.cfg.LogTail.Enabled,
|
||||
RandomizeClientPort: h.cfg.RandomizeClientPort,
|
||||
|
@@ -157,14 +157,14 @@ func (h *Headscale) DERPHandler(
|
||||
|
||||
if !fastStart {
|
||||
pubKey := h.privateKey.Public()
|
||||
pubKeyStr, _ := pubKey.MarshalText() //nolint
|
||||
pubKeyStr := pubKey.UntypedHexString() //nolint
|
||||
fmt.Fprintf(conn, "HTTP/1.1 101 Switching Protocols\r\n"+
|
||||
"Upgrade: DERP\r\n"+
|
||||
"Connection: Upgrade\r\n"+
|
||||
"Derp-Version: %v\r\n"+
|
||||
"Derp-Public-Key: %s\r\n\r\n",
|
||||
derp.ProtocolVersion,
|
||||
string(pubKeyStr))
|
||||
pubKeyStr)
|
||||
}
|
||||
|
||||
h.DERPServer.tailscaleDERP.Accept(req.Context(), netConn, conn, netConn.RemoteAddr().String())
|
||||
|
@@ -138,7 +138,6 @@ NoNewPrivileges=yes
|
||||
PrivateTmp=yes
|
||||
ProtectSystem=strict
|
||||
ProtectHome=yes
|
||||
WorkingDirectory=/var/lib/headscale
|
||||
ReadWritePaths=/var/lib/headscale /var/run/headscale
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
RuntimeDirectory=headscale
|
||||
|
@@ -10,14 +10,17 @@ describing how to make `headscale` run properly in a server environment.
|
||||
|
||||
1. Install from ports (Not Recommend)
|
||||
|
||||
As of OpenBSD 7.2, there's a headscale in ports collection, however, it's severely outdated(v0.12.4).
|
||||
As of OpenBSD 7.1, there's a headscale in ports collection, however, it's severely outdated(v0.12.4).
|
||||
You can install it via `pkg_add headscale`.
|
||||
|
||||
2. Install from source on OpenBSD 7.2
|
||||
2. Install from source on OpenBSD 7.1
|
||||
|
||||
```shell
|
||||
# Install prerequistes
|
||||
pkg_add go
|
||||
# 1. go v1.19+: headscale newer than 0.17 needs go 1.19+ to compile
|
||||
# 2. gmake: Makefile in the headscale repo is written in GNU make syntax
|
||||
pkg_add -D snap go
|
||||
pkg_add gmake
|
||||
|
||||
git clone https://github.com/juanfont/headscale.git
|
||||
|
||||
@@ -30,7 +33,7 @@ latestTag=$(git describe --tags `git rev-list --tags --max-count=1`)
|
||||
|
||||
git checkout $latestTag
|
||||
|
||||
go build -ldflags="-s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=$latestTag" github.com/juanfont/headscale
|
||||
gmake build
|
||||
|
||||
# make it executable
|
||||
chmod a+x headscale
|
||||
|
@@ -36,7 +36,7 @@
|
||||
|
||||
# When updating go.mod or go.sum, a new sha will need to be calculated,
|
||||
# update this if you have a mismatch after doing a change to thos files.
|
||||
vendorSha256 = "sha256-QkBBzyKthDnjPCYtGDeQtW46FyWfScRczBQEhAdKHbs=";
|
||||
vendorSha256 = "sha256-8p5NFxXKaZPsW4B6NMzfi0pqfVroIahSgA0fukvB3JI=";
|
||||
|
||||
ldflags = [ "-s" "-w" "-X github.com/juanfont/headscale/cmd/headscale/cli.Version=v${version}" ];
|
||||
};
|
||||
|
110
go.mod
110
go.mod
@@ -6,52 +6,51 @@ require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.6
|
||||
github.com/ccding/go-stun/stun v0.0.0-20200514191101-4dc67bcdb029
|
||||
github.com/cenkalti/backoff/v4 v4.2.0
|
||||
github.com/coreos/go-oidc/v3 v3.5.0
|
||||
github.com/coreos/go-oidc/v3 v3.4.0
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/deckarep/golang-set/v2 v2.1.0
|
||||
github.com/efekarakus/termcolor v1.0.1
|
||||
github.com/glebarez/sqlite v1.7.0
|
||||
github.com/gofrs/uuid v4.4.0+incompatible
|
||||
github.com/glebarez/sqlite v1.5.0
|
||||
github.com/gofrs/uuid v4.3.1+incompatible
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.1
|
||||
github.com/klauspost/compress v1.15.15
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.14.0
|
||||
github.com/klauspost/compress v1.15.12
|
||||
github.com/oauth2-proxy/mockoidc v0.0.0-20220308204021-b9169deeb282
|
||||
github.com/ory/dockertest/v3 v3.9.1
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/philip-bui/grpc-zerolog v1.0.1
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
github.com/prometheus/common v0.40.0
|
||||
github.com/pterm/pterm v0.12.54
|
||||
github.com/prometheus/common v0.37.0
|
||||
github.com/pterm/pterm v0.12.50
|
||||
github.com/puzpuzpuz/xsync/v2 v2.4.0
|
||||
github.com/rs/zerolog v1.29.0
|
||||
github.com/samber/lo v1.37.0
|
||||
github.com/rs/zerolog v1.28.0
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/viper v1.15.0
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a
|
||||
github.com/spf13/viper v1.14.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/tailscale/hujson v0.0.0-20220630195928-54599719472f
|
||||
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e
|
||||
go4.org/netipx v0.0.0-20230125063823-8449b0a6169f
|
||||
golang.org/x/crypto v0.6.0
|
||||
golang.org/x/net v0.7.0
|
||||
golang.org/x/oauth2 v0.5.0
|
||||
go4.org/netipx v0.0.0-20220925034521-797b0c90d8ab
|
||||
golang.org/x/crypto v0.3.0
|
||||
golang.org/x/net v0.2.0
|
||||
golang.org/x/oauth2 v0.2.0
|
||||
golang.org/x/sync v0.1.0
|
||||
google.golang.org/genproto v0.0.0-20230223222841-637eb2293923
|
||||
google.golang.org/grpc v1.53.0
|
||||
google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd
|
||||
google.golang.org/grpc v1.51.0
|
||||
google.golang.org/protobuf v1.28.1
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/driver/postgres v1.4.8
|
||||
gorm.io/gorm v1.24.5
|
||||
tailscale.com v1.36.2
|
||||
gorm.io/driver/postgres v1.4.5
|
||||
gorm.io/gorm v1.24.2
|
||||
tailscale.com v1.34.0
|
||||
)
|
||||
|
||||
require (
|
||||
atomicgo.dev/cursor v0.1.1 // indirect
|
||||
atomicgo.dev/keyboard v0.2.9 // indirect
|
||||
atomicgo.dev/keyboard v0.2.8 // indirect
|
||||
filippo.io/edwards25519 v1.0.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||
github.com/akutz/memconn v0.1.0 // indirect
|
||||
@@ -60,18 +59,15 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/containerd/console v1.0.3 // indirect
|
||||
github.com/containerd/continuity v0.3.0 // indirect
|
||||
github.com/docker/cli v23.0.1+incompatible // indirect
|
||||
github.com/docker/docker v23.0.1+incompatible // indirect
|
||||
github.com/docker/cli v20.10.21+incompatible // indirect
|
||||
github.com/docker/docker v20.10.21+incompatible // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.20.3 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.19.5 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/golang/glog v1.0.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
@@ -85,69 +81,71 @@ require (
|
||||
github.com/hdevalence/ed25519consensus v0.1.0 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
github.com/jackc/pgconn v1.13.0 // indirect
|
||||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgx/v5 v5.3.0 // indirect
|
||||
github.com/jackc/pgproto3/v2 v2.3.1 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
||||
github.com/jackc/pgtype v1.13.0 // indirect
|
||||
github.com/jackc/pgx/v4 v4.17.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/jsimonetti/rtnetlink v1.3.1 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/josharian/native v1.0.0 // indirect
|
||||
github.com/jsimonetti/rtnetlink v1.3.0 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/kr/pretty v0.3.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/lib/pq v1.10.7 // indirect
|
||||
github.com/lithammer/fuzzysearch v1.1.5 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mdlayher/netlink v1.7.1 // indirect
|
||||
github.com/mdlayher/netlink v1.7.0 // indirect
|
||||
github.com/mdlayher/socket v0.4.0 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/moby/term v0.0.0-20221128092401-c43b287e0e0f // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2 // indirect
|
||||
github.com/opencontainers/runc v1.1.4 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/procfs v0.9.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa // indirect
|
||||
github.com/rivo/uniseg v0.4.3 // indirect
|
||||
github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/spf13/afero v1.9.4 // indirect
|
||||
github.com/spf13/afero v1.9.3 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/subosito/gotenv v1.4.1 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect
|
||||
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/term v0.5.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 // indirect
|
||||
golang.org/x/mod v0.7.0 // indirect
|
||||
golang.org/x/sys v0.3.0 // indirect
|
||||
golang.org/x/term v0.2.0 // indirect
|
||||
golang.org/x/text v0.5.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
golang.org/x/tools v0.3.0 // indirect
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
modernc.org/libc v1.22.2 // indirect
|
||||
modernc.org/libc v1.21.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
modernc.org/sqlite v1.20.3 // indirect
|
||||
modernc.org/sqlite v1.20.0 // indirect
|
||||
nhooyr.io/websocket v1.8.7 // indirect
|
||||
)
|
||||
|
@@ -9,16 +9,6 @@ Headscale's test framework and the current set of scenarios are defined in this
|
||||
|
||||
Tests are located in files ending with `_test.go` and the framework are located in the rest.
|
||||
|
||||
## Running integration tests locally
|
||||
|
||||
The easiest way to run tests locally is to use `[act](INSERT LINK)`, a local GitHub Actions runner:
|
||||
|
||||
```
|
||||
act pull_request -W .github/workflows/test-integration-v2-TestPingAllByIP.yaml
|
||||
```
|
||||
|
||||
Alternatively, the `docker run` command in each GitHub workflow file can be used.
|
||||
|
||||
## Running integration tests on GitHub Actions
|
||||
|
||||
Each test currently runs as a separate workflows in GitHub actions, to add new test, run
|
||||
|
@@ -19,7 +19,6 @@ import (
|
||||
"github.com/juanfont/headscale/integration/hsic"
|
||||
"github.com/ory/dockertest/v3"
|
||||
"github.com/ory/dockertest/v3/docker"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -92,11 +91,7 @@ func TestOIDCAuthenticationPingAll(t *testing.T) {
|
||||
t.Errorf("failed wait for tailscale clients to be in sync: %s", err)
|
||||
}
|
||||
|
||||
allAddrs := lo.Map(allIps, func(x netip.Addr, index int) string {
|
||||
return x.String()
|
||||
})
|
||||
|
||||
success := pingAllHelper(t, allClients, allAddrs)
|
||||
success := pingAll(t, allClients, allIps)
|
||||
t.Logf("%d successful pings out of %d", success, len(allClients)*len(allIps))
|
||||
|
||||
err = scenario.Shutdown()
|
||||
@@ -162,11 +157,7 @@ func TestOIDCExpireNodesBasedOnTokenExpiry(t *testing.T) {
|
||||
t.Errorf("failed wait for tailscale clients to be in sync: %s", err)
|
||||
}
|
||||
|
||||
allAddrs := lo.Map(allIps, func(x netip.Addr, index int) string {
|
||||
return x.String()
|
||||
})
|
||||
|
||||
success := pingAllHelper(t, allClients, allAddrs)
|
||||
success := pingAll(t, allClients, allIps)
|
||||
t.Logf("%d successful pings out of %d (before expiry)", success, len(allClients)*len(allIps))
|
||||
|
||||
// await all nodes being logged out after OIDC token expiry
|
||||
@@ -368,6 +359,24 @@ func (s *AuthOIDCScenario) runTailscaleUp(
|
||||
return fmt.Errorf("failed to up tailscale node: %w", errNoUserAvailable)
|
||||
}
|
||||
|
||||
func pingAll(t *testing.T, clients []TailscaleClient, ips []netip.Addr) int {
|
||||
t.Helper()
|
||||
success := 0
|
||||
|
||||
for _, client := range clients {
|
||||
for _, ip := range ips {
|
||||
err := client.Ping(ip.String())
|
||||
if err != nil {
|
||||
t.Errorf("failed to ping %s from %s: %s", ip, client.Hostname(), err)
|
||||
} else {
|
||||
success++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return success
|
||||
}
|
||||
|
||||
func (s *AuthOIDCScenario) Shutdown() error {
|
||||
err := s.pool.Purge(s.mockOIDC)
|
||||
if err != nil {
|
||||
|
@@ -13,7 +13,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/juanfont/headscale/integration/hsic"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
var errParseAuthPage = errors.New("failed to parse auth page")
|
||||
@@ -60,11 +59,18 @@ func TestAuthWebFlowAuthenticationPingAll(t *testing.T) {
|
||||
t.Errorf("failed wait for tailscale clients to be in sync: %s", err)
|
||||
}
|
||||
|
||||
allAddrs := lo.Map(allIps, func(x netip.Addr, index int) string {
|
||||
return x.String()
|
||||
})
|
||||
success := 0
|
||||
for _, client := range allClients {
|
||||
for _, ip := range allIps {
|
||||
err := client.Ping(ip.String())
|
||||
if err != nil {
|
||||
t.Errorf("failed to ping %s from %s: %s", ip, client.Hostname(), err)
|
||||
} else {
|
||||
success++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
success := pingAllHelper(t, allClients, allAddrs)
|
||||
t.Logf("%d successful pings out of %d", success, len(allClients)*len(allIps))
|
||||
|
||||
err = scenario.Shutdown()
|
||||
@@ -111,11 +117,18 @@ func TestAuthWebFlowLogoutAndRelogin(t *testing.T) {
|
||||
t.Errorf("failed wait for tailscale clients to be in sync: %s", err)
|
||||
}
|
||||
|
||||
allAddrs := lo.Map(allIps, func(x netip.Addr, index int) string {
|
||||
return x.String()
|
||||
})
|
||||
success := 0
|
||||
for _, client := range allClients {
|
||||
for _, ip := range allIps {
|
||||
err := client.Ping(ip.String())
|
||||
if err != nil {
|
||||
t.Errorf("failed to ping %s from %s: %s", ip, client.Hostname(), err)
|
||||
} else {
|
||||
success++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
success := pingAllHelper(t, allClients, allAddrs)
|
||||
t.Logf("%d successful pings out of %d", success, len(allClients)*len(allIps))
|
||||
|
||||
clientIPs := make(map[TailscaleClient][]netip.Addr)
|
||||
@@ -162,11 +175,18 @@ func TestAuthWebFlowLogoutAndRelogin(t *testing.T) {
|
||||
t.Errorf("failed to get clients: %s", err)
|
||||
}
|
||||
|
||||
allAddrs = lo.Map(allIps, func(x netip.Addr, index int) string {
|
||||
return x.String()
|
||||
})
|
||||
success = 0
|
||||
for _, client := range allClients {
|
||||
for _, ip := range allIps {
|
||||
err := client.Ping(ip.String())
|
||||
if err != nil {
|
||||
t.Errorf("failed to ping %s from %s: %s", ip, client.Hostname(), err)
|
||||
} else {
|
||||
success++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
success = pingAllHelper(t, allClients, allAddrs)
|
||||
t.Logf("%d successful pings out of %d", success, len(allClients)*len(allIps))
|
||||
|
||||
for _, client := range allClients {
|
||||
@@ -191,12 +211,7 @@ func TestAuthWebFlowLogoutAndRelogin(t *testing.T) {
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Errorf(
|
||||
"IPs changed for client %s. Used to be %v now %v",
|
||||
client.Hostname(),
|
||||
clientIPs[client],
|
||||
ips,
|
||||
)
|
||||
t.Errorf("IPs changed for client %s. Used to be %v now %v", client.Hostname(), clientIPs[client], ips)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,19 +1,15 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
"github.com/juanfont/headscale/integration/hsic"
|
||||
"github.com/juanfont/headscale/integration/tsic"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPingAllByIP(t *testing.T) {
|
||||
@@ -50,11 +46,19 @@ func TestPingAllByIP(t *testing.T) {
|
||||
t.Errorf("failed wait for tailscale clients to be in sync: %s", err)
|
||||
}
|
||||
|
||||
allAddrs := lo.Map(allIps, func(x netip.Addr, index int) string {
|
||||
return x.String()
|
||||
})
|
||||
success := 0
|
||||
|
||||
for _, client := range allClients {
|
||||
for _, ip := range allIps {
|
||||
err := client.Ping(ip.String())
|
||||
if err != nil {
|
||||
t.Errorf("failed to ping %s from %s: %s", ip, client.Hostname(), err)
|
||||
} else {
|
||||
success++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
success := pingAllHelper(t, allClients, allAddrs)
|
||||
t.Logf("%d successful pings out of %d", success, len(allClients)*len(allIps))
|
||||
|
||||
err = scenario.Shutdown()
|
||||
@@ -144,11 +148,18 @@ func TestAuthKeyLogoutAndRelogin(t *testing.T) {
|
||||
t.Errorf("failed to get clients: %s", err)
|
||||
}
|
||||
|
||||
allAddrs := lo.Map(allIps, func(x netip.Addr, index int) string {
|
||||
return x.String()
|
||||
})
|
||||
success := 0
|
||||
for _, client := range allClients {
|
||||
for _, ip := range allIps {
|
||||
err := client.Ping(ip.String())
|
||||
if err != nil {
|
||||
t.Errorf("failed to ping %s from %s: %s", ip, client.Hostname(), err)
|
||||
} else {
|
||||
success++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
success := pingAllHelper(t, allClients, allAddrs)
|
||||
t.Logf("%d successful pings out of %d", success, len(allClients)*len(allIps))
|
||||
|
||||
for _, client := range allClients {
|
||||
@@ -173,12 +184,7 @@ func TestAuthKeyLogoutAndRelogin(t *testing.T) {
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Errorf(
|
||||
"IPs changed for client %s. Used to be %v now %v",
|
||||
client.Hostname(),
|
||||
clientIPs[client],
|
||||
ips,
|
||||
)
|
||||
t.Errorf("IPs changed for client %s. Used to be %v now %v", client.Hostname(), clientIPs[client], ips)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -247,11 +253,18 @@ func TestEphemeral(t *testing.T) {
|
||||
t.Errorf("failed to get clients: %s", err)
|
||||
}
|
||||
|
||||
allAddrs := lo.Map(allIps, func(x netip.Addr, index int) string {
|
||||
return x.String()
|
||||
})
|
||||
success := 0
|
||||
for _, client := range allClients {
|
||||
for _, ip := range allIps {
|
||||
err := client.Ping(ip.String())
|
||||
if err != nil {
|
||||
t.Errorf("failed to ping %s from %s: %s", ip, client.Hostname(), err)
|
||||
} else {
|
||||
success++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
success := pingAllHelper(t, allClients, allAddrs)
|
||||
t.Logf("%d successful pings out of %d", success, len(allClients)*len(allIps))
|
||||
|
||||
for _, client := range allClients {
|
||||
@@ -322,7 +335,18 @@ func TestPingAllByHostname(t *testing.T) {
|
||||
t.Errorf("failed to get FQDNs: %s", err)
|
||||
}
|
||||
|
||||
success := pingAllHelper(t, allClients, allHostnames)
|
||||
success := 0
|
||||
|
||||
for _, client := range allClients {
|
||||
for _, hostname := range allHostnames {
|
||||
err := client.Ping(hostname)
|
||||
if err != nil {
|
||||
t.Errorf("failed to ping %s from %s: %s", hostname, client.Hostname(), err)
|
||||
} else {
|
||||
success++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("%d successful pings out of %d", success, len(allClients)*len(allClients))
|
||||
|
||||
@@ -558,93 +582,3 @@ func TestResolveMagicDNS(t *testing.T) {
|
||||
t.Errorf("failed to tear down scenario: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpireNode(t *testing.T) {
|
||||
IntegrationSkip(t)
|
||||
t.Parallel()
|
||||
|
||||
scenario, err := NewScenario()
|
||||
if err != nil {
|
||||
t.Errorf("failed to create scenario: %s", err)
|
||||
}
|
||||
|
||||
spec := map[string]int{
|
||||
"user1": len(TailscaleVersions),
|
||||
}
|
||||
|
||||
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("expirenode"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to create headscale environment: %s", err)
|
||||
}
|
||||
|
||||
allClients, err := scenario.ListTailscaleClients()
|
||||
if err != nil {
|
||||
t.Errorf("failed to get clients: %s", err)
|
||||
}
|
||||
|
||||
allIps, err := scenario.ListTailscaleClientsIPs()
|
||||
if err != nil {
|
||||
t.Errorf("failed to get clients: %s", err)
|
||||
}
|
||||
|
||||
err = scenario.WaitForTailscaleSync()
|
||||
if err != nil {
|
||||
t.Errorf("failed wait for tailscale clients to be in sync: %s", err)
|
||||
}
|
||||
|
||||
allAddrs := lo.Map(allIps, func(x netip.Addr, index int) string {
|
||||
return x.String()
|
||||
})
|
||||
|
||||
success := pingAllHelper(t, allClients, allAddrs)
|
||||
t.Logf("before expire: %d successful pings out of %d", success, len(allClients)*len(allIps))
|
||||
|
||||
for _, client := range allClients {
|
||||
status, err := client.Status()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Assert that we have the original count - self
|
||||
assert.Len(t, status.Peers(), len(TailscaleVersions)-1)
|
||||
}
|
||||
|
||||
headscale, err := scenario.Headscale()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// TODO(kradalby): This is Headscale specific and would not play nicely
|
||||
// with other implementations of the ControlServer interface
|
||||
result, err := headscale.Execute([]string{
|
||||
"headscale", "nodes", "expire", "--identifier", "0", "--output", "json",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
var machine v1.Machine
|
||||
err = json.Unmarshal([]byte(result), &machine)
|
||||
assert.NoError(t, err)
|
||||
|
||||
time.Sleep(30 * time.Second)
|
||||
|
||||
// Verify that the expired not is no longer present in the Peer list
|
||||
// of connected nodes.
|
||||
for _, client := range allClients {
|
||||
status, err := client.Status()
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, peerKey := range status.Peers() {
|
||||
peerStatus := status.Peer[peerKey]
|
||||
|
||||
peerPublicKey := strings.TrimPrefix(peerStatus.PublicKey.String(), "nodekey:")
|
||||
|
||||
assert.NotEqual(t, machine.NodeKey, peerPublicKey)
|
||||
}
|
||||
|
||||
if client.Hostname() != machine.Name {
|
||||
// Assert that we have the original count - self - expired node
|
||||
assert.Len(t, status.Peers(), len(TailscaleVersions)-2)
|
||||
}
|
||||
}
|
||||
|
||||
err = scenario.Shutdown()
|
||||
if err != nil {
|
||||
t.Errorf("failed to tear down scenario: %s", err)
|
||||
}
|
||||
}
|
||||
|
@@ -41,8 +41,6 @@ type fileInContainer struct {
|
||||
contents []byte
|
||||
}
|
||||
|
||||
// HeadscaleInContainer is an implementation of ControlServer which
|
||||
// sets up a Headscale instance inside a container.
|
||||
type HeadscaleInContainer struct {
|
||||
hostname string
|
||||
|
||||
@@ -59,12 +57,8 @@ type HeadscaleInContainer struct {
|
||||
filesInContainer []fileInContainer
|
||||
}
|
||||
|
||||
// Option represent optional settings that can be given to a
|
||||
// Headscale instance.
|
||||
type Option = func(c *HeadscaleInContainer)
|
||||
|
||||
// WithACLPolicy adds a headscale.ACLPolicy policy to the
|
||||
// HeadscaleInContainer instance.
|
||||
func WithACLPolicy(acl *headscale.ACLPolicy) Option {
|
||||
return func(hsic *HeadscaleInContainer) {
|
||||
// TODO(kradalby): Move somewhere appropriate
|
||||
@@ -74,7 +68,6 @@ func WithACLPolicy(acl *headscale.ACLPolicy) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithTLS creates certificates and enables HTTPS.
|
||||
func WithTLS() Option {
|
||||
return func(hsic *HeadscaleInContainer) {
|
||||
cert, key, err := createCertificate()
|
||||
@@ -91,8 +84,6 @@ func WithTLS() Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithConfigEnv takes a map of environment variables that
|
||||
// can be used to override Headscale configuration.
|
||||
func WithConfigEnv(configEnv map[string]string) Option {
|
||||
return func(hsic *HeadscaleInContainer) {
|
||||
for key, value := range configEnv {
|
||||
@@ -101,15 +92,12 @@ func WithConfigEnv(configEnv map[string]string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithPort sets the port on where to run Headscale.
|
||||
func WithPort(port int) Option {
|
||||
return func(hsic *HeadscaleInContainer) {
|
||||
hsic.port = port
|
||||
}
|
||||
}
|
||||
|
||||
// WithTestName sets a a name for the test, this will be reflected
|
||||
// in the Docker container name.
|
||||
func WithTestName(testName string) Option {
|
||||
return func(hsic *HeadscaleInContainer) {
|
||||
hash, _ := headscale.GenerateRandomStringDNSSafe(hsicHashLength)
|
||||
@@ -119,8 +107,6 @@ func WithTestName(testName string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithHostnameAsServerURL sets the Headscale ServerURL based on
|
||||
// the Hostname.
|
||||
func WithHostnameAsServerURL() Option {
|
||||
return func(hsic *HeadscaleInContainer) {
|
||||
hsic.env["HEADSCALE_SERVER_URL"] = fmt.Sprintf("http://%s",
|
||||
@@ -130,7 +116,6 @@ func WithHostnameAsServerURL() Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithFileInContainer adds a file to the container at the given path.
|
||||
func WithFileInContainer(path string, contents []byte) Option {
|
||||
return func(hsic *HeadscaleInContainer) {
|
||||
hsic.filesInContainer = append(hsic.filesInContainer,
|
||||
@@ -141,7 +126,6 @@ func WithFileInContainer(path string, contents []byte) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a new HeadscaleInContainer instance.
|
||||
func New(
|
||||
pool *dockertest.Pool,
|
||||
network *dockertest.Network,
|
||||
@@ -260,19 +244,14 @@ func (t *HeadscaleInContainer) hasTLS() bool {
|
||||
return len(t.tlsCert) != 0 && len(t.tlsKey) != 0
|
||||
}
|
||||
|
||||
// Shutdown stops and cleans up the Headscale container.
|
||||
func (t *HeadscaleInContainer) Shutdown() error {
|
||||
return t.pool.Purge(t.container)
|
||||
}
|
||||
|
||||
// SaveLog saves the current stdout log of the container to a path
|
||||
// on the host system.
|
||||
func (t *HeadscaleInContainer) SaveLog(path string) error {
|
||||
return dockertestutil.SaveLog(t.pool, t.container, path)
|
||||
}
|
||||
|
||||
// Execute runs a command inside the Headscale container and returns the
|
||||
// result of stdout as a string.
|
||||
func (t *HeadscaleInContainer) Execute(
|
||||
command []string,
|
||||
) (string, error) {
|
||||
@@ -294,23 +273,18 @@ func (t *HeadscaleInContainer) Execute(
|
||||
return stdout, nil
|
||||
}
|
||||
|
||||
// GetIP returns the docker container IP as a string.
|
||||
func (t *HeadscaleInContainer) GetIP() string {
|
||||
return t.container.GetIPInNetwork(t.network)
|
||||
}
|
||||
|
||||
// GetPort returns the docker container port as a string.
|
||||
func (t *HeadscaleInContainer) GetPort() string {
|
||||
return fmt.Sprintf("%d", t.port)
|
||||
}
|
||||
|
||||
// GetHealthEndpoint returns a health endpoint for the HeadscaleInContainer
|
||||
// instance.
|
||||
func (t *HeadscaleInContainer) GetHealthEndpoint() string {
|
||||
return fmt.Sprintf("%s/health", t.GetEndpoint())
|
||||
}
|
||||
|
||||
// GetEndpoint returns the Headscale endpoint for the HeadscaleInContainer.
|
||||
func (t *HeadscaleInContainer) GetEndpoint() string {
|
||||
hostEndpoint := fmt.Sprintf("%s:%d",
|
||||
t.GetIP(),
|
||||
@@ -323,18 +297,14 @@ func (t *HeadscaleInContainer) GetEndpoint() string {
|
||||
return fmt.Sprintf("http://%s", hostEndpoint)
|
||||
}
|
||||
|
||||
// GetCert returns the public certificate of the HeadscaleInContainer.
|
||||
func (t *HeadscaleInContainer) GetCert() []byte {
|
||||
return t.tlsCert
|
||||
}
|
||||
|
||||
// GetHostname returns the hostname of the HeadscaleInContainer.
|
||||
func (t *HeadscaleInContainer) GetHostname() string {
|
||||
return t.hostname
|
||||
}
|
||||
|
||||
// WaitForReady blocks until the Headscale instance is ready to
|
||||
// serve clients.
|
||||
func (t *HeadscaleInContainer) WaitForReady() error {
|
||||
url := t.GetHealthEndpoint()
|
||||
|
||||
@@ -362,7 +332,6 @@ func (t *HeadscaleInContainer) WaitForReady() error {
|
||||
})
|
||||
}
|
||||
|
||||
// CreateUser adds a new user to the Headscale instance.
|
||||
func (t *HeadscaleInContainer) CreateUser(
|
||||
user string,
|
||||
) error {
|
||||
@@ -380,8 +349,6 @@ func (t *HeadscaleInContainer) CreateUser(
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateAuthKey creates a new "authorisation key" for a User that can be used
|
||||
// to authorise a TailscaleClient with the Headscale instance.
|
||||
func (t *HeadscaleInContainer) CreateAuthKey(
|
||||
user string,
|
||||
reusable bool,
|
||||
@@ -425,8 +392,6 @@ func (t *HeadscaleInContainer) CreateAuthKey(
|
||||
return &preAuthKey, nil
|
||||
}
|
||||
|
||||
// ListMachinesInUser list the TailscaleClients (Machine, Headscale internal representation)
|
||||
// associated with a user.
|
||||
func (t *HeadscaleInContainer) ListMachinesInUser(
|
||||
user string,
|
||||
) ([]*v1.Machine, error) {
|
||||
@@ -450,7 +415,6 @@ func (t *HeadscaleInContainer) ListMachinesInUser(
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// WriteFile save file inside the Headscale container.
|
||||
func (t *HeadscaleInContainer) WriteFile(path string, data []byte) error {
|
||||
return integrationutil.WriteFileToContainer(t.pool, t.container, path, data)
|
||||
}
|
||||
|
@@ -26,7 +26,6 @@ const (
|
||||
var (
|
||||
errNoHeadscaleAvailable = errors.New("no headscale available")
|
||||
errNoUserAvailable = errors.New("no user available")
|
||||
errNoClientFound = errors.New("client not found")
|
||||
|
||||
// Tailscale started adding TS2021 support in CapabilityVersion>=28 (v1.24.0), but
|
||||
// proper support in Headscale was only added for CapabilityVersion>=39 clients (v1.30.0).
|
||||
@@ -57,23 +56,12 @@ var (
|
||||
// "1.8.7",
|
||||
// }.
|
||||
|
||||
// TailscaleVersions represents a list of Tailscale versions the suite
|
||||
// uses to test compatibility with the ControlServer.
|
||||
//
|
||||
// The list contains two special cases, "head" and "unstable" which
|
||||
// points to the current tip of Tailscale's main branch and the latest
|
||||
// released unstable version.
|
||||
//
|
||||
// The rest of the version represents Tailscale versions that can be
|
||||
// found in Tailscale's apt repository.
|
||||
TailscaleVersions = append(
|
||||
tailscaleVersions2021,
|
||||
tailscaleVersions2019...,
|
||||
)
|
||||
)
|
||||
|
||||
// User represents a User in the ControlServer and a map of TailscaleClient's
|
||||
// associated with the User.
|
||||
type User struct {
|
||||
Clients map[string]TailscaleClient
|
||||
|
||||
@@ -82,10 +70,6 @@ type User struct {
|
||||
syncWaitGroup sync.WaitGroup
|
||||
}
|
||||
|
||||
// Scenario is a representation of an environment with one ControlServer and
|
||||
// one or more User's and its associated TailscaleClients.
|
||||
// A Scenario is intended to simplify setting up a new testcase for testing
|
||||
// a ControlServer with TailscaleClients.
|
||||
// TODO(kradalby): make control server configurable, test correctness with Tailscale SaaS.
|
||||
type Scenario struct {
|
||||
// TODO(kradalby): support multiple headcales for later, currently only
|
||||
@@ -100,8 +84,6 @@ type Scenario struct {
|
||||
headscaleLock sync.Mutex
|
||||
}
|
||||
|
||||
// NewScenario creates a test Scenario which can be used to bootstraps a ControlServer with
|
||||
// a set of Users and TailscaleClients.
|
||||
func NewScenario() (*Scenario, error) {
|
||||
hash, err := headscale.GenerateRandomStringDNSSafe(scenarioHashLength)
|
||||
if err != nil {
|
||||
@@ -142,10 +124,6 @@ func NewScenario() (*Scenario, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Shutdown shuts down and cleans up all the containers (ControlServer, TailscaleClient)
|
||||
// and networks associated with it.
|
||||
// In addition, it will save the logs of the ControlServer to `/tmp/control` in the
|
||||
// environment running the tests.
|
||||
func (s *Scenario) Shutdown() error {
|
||||
s.controlServers.Range(func(_ string, control ControlServer) bool {
|
||||
err := control.SaveLog("/tmp/control")
|
||||
@@ -189,7 +167,6 @@ func (s *Scenario) Shutdown() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Users returns the name of all users associated with the Scenario.
|
||||
func (s *Scenario) Users() []string {
|
||||
users := make([]string, 0)
|
||||
for user := range s.users {
|
||||
@@ -202,9 +179,6 @@ func (s *Scenario) Users() []string {
|
||||
/// Headscale related stuff
|
||||
// Note: These functions assume that there is a _single_ headscale instance for now
|
||||
|
||||
// Headscale returns a ControlServer instance based on hsic (HeadscaleInContainer)
|
||||
// If the Scenario already has an instance, the pointer to the running container
|
||||
// will be return, otherwise a new instance will be created.
|
||||
// TODO(kradalby): make port and headscale configurable, multiple instances support?
|
||||
func (s *Scenario) Headscale(opts ...hsic.Option) (ControlServer, error) {
|
||||
s.headscaleLock.Lock()
|
||||
@@ -229,13 +203,7 @@ func (s *Scenario) Headscale(opts ...hsic.Option) (ControlServer, error) {
|
||||
return headscale, nil
|
||||
}
|
||||
|
||||
// CreatePreAuthKey creates a "pre authentorised key" to be created in the
|
||||
// Headscale instance on behalf of the Scenario.
|
||||
func (s *Scenario) CreatePreAuthKey(
|
||||
user string,
|
||||
reusable bool,
|
||||
ephemeral bool,
|
||||
) (*v1.PreAuthKey, error) {
|
||||
func (s *Scenario) CreatePreAuthKey(user string, reusable bool, ephemeral bool) (*v1.PreAuthKey, error) {
|
||||
if headscale, err := s.Headscale(); err == nil {
|
||||
key, err := headscale.CreateAuthKey(user, reusable, ephemeral)
|
||||
if err != nil {
|
||||
@@ -248,8 +216,6 @@ func (s *Scenario) CreatePreAuthKey(
|
||||
return nil, fmt.Errorf("failed to create user: %w", errNoHeadscaleAvailable)
|
||||
}
|
||||
|
||||
// CreateUser creates a User to be created in the
|
||||
// Headscale instance on behalf of the Scenario.
|
||||
func (s *Scenario) CreateUser(user string) error {
|
||||
if headscale, err := s.Headscale(); err == nil {
|
||||
err := headscale.CreateUser(user)
|
||||
@@ -269,8 +235,6 @@ func (s *Scenario) CreateUser(user string) error {
|
||||
|
||||
/// Client related stuff
|
||||
|
||||
// CreateTailscaleNodesInUser creates and adds a new TailscaleClient to a
|
||||
// User in the Scenario.
|
||||
func (s *Scenario) CreateTailscaleNodesInUser(
|
||||
userStr string,
|
||||
requestedVersion string,
|
||||
@@ -331,8 +295,6 @@ func (s *Scenario) CreateTailscaleNodesInUser(
|
||||
return fmt.Errorf("failed to add tailscale node: %w", errNoUserAvailable)
|
||||
}
|
||||
|
||||
// RunTailscaleUp will log in all of the TailscaleClients associated with a
|
||||
// User to the given ControlServer (by URL).
|
||||
func (s *Scenario) RunTailscaleUp(
|
||||
userStr, loginServer, authKey string,
|
||||
) error {
|
||||
@@ -361,8 +323,6 @@ func (s *Scenario) RunTailscaleUp(
|
||||
return fmt.Errorf("failed to up tailscale node: %w", errNoUserAvailable)
|
||||
}
|
||||
|
||||
// CountTailscale returns the total number of TailscaleClients in a Scenario.
|
||||
// This is the sum of Users x TailscaleClients.
|
||||
func (s *Scenario) CountTailscale() int {
|
||||
count := 0
|
||||
|
||||
@@ -373,8 +333,6 @@ func (s *Scenario) CountTailscale() int {
|
||||
return count
|
||||
}
|
||||
|
||||
// WaitForTailscaleSync blocks execution until all the TailscaleClient reports
|
||||
// to have all other TailscaleClients present in their netmap.NetworkMap.
|
||||
func (s *Scenario) WaitForTailscaleSync() error {
|
||||
tsCount := s.CountTailscale()
|
||||
|
||||
@@ -395,7 +353,7 @@ func (s *Scenario) WaitForTailscaleSync() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateHeadscaleEnv is a conventient method returning a complete Headcale
|
||||
// CreateHeadscaleEnv is a conventient method returning a set up Headcale
|
||||
// test environment with nodes of all versions, joined to the server with X
|
||||
// users.
|
||||
func (s *Scenario) CreateHeadscaleEnv(
|
||||
@@ -433,8 +391,6 @@ func (s *Scenario) CreateHeadscaleEnv(
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetIPs returns all netip.Addr of TailscaleClients associated with a User
|
||||
// in a Scenario.
|
||||
func (s *Scenario) GetIPs(user string) ([]netip.Addr, error) {
|
||||
var ips []netip.Addr
|
||||
if ns, ok := s.users[user]; ok {
|
||||
@@ -452,7 +408,6 @@ func (s *Scenario) GetIPs(user string) ([]netip.Addr, error) {
|
||||
return ips, fmt.Errorf("failed to get ips: %w", errNoUserAvailable)
|
||||
}
|
||||
|
||||
// GetIPs returns all TailscaleClients associated with a User in a Scenario.
|
||||
func (s *Scenario) GetClients(user string) ([]TailscaleClient, error) {
|
||||
var clients []TailscaleClient
|
||||
if ns, ok := s.users[user]; ok {
|
||||
@@ -466,8 +421,6 @@ func (s *Scenario) GetClients(user string) ([]TailscaleClient, error) {
|
||||
return clients, fmt.Errorf("failed to get clients: %w", errNoUserAvailable)
|
||||
}
|
||||
|
||||
// ListTailscaleClients returns a list of TailscaleClients given the Users
|
||||
// passed as parameters.
|
||||
func (s *Scenario) ListTailscaleClients(users ...string) ([]TailscaleClient, error) {
|
||||
var allClients []TailscaleClient
|
||||
|
||||
@@ -487,28 +440,6 @@ func (s *Scenario) ListTailscaleClients(users ...string) ([]TailscaleClient, err
|
||||
return allClients, nil
|
||||
}
|
||||
|
||||
// FindTailscaleClientByIP returns a TailscaleClient associated with an IP address
|
||||
// if it exists.
|
||||
func (s *Scenario) FindTailscaleClientByIP(ip netip.Addr) (TailscaleClient, error) {
|
||||
clients, err := s.ListTailscaleClients()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, client := range clients {
|
||||
ips, _ := client.IPs()
|
||||
for _, ip2 := range ips {
|
||||
if ip == ip2 {
|
||||
return client, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errNoClientFound
|
||||
}
|
||||
|
||||
// ListTailscaleClientsIPs returns a list of netip.Addr based on Users
|
||||
// passed as parameters.
|
||||
func (s *Scenario) ListTailscaleClientsIPs(users ...string) ([]netip.Addr, error) {
|
||||
var allIps []netip.Addr
|
||||
|
||||
@@ -528,8 +459,6 @@ func (s *Scenario) ListTailscaleClientsIPs(users ...string) ([]netip.Addr, error
|
||||
return allIps, nil
|
||||
}
|
||||
|
||||
// ListTailscaleClientsIPs returns a list of FQDN based on Users
|
||||
// passed as parameters.
|
||||
func (s *Scenario) ListTailscaleClientsFQDNs(users ...string) ([]string, error) {
|
||||
allFQDNs := make([]string, 0)
|
||||
|
||||
@@ -550,8 +479,6 @@ func (s *Scenario) ListTailscaleClientsFQDNs(users ...string) ([]string, error)
|
||||
return allFQDNs, nil
|
||||
}
|
||||
|
||||
// WaitForTailscaleLogout blocks execution until all TailscaleClients have
|
||||
// logged out of the ControlServer.
|
||||
func (s *Scenario) WaitForTailscaleLogout() {
|
||||
for _, user := range s.users {
|
||||
for _, client := range user.Clients {
|
||||
|
@@ -4,7 +4,6 @@ import (
|
||||
"net/netip"
|
||||
"net/url"
|
||||
|
||||
"github.com/juanfont/headscale/integration/tsic"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
)
|
||||
|
||||
@@ -23,6 +22,6 @@ type TailscaleClient interface {
|
||||
WaitForReady() error
|
||||
WaitForLogout() error
|
||||
WaitForPeers(expected int) error
|
||||
Ping(hostnameOrIP string, opts ...tsic.PingOption) error
|
||||
Ping(hostnameOrIP string) error
|
||||
ID() string
|
||||
}
|
||||
|
@@ -7,9 +7,7 @@ import (
|
||||
"log"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/juanfont/headscale"
|
||||
@@ -22,7 +20,6 @@ import (
|
||||
|
||||
const (
|
||||
tsicHashLength = 6
|
||||
defaultPingCount = 10
|
||||
dockerContextPath = "../."
|
||||
headscaleCertPath = "/usr/local/share/ca-certificates/headscale.crt"
|
||||
)
|
||||
@@ -36,8 +33,6 @@ var (
|
||||
errTailscaleNotLoggedOut = errors.New("tailscale not logged out")
|
||||
)
|
||||
|
||||
// TailscaleInContainer is an implementation of TailscaleClient which
|
||||
// sets up a Tailscale instance inside a container.
|
||||
type TailscaleInContainer struct {
|
||||
version string
|
||||
hostname string
|
||||
@@ -54,25 +49,16 @@ type TailscaleInContainer struct {
|
||||
headscaleCert []byte
|
||||
headscaleHostname string
|
||||
withSSH bool
|
||||
withTags []string
|
||||
}
|
||||
|
||||
// Option represent optional settings that can be given to a
|
||||
// Tailscale instance.
|
||||
type Option = func(c *TailscaleInContainer)
|
||||
|
||||
// WithHeadscaleTLS takes the certificate of the Headscale instance
|
||||
// and adds it to the trusted surtificate of the Tailscale container.
|
||||
func WithHeadscaleTLS(cert []byte) Option {
|
||||
return func(tsic *TailscaleInContainer) {
|
||||
tsic.headscaleCert = cert
|
||||
}
|
||||
}
|
||||
|
||||
// WithOrCreateNetwork sets the Docker container network to use with
|
||||
// the Tailscale instance, if the parameter is nil, a new network,
|
||||
// isolating the TailscaleClient, will be created. If a network is
|
||||
// passed, the Tailscale instance will join the given network.
|
||||
func WithOrCreateNetwork(network *dockertest.Network) Option {
|
||||
return func(tsic *TailscaleInContainer) {
|
||||
if network != nil {
|
||||
@@ -93,29 +79,18 @@ func WithOrCreateNetwork(network *dockertest.Network) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithHeadscaleName set the name of the headscale instance,
|
||||
// mostly useful in combination with TLS and WithHeadscaleTLS.
|
||||
func WithHeadscaleName(hsName string) Option {
|
||||
return func(tsic *TailscaleInContainer) {
|
||||
tsic.headscaleHostname = hsName
|
||||
}
|
||||
}
|
||||
|
||||
// WithTags associates the given tags to the Tailscale instance.
|
||||
func WithTags(tags []string) Option {
|
||||
return func(tsic *TailscaleInContainer) {
|
||||
tsic.withTags = tags
|
||||
}
|
||||
}
|
||||
|
||||
// WithSSH enables SSH for the Tailscale instance.
|
||||
func WithSSH() Option {
|
||||
return func(tsic *TailscaleInContainer) {
|
||||
tsic.withSSH = true
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a new TailscaleInContainer instance.
|
||||
func New(
|
||||
pool *dockertest.Pool,
|
||||
version string,
|
||||
@@ -197,29 +172,22 @@ func (t *TailscaleInContainer) hasTLS() bool {
|
||||
return len(t.headscaleCert) != 0
|
||||
}
|
||||
|
||||
// Shutdown stops and cleans up the Tailscale container.
|
||||
func (t *TailscaleInContainer) Shutdown() error {
|
||||
return t.pool.Purge(t.container)
|
||||
}
|
||||
|
||||
// Hostname returns the hostname of the Tailscale instance.
|
||||
func (t *TailscaleInContainer) Hostname() string {
|
||||
return t.hostname
|
||||
}
|
||||
|
||||
// Version returns the running Tailscale version of the instance.
|
||||
func (t *TailscaleInContainer) Version() string {
|
||||
return t.version
|
||||
}
|
||||
|
||||
// ID returns the Docker container ID of the TailscaleInContainer
|
||||
// instance.
|
||||
func (t *TailscaleInContainer) ID() string {
|
||||
return t.container.Container.ID
|
||||
}
|
||||
|
||||
// Execute runs a command inside the Tailscale container and returns the
|
||||
// result of stdout as a string.
|
||||
func (t *TailscaleInContainer) Execute(
|
||||
command []string,
|
||||
) (string, string, error) {
|
||||
@@ -245,8 +213,6 @@ func (t *TailscaleInContainer) Execute(
|
||||
return stdout, stderr, nil
|
||||
}
|
||||
|
||||
// Up runs the login routine on the given Tailscale instance.
|
||||
// This login mechanism uses the authorised key for authentication.
|
||||
func (t *TailscaleInContainer) Up(
|
||||
loginServer, authKey string,
|
||||
) error {
|
||||
@@ -265,12 +231,6 @@ func (t *TailscaleInContainer) Up(
|
||||
command = append(command, "--ssh")
|
||||
}
|
||||
|
||||
if len(t.withTags) > 0 {
|
||||
command = append(command,
|
||||
fmt.Sprintf(`--advertise-tags=%s`, strings.Join(t.withTags, ",")),
|
||||
)
|
||||
}
|
||||
|
||||
if _, _, err := t.Execute(command); err != nil {
|
||||
return fmt.Errorf("failed to join tailscale client: %w", err)
|
||||
}
|
||||
@@ -278,8 +238,6 @@ func (t *TailscaleInContainer) Up(
|
||||
return nil
|
||||
}
|
||||
|
||||
// Up runs the login routine on the given Tailscale instance.
|
||||
// This login mechanism uses web + command line flow for authentication.
|
||||
func (t *TailscaleInContainer) UpWithLoginURL(
|
||||
loginServer string,
|
||||
) (*url.URL, error) {
|
||||
@@ -312,7 +270,6 @@ func (t *TailscaleInContainer) UpWithLoginURL(
|
||||
return loginURL, nil
|
||||
}
|
||||
|
||||
// Logout runs the logout routine on the given Tailscale instance.
|
||||
func (t *TailscaleInContainer) Logout() error {
|
||||
_, _, err := t.Execute([]string{"tailscale", "logout"})
|
||||
if err != nil {
|
||||
@@ -322,7 +279,6 @@ func (t *TailscaleInContainer) Logout() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IPs returns the netip.Addr of the Tailscale instance.
|
||||
func (t *TailscaleInContainer) IPs() ([]netip.Addr, error) {
|
||||
if t.ips != nil && len(t.ips) != 0 {
|
||||
return t.ips, nil
|
||||
@@ -355,7 +311,6 @@ func (t *TailscaleInContainer) IPs() ([]netip.Addr, error) {
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
// Status returns the ipnstate.Status of the Tailscale instance.
|
||||
func (t *TailscaleInContainer) Status() (*ipnstate.Status, error) {
|
||||
command := []string{
|
||||
"tailscale",
|
||||
@@ -377,7 +332,6 @@ func (t *TailscaleInContainer) Status() (*ipnstate.Status, error) {
|
||||
return &status, err
|
||||
}
|
||||
|
||||
// FQDN returns the FQDN as a string of the Tailscale instance.
|
||||
func (t *TailscaleInContainer) FQDN() (string, error) {
|
||||
if t.fqdn != "" {
|
||||
return t.fqdn, nil
|
||||
@@ -391,8 +345,6 @@ func (t *TailscaleInContainer) FQDN() (string, error) {
|
||||
return status.Self.DNSName, nil
|
||||
}
|
||||
|
||||
// WaitForReady blocks until the Tailscale (tailscaled) instance is ready
|
||||
// to login or be used.
|
||||
func (t *TailscaleInContainer) WaitForReady() error {
|
||||
return t.pool.Retry(func() error {
|
||||
status, err := t.Status()
|
||||
@@ -408,7 +360,6 @@ func (t *TailscaleInContainer) WaitForReady() error {
|
||||
})
|
||||
}
|
||||
|
||||
// WaitForLogout blocks until the Tailscale instance has logged out.
|
||||
func (t *TailscaleInContainer) WaitForLogout() error {
|
||||
return t.pool.Retry(func() error {
|
||||
status, err := t.Status()
|
||||
@@ -424,8 +375,6 @@ func (t *TailscaleInContainer) WaitForLogout() error {
|
||||
})
|
||||
}
|
||||
|
||||
// WaitForPeers blocks until N number of peers is present in the
|
||||
// Peer list of the Tailscale instance.
|
||||
func (t *TailscaleInContainer) WaitForPeers(expected int) error {
|
||||
return t.pool.Retry(func() error {
|
||||
status, err := t.Status()
|
||||
@@ -441,65 +390,17 @@ func (t *TailscaleInContainer) WaitForPeers(expected int) error {
|
||||
})
|
||||
}
|
||||
|
||||
type (
|
||||
// PingOption repreent optional settings that can be given
|
||||
// to ping another host.
|
||||
PingOption = func(args *pingArgs)
|
||||
|
||||
pingArgs struct {
|
||||
timeout time.Duration
|
||||
count int
|
||||
direct bool
|
||||
}
|
||||
)
|
||||
|
||||
// WithPingTimeout sets the timeout for the ping command.
|
||||
func WithPingTimeout(timeout time.Duration) PingOption {
|
||||
return func(args *pingArgs) {
|
||||
args.timeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
// WithPingCount sets the count of pings to attempt.
|
||||
func WithPingCount(count int) PingOption {
|
||||
return func(args *pingArgs) {
|
||||
args.count = count
|
||||
}
|
||||
}
|
||||
|
||||
// WithPingUntilDirect decides if the ping should only succeed
|
||||
// if a direct connection is established or if successful
|
||||
// DERP ping is sufficient.
|
||||
func WithPingUntilDirect(direct bool) PingOption {
|
||||
return func(args *pingArgs) {
|
||||
args.direct = direct
|
||||
}
|
||||
}
|
||||
|
||||
// Ping executes the Tailscale ping command and pings a hostname
|
||||
// or IP. It accepts a series of PingOption.
|
||||
// TODO(kradalby): Make multiping, go routine magic.
|
||||
func (t *TailscaleInContainer) Ping(hostnameOrIP string, opts ...PingOption) error {
|
||||
args := pingArgs{
|
||||
timeout: time.Second,
|
||||
count: defaultPingCount,
|
||||
direct: true,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(&args)
|
||||
}
|
||||
|
||||
command := []string{
|
||||
"tailscale", "ping",
|
||||
fmt.Sprintf("--timeout=%s", args.timeout),
|
||||
fmt.Sprintf("--c=%d", args.count),
|
||||
fmt.Sprintf("--until-direct=%s", strconv.FormatBool(args.direct)),
|
||||
}
|
||||
|
||||
command = append(command, hostnameOrIP)
|
||||
|
||||
func (t *TailscaleInContainer) Ping(hostnameOrIP string) error {
|
||||
return t.pool.Retry(func() error {
|
||||
command := []string{
|
||||
"tailscale", "ping",
|
||||
"--timeout=1s",
|
||||
"--c=10",
|
||||
"--until-direct=true",
|
||||
hostnameOrIP,
|
||||
}
|
||||
|
||||
result, _, err := t.Execute(command)
|
||||
if err != nil {
|
||||
log.Printf(
|
||||
@@ -520,7 +421,6 @@ func (t *TailscaleInContainer) Ping(hostnameOrIP string, opts ...PingOption) err
|
||||
})
|
||||
}
|
||||
|
||||
// WriteFile save file inside the Tailscale container.
|
||||
func (t *TailscaleInContainer) WriteFile(path string, data []byte) error {
|
||||
return integrationutil.WriteFileToContainer(t.pool, t.container, path, data)
|
||||
}
|
||||
|
@@ -1,48 +0,0 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func pingAllHelper(t *testing.T, clients []TailscaleClient, addrs []string) int {
|
||||
t.Helper()
|
||||
success := 0
|
||||
|
||||
for _, client := range clients {
|
||||
for _, addr := range addrs {
|
||||
err := client.Ping(addr)
|
||||
if err != nil {
|
||||
t.Errorf("failed to ping %s from %s: %s", addr, client.Hostname(), err)
|
||||
} else {
|
||||
success++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return success
|
||||
}
|
||||
|
||||
// pingAllNegativeHelper is intended to have 1 or more nodes timeing out from the ping,
|
||||
// it counts failures instead of successes.
|
||||
// func pingAllNegativeHelper(t *testing.T, clients []TailscaleClient, addrs []string) int {
|
||||
// t.Helper()
|
||||
// failures := 0
|
||||
//
|
||||
// timeout := 100
|
||||
// count := 3
|
||||
//
|
||||
// for _, client := range clients {
|
||||
// for _, addr := range addrs {
|
||||
// err := client.Ping(
|
||||
// addr,
|
||||
// tsic.WithPingTimeout(time.Duration(timeout)*time.Millisecond),
|
||||
// tsic.WithPingCount(count),
|
||||
// )
|
||||
// if err != nil {
|
||||
// failures++
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return failures
|
||||
// }
|
@@ -24,6 +24,7 @@ type IntegrationCLITestSuite struct {
|
||||
pool dockertest.Pool
|
||||
network dockertest.Network
|
||||
headscale dockertest.Resource
|
||||
env []string
|
||||
}
|
||||
|
||||
func TestIntegrationCLITestSuite(t *testing.T) {
|
||||
|
@@ -9,6 +9,7 @@ import (
|
||||
"net/netip"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
@@ -31,8 +32,7 @@ var (
|
||||
tailscaleVersions = []string{
|
||||
"head",
|
||||
"unstable",
|
||||
"1.36.2",
|
||||
"1.34.2",
|
||||
"1.34.0",
|
||||
"1.32.3",
|
||||
"1.30.2",
|
||||
"1.28.0",
|
||||
@@ -47,6 +47,11 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
type TestUser struct {
|
||||
count int
|
||||
tailscales map[string]dockertest.Resource
|
||||
}
|
||||
|
||||
type ExecuteCommandConfig struct {
|
||||
timeout time.Duration
|
||||
}
|
||||
@@ -196,6 +201,38 @@ func getDockerBuildOptions(version string) *dockertest.BuildOptions {
|
||||
return tailscaleBuildOptions
|
||||
}
|
||||
|
||||
func getIPs(
|
||||
tailscales map[string]dockertest.Resource,
|
||||
) (map[string][]netip.Addr, error) {
|
||||
ips := make(map[string][]netip.Addr)
|
||||
for hostname, tailscale := range tailscales {
|
||||
command := []string{"tailscale", "ip"}
|
||||
|
||||
result, _, err := ExecuteCommand(
|
||||
&tailscale,
|
||||
command,
|
||||
[]string{},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, address := range strings.Split(result, "\n") {
|
||||
address = strings.TrimSuffix(address, "\n")
|
||||
if len(address) < 1 {
|
||||
continue
|
||||
}
|
||||
ip, err := netip.ParseAddr(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ips[hostname] = append(ips[hostname], ip)
|
||||
}
|
||||
}
|
||||
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
func getDNSNames(
|
||||
headscale *dockertest.Resource,
|
||||
) ([]string, error) {
|
||||
@@ -229,6 +266,43 @@ func getDNSNames(
|
||||
return hostnames, nil
|
||||
}
|
||||
|
||||
func getMagicFQDN(
|
||||
headscale *dockertest.Resource,
|
||||
) ([]string, error) {
|
||||
listAllResult, _, err := ExecuteCommand(
|
||||
headscale,
|
||||
[]string{
|
||||
"headscale",
|
||||
"nodes",
|
||||
"list",
|
||||
"--output",
|
||||
"json",
|
||||
},
|
||||
[]string{},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var listAll []v1.Machine
|
||||
err = json.Unmarshal([]byte(listAllResult), &listAll)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hostnames := make([]string, len(listAll))
|
||||
|
||||
for index := range listAll {
|
||||
hostnames[index] = fmt.Sprintf(
|
||||
"%s.%s.headscale.net",
|
||||
listAll[index].GetGivenName(),
|
||||
listAll[index].GetUser().GetName(),
|
||||
)
|
||||
}
|
||||
|
||||
return hostnames, nil
|
||||
}
|
||||
|
||||
func GetEnvStr(key string) (string, error) {
|
||||
v := os.Getenv(key)
|
||||
if v == "" {
|
||||
|
@@ -382,7 +382,7 @@ func (s *IntegrationDERPTestSuite) saveLog(
|
||||
|
||||
err = os.WriteFile(
|
||||
path.Join(basePath, resource.Container.Name+".stdout.log"),
|
||||
stderr.Bytes(),
|
||||
[]byte(stdout.String()),
|
||||
0o644,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -391,7 +391,7 @@ func (s *IntegrationDERPTestSuite) saveLog(
|
||||
|
||||
err = os.WriteFile(
|
||||
path.Join(basePath, resource.Container.Name+".stderr.log"),
|
||||
stderr.Bytes(),
|
||||
[]byte(stdout.String()),
|
||||
0o644,
|
||||
)
|
||||
if err != nil {
|
||||
|
47
machine.go
47
machine.go
@@ -12,7 +12,6 @@ import (
|
||||
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/samber/lo"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
"gorm.io/gorm"
|
||||
"tailscale.com/tailcfg"
|
||||
@@ -739,39 +738,30 @@ func (h *Headscale) toNode(
|
||||
|
||||
online := machine.isOnline()
|
||||
|
||||
tags, _ := getTags(h.aclPolicy, machine, h.cfg.OIDC.StripEmaildomain)
|
||||
tags = lo.Uniq(append(tags, machine.ForcedTags...))
|
||||
|
||||
node := tailcfg.Node{
|
||||
ID: tailcfg.NodeID(machine.ID), // this is the actual ID
|
||||
StableID: tailcfg.StableNodeID(
|
||||
strconv.FormatUint(machine.ID, Base10),
|
||||
), // in headscale, unlike tailcontrol server, IDs are permanent
|
||||
Name: hostname,
|
||||
|
||||
User: tailcfg.UserID(machine.UserID),
|
||||
|
||||
Key: nodeKey,
|
||||
KeyExpiry: keyExpiry,
|
||||
|
||||
Machine: machineKey,
|
||||
DiscoKey: discoKey,
|
||||
Addresses: addrs,
|
||||
AllowedIPs: allowedIPs,
|
||||
Endpoints: machine.Endpoints,
|
||||
DERP: derp,
|
||||
Hostinfo: hostInfo.View(),
|
||||
Created: machine.CreatedAt,
|
||||
|
||||
Tags: tags,
|
||||
|
||||
Name: hostname,
|
||||
User: tailcfg.UserID(machine.UserID),
|
||||
Key: nodeKey,
|
||||
KeyExpiry: keyExpiry,
|
||||
Machine: machineKey,
|
||||
DiscoKey: discoKey,
|
||||
Addresses: addrs,
|
||||
AllowedIPs: allowedIPs,
|
||||
PrimaryRoutes: primaryPrefixes,
|
||||
Endpoints: machine.Endpoints,
|
||||
DERP: derp,
|
||||
|
||||
Online: &online,
|
||||
Hostinfo: hostInfo.View(),
|
||||
Created: machine.CreatedAt,
|
||||
LastSeen: machine.LastSeen,
|
||||
|
||||
LastSeen: machine.LastSeen,
|
||||
Online: &online,
|
||||
KeepAlive: true,
|
||||
MachineAuthorized: !machine.isExpired(),
|
||||
|
||||
Capabilities: []string{
|
||||
tailcfg.CapabilityFileSharing,
|
||||
tailcfg.CapabilityAdmin,
|
||||
@@ -1122,8 +1112,7 @@ func (h *Headscale) EnableAutoApprovedRoutes(machine *Machine) error {
|
||||
routes := []Route{}
|
||||
err := h.db.
|
||||
Preload("Machine").
|
||||
Where("machine_id = ? AND advertised = true AND enabled = false", machine.ID).
|
||||
Find(&routes).Error
|
||||
Where("machine_id = ? AND advertised = true AND enabled = false", machine.ID).Find(&routes).Error
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
log.Error().
|
||||
Caller().
|
||||
@@ -1137,9 +1126,7 @@ func (h *Headscale) EnableAutoApprovedRoutes(machine *Machine) error {
|
||||
approvedRoutes := []Route{}
|
||||
|
||||
for _, advertisedRoute := range routes {
|
||||
routeApprovers, err := h.aclPolicy.AutoApprovers.GetRouteApprovers(
|
||||
netip.Prefix(advertisedRoute.Prefix),
|
||||
)
|
||||
routeApprovers, err := h.aclPolicy.AutoApprovers.GetRouteApprovers(netip.Prefix(advertisedRoute.Prefix))
|
||||
if err != nil {
|
||||
log.Err(err).
|
||||
Str("advertisedRoute", advertisedRoute.String()).
|
||||
|
@@ -4,27 +4,11 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>headscale - Apple</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 40px auto;
|
||||
max-width: 800px;
|
||||
line-height: 1.5;
|
||||
font-size: 16px;
|
||||
color: #444;
|
||||
padding: 0 10px;
|
||||
font-family: Sans-serif;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
line-height: 1.2;
|
||||
}
|
||||
</style>
|
||||
<title>Document</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>headscale: Apple configuration</h1>
|
||||
<h1>headscale</h1>
|
||||
<h2>Recent Tailscale versions (1.34.0 and higher)</h2>
|
||||
<p>
|
||||
Tailscale added Fast User Switching in version 1.34 and you can now use
|
||||
|
@@ -4,27 +4,11 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>headscale - Windows</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 40px auto;
|
||||
max-width: 800px;
|
||||
line-height: 1.5;
|
||||
font-size: 16px;
|
||||
color: #444;
|
||||
padding: 0 10px;
|
||||
font-family: Sans-serif;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
line-height: 1.2;
|
||||
}
|
||||
</style>
|
||||
<title>Document</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>headscale: Windows configuration</h1>
|
||||
<h1>headscale</h1>
|
||||
<h2>Recent Tailscale versions (1.34.0 and higher)</h2>
|
||||
<p>
|
||||
Tailscale added Fast User Switching in version 1.34 and you can now use
|
||||
|
Reference in New Issue
Block a user