Compare commits

..

1 Commits

Author SHA1 Message Date
Juan Font
b36df48d78 Working on migrate DERP tests to scenarios 2023-04-07 22:06:44 +00:00
57 changed files with 726 additions and 1502 deletions

View File

@@ -1,45 +0,0 @@
name: Build documentation
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Install python
uses: actions/setup-python@v4
with:
python-version: 3.x
- name: Setup cache
uses: actions/cache@v2
with:
key: ${{ github.ref }}
path: .cache
- name: Setup dependencies
run: pip install mkdocs-material pillow cairosvg mkdocs-minify-plugin
- name: Build docs
run: mkdocs build --strict
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
with:
path: ./site
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1

View File

@@ -19,6 +19,6 @@ jobs:
- uses: cachix/install-nix-action@v16
- name: Run goreleaser
run: nix develop --command -- goreleaser release --clean
run: nix develop --command -- goreleaser release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -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 - TestACLDevice1CanAccessDevice2
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 "^TestACLDevice1CanAccessDevice2$"
- uses: actions/upload-artifact@v3
if: always() && steps.changed-files.outputs.any_changed == 'true'
with:
name: logs
path: "control_logs/*.log"

View File

@@ -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 - TestACLNamedHostsCanReach
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 "^TestACLNamedHostsCanReach$"
- uses: actions/upload-artifact@v3
if: always() && steps.changed-files.outputs.any_changed == 'true'
with:
name: logs
path: "control_logs/*.log"

View File

@@ -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 - TestACLNamedHostsCanReachBySubnet
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 "^TestACLNamedHostsCanReachBySubnet$"
- uses: actions/upload-artifact@v3
if: always() && steps.changed-files.outputs.any_changed == 'true'
with:
name: logs
path: "control_logs/*.log"

8
.gitignore vendored
View File

@@ -1,5 +1,3 @@
ignored/
# Binaries for programs and plugins
*.exe
*.exe~
@@ -14,7 +12,7 @@ ignored/
*.out
# Dependency directories (remove the comment below to include it)
vendor/
# vendor/
dist/
/headscale
@@ -37,7 +35,3 @@ result
.direnv/
integration_test/etc/config.dump.yaml
# MkDocs
.cache
/site

View File

@@ -2,7 +2,6 @@
before:
hooks:
- go mod tidy -compat=1.20
- go mod vendor
release:
prerelease: auto
@@ -32,16 +31,19 @@ builds:
archives:
- id: golang-cross
name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
builds:
- darwin_amd64
- darwin_arm64
- freebsd_amd64
- linux_386
- linux_amd64
- linux_arm64
- linux_arm_5
- linux_arm_6
- linux_arm_7
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
format: binary
source:
enabled: true
name_template: "{{ .ProjectName }}_{{ .Version }}"
format: tar.gz
files:
- "vendor/"
nfpms:
# Configure nFPM for .deb and .rpm releases
#
@@ -63,7 +65,7 @@ nfpms:
bindir: /usr/bin
formats:
- deb
# - rpm
- rpm
contents:
- src: ./config-example.yaml
dst: /etc/headscale/config.yaml

View File

@@ -1,18 +1,12 @@
# CHANGELOG
## 0.23.0 (2023-XX-XX)
## 0.22.0 (2023-XX-XX)
### Changes
## 0.22.0 (2023-04-20)
### Changes
- Add `.deb` packages to release process [#1297](https://github.com/juanfont/headscale/pull/1297)
- Update and simplify the documentation to use new `.deb` packages [#1349](https://github.com/juanfont/headscale/pull/1349)
- Add `.deb` and `.rpm` packages to release process [#1297](https://github.com/juanfont/headscale/pull/1297)
- Add 32-bit Arm platforms to release process [#1297](https://github.com/juanfont/headscale/pull/1297)
- Fix longstanding bug that would prevent "\*" from working properly in ACLs (issue [#699](https://github.com/juanfont/headscale/issues/699)) [#1279](https://github.com/juanfont/headscale/pull/1279)
- Fix issue where IPv6 could not be used in, or while using ACLs (part of [#809](https://github.com/juanfont/headscale/issues/809)) [#1339](https://github.com/juanfont/headscale/pull/1339)
- Target Go 1.20 and Tailscale 1.38 for Headscale [#1323](https://github.com/juanfont/headscale/pull/1323)
## 0.21.0 (2023-03-20)

View File

@@ -36,7 +36,7 @@ test_integration_cli:
-v ~/.cache/hs-integration-go:/go \
-v $$PWD:$$PWD -w $$PWD \
-v /var/run/docker.sock:/var/run/docker.sock golang:1 \
go run gotest.tools/gotestsum@latest -- $(TAGS) -failfast -timeout 30m -count=1 -run IntegrationCLI ./...
go test $(TAGS) -failfast -timeout 30m -count=1 -run IntegrationCLI ./...
test_integration_derp:
docker network rm $$(docker network ls --filter name=headscale --quiet) || true
@@ -46,7 +46,7 @@ test_integration_derp:
-v ~/.cache/hs-integration-go:/go \
-v $$PWD:$$PWD -w $$PWD \
-v /var/run/docker.sock:/var/run/docker.sock golang:1 \
go run gotest.tools/gotestsum@latest -- $(TAGS) -failfast -timeout 30m -count=1 -run IntegrationDERP ./...
go test $(TAGS) -failfast -timeout 30m -count=1 -run IntegrationDERP ./...
test_integration_v2_general:
docker run \
@@ -56,7 +56,13 @@ test_integration_v2_general:
-v $$PWD:$$PWD -w $$PWD/integration \
-v /var/run/docker.sock:/var/run/docker.sock \
golang:1 \
go run gotest.tools/gotestsum@latest -- $(TAGS) -failfast ./... -timeout 120m -parallel 8
go test $(TAGS) -failfast ./... -timeout 120m -parallel 8
coverprofile_func:
go tool cover -func=coverage.out
coverprofile_html:
go tool cover -html=coverage.out
lint:
golangci-lint run --fix --timeout 10m
@@ -74,4 +80,11 @@ compress: build
generate:
rm -rf gen
buf generate proto
go run github.com/bufbuild/buf/cmd/buf generate proto
install-protobuf-plugins:
go install \
github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway \
github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 \
google.golang.org/protobuf/cmd/protoc-gen-go \
google.golang.org/grpc/cmd/protoc-gen-go-grpc

177
README.md
View File

@@ -188,6 +188,13 @@ make build
<sub style="font-size:14px"><b>Juan Font</b></sub>
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/restanrm>
<img src=https://avatars.githubusercontent.com/u/4344371?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Adrien Raffin-Caboisse/>
<br />
<sub style="font-size:14px"><b>Adrien Raffin-Caboisse</b></sub>
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/cure>
<img src=https://avatars.githubusercontent.com/u/149135?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Ward Vandewege/>
@@ -209,6 +216,8 @@ make build
<sub style="font-size:14px"><b>Benjamin Roberts</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/reynico>
<img src=https://avatars.githubusercontent.com/u/715768?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Nico/>
@@ -216,8 +225,6 @@ make build
<sub style="font-size:14px"><b>Nico</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/evenh>
<img src=https://avatars.githubusercontent.com/u/2701536?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Even Holthe/>
@@ -253,13 +260,6 @@ make build
<sub style="font-size:14px"><b>unreality</b></sub>
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/mpldr>
<img src=https://avatars.githubusercontent.com/u/33086936?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Moritz Poldrack/>
<br />
<sub style="font-size:14px"><b>Moritz Poldrack</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
@@ -270,10 +270,10 @@ make build
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/restanrm>
<img src=https://avatars.githubusercontent.com/u/4344371?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Adrien Raffin-Caboisse/>
<a href=https://github.com/mpldr>
<img src=https://avatars.githubusercontent.com/u/33086936?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Moritz Poldrack/>
<br />
<sub style="font-size:14px"><b>Adrien Raffin-Caboisse</b></sub>
<sub style="font-size:14px"><b>Moritz Poldrack</b></sub>
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
@@ -350,13 +350,6 @@ make build
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/majst01>
<img src=https://avatars.githubusercontent.com/u/410110?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Stefan Majer/>
<br />
<sub style="font-size:14px"><b>Stefan Majer</b></sub>
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/fdelucchijr>
<img src=https://avatars.githubusercontent.com/u/69133647?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Fernando De Lucchi/>
@@ -371,6 +364,13 @@ make build
<sub style="font-size:14px"><b>Orville Q. Song</b></sub>
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/majst01>
<img src=https://avatars.githubusercontent.com/u/410110?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Stefan Majer/>
<br />
<sub style="font-size:14px"><b>Stefan Majer</b></sub>
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/hdhoang>
<img src=https://avatars.githubusercontent.com/u/12537?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=hdhoang/>
@@ -385,15 +385,6 @@ make build
<sub style="font-size:14px"><b>bravechamp</b></sub>
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/bravechamp>
<img src=https://avatars.githubusercontent.com/u/48980452?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=bravechamp/>
<br />
<sub style="font-size:14px"><b>bravechamp</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/deonthomasgy>
<img src=https://avatars.githubusercontent.com/u/150036?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Deon Thomas/>
@@ -401,6 +392,8 @@ make build
<sub style="font-size:14px"><b>Deon Thomas</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/madjam002>
<img src=https://avatars.githubusercontent.com/u/679137?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Jamie Greeff/>
@@ -436,8 +429,6 @@ make build
<sub style="font-size:14px"><b>Paul Tötterman</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/samson4649>
<img src=https://avatars.githubusercontent.com/u/12725953?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Samuel Lock/>
@@ -445,6 +436,8 @@ make build
<sub style="font-size:14px"><b>Samuel Lock</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/kevin1sMe>
<img src=https://avatars.githubusercontent.com/u/6886076?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=kevinlin/>
@@ -480,8 +473,6 @@ make build
<sub style="font-size:14px"><b>dbevacqua</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/joshuataylor>
<img src=https://avatars.githubusercontent.com/u/225131?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Josh Taylor/>
@@ -489,6 +480,8 @@ make build
<sub style="font-size:14px"><b>Josh Taylor</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/CNLHC>
<img src=https://avatars.githubusercontent.com/u/21005146?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=LiuHanCheng/>
@@ -524,8 +517,6 @@ make build
<sub style="font-size:14px"><b>Steven Honson</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/ratsclub>
<img src=https://avatars.githubusercontent.com/u/25647735?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Victor Freire/>
@@ -533,6 +524,15 @@ make build
<sub style="font-size:14px"><b>Victor Freire</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/lachy2849>
<img src=https://avatars.githubusercontent.com/u/98844035?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=lachy2849/>
<br />
<sub style="font-size:14px"><b>lachy2849</b></sub>
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/t56k>
<img src=https://avatars.githubusercontent.com/u/12165422?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=thomas/>
@@ -540,13 +540,6 @@ make build
<sub style="font-size:14px"><b>thomas</b></sub>
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/linsomniac>
<img src=https://avatars.githubusercontent.com/u/466380?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Sean Reifschneider/>
<br />
<sub style="font-size:14px"><b>Sean Reifschneider</b></sub>
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/aberoham>
<img src=https://avatars.githubusercontent.com/u/586805?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Abraham Ingersoll/>
@@ -568,8 +561,6 @@ make build
<sub style="font-size:14px"><b>Andrei Pechkurov</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/theryecatcher>
<img src=https://avatars.githubusercontent.com/u/16442416?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Anoop Sundaresh/>
@@ -577,6 +568,8 @@ make build
<sub style="font-size:14px"><b>Anoop Sundaresh</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/apognu>
<img src=https://avatars.githubusercontent.com/u/3017182?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Antoine POPINEAU/>
@@ -584,13 +577,6 @@ make build
<sub style="font-size:14px"><b>Antoine POPINEAU</b></sub>
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/tony1661>
<img src=https://avatars.githubusercontent.com/u/5287266?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Antonio Fernandez/>
<br />
<sub style="font-size:14px"><b>Antonio Fernandez</b></sub>
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/aofei>
<img src=https://avatars.githubusercontent.com/u/5037285?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Aofei Sheng/>
@@ -605,6 +591,13 @@ make build
<sub style="font-size:14px"><b>Arnar</b></sub>
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/awoimbee>
<img src=https://avatars.githubusercontent.com/u/22431493?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Arthur Woimbée/>
<br />
<sub style="font-size:14px"><b>Arthur Woimbée</b></sub>
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/avirut>
<img src=https://avatars.githubusercontent.com/u/27095602?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Avirut Mehta/>
@@ -612,8 +605,6 @@ make build
<sub style="font-size:14px"><b>Avirut Mehta</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/stensonb>
<img src=https://avatars.githubusercontent.com/u/933389?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Bryan Stenson/>
@@ -621,6 +612,8 @@ make build
<sub style="font-size:14px"><b>Bryan Stenson</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/yangchuansheng>
<img src=https://avatars.githubusercontent.com/u/15308462?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt= Carson Yang/>
@@ -656,15 +649,6 @@ make build
<sub style="font-size:14px"><b>Felix Yan</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/gabe565>
<img src=https://avatars.githubusercontent.com/u/7717888?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Gabe Cook/>
<br />
<sub style="font-size:14px"><b>Gabe Cook</b></sub>
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/JJGadgets>
<img src=https://avatars.githubusercontent.com/u/5709019?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=JJGadgets/>
@@ -672,6 +656,8 @@ make build
<sub style="font-size:14px"><b>JJGadgets</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/hrtkpf>
<img src=https://avatars.githubusercontent.com/u/42646788?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=hrtkpf/>
@@ -700,8 +686,6 @@ make build
<sub style="font-size:14px"><b>John Axel Eriksson</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/ShadowJonathan>
<img src=https://avatars.githubusercontent.com/u/22740616?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Jonathan de Jong/>
@@ -709,20 +693,6 @@ make build
<sub style="font-size:14px"><b>Jonathan de Jong</b></sub>
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/JulienFloris>
<img src=https://avatars.githubusercontent.com/u/20380255?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Julien Zweverink/>
<br />
<sub style="font-size:14px"><b>Julien Zweverink</b></sub>
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/win-t>
<img src=https://avatars.githubusercontent.com/u/1589120?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Kurnia D Win/>
<br />
<sub style="font-size:14px"><b>Kurnia D Win</b></sub>
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/foxtrot>
<img src=https://avatars.githubusercontent.com/u/4153572?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Marc/>
@@ -730,6 +700,8 @@ make build
<sub style="font-size:14px"><b>Marc</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/magf>
<img src=https://avatars.githubusercontent.com/u/11992737?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Maxim Gajdaj/>
@@ -744,8 +716,6 @@ make build
<sub style="font-size:14px"><b>Michael Savage</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/piec>
<img src=https://avatars.githubusercontent.com/u/781471?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Pierre Carru/>
@@ -774,6 +744,8 @@ make build
<sub style="font-size:14px"><b>rcursaru</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/renovate-bot>
<img src=https://avatars.githubusercontent.com/u/25180681?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Mend Renovate/>
@@ -788,8 +760,13 @@ make build
<sub style="font-size:14px"><b>Ryan Fowler</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/linsomniac>
<img src=https://avatars.githubusercontent.com/u/466380?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Sean Reifschneider/>
<br />
<sub style="font-size:14px"><b>Sean Reifschneider</b></sub>
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/shaananc>
<img src=https://avatars.githubusercontent.com/u/2287839?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Shaanan Cohney/>
@@ -811,6 +788,8 @@ make build
<sub style="font-size:14px"><b>sophware</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/m-tanner-dev0>
<img src=https://avatars.githubusercontent.com/u/97977342?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Tanner/>
@@ -825,15 +804,6 @@ make build
<sub style="font-size:14px"><b>Teteros</b></sub>
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/Teteros>
<img src=https://avatars.githubusercontent.com/u/5067989?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Teteros/>
<br />
<sub style="font-size:14px"><b>Teteros</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/gitter-badger>
<img src=https://avatars.githubusercontent.com/u/8518239?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=The Gitter Badger/>
@@ -862,6 +832,8 @@ make build
<sub style="font-size:14px"><b>Tjerk Woudsma</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/y0ngb1n>
<img src=https://avatars.githubusercontent.com/u/25719408?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Yang Bin/>
@@ -876,8 +848,6 @@ make build
<sub style="font-size:14px"><b>Yujie Xia</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/newellz2>
<img src=https://avatars.githubusercontent.com/u/52436542?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Zachary Newell/>
@@ -906,6 +876,8 @@ make build
<sub style="font-size:14px"><b>Ziyuan Han</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/caelansar>
<img src=https://avatars.githubusercontent.com/u/31852257?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=caelansar/>
@@ -920,8 +892,6 @@ make build
<sub style="font-size:14px"><b>derelm</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/dnaq>
<img src=https://avatars.githubusercontent.com/u/1299717?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=dnaq/>
@@ -950,6 +920,8 @@ make build
<sub style="font-size:14px"><b>jimyag</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/magichuihui>
<img src=https://avatars.githubusercontent.com/u/10866198?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=suhelen/>
@@ -964,8 +936,6 @@ make build
<sub style="font-size:14px"><b>sharkonet</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/ma6174>
<img src=https://avatars.githubusercontent.com/u/1449133?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=ma6174/>
@@ -980,18 +950,11 @@ make build
<sub style="font-size:14px"><b>manju-rn</b></sub>
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/nicholas-yap>
<img src=https://avatars.githubusercontent.com/u/38109533?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=nicholas-yap/>
<br />
<sub style="font-size:14px"><b>nicholas-yap</b></sub>
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/pernila>
<img src=https://avatars.githubusercontent.com/u/12460060?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Tommi Pernila/>
<img src=https://avatars.githubusercontent.com/u/12460060?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=pernila/>
<br />
<sub style="font-size:14px"><b>Tommi Pernila</b></sub>
<sub style="font-size:14px"><b>pernila</b></sub>
</a>
</td>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
@@ -1001,6 +964,8 @@ make build
<sub style="font-size:14px"><b>phpmalik</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/Wakeful-Cloud>
<img src=https://avatars.githubusercontent.com/u/38930607?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Wakeful-Cloud/>
@@ -1008,8 +973,6 @@ make build
<sub style="font-size:14px"><b>Wakeful-Cloud</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
<a href=https://github.com/xpzouying>
<img src=https://avatars.githubusercontent.com/u/3946563?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=zy/>

72
acls.go
View File

@@ -13,7 +13,6 @@ import (
"time"
"github.com/rs/zerolog/log"
"github.com/samber/lo"
"github.com/tailscale/hujson"
"go4.org/netipx"
"gopkg.in/yaml.v3"
@@ -408,40 +407,15 @@ func generateACLPolicyDest(
needsWildcard bool,
stripEmaildomain bool,
) ([]tailcfg.NetPortRange, error) {
var tokens []string
log.Trace().Str("destination", dest).Msg("generating policy destination")
// Check if there is a IPv4/6:Port combination, IPv6 has more than
// three ":".
tokens = strings.Split(dest, ":")
tokens := strings.Split(dest, ":")
if len(tokens) < expectedTokenItems || len(tokens) > 3 {
port := tokens[len(tokens)-1]
maybeIPv6Str := strings.TrimSuffix(dest, ":"+port)
log.Trace().Str("maybeIPv6Str", maybeIPv6Str).Msg("")
if maybeIPv6, err := netip.ParseAddr(maybeIPv6Str); err != nil && !maybeIPv6.Is6() {
log.Trace().Err(err).Msg("trying to parse as IPv6")
return nil, fmt.Errorf(
"failed to parse destination, tokens %v: %w",
tokens,
errInvalidPortFormat,
)
} else {
tokens = []string{maybeIPv6Str, port}
}
return nil, errInvalidPortFormat
}
log.Trace().Strs("tokens", tokens).Msg("generating policy destination")
var alias string
// We can have here stuff like:
// git-server:*
// 192.168.1.0/24:22
// fd7a:115c:a1e0::2:22
// fd7a:115c:a1e0::2/128:22
// tag:montreal-webserver:80,443
// tag:api-server:443
// example-host-1:*
@@ -534,11 +508,9 @@ func parseProtocol(protocol string) ([]int, bool, error) {
// - a group
// - a tag
// - a host
// - an ip
// - a cidr
// and transform these in IPAddresses.
func expandAlias(
machines Machines,
machines []Machine,
aclPolicy ACLPolicy,
alias string,
stripEmailDomain bool,
@@ -620,40 +592,19 @@ func expandAlias(
// if alias is an host
if h, ok := aclPolicy.Hosts[alias]; ok {
log.Trace().Str("host", h.String()).Msg("expandAlias got hosts entry")
return expandAlias(machines, aclPolicy, h.String(), stripEmailDomain)
return []string{h.String()}, nil
}
// if alias is an IP
if ip, err := netip.ParseAddr(alias); err == nil {
log.Trace().Str("ip", ip.String()).Msg("expandAlias got ip")
ips := []string{ip.String()}
matches := machines.FilterByIP(ip)
for _, machine := range matches {
ips = append(ips, machine.IPAddresses.ToStringSlice()...)
}
return lo.Uniq(ips), nil
ip, err := netip.ParseAddr(alias)
if err == nil {
return []string{ip.String()}, nil
}
if cidr, err := netip.ParsePrefix(alias); err == nil {
log.Trace().Str("cidr", cidr.String()).Msg("expandAlias got cidr")
val := []string{cidr.String()}
// This is suboptimal and quite expensive, but if we only add the cidr, we will miss all the relevant IPv6
// addresses for the hosts that belong to tailscale. This doesnt really affect stuff like subnet routers.
for _, machine := range machines {
for _, ip := range machine.IPAddresses {
// log.Trace().
// Msgf("checking if machine ip (%s) is part of cidr (%s): %v, is single ip cidr (%v), addr: %s", ip.String(), cidr.String(), cidr.Contains(ip), cidr.IsSingleIP(), cidr.Addr().String())
if cidr.Contains(ip) {
val = append(val, machine.IPAddresses.ToStringSlice()...)
}
}
}
return lo.Uniq(val), nil
// if alias is an CIDR
cidr, err := netip.ParsePrefix(alias)
if err == nil {
return []string{cidr.String()}, nil
}
log.Warn().Msgf("No IPs found with the alias %v", alias)
@@ -715,7 +666,6 @@ func expandPorts(portsStr string, needsWildcard bool) (*[]tailcfg.PortRange, err
ports := []tailcfg.PortRange{}
for _, portStr := range strings.Split(portsStr, ",") {
log.Trace().Msgf("parsing portstring: %s", portStr)
rang := strings.Split(portStr, "-")
switch len(rang) {
case 1:

View File

@@ -1026,7 +1026,22 @@ func Test_expandAlias(t *testing.T) {
wantErr: false,
},
{
name: "simple host by ip passed through",
name: "private network",
args: args{
alias: "homeNetwork",
machines: []Machine{},
aclPolicy: ACLPolicy{
Hosts: Hosts{
"homeNetwork": netip.MustParsePrefix("192.168.1.0/24"),
},
},
stripEmailDomain: true,
},
want: []string{"192.168.1.0/24"},
wantErr: false,
},
{
name: "simple host by ip",
args: args{
alias: "10.0.0.1",
machines: []Machine{},
@@ -1036,62 +1051,6 @@ func Test_expandAlias(t *testing.T) {
want: []string{"10.0.0.1"},
wantErr: false,
},
{
name: "simple host by ipv4 single ipv4",
args: args{
alias: "10.0.0.1",
machines: []Machine{
{
IPAddresses: MachineAddresses{
netip.MustParseAddr("10.0.0.1"),
},
User: User{Name: "mickael"},
},
},
aclPolicy: ACLPolicy{},
stripEmailDomain: true,
},
want: []string{"10.0.0.1"},
wantErr: false,
},
{
name: "simple host by ipv4 single dual stack",
args: args{
alias: "10.0.0.1",
machines: []Machine{
{
IPAddresses: MachineAddresses{
netip.MustParseAddr("10.0.0.1"),
netip.MustParseAddr("fd7a:115c:a1e0:ab12:4843:2222:6273:2222"),
},
User: User{Name: "mickael"},
},
},
aclPolicy: ACLPolicy{},
stripEmailDomain: true,
},
want: []string{"10.0.0.1", "fd7a:115c:a1e0:ab12:4843:2222:6273:2222"},
wantErr: false,
},
{
name: "simple host by ipv6 single dual stack",
args: args{
alias: "fd7a:115c:a1e0:ab12:4843:2222:6273:2222",
machines: []Machine{
{
IPAddresses: MachineAddresses{
netip.MustParseAddr("10.0.0.1"),
netip.MustParseAddr("fd7a:115c:a1e0:ab12:4843:2222:6273:2222"),
},
User: User{Name: "mickael"},
},
},
aclPolicy: ACLPolicy{},
stripEmailDomain: true,
},
want: []string{"fd7a:115c:a1e0:ab12:4843:2222:6273:2222", "10.0.0.1"},
wantErr: false,
},
{
name: "simple host by hostname alias",
args: args{
@@ -1107,21 +1066,6 @@ func Test_expandAlias(t *testing.T) {
want: []string{"10.0.0.132/32"},
wantErr: false,
},
{
name: "private network",
args: args{
alias: "homeNetwork",
machines: []Machine{},
aclPolicy: ACLPolicy{
Hosts: Hosts{
"homeNetwork": netip.MustParsePrefix("192.168.1.0/24"),
},
},
stripEmailDomain: true,
},
want: []string{"192.168.1.0/24"},
wantErr: false,
},
{
name: "simple CIDR",
args: args{

56
docs/README.md Normal file
View File

@@ -0,0 +1,56 @@
# headscale documentation
This page contains the official and community contributed documentation for `headscale`.
If you are having trouble with following the documentation or get unexpected results,
please ask on [Discord](https://discord.gg/c84AZQhmpx) instead of opening an Issue.
## Official documentation
### How-to
- [Running headscale on Linux](running-headscale-linux.md)
- [Control headscale remotely](remote-cli.md)
- [Using a Windows client with headscale](windows-client.md)
- [Configuring OIDC](oidc.md)
### References
- [Configuration](../config-example.yaml)
- [Glossary](glossary.md)
- [TLS](tls.md)
## Community documentation
Community documentation is not actively maintained by the headscale authors and is
written by community members. It is _not_ verified by `headscale` developers.
**It might be outdated and it might miss necessary steps**.
- [Running headscale in a container](running-headscale-container.md)
- [Running headscale on OpenBSD](running-headscale-openbsd.md)
- [Running headscale behind a reverse proxy](reverse-proxy.md)
- [Set Custom DNS records](dns-records.md)
## Misc
### Policy ACLs
Headscale implements the same policy ACLs as Tailscale.com, adapted to the self-hosted environment.
For instance, instead of referring to users when defining groups you must
use users (which are the equivalent to user/logins in Tailscale.com).
Please check https://tailscale.com/kb/1018/acls/, and `./tests/acls/` in this repo for working examples.
When using ACL's the User borders are no longer applied. All machines
whichever the User have the ability to communicate with other hosts as
long as the ACL's permits this exchange.
The [ACLs](acls.md) document should help understand a fictional case of setting
up ACLs in a small company. All concepts presented in this document could be
applied outside of business oriented usage.
### Apple devices
An endpoint with information on how to connect your Apple devices (currently macOS only) is available at `/apple` on your running instance.

View File

@@ -1,15 +1,4 @@
Headscale implements the same policy ACLs as Tailscale.com, adapted to the self-hosted environment.
For instance, instead of referring to users when defining groups you must
use users (which are the equivalent to user/logins in Tailscale.com).
Please check https://tailscale.com/kb/1018/acls/, and `./tests/acls/` in this repo for working examples.
When using ACL's the User borders are no longer applied. All machines
whichever the User have the ability to communicate with other hosts as
long as the ACL's permits this exchange.
## ACLs use case example
# ACLs use case example
Let's build an example use case for a small business (It may be the place where
ACL's are the most useful).

View File

@@ -1,12 +1,5 @@
# Setting custom DNS records
!!! warning "Community documentation"
This page is not actively maintained by the headscale authors and is
written by community members. It is _not_ verified by `headscale` developers.
**It might be outdated and it might miss necessary steps**.
## Goal
This documentation has the goal of showing how a user can set custom DNS records with `headscale`s magic dns.

View File

@@ -12,11 +12,6 @@ Ensure that the installed version is at least 1.38.1, as that is the first relea
## Configuring the headscale URL
!!! info "Apple devices"
An endpoint with information on how to connect your Apple devices
(currently macOS only) is available at `/apple` on your running instance.
Ensure that the tailscale app is logged out before proceeding.
Go to iOS settings, scroll down past game center and tv provider to the tailscale app and select it. The headscale URL can be entered into the _"ALTERNATE COORDINATION SERVER URL"_ box.

View File

@@ -1,12 +0,0 @@
---
hide:
- navigation
- toc
---
# headscale documentation
This site contains the official and community contributed documentation for `headscale`.
If you are having trouble with following the documentation or get unexpected results,
please ask on [Discord](https://discord.gg/c84AZQhmpx) instead of opening an Issue.

View File

@@ -64,7 +64,6 @@ summary() {
echo ""
echo " Please follow the next steps to start the software:"
echo ""
echo " sudo systemctl enable headscale"
echo " sudo systemctl start headscale"
echo ""
echo " Configuration settings can be adjusted here:"

View File

@@ -1,12 +1,5 @@
# Running headscale behind a reverse proxy
!!! warning "Community documentation"
This page is not actively maintained by the headscale authors and is
written by community members. It is _not_ verified by `headscale` developers.
**It might be outdated and it might miss necessary steps**.
Running headscale behind a reverse proxy is useful when running multiple applications on the same server, and you want to reuse the same external IP and port - usually tcp/443 for HTTPS.
### WebSockets

View File

@@ -1,11 +1,7 @@
# Running headscale in a container
!!! warning "Community documentation"
This page is not actively maintained by the headscale authors and is
written by community members. It is _not_ verified by `headscale` developers.
**It might be outdated and it might miss necessary steps**.
**Note:** the container documentation is maintained by the _community_ and there is no guarentee
it is up to date, or working.
## Goal
@@ -28,7 +24,7 @@ cd ./headscale
touch ./config/db.sqlite
```
3. **(Strongly Recommended)** Download a copy of the [example configuration][config-example.yaml](https://github.com/juanfont/headscale/blob/main/config-example.yaml) from the headscale repository.
3. **(Strongly Recommended)** Download a copy of the [example configuration](../config-example.yaml) from the [headscale repository](https://github.com/juanfont/headscale/).
Using wget:

View File

@@ -1,198 +0,0 @@
# Running headscale on Linux
## Note: Outdated and "advanced"
This documentation is considered the "legacy"/advanced/manual version of the documentation, you most likely do not
want to use this documentation and rather look at the distro specific documentation (TODO LINK)[].
## Goal
This documentation has the goal of showing a user how-to set up and run `headscale` on Linux.
In additional to the "get up and running section", there is an optional [SystemD section](#running-headscale-in-the-background-with-systemd)
describing how to make `headscale` run properly in a server environment.
## Configure and run `headscale`
1. Download the latest [`headscale` binary from GitHub's release page](https://github.com/juanfont/headscale/releases):
```shell
wget --output-document=/usr/local/bin/headscale \
https://github.com/juanfont/headscale/releases/download/v<HEADSCALE VERSION>/headscale_<HEADSCALE VERSION>_linux_<ARCH>
```
2. Make `headscale` executable:
```shell
chmod +x /usr/local/bin/headscale
```
3. Prepare a directory to hold `headscale` configuration and the [SQLite](https://www.sqlite.org/) database:
```shell
# Directory for configuration
mkdir -p /etc/headscale
# Directory for Database, and other variable data (like certificates)
mkdir -p /var/lib/headscale
# or if you create a headscale user:
useradd \
--create-home \
--home-dir /var/lib/headscale/ \
--system \
--user-group \
--shell /usr/bin/nologin \
headscale
```
4. Create an empty SQLite database:
```shell
touch /var/lib/headscale/db.sqlite
```
5. Create a `headscale` configuration:
```shell
touch /etc/headscale/config.yaml
```
**(Strongly Recommended)** Download a copy of the [example configuration][config-example.yaml](https://github.com/juanfont/headscale/blob/main/config-example.yaml) from the headscale repository.
6. Start the headscale server:
```shell
headscale serve
```
This command will start `headscale` in the current terminal session.
---
To continue the tutorial, open a new terminal and let it run in the background.
Alternatively use terminal emulators like [tmux](https://github.com/tmux/tmux) or [screen](https://www.gnu.org/software/screen/).
To run `headscale` in the background, please follow the steps in the [SystemD section](#running-headscale-in-the-background-with-systemd) before continuing.
7. Verify `headscale` is running:
Verify `headscale` is available:
```shell
curl http://127.0.0.1:9090/metrics
```
8. Create a user ([tailnet](https://tailscale.com/kb/1136/tailnet/)):
```shell
headscale users create myfirstuser
```
### Register a machine (normal login)
On a client machine, execute the `tailscale` login command:
```shell
tailscale up --login-server YOUR_HEADSCALE_URL
```
Register the machine:
```shell
headscale --user myfirstuser nodes register --key <YOUR_MACHINE_KEY>
```
### Register machine using a pre authenticated key
Generate a key using the command line:
```shell
headscale --user myfirstuser preauthkeys create --reusable --expiration 24h
```
This will return a pre-authenticated key that can be used to connect a node to `headscale` during the `tailscale` command:
```shell
tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>
```
## Running `headscale` in the background with SystemD
:warning: **Deprecated**: This part is very outdated and you should use the [pre-packaged Headscale for this](./running-headscale-linux.md
This section demonstrates how to run `headscale` as a service in the background with [SystemD](https://www.freedesktop.org/wiki/Software/systemd/).
This should work on most modern Linux distributions.
1. Create a SystemD service configuration at `/etc/systemd/system/headscale.service` containing:
```systemd
[Unit]
Description=headscale controller
After=syslog.target
After=network.target
[Service]
Type=simple
User=headscale
Group=headscale
ExecStart=/usr/local/bin/headscale serve
Restart=always
RestartSec=5
# Optional security enhancements
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
[Install]
WantedBy=multi-user.target
```
Note that when running as the headscale user ensure that, either you add your current user to the headscale group:
```shell
usermod -a -G headscale current_user
```
or run all headscale commands as the headscale user:
```shell
su - headscale
```
2. In `/etc/headscale/config.yaml`, override the default `headscale` unix socket with path that is writable by the `headscale` user or group:
```yaml
unix_socket: /var/run/headscale/headscale.sock
```
3. Reload SystemD to load the new configuration file:
```shell
systemctl daemon-reload
```
4. Enable and start the new `headscale` service:
```shell
systemctl enable --now headscale
```
5. Verify the headscale service:
```shell
systemctl status headscale
```
Verify `headscale` is available:
```shell
curl http://127.0.0.1:9090/metrics
```
`headscale` will now run in the background and start at boot.

View File

@@ -1,65 +1,84 @@
# Running headscale on Linux
## Requirements
- Ubuntu 20.04 or newer, Debian 11 or newer.
## Goal
Get Headscale up and running.
This documentation has the goal of showing a user how-to set up and run `headscale` on Linux.
In additional to the "get up and running section", there is an optional [SystemD section](#running-headscale-in-the-background-with-systemd)
describing how to make `headscale` run properly in a server environment.
This includes running Headscale with SystemD.
## Configure and run `headscale`
## Migrating from manual install
If you are migrating from the old manual install, the best thing would be to remove
the files installed by following [the guide in reverse](./running-headscale-linux-manual.md).
You should _not_ delete the database (`/var/headscale/db.sqlite`) and the
configuration (`/etc/headscale/config.yaml`).
## Installation
1. Download the lastest Headscale package for your platform (`.deb` for Ubuntu and Debian) from [Headscale's releases page]():
1. Download the latest [`headscale` binary from GitHub's release page](https://github.com/juanfont/headscale/releases):
```shell
wget --output-document=headscale.deb \
https://github.com/juanfont/headscale/releases/download/v<HEADSCALE VERSION>/headscale_<HEADSCALE VERSION>_linux_<ARCH>.deb
wget --output-document=/usr/local/bin/headscale \
https://github.com/juanfont/headscale/releases/download/v<HEADSCALE VERSION>/headscale_<HEADSCALE VERSION>_linux_<ARCH>
```
2. Install Headscale:
2. Make `headscale` executable:
```shell
sudo dpkg --install headscale.deb
chmod +x /usr/local/bin/headscale
```
3. Enable Headscale service, this will start Headscale at boot:
3. Prepare a directory to hold `headscale` configuration and the [SQLite](https://www.sqlite.org/) database:
```shell
sudo systemctl enable headscale
# Directory for configuration
mkdir -p /etc/headscale
# Directory for Database, and other variable data (like certificates)
mkdir -p /var/lib/headscale
# or if you create a headscale user:
useradd \
--create-home \
--home-dir /var/lib/headscale/ \
--system \
--user-group \
--shell /usr/bin/nologin \
headscale
```
4. Configure Headscale by editing the configuration file:
4. Create an empty SQLite database:
```shell
nano /etc/headscale/config.yaml
touch /var/lib/headscale/db.sqlite
```
5. Start Headscale:
5. Create a `headscale` configuration:
```shell
sudo systemctl start headscale
touch /etc/headscale/config.yaml
```
6. Check that Headscale is running as intended:
It is **strongly recommended** to copy and modify the [example configuration](../config-example.yaml)
from the [headscale repository](../)
6. Start the headscale server:
```shell
systemctl status headscale
headscale serve
```
## Using Headscale
This command will start `headscale` in the current terminal session.
### Create a user
---
To continue the tutorial, open a new terminal and let it run in the background.
Alternatively use terminal emulators like [tmux](https://github.com/tmux/tmux) or [screen](https://www.gnu.org/software/screen/).
To run `headscale` in the background, please follow the steps in the [SystemD section](#running-headscale-in-the-background-with-systemd) before continuing.
7. Verify `headscale` is running:
Verify `headscale` is available:
```shell
curl http://127.0.0.1:9090/metrics
```
8. Create a user ([tailnet](https://tailscale.com/kb/1136/tailnet/)):
```shell
headscale users create myfirstuser
@@ -67,16 +86,16 @@ headscale users create myfirstuser
### Register a machine (normal login)
On a client machine, run the `tailscale` login command:
On a client machine, execute the `tailscale` login command:
```shell
tailscale up --login-server <YOUR_HEADSCALE_URL>
tailscale up --login-server YOUR_HEADSCALE_URL
```
Register the machine:
```shell
headscale --user myfirstuser nodes register --key <YOUR_MACHINE_KEY>
headscale --user myfirstuser nodes register --key <YOU_+MACHINE_KEY>
```
### Register machine using a pre authenticated key
@@ -87,9 +106,87 @@ Generate a key using the command line:
headscale --user myfirstuser preauthkeys create --reusable --expiration 24h
```
This will return a pre-authenticated key that is used to
connect a node to `headscale` during the `tailscale` command:
This will return a pre-authenticated key that can be used to connect a node to `headscale` during the `tailscale` command:
```shell
tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>
```
## Running `headscale` in the background with SystemD
This section demonstrates how to run `headscale` as a service in the background with [SystemD](https://www.freedesktop.org/wiki/Software/systemd/).
This should work on most modern Linux distributions.
1. Create a SystemD service configuration at `/etc/systemd/system/headscale.service` containing:
```systemd
[Unit]
Description=headscale controller
After=syslog.target
After=network.target
[Service]
Type=simple
User=headscale
Group=headscale
ExecStart=/usr/local/bin/headscale serve
Restart=always
RestartSec=5
# Optional security enhancements
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
[Install]
WantedBy=multi-user.target
```
Note that when running as the headscale user ensure that, either you add your current user to the headscale group:
```shell
usermod -a -G headscale current_user
```
or run all headscale commands as the headscale user:
```shell
su - headscale
```
2. In `/etc/headscale/config.yaml`, override the default `headscale` unix socket with path that is writable by the `headscale` user or group:
```yaml
unix_socket: /var/run/headscale/headscale.sock
```
3. Reload SystemD to load the new configuration file:
```shell
systemctl daemon-reload
```
4. Enable and start the new `headscale` service:
```shell
systemctl enable --now headscale
```
5. Verify the headscale service:
```shell
systemctl status headscale
```
Verify `headscale` is available:
```shell
curl http://127.0.0.1:9090/metrics
```
`headscale` will now run in the background and start at boot.

View File

@@ -1,12 +1,5 @@
# Running headscale on OpenBSD
!!! warning "Community documentation"
This page is not actively maintained by the headscale authors and is
written by community members. It is _not_ verified by `headscale` developers.
**It might be outdated and it might miss necessary steps**.
## Goal
This documentation has the goal of showing a user how-to install and run `headscale` on OpenBSD 7.1.
@@ -94,7 +87,8 @@ touch /var/lib/headscale/db.sqlite
touch /etc/headscale/config.yaml
```
**(Strongly Recommended)** Download a copy of the [example configuration][config-example.yaml](https://github.com/juanfont/headscale/blob/main/config-example.yaml) from the headscale repository.
It is **strongly recommended** to copy and modify the [example configuration](../config-example.yaml)
from the [headscale repository](../)
4. Start the headscale server:

30
flake.lock generated
View File

@@ -1,15 +1,12 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1681202837,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
"lastModified": 1680776469,
"narHash": "sha256-3CXUDK/3q/kieWtdsYpDOBJw3Gw4Af6x+2EiSnIkNQw=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
"rev": "411e8764155aa9354dbcd6d5faaeb97e9e3dce24",
"type": "github"
},
"original": {
@@ -20,11 +17,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1681753173,
"narHash": "sha256-MrGmzZWLUqh2VstoikKLFFIELXm/lsf/G9U9zR96VD4=",
"lastModified": 1680789907,
"narHash": "sha256-0AOMkabjbOauxspnqfzqgLKhB2gSh3sLkz1p/jIckcs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "0a4206a51b386e5cda731e8ac78d76ad924c7125",
"rev": "9de84cd029054adc54fdc6442e121fbc5ac33baf",
"type": "github"
},
"original": {
@@ -39,21 +36,6 @@
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",

View File

@@ -129,14 +129,6 @@
shellHook = ''
export GOFLAGS=-tags="ts2019"
export PATH="$PWD/result/bin:$PATH"
mkdir -p ./ignored
export HEADSCALE_PRIVATE_KEY_PATH="./ignored/private.key"
export HEADSCALE_NOISE_PRIVATE_KEY_PATH="./ignored/noise_private.key"
export HEADSCALE_DB_PATH="./ignored/db.sqlite"
export HEADSCALE_TLS_LETSENCRYPT_CACHE_DIR="./ignored/cache"
export HEADSCALE_UNIX_SOCKET="./ignored/headscale.sock"
'';
};

View File

@@ -12,14 +12,16 @@ import (
"github.com/stretchr/testify/assert"
)
func aclScenario(t *testing.T, policy *headscale.ACLPolicy, clientsPerUser int) *Scenario {
const numberOfTestClients = 2
func aclScenario(t *testing.T, policy headscale.ACLPolicy) *Scenario {
t.Helper()
scenario, err := NewScenario()
assert.NoError(t, err)
spec := map[string]int{
"user1": clientsPerUser,
"user2": clientsPerUser,
"user1": numberOfTestClients,
"user2": numberOfTestClients,
}
err = scenario.CreateHeadscaleEnv(spec,
@@ -27,15 +29,18 @@ func aclScenario(t *testing.T, policy *headscale.ACLPolicy, clientsPerUser int)
tsic.WithDockerEntrypoint([]string{
"/bin/bash",
"-c",
"/bin/sleep 3 ; update-ca-certificates ; python3 -m http.server --bind :: 80 & tailscaled --tun=tsdev",
"/bin/sleep 3 ; update-ca-certificates ; python3 -m http.server 80 & tailscaled --tun=tsdev",
}),
tsic.WithDockerWorkdir("/"),
},
hsic.WithACLPolicy(policy),
hsic.WithACLPolicy(&policy),
hsic.WithTestName("acl"),
)
assert.NoError(t, err)
// allClients, err := scenario.ListTailscaleClients()
// assert.NoError(t, err)
err = scenario.WaitForTailscaleSync()
assert.NoError(t, err)
@@ -225,7 +230,7 @@ func TestACLAllowUser80Dst(t *testing.T) {
IntegrationSkip(t)
scenario := aclScenario(t,
&headscale.ACLPolicy{
headscale.ACLPolicy{
ACLs: []headscale.ACL{
{
Action: "accept",
@@ -234,7 +239,6 @@ func TestACLAllowUser80Dst(t *testing.T) {
},
},
},
1,
)
user1Clients, err := scenario.ListTailscaleClients("user1")
@@ -281,7 +285,7 @@ func TestACLDenyAllPort80(t *testing.T) {
IntegrationSkip(t)
scenario := aclScenario(t,
&headscale.ACLPolicy{
headscale.ACLPolicy{
Groups: map[string][]string{
"group:integration-acl-test": {"user1", "user2"},
},
@@ -293,7 +297,6 @@ func TestACLDenyAllPort80(t *testing.T) {
},
},
},
4,
)
allClients, err := scenario.ListTailscaleClients()
@@ -330,7 +333,7 @@ func TestACLAllowUserDst(t *testing.T) {
IntegrationSkip(t)
scenario := aclScenario(t,
&headscale.ACLPolicy{
headscale.ACLPolicy{
ACLs: []headscale.ACL{
{
Action: "accept",
@@ -339,7 +342,6 @@ func TestACLAllowUserDst(t *testing.T) {
},
},
},
2,
)
user1Clients, err := scenario.ListTailscaleClients("user1")
@@ -388,7 +390,7 @@ func TestACLAllowStarDst(t *testing.T) {
IntegrationSkip(t)
scenario := aclScenario(t,
&headscale.ACLPolicy{
headscale.ACLPolicy{
ACLs: []headscale.ACL{
{
Action: "accept",
@@ -397,7 +399,6 @@ func TestACLAllowStarDst(t *testing.T) {
},
},
},
2,
)
user1Clients, err := scenario.ListTailscaleClients("user1")
@@ -440,6 +441,155 @@ func TestACLAllowStarDst(t *testing.T) {
assert.NoError(t, err)
}
// This test aims to cover cases where individual hosts are allowed and denied
// access based on their assigned hostname
// https://github.com/juanfont/headscale/issues/941
// ACL = [{
// "DstPorts": [{
// "Bits": null,
// "IP": "100.64.0.3/32",
// "Ports": {
// "First": 0,
// "Last": 65535
// }
// }],
// "SrcIPs": ["*"]
// }, {
//
// "DstPorts": [{
// "Bits": null,
// "IP": "100.64.0.2/32",
// "Ports": {
// "First": 0,
// "Last": 65535
// }
// }],
// "SrcIPs": ["100.64.0.1/32"]
// }]
//
// ACL Cache Map= {
// "*": {
// "100.64.0.3/32": {}
// },
// "100.64.0.1/32": {
// "100.64.0.2/32": {}
// }
// }
func TestACLNamedHostsCanReach(t *testing.T) {
IntegrationSkip(t)
scenario := aclScenario(t,
headscale.ACLPolicy{
Hosts: headscale.Hosts{
"test1": netip.MustParsePrefix("100.64.0.1/32"),
"test2": netip.MustParsePrefix("100.64.0.2/32"),
"test3": netip.MustParsePrefix("100.64.0.3/32"),
},
ACLs: []headscale.ACL{
// Everyone can curl test3
{
Action: "accept",
Sources: []string{"*"},
Destinations: []string{"test3:*"},
},
// test1 can curl test2
{
Action: "accept",
Sources: []string{"test1"},
Destinations: []string{"test2:*"},
},
},
},
)
// Since user/users dont matter here, we basically expect that some clients
// will be assigned these ips and that we can pick them up for our own use.
test1ip := netip.MustParseAddr("100.64.0.1")
test1, err := scenario.FindTailscaleClientByIP(test1ip)
assert.NoError(t, err)
test1fqdn, err := test1.FQDN()
assert.NoError(t, err)
test1ipURL := fmt.Sprintf("http://%s/etc/hostname", test1ip.String())
test1fqdnURL := fmt.Sprintf("http://%s/etc/hostname", test1fqdn)
test2ip := netip.MustParseAddr("100.64.0.2")
test2, err := scenario.FindTailscaleClientByIP(test2ip)
assert.NoError(t, err)
test2fqdn, err := test2.FQDN()
assert.NoError(t, err)
test2ipURL := fmt.Sprintf("http://%s/etc/hostname", test2ip.String())
test2fqdnURL := fmt.Sprintf("http://%s/etc/hostname", test2fqdn)
test3ip := netip.MustParseAddr("100.64.0.3")
test3, err := scenario.FindTailscaleClientByIP(test3ip)
assert.NoError(t, err)
test3fqdn, err := test3.FQDN()
assert.NoError(t, err)
test3ipURL := fmt.Sprintf("http://%s/etc/hostname", test3ip.String())
test3fqdnURL := fmt.Sprintf("http://%s/etc/hostname", test3fqdn)
// test1 can query test3
result, err := test1.Curl(test3ipURL)
assert.Len(t, result, 13)
assert.NoError(t, err)
result, err = test1.Curl(test3fqdnURL)
assert.Len(t, result, 13)
assert.NoError(t, err)
// test2 can query test3
result, err = test2.Curl(test3ipURL)
assert.Len(t, result, 13)
assert.NoError(t, err)
result, err = test2.Curl(test3fqdnURL)
assert.Len(t, result, 13)
assert.NoError(t, err)
// test3 cannot query test1
result, err = test3.Curl(test1ipURL)
assert.Empty(t, result)
assert.Error(t, err)
result, err = test3.Curl(test1fqdnURL)
assert.Empty(t, result)
assert.Error(t, err)
// test3 cannot query test2
result, err = test3.Curl(test2ipURL)
assert.Empty(t, result)
assert.Error(t, err)
result, err = test3.Curl(test2fqdnURL)
assert.Empty(t, result)
assert.Error(t, err)
// test1 can query test2
result, err = test1.Curl(test2ipURL)
assert.Len(t, result, 13)
assert.NoError(t, err)
result, err = test1.Curl(test2fqdnURL)
assert.Len(t, result, 13)
assert.NoError(t, err)
// test2 cannot query test1
result, err = test2.Curl(test1ipURL)
assert.Empty(t, result)
assert.Error(t, err)
result, err = test2.Curl(test1fqdnURL)
assert.Empty(t, result)
assert.Error(t, err)
err = scenario.Shutdown()
assert.NoError(t, err)
}
// TestACLNamedHostsCanReachBySubnet is the same as
// TestACLNamedHostsCanReach, but it tests if we expand a
// full CIDR correctly. All routes should work.
@@ -447,7 +597,7 @@ func TestACLNamedHostsCanReachBySubnet(t *testing.T) {
IntegrationSkip(t)
scenario := aclScenario(t,
&headscale.ACLPolicy{
headscale.ACLPolicy{
Hosts: headscale.Hosts{
"all": netip.MustParsePrefix("100.64.0.0/24"),
},
@@ -460,7 +610,6 @@ func TestACLNamedHostsCanReachBySubnet(t *testing.T) {
},
},
},
3,
)
user1Clients, err := scenario.ListTailscaleClients("user1")
@@ -502,450 +651,3 @@ func TestACLNamedHostsCanReachBySubnet(t *testing.T) {
err = scenario.Shutdown()
assert.NoError(t, err)
}
// This test aims to cover cases where individual hosts are allowed and denied
// access based on their assigned hostname
// https://github.com/juanfont/headscale/issues/941
//
// ACL = [{
// "DstPorts": [{
// "Bits": null,
// "IP": "100.64.0.3/32",
// "Ports": {
// "First": 0,
// "Last": 65535
// }
// }],
// "SrcIPs": ["*"]
// }, {
//
// "DstPorts": [{
// "Bits": null,
// "IP": "100.64.0.2/32",
// "Ports": {
// "First": 0,
// "Last": 65535
// }
// }],
// "SrcIPs": ["100.64.0.1/32"]
// }]
//
// ACL Cache Map= {
// "*": {
// "100.64.0.3/32": {}
// },
// "100.64.0.1/32": {
// "100.64.0.2/32": {}
// }
// }
//
// https://github.com/juanfont/headscale/issues/941
// Additionally verify ipv6 behaviour, part of
// https://github.com/juanfont/headscale/issues/809
func TestACLNamedHostsCanReach(t *testing.T) {
IntegrationSkip(t)
tests := map[string]struct {
policy headscale.ACLPolicy
}{
"ipv4": {
policy: headscale.ACLPolicy{
Hosts: headscale.Hosts{
"test1": netip.MustParsePrefix("100.64.0.1/32"),
"test2": netip.MustParsePrefix("100.64.0.2/32"),
"test3": netip.MustParsePrefix("100.64.0.3/32"),
},
ACLs: []headscale.ACL{
// Everyone can curl test3
{
Action: "accept",
Sources: []string{"*"},
Destinations: []string{"test3:*"},
},
// test1 can curl test2
{
Action: "accept",
Sources: []string{"test1"},
Destinations: []string{"test2:*"},
},
},
},
},
"ipv6": {
policy: headscale.ACLPolicy{
Hosts: headscale.Hosts{
"test1": netip.MustParsePrefix("fd7a:115c:a1e0::1/128"),
"test2": netip.MustParsePrefix("fd7a:115c:a1e0::2/128"),
"test3": netip.MustParsePrefix("fd7a:115c:a1e0::3/128"),
},
ACLs: []headscale.ACL{
// Everyone can curl test3
{
Action: "accept",
Sources: []string{"*"},
Destinations: []string{"test3:*"},
},
// test1 can curl test2
{
Action: "accept",
Sources: []string{"test1"},
Destinations: []string{"test2:*"},
},
},
},
},
}
for name, testCase := range tests {
t.Run(name, func(t *testing.T) {
scenario := aclScenario(t,
&testCase.policy,
2,
)
// Since user/users dont matter here, we basically expect that some clients
// will be assigned these ips and that we can pick them up for our own use.
test1ip4 := netip.MustParseAddr("100.64.0.1")
test1ip6 := netip.MustParseAddr("fd7a:115c:a1e0::1")
test1, err := scenario.FindTailscaleClientByIP(test1ip6)
assert.NoError(t, err)
test1fqdn, err := test1.FQDN()
assert.NoError(t, err)
test1ip4URL := fmt.Sprintf("http://%s/etc/hostname", test1ip4.String())
test1ip6URL := fmt.Sprintf("http://[%s]/etc/hostname", test1ip6.String())
test1fqdnURL := fmt.Sprintf("http://%s/etc/hostname", test1fqdn)
test2ip4 := netip.MustParseAddr("100.64.0.2")
test2ip6 := netip.MustParseAddr("fd7a:115c:a1e0::2")
test2, err := scenario.FindTailscaleClientByIP(test2ip6)
assert.NoError(t, err)
test2fqdn, err := test2.FQDN()
assert.NoError(t, err)
test2ip4URL := fmt.Sprintf("http://%s/etc/hostname", test2ip4.String())
test2ip6URL := fmt.Sprintf("http://[%s]/etc/hostname", test2ip6.String())
test2fqdnURL := fmt.Sprintf("http://%s/etc/hostname", test2fqdn)
test3ip4 := netip.MustParseAddr("100.64.0.3")
test3ip6 := netip.MustParseAddr("fd7a:115c:a1e0::3")
test3, err := scenario.FindTailscaleClientByIP(test3ip6)
assert.NoError(t, err)
test3fqdn, err := test3.FQDN()
assert.NoError(t, err)
test3ip4URL := fmt.Sprintf("http://%s/etc/hostname", test3ip4.String())
test3ip6URL := fmt.Sprintf("http://[%s]/etc/hostname", test3ip6.String())
test3fqdnURL := fmt.Sprintf("http://%s/etc/hostname", test3fqdn)
// test1 can query test3
result, err := test1.Curl(test3ip4URL)
assert.Lenf(
t,
result,
13,
"failed to connect from test1 to test3 with URL %s, expected hostname of 13 chars, got %s",
test3ip4URL,
result,
)
assert.NoError(t, err)
result, err = test1.Curl(test3ip6URL)
assert.Lenf(
t,
result,
13,
"failed to connect from test1 to test3 with URL %s, expected hostname of 13 chars, got %s",
test3ip6URL,
result,
)
assert.NoError(t, err)
result, err = test1.Curl(test3fqdnURL)
assert.Lenf(
t,
result,
13,
"failed to connect from test1 to test3 with URL %s, expected hostname of 13 chars, got %s",
test3fqdnURL,
result,
)
assert.NoError(t, err)
// test2 can query test3
result, err = test2.Curl(test3ip4URL)
assert.Lenf(
t,
result,
13,
"failed to connect from test1 to test3 with URL %s, expected hostname of 13 chars, got %s",
test3ip4URL,
result,
)
assert.NoError(t, err)
result, err = test2.Curl(test3ip6URL)
assert.Lenf(
t,
result,
13,
"failed to connect from test1 to test3 with URL %s, expected hostname of 13 chars, got %s",
test3ip6URL,
result,
)
assert.NoError(t, err)
result, err = test2.Curl(test3fqdnURL)
assert.Lenf(
t,
result,
13,
"failed to connect from test1 to test3 with URL %s, expected hostname of 13 chars, got %s",
test3fqdnURL,
result,
)
assert.NoError(t, err)
// test3 cannot query test1
result, err = test3.Curl(test1ip4URL)
assert.Empty(t, result)
assert.Error(t, err)
result, err = test3.Curl(test1ip6URL)
assert.Empty(t, result)
assert.Error(t, err)
result, err = test3.Curl(test1fqdnURL)
assert.Empty(t, result)
assert.Error(t, err)
// test3 cannot query test2
result, err = test3.Curl(test2ip4URL)
assert.Empty(t, result)
assert.Error(t, err)
result, err = test3.Curl(test2ip6URL)
assert.Empty(t, result)
assert.Error(t, err)
result, err = test3.Curl(test2fqdnURL)
assert.Empty(t, result)
assert.Error(t, err)
// test1 can query test2
result, err = test1.Curl(test2ip4URL)
assert.Lenf(
t,
result,
13,
"failed to connect from test1 to test2 with URL %s, expected hostname of 13 chars, got %s",
test2ip4URL,
result,
)
assert.NoError(t, err)
result, err = test1.Curl(test2ip6URL)
assert.Lenf(
t,
result,
13,
"failed to connect from test1 to test2 with URL %s, expected hostname of 13 chars, got %s",
test2ip6URL,
result,
)
assert.NoError(t, err)
result, err = test1.Curl(test2fqdnURL)
assert.Lenf(
t,
result,
13,
"failed to connect from test1 to test2 with URL %s, expected hostname of 13 chars, got %s",
test2fqdnURL,
result,
)
assert.NoError(t, err)
// test2 cannot query test1
result, err = test2.Curl(test1ip4URL)
assert.Empty(t, result)
assert.Error(t, err)
result, err = test2.Curl(test1ip6URL)
assert.Empty(t, result)
assert.Error(t, err)
result, err = test2.Curl(test1fqdnURL)
assert.Empty(t, result)
assert.Error(t, err)
err = scenario.Shutdown()
assert.NoError(t, err)
})
}
}
// TestACLDevice1CanAccessDevice2 is a table driven test that aims to test
// the various ways to achieve a connection between device1 and device2 where
// device1 can access device2, but not the other way around. This can be
// viewed as one of the most important tests here as it covers most of the
// syntax that can be used.
//
// Before adding new taste cases, consider if it can be reduced to a case
// in this function.
func TestACLDevice1CanAccessDevice2(t *testing.T) {
IntegrationSkip(t)
tests := map[string]struct {
policy headscale.ACLPolicy
}{
"ipv4": {
policy: headscale.ACLPolicy{
ACLs: []headscale.ACL{
{
Action: "accept",
Sources: []string{"100.64.0.1"},
Destinations: []string{"100.64.0.2:*"},
},
},
},
},
"ipv6": {
policy: headscale.ACLPolicy{
ACLs: []headscale.ACL{
{
Action: "accept",
Sources: []string{"fd7a:115c:a1e0::1"},
Destinations: []string{"fd7a:115c:a1e0::2:*"},
},
},
},
},
"hostv4cidr": {
policy: headscale.ACLPolicy{
Hosts: headscale.Hosts{
"test1": netip.MustParsePrefix("100.64.0.1/32"),
"test2": netip.MustParsePrefix("100.64.0.2/32"),
},
ACLs: []headscale.ACL{
{
Action: "accept",
Sources: []string{"test1"},
Destinations: []string{"test2:*"},
},
},
},
},
"hostv6cidr": {
policy: headscale.ACLPolicy{
Hosts: headscale.Hosts{
"test1": netip.MustParsePrefix("fd7a:115c:a1e0::1/128"),
"test2": netip.MustParsePrefix("fd7a:115c:a1e0::2/128"),
},
ACLs: []headscale.ACL{
{
Action: "accept",
Sources: []string{"test1"},
Destinations: []string{"test2:*"},
},
},
},
},
"group": {
policy: headscale.ACLPolicy{
Groups: map[string][]string{
"group:one": {"user1"},
"group:two": {"user2"},
},
ACLs: []headscale.ACL{
{
Action: "accept",
Sources: []string{"group:one"},
Destinations: []string{"group:two:*"},
},
},
},
},
// TODO(kradalby): Add similar tests for Tags, might need support
// in the scenario function when we create or join the clients.
}
for name, testCase := range tests {
t.Run(name, func(t *testing.T) {
scenario := aclScenario(t, &testCase.policy, 1)
test1ip := netip.MustParseAddr("100.64.0.1")
test1ip6 := netip.MustParseAddr("fd7a:115c:a1e0::1")
test1, err := scenario.FindTailscaleClientByIP(test1ip)
assert.NotNil(t, test1)
assert.NoError(t, err)
test1fqdn, err := test1.FQDN()
assert.NoError(t, err)
test1ipURL := fmt.Sprintf("http://%s/etc/hostname", test1ip.String())
test1ip6URL := fmt.Sprintf("http://[%s]/etc/hostname", test1ip6.String())
test1fqdnURL := fmt.Sprintf("http://%s/etc/hostname", test1fqdn)
test2ip := netip.MustParseAddr("100.64.0.2")
test2ip6 := netip.MustParseAddr("fd7a:115c:a1e0::2")
test2, err := scenario.FindTailscaleClientByIP(test2ip)
assert.NotNil(t, test2)
assert.NoError(t, err)
test2fqdn, err := test2.FQDN()
assert.NoError(t, err)
test2ipURL := fmt.Sprintf("http://%s/etc/hostname", test2ip.String())
test2ip6URL := fmt.Sprintf("http://[%s]/etc/hostname", test2ip6.String())
test2fqdnURL := fmt.Sprintf("http://%s/etc/hostname", test2fqdn)
// test1 can query test2
result, err := test1.Curl(test2ipURL)
assert.Lenf(
t,
result,
13,
"failed to connect from test1 to test with URL %s, expected hostname of 13 chars, got %s",
test2ipURL,
result,
)
assert.NoError(t, err)
result, err = test1.Curl(test2ip6URL)
assert.Lenf(
t,
result,
13,
"failed to connect from test1 to test with URL %s, expected hostname of 13 chars, got %s",
test2ip6URL,
result,
)
assert.NoError(t, err)
result, err = test1.Curl(test2fqdnURL)
assert.Lenf(
t,
result,
13,
"failed to connect from test1 to test with URL %s, expected hostname of 13 chars, got %s",
test2fqdnURL,
result,
)
assert.NoError(t, err)
result, err = test2.Curl(test1ipURL)
assert.Empty(t, result)
assert.Error(t, err)
result, err = test2.Curl(test1ip6URL)
assert.Empty(t, result)
assert.Error(t, err)
result, err = test2.Curl(test1fqdnURL)
assert.Empty(t, result)
assert.Error(t, err)
err = scenario.Shutdown()
assert.NoError(t, err)
})
}
}

View File

@@ -362,15 +362,6 @@ func (s *AuthOIDCScenario) runTailscaleUp(
user.joinWaitGroup.Wait()
for _, client := range user.Clients {
err := client.WaitForReady()
if err != nil {
log.Printf("client %s was not ready: %s", client.Hostname(), err)
return fmt.Errorf("failed to up tailscale node: %w", err)
}
}
return nil
}

View File

@@ -274,15 +274,6 @@ func (s *AuthWebFlowScenario) runTailscaleUp(
}
user.joinWaitGroup.Wait()
for _, client := range user.Clients {
err := client.WaitForReady()
if err != nil {
log.Printf("client %s was not ready: %s", client.Hostname(), err)
return fmt.Errorf("failed to up tailscale node: %w", err)
}
}
return nil
}

View File

@@ -0,0 +1,221 @@
package integration
import (
"fmt"
"log"
"net/netip"
"testing"
"github.com/juanfont/headscale"
"github.com/juanfont/headscale/integration/dockertestutil"
"github.com/juanfont/headscale/integration/hsic"
"github.com/juanfont/headscale/integration/tsic"
"github.com/ory/dockertest/v3"
"github.com/samber/lo"
)
type DERPServerScenario struct {
*Scenario
tsicNetworks map[string]*dockertest.Network
}
func TestDERPServerScenario(t *testing.T) {
IntegrationSkip(t)
t.Parallel()
baseScenario, err := NewScenario()
if err != nil {
t.Errorf("failed to create scenario: %s", err)
}
scenario := DERPServerScenario{
Scenario: baseScenario,
tsicNetworks: map[string]*dockertest.Network{},
}
spec := map[string]int{
"user1": len(TailscaleVersions),
}
headscaleConfig := hsic.DefaultConfigEnv()
// headscaleConfig["HEADSCALE_DERP_URLS"] = ""
// headscaleConfig["HEADSCALE_DERP_SERVER_ENABLED"] = "true"
// headscaleConfig["HEADSCALE_DERP_SERVER_REGION_ID"] = "999"
// headscaleConfig["HEADSCALE_DERP_SERVER_REGION_CODE"] = "headscale"
// headscaleConfig["HEADSCALE_DERP_SERVER_REGION_NAME"] = "Headscale Embedded DERP"
// headscaleConfig["HEADSCALE_DERP_SERVER_STUN_LISTEN_ADDR"] = "0.0.0.0:3478"
err = scenario.CreateHeadscaleEnv(
spec,
hsic.WithHostnameAsServerURL(),
hsic.WithTestName("derpserver"),
hsic.WithConfigEnv(headscaleConfig),
hsic.WithExtraPorts([]string{"3478/udp"}),
hsic.WithTLS(),
)
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("%d successful pings out of %d", success, len(allClients)*len(allIps))
err = scenario.Shutdown()
if err != nil {
t.Errorf("failed to tear down scenario: %s", err)
}
}
func (s *DERPServerScenario) CreateHeadscaleEnv(
users map[string]int,
opts ...hsic.Option,
) error {
hs, err := s.Headscale(opts...)
if err != nil {
return err
}
err = hs.WaitForReady()
if err != nil {
return err
}
hash, err := headscale.GenerateRandomStringDNSSafe(scenarioHashLength)
if err != nil {
return err
}
for userName, clientCount := range users {
log.Printf("creating user %s with %d clients", userName, clientCount)
err = s.CreateUser(userName)
if err != nil {
return err
}
err = s.CreateTailscaleIsolatedNodesInUser(hash, userName, "all", clientCount)
if err != nil {
return err
}
key, err := s.CreatePreAuthKey(userName, true, false)
if err != nil {
return err
}
err = s.RunTailscaleUp(userName, hs.GetEndpoint(), key.GetKey())
if err != nil {
return err
}
}
return nil
}
func (s *DERPServerScenario) CreateTailscaleIsolatedNodesInUser(
hash string,
userStr string,
requestedVersion string,
count int,
opts ...tsic.Option,
) error {
if user, ok := s.users[userStr]; ok {
for i := 0; i < count; i++ {
networkName := fmt.Sprintf("tsnet-%s-%s-%d",
hash,
userStr,
i,
)
network, err := dockertestutil.GetFirstOrCreateNetwork(
s.pool,
networkName,
)
if err != nil {
return fmt.Errorf("failed to create or get %s network: %w", networkName, err)
}
log.Printf("created network %s", networkName)
s.tsicNetworks[networkName] = network
version := requestedVersion
if requestedVersion == "all" {
version = TailscaleVersions[i%len(TailscaleVersions)]
}
headscale, err := s.Headscale()
if err != nil {
return fmt.Errorf("failed to create tailscale node: %w", err)
}
cert := headscale.GetCert()
hostname := headscale.GetHostname()
user.createWaitGroup.Add(1)
opts = append(opts,
tsic.WithHeadscaleTLS(cert),
tsic.WithHeadscaleName(hostname),
)
go func() {
defer user.createWaitGroup.Done()
// TODO(kradalby): error handle this
tsClient, err := tsic.New(
s.pool,
version,
network,
opts...,
)
if err != nil {
// return fmt.Errorf("failed to add tailscale node: %w", err)
log.Printf("failed to create tailscale node: %s", err)
}
err = tsClient.WaitForReady()
if err != nil {
// return fmt.Errorf("failed to add tailscale node: %w", err)
log.Printf("failed to wait for tailscaled: %s", err)
}
user.Clients[tsClient.Hostname()] = tsClient
}()
}
user.createWaitGroup.Wait()
}
return fmt.Errorf("failed to add tailscale node: %w", errNoUserAvailable)
}
func (s *DERPServerScenario) Shutdown() error {
for _, network := range s.tsicNetworks {
err := s.pool.RemoveNetwork(network)
if err != nil {
return err
}
}
return s.Scenario.Shutdown()
}

View File

@@ -52,6 +52,7 @@ type HeadscaleInContainer struct {
// optional config
port int
extraPorts []string
aclPolicy *headscale.ACLPolicy
env map[string]string
tlsCert []byte
@@ -108,6 +109,13 @@ func WithPort(port int) Option {
}
}
// WithExtraPorts exposes additional ports on the container (e.g. 3478/udp for STUN)
func WithExtraPorts(ports []string) Option {
return func(hsic *HeadscaleInContainer) {
hsic.extraPorts = ports
}
}
// WithTestName sets a name for the test, this will be reflected
// in the Docker container name.
func WithTestName(testName string) Option {
@@ -187,7 +195,7 @@ func New(
runOptions := &dockertest.RunOptions{
Name: hsic.hostname,
ExposedPorts: []string{portProto},
ExposedPorts: append([]string{portProto}, hsic.extraPorts...),
Networks: []*dockertest.Network{network},
// Cmd: []string{"headscale", "serve"},
// TODO(kradalby): Get rid of this hack, we currently need to give us some

View File

@@ -33,8 +33,7 @@ var (
tailscaleVersions2021 = []string{
"head",
"unstable",
"1.38.4",
"1.36.2",
"1.36.0",
"1.34.2",
"1.32.3",
"1.30.2",
@@ -356,15 +355,6 @@ func (s *Scenario) RunTailscaleUp(
user.joinWaitGroup.Wait()
for _, client := range user.Clients {
err := client.WaitForReady()
if err != nil {
log.Printf("client %s was not ready: %s", client.Hostname(), err)
return fmt.Errorf("failed to up tailscale node: %w", err)
}
}
return nil
}

View File

@@ -430,15 +430,6 @@ func (t *TailscaleInContainer) WaitForReady() error {
return nil
}
// ipnstate.Status.CurrentTailnet was added in Tailscale 1.22.0
// https://github.com/tailscale/tailscale/pull/3865
//
// Before that, we can check the BackendState to see if the
// tailscaled daemon is connected to the control system.
if status.BackendState == "Running" {
return nil
}
return errTailscaleNotConnected
})
}

View File

@@ -46,35 +46,3 @@ func pingAllHelper(t *testing.T, clients []TailscaleClient, addrs []string) int
//
// return failures
// }
// // findPeerByIP takes an IP and a map of peers from status.Peer, and returns a *ipnstate.PeerStatus
// // if there is a peer with the given IP. If no peer is found, nil is returned.
// func findPeerByIP(
// ip netip.Addr,
// peers map[key.NodePublic]*ipnstate.PeerStatus,
// ) *ipnstate.PeerStatus {
// for _, peer := range peers {
// for _, peerIP := range peer.TailscaleIPs {
// if ip == peerIP {
// return peer
// }
// }
// }
//
// return nil
// }
//
// // findPeerByHostname takes a hostname and a map of peers from status.Peer, and returns a *ipnstate.PeerStatus
// // if there is a peer with the given hostname. If no peer is found, nil is returned.
// func findPeerByHostname(
// hostname string,
// peers map[key.NodePublic]*ipnstate.PeerStatus,
// ) *ipnstate.PeerStatus {
// for _, peer := range peers {
// if hostname == peer.HostName {
// return peer
// }
// }
//
// return nil
// }

View File

@@ -1267,17 +1267,3 @@ func (h *Headscale) GenerateGivenName(machineKey string, suppliedName string) (s
return givenName, nil
}
func (machines Machines) FilterByIP(ip netip.Addr) Machines {
found := make(Machines, 0)
for _, machine := range machines {
for _, mIP := range machine.IPAddresses {
if ip == mIP {
found = append(found, machine)
}
}
}
return found
}

View File

@@ -1,144 +0,0 @@
site_name: Headscale
site_url: https://juanfont.github.io/headscale
site_author: Headscale authors
site_description: >-
An open source, self-hosted implementation of the Tailscale control server.
# Repository
repo_name: juanfont/headscale
repo_url: https://github.com/juanfont/headscale
# Copyright
copyright: Copyright &copy; 2023 Headscale authors
# Configuration
theme:
name: material
features:
- announce.dismiss
- content.action.edit
- content.action.view
- content.code.annotate
- content.code.copy
# - content.tabs.link
- content.tooltips
# - header.autohide
# - navigation.expand
- navigation.footer
- navigation.indexes
# - navigation.instant
# - navigation.prune
- navigation.sections
- navigation.tabs
# - navigation.tabs.sticky
- navigation.top
- navigation.tracking
- search.highlight
- search.share
- search.suggest
- toc.follow
# - toc.integrate
palette:
- scheme: default
primary: white
toggle:
icon: material/brightness-7
name: Switch to dark mode
- scheme: slate
toggle:
icon: material/brightness-4
name: Switch to light mode
font:
text: Roboto
code: Roboto Mono
favicon: assets/favicon.png
logo: ./logo/headscale3-dots.svg
# Plugins
plugins:
- search:
separator: '[\s\-,:!=\[\]()"`/]+|\.(?!\d)|&[lg]t;|(?!\b)(?=[A-Z][a-z])'
- minify:
minify_html: true
- social: {}
# Customization
extra:
annotate:
json: [.s2]
social:
- icon: fontawesome/brands/github
link: https://github.com/juanfont/headscale
- icon: material/coffee
link: https://ko-fi.com/headscale
- icon: fontawesome/brands/docker
link: https://github.com/juanfont/headscale/pkgs/container/headscale
- icon: fontawesome/brands/discord
link: https://discord.gg/c84AZQhmpx
# Extensions
markdown_extensions:
- abbr
- admonition
- attr_list
- def_list
- footnotes
- md_in_html
- toc:
permalink: true
- pymdownx.arithmatex:
generic: true
- pymdownx.betterem:
smart_enable: all
- pymdownx.caret
- pymdownx.details
- pymdownx.emoji:
emoji_generator: !!python/name:materialx.emoji.to_svg
emoji_index: !!python/name:materialx.emoji.twemoji
- pymdownx.highlight:
anchor_linenums: true
line_spans: __span
pygments_lang_class: true
- pymdownx.inlinehilite
- pymdownx.keys
- pymdownx.magiclink:
repo_url_shorthand: true
user: squidfunk
repo: mkdocs-material
- pymdownx.mark
- pymdownx.smartsymbols
- pymdownx.superfences:
custom_fences:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
- pymdownx.tabbed:
alternate_style: true
- pymdownx.tasklist:
custom_checkbox: true
- pymdownx.tilde
# Page tree
nav:
- Home: index.md
- Getting started:
- Installation:
- Linux: running-headscale-linux.md
- OpenBSD: running-headscale-openbsd.md
- Container: running-headscale-container.md
- Configuration:
- OIDC authentication: oidc.md
- Exit node: exit-node.md
- Reverse proxy: reverse-proxy.md
- TLS: tls.md
- ACLs: acls.md
- Custom DNS records: dns-records.md
- Remote CLI: remote-cli.md
- Usage:
- Android: android-client.md
- Windows: windows-client.md
- iOS: iOS-client.md
- Proposals:
- ACLs: proposals/001-acls.md
- Better routing: proposals/002-better-routing.md
- Glossary: glossary.md