mirror of
https://github.com/juanfont/headscale.git
synced 2025-08-16 09:07:33 +00:00
Compare commits
11 Commits
v0.21.0
...
v0.22.0-nf
Author | SHA1 | Date | |
---|---|---|---|
![]() |
82ef3f89e2 | ||
![]() |
d6224f2454 | ||
![]() |
dfc5d861c7 | ||
![]() |
50b706eeed | ||
![]() |
036ff1cbb9 | ||
![]() |
ceeef40cdf | ||
![]() |
681c86cc95 | ||
![]() |
c7b459b615 | ||
![]() |
56a7b1e349 | ||
![]() |
f1eee841cb | ||
![]() |
45fbd34480 |
138
.github/workflows/release-docker.yml
vendored
Normal file
138
.github/workflows/release-docker.yml
vendored
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
---
|
||||||
|
name: Release Docker
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "*" # triggers only if push new tag version
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
- name: Set up QEMU for multiple platforms
|
||||||
|
uses: docker/setup-qemu-action@master
|
||||||
|
with:
|
||||||
|
platforms: arm64,amd64
|
||||||
|
- name: Cache Docker layers
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: /tmp/.buildx-cache
|
||||||
|
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-buildx-
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v3
|
||||||
|
with:
|
||||||
|
# list of Docker images to use as base name for tags
|
||||||
|
images: |
|
||||||
|
${{ secrets.DOCKERHUB_USERNAME }}/headscale
|
||||||
|
ghcr.io/${{ github.repository_owner }}/headscale
|
||||||
|
tags: |
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=semver,pattern={{major}}
|
||||||
|
type=sha
|
||||||
|
type=raw,value=develop
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GHCR
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build and push
|
||||||
|
id: docker_build
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
push: true
|
||||||
|
context: .
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
cache-from: type=local,src=/tmp/.buildx-cache
|
||||||
|
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
||||||
|
build-args: |
|
||||||
|
VERSION=${{ steps.meta.outputs.version }}
|
||||||
|
- name: Prepare cache for next build
|
||||||
|
run: |
|
||||||
|
rm -rf /tmp/.buildx-cache
|
||||||
|
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
||||||
|
|
||||||
|
docker-debug-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
- name: Set up QEMU for multiple platforms
|
||||||
|
uses: docker/setup-qemu-action@master
|
||||||
|
with:
|
||||||
|
platforms: arm64,amd64
|
||||||
|
- name: Cache Docker layers
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: /tmp/.buildx-cache-debug
|
||||||
|
key: ${{ runner.os }}-buildx-debug-${{ github.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-buildx-debug-
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta-debug
|
||||||
|
uses: docker/metadata-action@v3
|
||||||
|
with:
|
||||||
|
# list of Docker images to use as base name for tags
|
||||||
|
images: |
|
||||||
|
${{ secrets.DOCKERHUB_USERNAME }}/headscale
|
||||||
|
ghcr.io/${{ github.repository_owner }}/headscale
|
||||||
|
flavor: |
|
||||||
|
suffix=-debug,onlatest=true
|
||||||
|
tags: |
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=semver,pattern={{major}}
|
||||||
|
type=sha
|
||||||
|
type=raw,value=develop
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GHCR
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build and push
|
||||||
|
id: docker_build
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
push: true
|
||||||
|
context: .
|
||||||
|
file: Dockerfile.debug
|
||||||
|
tags: ${{ steps.meta-debug.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta-debug.outputs.labels }}
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
cache-from: type=local,src=/tmp/.buildx-cache-debug
|
||||||
|
cache-to: type=local,dest=/tmp/.buildx-cache-debug-new
|
||||||
|
build-args: |
|
||||||
|
VERSION=${{ steps.meta-debug.outputs.version }}
|
||||||
|
- name: Prepare cache for next build
|
||||||
|
run: |
|
||||||
|
rm -rf /tmp/.buildx-cache-debug
|
||||||
|
mv /tmp/.buildx-cache-debug-new /tmp/.buildx-cache-debug
|
131
.github/workflows/release.yml
vendored
131
.github/workflows/release.yml
vendored
@@ -19,135 +19,6 @@ jobs:
|
|||||||
- uses: cachix/install-nix-action@v16
|
- uses: cachix/install-nix-action@v16
|
||||||
|
|
||||||
- name: Run goreleaser
|
- name: Run goreleaser
|
||||||
run: nix develop --command -- goreleaser release --rm-dist
|
run: nix develop --command -- goreleaser release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
docker-release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
- name: Set up QEMU for multiple platforms
|
|
||||||
uses: docker/setup-qemu-action@master
|
|
||||||
with:
|
|
||||||
platforms: arm64,amd64
|
|
||||||
- name: Cache Docker layers
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: /tmp/.buildx-cache
|
|
||||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-buildx-
|
|
||||||
- name: Docker meta
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v3
|
|
||||||
with:
|
|
||||||
# list of Docker images to use as base name for tags
|
|
||||||
images: |
|
|
||||||
${{ secrets.DOCKERHUB_USERNAME }}/headscale
|
|
||||||
ghcr.io/${{ github.repository_owner }}/headscale
|
|
||||||
tags: |
|
|
||||||
type=semver,pattern={{version}}
|
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
|
||||||
type=semver,pattern={{major}}
|
|
||||||
type=sha
|
|
||||||
type=raw,value=develop
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Login to GHCR
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Build and push
|
|
||||||
id: docker_build
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
push: true
|
|
||||||
context: .
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
cache-from: type=local,src=/tmp/.buildx-cache
|
|
||||||
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
|
||||||
build-args: |
|
|
||||||
VERSION=${{ steps.meta.outputs.version }}
|
|
||||||
- name: Prepare cache for next build
|
|
||||||
run: |
|
|
||||||
rm -rf /tmp/.buildx-cache
|
|
||||||
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
|
||||||
|
|
||||||
docker-debug-release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
- name: Set up QEMU for multiple platforms
|
|
||||||
uses: docker/setup-qemu-action@master
|
|
||||||
with:
|
|
||||||
platforms: arm64,amd64
|
|
||||||
- name: Cache Docker layers
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: /tmp/.buildx-cache-debug
|
|
||||||
key: ${{ runner.os }}-buildx-debug-${{ github.sha }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-buildx-debug-
|
|
||||||
- name: Docker meta
|
|
||||||
id: meta-debug
|
|
||||||
uses: docker/metadata-action@v3
|
|
||||||
with:
|
|
||||||
# list of Docker images to use as base name for tags
|
|
||||||
images: |
|
|
||||||
${{ secrets.DOCKERHUB_USERNAME }}/headscale
|
|
||||||
ghcr.io/${{ github.repository_owner }}/headscale
|
|
||||||
flavor: |
|
|
||||||
suffix=-debug,onlatest=true
|
|
||||||
tags: |
|
|
||||||
type=semver,pattern={{version}}
|
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
|
||||||
type=semver,pattern={{major}}
|
|
||||||
type=sha
|
|
||||||
type=raw,value=develop
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Login to GHCR
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Build and push
|
|
||||||
id: docker_build
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
push: true
|
|
||||||
context: .
|
|
||||||
file: Dockerfile.debug
|
|
||||||
tags: ${{ steps.meta-debug.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta-debug.outputs.labels }}
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
cache-from: type=local,src=/tmp/.buildx-cache-debug
|
|
||||||
cache-to: type=local,dest=/tmp/.buildx-cache-debug-new
|
|
||||||
build-args: |
|
|
||||||
VERSION=${{ steps.meta-debug.outputs.version }}
|
|
||||||
- name: Prepare cache for next build
|
|
||||||
run: |
|
|
||||||
rm -rf /tmp/.buildx-cache-debug
|
|
||||||
mv /tmp/.buildx-cache-debug-new /tmp/.buildx-cache-debug
|
|
||||||
|
57
.github/workflows/test-integration-v2-TestACLAllowStarDst.yaml
vendored
Normal file
57
.github/workflows/test-integration-v2-TestACLAllowStarDst.yaml
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# 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 - TestACLAllowStarDst
|
||||||
|
|
||||||
|
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 "^TestACLAllowStarDst$"
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
if: always() && steps.changed-files.outputs.any_changed == 'true'
|
||||||
|
with:
|
||||||
|
name: logs
|
||||||
|
path: "control_logs/*.log"
|
57
.github/workflows/test-integration-v2-TestACLAllowUserDst.yaml
vendored
Normal file
57
.github/workflows/test-integration-v2-TestACLAllowUserDst.yaml
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# 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 - TestACLAllowUserDst
|
||||||
|
|
||||||
|
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 "^TestACLAllowUserDst$"
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
if: always() && steps.changed-files.outputs.any_changed == 'true'
|
||||||
|
with:
|
||||||
|
name: logs
|
||||||
|
path: "control_logs/*.log"
|
57
.github/workflows/test-integration-v2-TestACLDenyAllPort80.yaml
vendored
Normal file
57
.github/workflows/test-integration-v2-TestACLDenyAllPort80.yaml
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# 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 - TestACLDenyAllPort80
|
||||||
|
|
||||||
|
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 "^TestACLDenyAllPort80$"
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
if: always() && steps.changed-files.outputs.any_changed == 'true'
|
||||||
|
with:
|
||||||
|
name: logs
|
||||||
|
path: "control_logs/*.log"
|
@@ -29,6 +29,14 @@ linters:
|
|||||||
- execinquery
|
- execinquery
|
||||||
- exhaustruct
|
- exhaustruct
|
||||||
- nolintlint
|
- nolintlint
|
||||||
|
- musttag # causes issues with imported libs
|
||||||
|
|
||||||
|
# deprecated
|
||||||
|
- structcheck # replaced by unused
|
||||||
|
- ifshort # deprecated by the owner
|
||||||
|
- varcheck # replaced by unused
|
||||||
|
- nosnakecase # replaced by revive
|
||||||
|
- deadcode # replaced by unused
|
||||||
|
|
||||||
# We should strive to enable these:
|
# We should strive to enable these:
|
||||||
- wrapcheck
|
- wrapcheck
|
||||||
|
27
.nfpm.yaml
Normal file
27
.nfpm.yaml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# this is the base "template" for the package
|
||||||
|
name: headscale
|
||||||
|
description: headscale coordination server for Tailscale
|
||||||
|
arch: ${ARCH}
|
||||||
|
version: ${VERSION}
|
||||||
|
priority: optional
|
||||||
|
vendor: Juan Font
|
||||||
|
maintainer: Kristoffer Dalby <kristoffer@dalby.cc>
|
||||||
|
homepage: https://github.com/juanfont/headscale
|
||||||
|
license: BSD
|
||||||
|
contents:
|
||||||
|
- src: ./build/headscale
|
||||||
|
dst: /usr/bin/headscale
|
||||||
|
- src: ./config-example.yaml
|
||||||
|
dst: /etc/headscale/config.yaml
|
||||||
|
type: config|noreplace
|
||||||
|
file_info:
|
||||||
|
mode: 0640
|
||||||
|
- src: ./docs/packaging/headscale.systemd.service
|
||||||
|
dst: /etc/systemd/system/headscale.service
|
||||||
|
- dst: /var/lib/headscale
|
||||||
|
type: dir
|
||||||
|
- dst: /var/run/headscale
|
||||||
|
type: dir
|
||||||
|
scripts:
|
||||||
|
postinstall: ./docs/packaging/postinstall.sh
|
||||||
|
postremove: ./docs/packaging/postremove.sh
|
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
|
- 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)
|
||||||
|
|
||||||
## 0.21.0 (2023-03-20)
|
## 0.21.0 (2023-03-20)
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
62
acls.go
62
acls.go
@@ -14,6 +14,7 @@ import (
|
|||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/tailscale/hujson"
|
"github.com/tailscale/hujson"
|
||||||
|
"go4.org/netipx"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"tailscale.com/envknob"
|
"tailscale.com/envknob"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
@@ -165,23 +166,66 @@ func generateACLPeerCacheMap(rules []tailcfg.FilterRule) map[string]map[string]s
|
|||||||
aclCachePeerMap := make(map[string]map[string]struct{})
|
aclCachePeerMap := make(map[string]map[string]struct{})
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
for _, srcIP := range rule.SrcIPs {
|
for _, srcIP := range rule.SrcIPs {
|
||||||
if data, ok := aclCachePeerMap[srcIP]; ok {
|
for _, ip := range expandACLPeerAddr(srcIP) {
|
||||||
for _, dstPort := range rule.DstPorts {
|
if data, ok := aclCachePeerMap[ip]; ok {
|
||||||
data[dstPort.IP] = struct{}{}
|
for _, dstPort := range rule.DstPorts {
|
||||||
|
for _, dstIP := range expandACLPeerAddr(dstPort.IP) {
|
||||||
|
data[dstIP] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dstPortsMap := make(map[string]struct{}, len(rule.DstPorts))
|
||||||
|
for _, dstPort := range rule.DstPorts {
|
||||||
|
for _, dstIP := range expandACLPeerAddr(dstPort.IP) {
|
||||||
|
dstPortsMap[dstIP] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aclCachePeerMap[ip] = dstPortsMap
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
dstPortsMap := make(map[string]struct{}, len(rule.DstPorts))
|
|
||||||
for _, dstPort := range rule.DstPorts {
|
|
||||||
dstPortsMap[dstPort.IP] = struct{}{}
|
|
||||||
}
|
|
||||||
aclCachePeerMap[srcIP] = dstPortsMap
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Trace().Interface("ACL Cache Map", aclCachePeerMap).Msg("ACL Peer Cache Map generated")
|
||||||
|
|
||||||
return aclCachePeerMap
|
return aclCachePeerMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// expandACLPeerAddr takes a "tailcfg.FilterRule" "IP" and expands it into
|
||||||
|
// something our cache logic can look up, which is "*" or single IP addresses.
|
||||||
|
// This is probably quite inefficient, but it is a result of
|
||||||
|
// "make it work, then make it fast", and a lot of the ACL stuff does not
|
||||||
|
// work, but people have tried to make it fast.
|
||||||
|
func expandACLPeerAddr(srcIP string) []string {
|
||||||
|
if ip, err := netip.ParseAddr(srcIP); err == nil {
|
||||||
|
return []string{ip.String()}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cidr, err := netip.ParsePrefix(srcIP); err == nil {
|
||||||
|
addrs := []string{}
|
||||||
|
|
||||||
|
ipRange := netipx.RangeOfPrefix(cidr)
|
||||||
|
|
||||||
|
from := ipRange.From()
|
||||||
|
too := ipRange.To()
|
||||||
|
|
||||||
|
if from == too {
|
||||||
|
return []string{from.String()}
|
||||||
|
}
|
||||||
|
|
||||||
|
for from != too && from.Less(too) {
|
||||||
|
addrs = append(addrs, from.String())
|
||||||
|
from = from.Next()
|
||||||
|
}
|
||||||
|
addrs = append(addrs, too.String()) // Add the last IP address in the range
|
||||||
|
|
||||||
|
return addrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// probably "*" or other string based "IP"
|
||||||
|
return []string{srcIP}
|
||||||
|
}
|
||||||
|
|
||||||
func generateACLRules(
|
func generateACLRules(
|
||||||
machines []Machine,
|
machines []Machine,
|
||||||
aclPolicy ACLPolicy,
|
aclPolicy ACLPolicy,
|
||||||
|
135
acls_test.go
135
acls_test.go
@@ -1556,3 +1556,138 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_expandACLPeerAddr(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
srcIP string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "asterix",
|
||||||
|
args: args{
|
||||||
|
srcIP: "*",
|
||||||
|
},
|
||||||
|
want: []string{"*"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ip",
|
||||||
|
args: args{
|
||||||
|
srcIP: "10.0.0.1",
|
||||||
|
},
|
||||||
|
want: []string{"10.0.0.1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ip/32",
|
||||||
|
args: args{
|
||||||
|
srcIP: "10.0.0.1/32",
|
||||||
|
},
|
||||||
|
want: []string{"10.0.0.1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ip/30",
|
||||||
|
args: args{
|
||||||
|
srcIP: "10.0.0.1/30",
|
||||||
|
},
|
||||||
|
want: []string{
|
||||||
|
"10.0.0.0",
|
||||||
|
"10.0.0.1",
|
||||||
|
"10.0.0.2",
|
||||||
|
"10.0.0.3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ip/28",
|
||||||
|
args: args{
|
||||||
|
srcIP: "192.168.0.128/28",
|
||||||
|
},
|
||||||
|
want: []string{
|
||||||
|
"192.168.0.128", "192.168.0.129", "192.168.0.130",
|
||||||
|
"192.168.0.131", "192.168.0.132", "192.168.0.133",
|
||||||
|
"192.168.0.134", "192.168.0.135", "192.168.0.136",
|
||||||
|
"192.168.0.137", "192.168.0.138", "192.168.0.139",
|
||||||
|
"192.168.0.140", "192.168.0.141", "192.168.0.142",
|
||||||
|
"192.168.0.143",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := expandACLPeerAddr(tt.args.srcIP); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("expandACLPeerAddr() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_expandACLPeerAddrV6(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
srcIP string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "asterix",
|
||||||
|
args: args{
|
||||||
|
srcIP: "*",
|
||||||
|
},
|
||||||
|
want: []string{"*"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ipfull",
|
||||||
|
args: args{
|
||||||
|
srcIP: "fd7a:115c:a1e0:ab12:4943:cd96:624c:3166",
|
||||||
|
},
|
||||||
|
want: []string{"fd7a:115c:a1e0:ab12:4943:cd96:624c:3166"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ipzerocompression",
|
||||||
|
args: args{
|
||||||
|
srcIP: "fd7a:115c:a1e0:ab12:4943:cd96:624c::",
|
||||||
|
},
|
||||||
|
want: []string{"fd7a:115c:a1e0:ab12:4943:cd96:624c:0"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ip/128",
|
||||||
|
args: args{
|
||||||
|
srcIP: "fd7a:115c:a1e0:ab12:4943:cd96:624c:3166/128",
|
||||||
|
},
|
||||||
|
want: []string{"fd7a:115c:a1e0:ab12:4943:cd96:624c:3166"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ip/127",
|
||||||
|
args: args{
|
||||||
|
srcIP: "fd7a:115c:a1e0:ab12:4943:cd96:624c:0000/127",
|
||||||
|
},
|
||||||
|
want: []string{
|
||||||
|
"fd7a:115c:a1e0:ab12:4943:cd96:624c:0",
|
||||||
|
"fd7a:115c:a1e0:ab12:4943:cd96:624c:1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ip/126",
|
||||||
|
args: args{
|
||||||
|
srcIP: "fd7a:115c:a1e0:ab12:4943:cd96:624c:0000/126",
|
||||||
|
},
|
||||||
|
want: []string{
|
||||||
|
"fd7a:115c:a1e0:ab12:4943:cd96:624c:0",
|
||||||
|
"fd7a:115c:a1e0:ab12:4943:cd96:624c:1",
|
||||||
|
"fd7a:115c:a1e0:ab12:4943:cd96:624c:2",
|
||||||
|
"fd7a:115c:a1e0:ab12:4943:cd96:624c:3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := expandACLPeerAddr(tt.args.srcIP); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("expandACLPeerAddr() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -14,7 +14,7 @@ import (
|
|||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
2
derp.go
2
derp.go
@@ -10,7 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v3"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -42,6 +42,8 @@ a server they can register, the check of the tags is done on headscale server
|
|||||||
and only valid tags are applied. A tag is valid if the user that is
|
and only valid tags are applied. A tag is valid if the user that is
|
||||||
registering it is allowed to do it.
|
registering it is allowed to do it.
|
||||||
|
|
||||||
|
To use ACLs in headscale, you must edit your config.yaml file. In there you will find a `acl_policy_path: ""` parameter. This will need to point to your ACL file. More info on how these policies are written can be found [here](https://tailscale.com/kb/1018/acls/).
|
||||||
|
|
||||||
Here are the ACL's to implement the same permissions as above:
|
Here are the ACL's to implement the same permissions as above:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
1
docs/logo/headscale3-dots.svg
Normal file
1
docs/logo/headscale3-dots.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2" viewBox="0 0 1280 640"><circle cx="141.023" cy="338.36" r="117.472" style="fill:#f8b5cb" transform="matrix(.997276 0 0 1.00556 10.0024 -14.823)"/><circle cx="352.014" cy="268.302" r="33.095" style="fill:#a2a2a2" transform="matrix(1.01749 0 0 1 -3.15847 0)"/><circle cx="352.014" cy="268.302" r="33.095" style="fill:#a2a2a2" transform="matrix(1.01749 0 0 1 -3.15847 115.914)"/><circle cx="352.014" cy="268.302" r="33.095" style="fill:#a2a2a2" transform="matrix(1.01749 0 0 1 148.43 115.914)"/><circle cx="352.014" cy="268.302" r="33.095" style="fill:#a2a2a2" transform="matrix(1.01749 0 0 1 148.851 0)"/><circle cx="805.557" cy="336.915" r="118.199" style="fill:#8d8d8d" transform="matrix(.99196 0 0 1 3.36978 -10.2458)"/><circle cx="805.557" cy="336.915" r="118.199" style="fill:#8d8d8d" transform="matrix(.99196 0 0 1 255.633 -10.2458)"/><path d="M680.282 124.808h-68.093v390.325h68.081v-28.23H640V153.228h40.282v-28.42Z" style="fill:#303030"/><path d="M680.282 124.808h-68.093v390.325h68.081v-28.23H640V153.228h40.282v-28.42Z" style="fill:#303030" transform="matrix(-1 0 0 1 1857.19 0)"/></svg>
|
After Width: | Height: | Size: 1.2 KiB |
1
docs/logo/headscale3_header_stacked_left.svg
Normal file
1
docs/logo/headscale3_header_stacked_left.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 7.8 KiB |
5
docs/packaging/README.md
Normal file
5
docs/packaging/README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Packaging
|
||||||
|
|
||||||
|
We use [nFPM](https://nfpm.goreleaser.com/) for making `.deb`, `.rpm` and `.apk`.
|
||||||
|
|
||||||
|
This folder contains files we need to package with these releases.
|
52
docs/packaging/headscale.systemd.service
Normal file
52
docs/packaging/headscale.systemd.service
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
[Unit]
|
||||||
|
After=syslog.target
|
||||||
|
After=network.target
|
||||||
|
Description=headscale coordination server for Tailscale
|
||||||
|
X-Restart-Triggers=/etc/headscale/config.yaml
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=headscale
|
||||||
|
Group=headscale
|
||||||
|
ExecStart=/usr/bin/headscale serve
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
WorkingDirectory=/var/lib/headscale
|
||||||
|
ReadWritePaths=/var/lib/headscale /var/run
|
||||||
|
|
||||||
|
AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_CHOWN
|
||||||
|
CapabilityBoundingSet=CAP_CHOWN
|
||||||
|
LockPersonality=true
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateDevices=true
|
||||||
|
PrivateMounts=true
|
||||||
|
PrivateTmp=true
|
||||||
|
ProcSubset=pid
|
||||||
|
ProtectClock=true
|
||||||
|
ProtectControlGroups=true
|
||||||
|
ProtectHome=true
|
||||||
|
ProtectHome=yes
|
||||||
|
ProtectHostname=true
|
||||||
|
ProtectKernelLogs=true
|
||||||
|
ProtectKernelModules=true
|
||||||
|
ProtectKernelTunables=true
|
||||||
|
ProtectProc=invisible
|
||||||
|
ProtectSystem=strict
|
||||||
|
RemoveIPC=true
|
||||||
|
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
||||||
|
RestrictNamespaces=true
|
||||||
|
RestrictRealtime=true
|
||||||
|
RestrictSUIDSGID=true
|
||||||
|
RuntimeDirectory=headscale
|
||||||
|
RuntimeDirectoryMode=0750
|
||||||
|
StateDirectory=headscale
|
||||||
|
StateDirectoryMode=0750
|
||||||
|
SystemCallArchitectures=native
|
||||||
|
SystemCallFilter=@chown
|
||||||
|
SystemCallFilter=@system-service
|
||||||
|
SystemCallFilter=~@privileged
|
||||||
|
UMask=0077
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
85
docs/packaging/postinstall.sh
Normal file
85
docs/packaging/postinstall.sh
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Determine OS platform
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. /etc/os-release
|
||||||
|
|
||||||
|
HEADSCALE_EXE="/usr/bin/headscale"
|
||||||
|
BSD_HIER=""
|
||||||
|
HEADSCALE_RUN_DIR="/var/run/headscale"
|
||||||
|
HEADSCALE_USER="headscale"
|
||||||
|
HEADSCALE_GROUP="headscale"
|
||||||
|
|
||||||
|
ensure_sudo() {
|
||||||
|
if [ "$(id -u)" = "0" ]; then
|
||||||
|
echo "Sudo permissions detected"
|
||||||
|
else
|
||||||
|
echo "No sudo permission detected, please run as sudo"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_headscale_path() {
|
||||||
|
if [ ! -f "$HEADSCALE_EXE" ]; then
|
||||||
|
echo "headscale not in default path, exiting..."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "Found headscale %s\n" "$HEADSCALE_EXE"
|
||||||
|
}
|
||||||
|
|
||||||
|
create_headscale_user() {
|
||||||
|
printf "PostInstall: Adding headscale user %s\n" "$HEADSCALE_USER"
|
||||||
|
useradd -s /bin/sh -c "headscale default user" headscale
|
||||||
|
}
|
||||||
|
|
||||||
|
create_headscale_group() {
|
||||||
|
if command -V systemctl >/dev/null 2>&1; then
|
||||||
|
printf "PostInstall: Adding headscale group %s\n" "$HEADSCALE_GROUP"
|
||||||
|
groupadd "$HEADSCALE_GROUP"
|
||||||
|
|
||||||
|
printf "PostInstall: Adding headscale user %s to group %s\n" "$HEADSCALE_USER" "$HEADSCALE_GROUP"
|
||||||
|
usermod -a -G "$HEADSCALE_GROUP" "$HEADSCALE_USER"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$ID" = "alpine" ]; then
|
||||||
|
printf "PostInstall: Adding headscale group %s\n" "$HEADSCALE_GROUP"
|
||||||
|
addgroup "$HEADSCALE_GROUP"
|
||||||
|
|
||||||
|
printf "PostInstall: Adding headscale user %s to group %s\n" "$HEADSCALE_USER" "$HEADSCALE_GROUP"
|
||||||
|
addgroup "$HEADSCALE_USER" "$HEADSCALE_GROUP"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
create_run_dir() {
|
||||||
|
printf "PostInstall: Creating headscale run directory \n"
|
||||||
|
mkdir -p "$HEADSCALE_RUN_DIR"
|
||||||
|
|
||||||
|
printf "PostInstall: Modifying group ownership of headscale run directory \n"
|
||||||
|
chown "$HEADSCALE_USER":"$HEADSCALE_GROUP" "$HEADSCALE_RUN_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
summary() {
|
||||||
|
echo "----------------------------------------------------------------------"
|
||||||
|
echo " headscale package has been successfully installed."
|
||||||
|
echo ""
|
||||||
|
echo " Please follow the next steps to start the software:"
|
||||||
|
echo ""
|
||||||
|
echo " sudo systemctl start headscale"
|
||||||
|
echo ""
|
||||||
|
echo " Configuration settings can be adjusted here:"
|
||||||
|
echo " ${BSD_HIER}/etc/headscale/config.yaml"
|
||||||
|
echo ""
|
||||||
|
echo "----------------------------------------------------------------------"
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Main body of the script
|
||||||
|
#
|
||||||
|
{
|
||||||
|
ensure_sudo
|
||||||
|
ensure_headscale_path
|
||||||
|
create_headscale_user
|
||||||
|
create_headscale_group
|
||||||
|
create_run_dir
|
||||||
|
summary
|
||||||
|
}
|
15
docs/packaging/postremove.sh
Normal file
15
docs/packaging/postremove.sh
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Determine OS platform
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. /etc/os-release
|
||||||
|
|
||||||
|
if command -V systemctl >/dev/null 2>&1; then
|
||||||
|
echo "Stop and disable headscale service"
|
||||||
|
systemctl stop headscale >/dev/null 2>&1 || true
|
||||||
|
systemctl disable headscale >/dev/null 2>&1 || true
|
||||||
|
echo "Running daemon-reload"
|
||||||
|
systemctl daemon-reload || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Removing run directory"
|
||||||
|
rm -rf "/var/run/headscale.sock"
|
@@ -11,8 +11,17 @@ To make the Windows client behave as expected and to run well with `headscale`,
|
|||||||
- `HKLM:\SOFTWARE\Tailscale IPN\UnattendedMode` must be set to `always` as a `string` type, to allow Tailscale to run properly in the background
|
- `HKLM:\SOFTWARE\Tailscale IPN\UnattendedMode` must be set to `always` as a `string` type, to allow Tailscale to run properly in the background
|
||||||
- `HKLM:\SOFTWARE\Tailscale IPN\LoginURL` must be set to `<YOUR HEADSCALE URL>` as a `string` type, to ensure Tailscale contacts the correct control server.
|
- `HKLM:\SOFTWARE\Tailscale IPN\LoginURL` must be set to `<YOUR HEADSCALE URL>` as a `string` type, to ensure Tailscale contacts the correct control server.
|
||||||
|
|
||||||
|
You can set these using the Windows Registry Editor:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
Or via the following Powershell commands (right click Powershell icon and select "Run as administrator"):
|
||||||
|
|
||||||
|
```
|
||||||
|
New-ItemProperty -Path 'HKLM:\Software\Tailscale IPN' -Name UnattendedMode -PropertyType String -Value always
|
||||||
|
New-ItemProperty -Path 'HKLM:\Software\Tailscale IPN' -Name LoginURL -PropertyType String -Value https://YOUR-HEADSCALE-URL
|
||||||
|
```
|
||||||
|
|
||||||
The Tailscale Windows client has been observed to reset its configuration on logout/reboot and these two keys [resolves that issue](https://github.com/tailscale/tailscale/issues/2798).
|
The Tailscale Windows client has been observed to reset its configuration on logout/reboot and these two keys [resolves that issue](https://github.com/tailscale/tailscale/issues/2798).
|
||||||
|
|
||||||
For a guide on how to edit registry keys, [check out Computer Hope](https://www.computerhope.com/issues/ch001348.htm).
|
For a guide on how to edit registry keys, [check out Computer Hope](https://www.computerhope.com/issues/ch001348.htm).
|
||||||
|
@@ -97,6 +97,7 @@
|
|||||||
golines
|
golines
|
||||||
nodePackages.prettier
|
nodePackages.prettier
|
||||||
goreleaser
|
goreleaser
|
||||||
|
nfpm
|
||||||
gotestsum
|
gotestsum
|
||||||
|
|
||||||
# Protobuf dependencies
|
# Protobuf dependencies
|
||||||
|
2
go.mod
2
go.mod
@@ -40,7 +40,6 @@ require (
|
|||||||
google.golang.org/grpc v1.53.0
|
google.golang.org/grpc v1.53.0
|
||||||
google.golang.org/protobuf v1.28.1
|
google.golang.org/protobuf v1.28.1
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gorm.io/driver/postgres v1.4.8
|
gorm.io/driver/postgres v1.4.8
|
||||||
gorm.io/gorm v1.24.5
|
gorm.io/gorm v1.24.5
|
||||||
@@ -145,6 +144,7 @@ require (
|
|||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
modernc.org/libc v1.22.2 // indirect
|
modernc.org/libc v1.22.2 // indirect
|
||||||
modernc.org/mathutil v1.5.0 // indirect
|
modernc.org/mathutil v1.5.0 // indirect
|
||||||
modernc.org/memory v1.5.0 // indirect
|
modernc.org/memory v1.5.0 // indirect
|
||||||
|
@@ -2,6 +2,8 @@ package integration
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/juanfont/headscale"
|
"github.com/juanfont/headscale"
|
||||||
@@ -32,7 +34,7 @@ func aclScenario(t *testing.T, policy headscale.ACLPolicy) *Scenario {
|
|||||||
tsic.WithDockerWorkdir("/"),
|
tsic.WithDockerWorkdir("/"),
|
||||||
},
|
},
|
||||||
hsic.WithACLPolicy(&policy),
|
hsic.WithACLPolicy(&policy),
|
||||||
hsic.WithTestName("acldenyallping"),
|
hsic.WithTestName("acl"),
|
||||||
)
|
)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
@@ -278,3 +280,374 @@ func TestACLAllowUser80Dst(t *testing.T) {
|
|||||||
err = scenario.Shutdown()
|
err = scenario.Shutdown()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestACLDenyAllPort80(t *testing.T) {
|
||||||
|
IntegrationSkip(t)
|
||||||
|
|
||||||
|
scenario := aclScenario(t,
|
||||||
|
headscale.ACLPolicy{
|
||||||
|
Groups: map[string][]string{
|
||||||
|
"group:integration-acl-test": {"user1", "user2"},
|
||||||
|
},
|
||||||
|
ACLs: []headscale.ACL{
|
||||||
|
{
|
||||||
|
Action: "accept",
|
||||||
|
Sources: []string{"group:integration-acl-test"},
|
||||||
|
Destinations: []string{"*:22"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
allClients, err := scenario.ListTailscaleClients()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
allHostnames, err := scenario.ListTailscaleClientsFQDNs()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
for _, client := range allClients {
|
||||||
|
for _, hostname := range allHostnames {
|
||||||
|
// We will always be allowed to check _self_ so shortcircuit
|
||||||
|
// the test here.
|
||||||
|
if strings.Contains(hostname, client.Hostname()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("http://%s/etc/hostname", hostname)
|
||||||
|
t.Logf("url from %s to %s", client.Hostname(), url)
|
||||||
|
|
||||||
|
result, err := client.Curl(url)
|
||||||
|
assert.Empty(t, result)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scenario.Shutdown()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test to confirm that we can use user:* from one user.
|
||||||
|
// This ACL will not allow user1 access its own machines.
|
||||||
|
// Reported: https://github.com/juanfont/headscale/issues/699
|
||||||
|
func TestACLAllowUserDst(t *testing.T) {
|
||||||
|
IntegrationSkip(t)
|
||||||
|
|
||||||
|
scenario := aclScenario(t,
|
||||||
|
headscale.ACLPolicy{
|
||||||
|
ACLs: []headscale.ACL{
|
||||||
|
{
|
||||||
|
Action: "accept",
|
||||||
|
Sources: []string{"user1"},
|
||||||
|
Destinations: []string{"user2:*"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
user1Clients, err := scenario.ListTailscaleClients("user1")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
user2Clients, err := scenario.ListTailscaleClients("user2")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Test that user1 can visit all user2
|
||||||
|
for _, client := range user1Clients {
|
||||||
|
for _, peer := range user2Clients {
|
||||||
|
fqdn, err := peer.FQDN()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
|
||||||
|
t.Logf("url from %s to %s", client.Hostname(), url)
|
||||||
|
|
||||||
|
result, err := client.Curl(url)
|
||||||
|
assert.Len(t, result, 13)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that user2 _cannot_ visit user1
|
||||||
|
for _, client := range user2Clients {
|
||||||
|
for _, peer := range user1Clients {
|
||||||
|
fqdn, err := peer.FQDN()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
|
||||||
|
t.Logf("url from %s to %s", client.Hostname(), url)
|
||||||
|
|
||||||
|
result, err := client.Curl(url)
|
||||||
|
assert.Empty(t, result)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scenario.Shutdown()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test to confirm that we can use *:* from one user
|
||||||
|
// Reported: https://github.com/juanfont/headscale/issues/699
|
||||||
|
func TestACLAllowStarDst(t *testing.T) {
|
||||||
|
IntegrationSkip(t)
|
||||||
|
|
||||||
|
scenario := aclScenario(t,
|
||||||
|
headscale.ACLPolicy{
|
||||||
|
ACLs: []headscale.ACL{
|
||||||
|
{
|
||||||
|
Action: "accept",
|
||||||
|
Sources: []string{"user1"},
|
||||||
|
Destinations: []string{"*:*"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
user1Clients, err := scenario.ListTailscaleClients("user1")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
user2Clients, err := scenario.ListTailscaleClients("user2")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Test that user1 can visit all user2
|
||||||
|
for _, client := range user1Clients {
|
||||||
|
for _, peer := range user2Clients {
|
||||||
|
fqdn, err := peer.FQDN()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
|
||||||
|
t.Logf("url from %s to %s", client.Hostname(), url)
|
||||||
|
|
||||||
|
result, err := client.Curl(url)
|
||||||
|
assert.Len(t, result, 13)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that user2 _cannot_ visit user1
|
||||||
|
for _, client := range user2Clients {
|
||||||
|
for _, peer := range user1Clients {
|
||||||
|
fqdn, err := peer.FQDN()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
|
||||||
|
t.Logf("url from %s to %s", client.Hostname(), url)
|
||||||
|
|
||||||
|
result, err := client.Curl(url)
|
||||||
|
assert.Empty(t, result)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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": {}
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
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.
|
||||||
|
func TestACLNamedHostsCanReachBySubnet(t *testing.T) {
|
||||||
|
IntegrationSkip(t)
|
||||||
|
|
||||||
|
scenario := aclScenario(t,
|
||||||
|
headscale.ACLPolicy{
|
||||||
|
Hosts: headscale.Hosts{
|
||||||
|
"all": netip.MustParsePrefix("100.64.0.0/24"),
|
||||||
|
},
|
||||||
|
ACLs: []headscale.ACL{
|
||||||
|
// Everyone can curl test3
|
||||||
|
{
|
||||||
|
Action: "accept",
|
||||||
|
Sources: []string{"*"},
|
||||||
|
Destinations: []string{"all:*"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
user1Clients, err := scenario.ListTailscaleClients("user1")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
user2Clients, err := scenario.ListTailscaleClients("user2")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Test that user1 can visit all user2
|
||||||
|
for _, client := range user1Clients {
|
||||||
|
for _, peer := range user2Clients {
|
||||||
|
fqdn, err := peer.FQDN()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
|
||||||
|
t.Logf("url from %s to %s", client.Hostname(), url)
|
||||||
|
|
||||||
|
result, err := client.Curl(url)
|
||||||
|
assert.Len(t, result, 13)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that user2 can visit all user1
|
||||||
|
for _, client := range user2Clients {
|
||||||
|
for _, peer := range user1Clients {
|
||||||
|
fqdn, err := peer.FQDN()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
|
||||||
|
t.Logf("url from %s to %s", client.Hostname(), url)
|
||||||
|
|
||||||
|
result, err := client.Curl(url)
|
||||||
|
assert.Len(t, result, 13)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scenario.Shutdown()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
23
machine.go
23
machine.go
@@ -170,13 +170,14 @@ func (h *Headscale) filterMachinesByACL(currentMachine *Machine, peers Machines)
|
|||||||
// filterMachinesByACL returns the list of peers authorized to be accessed from a given machine.
|
// filterMachinesByACL returns the list of peers authorized to be accessed from a given machine.
|
||||||
func filterMachinesByACL(
|
func filterMachinesByACL(
|
||||||
machine *Machine,
|
machine *Machine,
|
||||||
machines []Machine,
|
machines Machines,
|
||||||
lock *sync.RWMutex,
|
lock *sync.RWMutex,
|
||||||
aclPeerCacheMap map[string]map[string]struct{},
|
aclPeerCacheMap map[string]map[string]struct{},
|
||||||
) Machines {
|
) Machines {
|
||||||
log.Trace().
|
log.Trace().
|
||||||
Caller().
|
Caller().
|
||||||
Str("machine", machine.Hostname).
|
Str("self", machine.Hostname).
|
||||||
|
Str("input", machines.String()).
|
||||||
Msg("Finding peers filtered by ACLs")
|
Msg("Finding peers filtered by ACLs")
|
||||||
|
|
||||||
peers := make(map[uint64]Machine)
|
peers := make(map[uint64]Machine)
|
||||||
@@ -243,6 +244,12 @@ func filterMachinesByACL(
|
|||||||
|
|
||||||
for _, peerIP := range peerIPs {
|
for _, peerIP := range peerIPs {
|
||||||
if dstMap, ok := aclPeerCacheMap[peerIP]; ok {
|
if dstMap, ok := aclPeerCacheMap[peerIP]; ok {
|
||||||
|
// match source and all destination
|
||||||
|
if _, dstOk := dstMap["*"]; dstOk {
|
||||||
|
peers[peer.ID] = peer
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
// match return path
|
// match return path
|
||||||
for _, machineIP := range machineIPs {
|
for _, machineIP := range machineIPs {
|
||||||
if _, dstOk := dstMap[machineIP]; dstOk {
|
if _, dstOk := dstMap[machineIP]; dstOk {
|
||||||
@@ -257,7 +264,7 @@ func filterMachinesByACL(
|
|||||||
|
|
||||||
lock.RUnlock()
|
lock.RUnlock()
|
||||||
|
|
||||||
authorizedPeers := make([]Machine, 0, len(peers))
|
authorizedPeers := make(Machines, 0, len(peers))
|
||||||
for _, m := range peers {
|
for _, m := range peers {
|
||||||
authorizedPeers = append(authorizedPeers, m)
|
authorizedPeers = append(authorizedPeers, m)
|
||||||
}
|
}
|
||||||
@@ -268,8 +275,9 @@ func filterMachinesByACL(
|
|||||||
|
|
||||||
log.Trace().
|
log.Trace().
|
||||||
Caller().
|
Caller().
|
||||||
Str("machine", machine.Hostname).
|
Str("self", machine.Hostname).
|
||||||
Msgf("Found some machines: %v", machines)
|
Str("peers", authorizedPeers.String()).
|
||||||
|
Msg("Authorized peers")
|
||||||
|
|
||||||
return authorizedPeers
|
return authorizedPeers
|
||||||
}
|
}
|
||||||
@@ -329,8 +337,9 @@ func (h *Headscale) getPeers(machine *Machine) (Machines, error) {
|
|||||||
|
|
||||||
log.Trace().
|
log.Trace().
|
||||||
Caller().
|
Caller().
|
||||||
Str("machine", machine.Hostname).
|
Str("self", machine.Hostname).
|
||||||
Msgf("Found total peers: %s", peers.String())
|
Str("peers", peers.String()).
|
||||||
|
Msg("Peers returned to caller")
|
||||||
|
|
||||||
return peers, nil
|
return peers, nil
|
||||||
}
|
}
|
||||||
|
@@ -282,10 +282,10 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
|
|||||||
peersOfAdminMachine := app.filterMachinesByACL(adminMachine, machines)
|
peersOfAdminMachine := app.filterMachinesByACL(adminMachine, machines)
|
||||||
|
|
||||||
c.Log(peersOfTestMachine)
|
c.Log(peersOfTestMachine)
|
||||||
c.Assert(len(peersOfTestMachine), check.Equals, 4)
|
c.Assert(len(peersOfTestMachine), check.Equals, 9)
|
||||||
c.Assert(peersOfTestMachine[0].Hostname, check.Equals, "testmachine4")
|
c.Assert(peersOfTestMachine[0].Hostname, check.Equals, "testmachine1")
|
||||||
c.Assert(peersOfTestMachine[1].Hostname, check.Equals, "testmachine6")
|
c.Assert(peersOfTestMachine[1].Hostname, check.Equals, "testmachine3")
|
||||||
c.Assert(peersOfTestMachine[3].Hostname, check.Equals, "testmachine10")
|
c.Assert(peersOfTestMachine[3].Hostname, check.Equals, "testmachine5")
|
||||||
|
|
||||||
c.Log(peersOfAdminMachine)
|
c.Log(peersOfAdminMachine)
|
||||||
c.Assert(len(peersOfAdminMachine), check.Equals, 9)
|
c.Assert(len(peersOfAdminMachine), check.Equals, 9)
|
||||||
@@ -950,6 +950,96 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
|||||||
},
|
},
|
||||||
want: Machines{},
|
want: Machines{},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Investigating 699
|
||||||
|
// Found some machines: [ts-head-8w6paa ts-unstable-lys2ib ts-head-upcrmb ts-unstable-rlwpvr] machine=ts-head-8w6paa
|
||||||
|
// ACL rules generated ACL=[{"DstPorts":[{"Bits":null,"IP":"*","Ports":{"First":0,"Last":65535}}],"SrcIPs":["fd7a:115c:a1e0::3","100.64.0.3","fd7a:115c:a1e0::4","100.64.0.4"]}]
|
||||||
|
// ACL Cache Map={"100.64.0.3":{"*":{}},"100.64.0.4":{"*":{}},"fd7a:115c:a1e0::3":{"*":{}},"fd7a:115c:a1e0::4":{"*":{}}}
|
||||||
|
name: "issue-699-broken-star",
|
||||||
|
args: args{
|
||||||
|
machines: Machines{ //
|
||||||
|
{
|
||||||
|
ID: 1,
|
||||||
|
Hostname: "ts-head-upcrmb",
|
||||||
|
IPAddresses: MachineAddresses{
|
||||||
|
netip.MustParseAddr("100.64.0.3"),
|
||||||
|
netip.MustParseAddr("fd7a:115c:a1e0::3"),
|
||||||
|
},
|
||||||
|
User: User{Name: "user1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 2,
|
||||||
|
Hostname: "ts-unstable-rlwpvr",
|
||||||
|
IPAddresses: MachineAddresses{
|
||||||
|
netip.MustParseAddr("100.64.0.4"),
|
||||||
|
netip.MustParseAddr("fd7a:115c:a1e0::4"),
|
||||||
|
},
|
||||||
|
User: User{Name: "user1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 3,
|
||||||
|
Hostname: "ts-head-8w6paa",
|
||||||
|
IPAddresses: MachineAddresses{
|
||||||
|
netip.MustParseAddr("100.64.0.1"),
|
||||||
|
netip.MustParseAddr("fd7a:115c:a1e0::1"),
|
||||||
|
},
|
||||||
|
User: User{Name: "user2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 4,
|
||||||
|
Hostname: "ts-unstable-lys2ib",
|
||||||
|
IPAddresses: MachineAddresses{
|
||||||
|
netip.MustParseAddr("100.64.0.2"),
|
||||||
|
netip.MustParseAddr("fd7a:115c:a1e0::2"),
|
||||||
|
},
|
||||||
|
User: User{Name: "user2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: []tailcfg.FilterRule{ // list of all ACLRules registered
|
||||||
|
{
|
||||||
|
DstPorts: []tailcfg.NetPortRange{
|
||||||
|
{
|
||||||
|
IP: "*",
|
||||||
|
Ports: tailcfg.PortRange{First: 0, Last: 65535},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SrcIPs: []string{
|
||||||
|
"fd7a:115c:a1e0::3", "100.64.0.3",
|
||||||
|
"fd7a:115c:a1e0::4", "100.64.0.4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
machine: &Machine{ // current machine
|
||||||
|
ID: 3,
|
||||||
|
Hostname: "ts-head-8w6paa",
|
||||||
|
IPAddresses: MachineAddresses{
|
||||||
|
netip.MustParseAddr("100.64.0.1"),
|
||||||
|
netip.MustParseAddr("fd7a:115c:a1e0::1"),
|
||||||
|
},
|
||||||
|
User: User{Name: "user2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: Machines{
|
||||||
|
{
|
||||||
|
ID: 1,
|
||||||
|
Hostname: "ts-head-upcrmb",
|
||||||
|
IPAddresses: MachineAddresses{
|
||||||
|
netip.MustParseAddr("100.64.0.3"),
|
||||||
|
netip.MustParseAddr("fd7a:115c:a1e0::3"),
|
||||||
|
},
|
||||||
|
User: User{Name: "user1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 2,
|
||||||
|
Hostname: "ts-unstable-rlwpvr",
|
||||||
|
IPAddresses: MachineAddresses{
|
||||||
|
netip.MustParseAddr("100.64.0.4"),
|
||||||
|
netip.MustParseAddr("fd7a:115c:a1e0::4"),
|
||||||
|
},
|
||||||
|
User: User{Name: "user1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
var lock sync.RWMutex
|
var lock sync.RWMutex
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@@ -74,7 +74,7 @@
|
|||||||
clicking the icon in the system tray
|
clicking the icon in the system tray
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
<p>Or</p>
|
<p>Or using REG:</p>
|
||||||
<p>
|
<p>
|
||||||
Open command prompt with Administrator rights. Issue the following
|
Open command prompt with Administrator rights. Issue the following
|
||||||
commands to add the required registry entries:
|
commands to add the required registry entries:
|
||||||
@@ -83,7 +83,16 @@
|
|||||||
<code>REG ADD "HKLM\Software\Tailscale IPN" /v UnattendedMode /t REG_SZ /d always
|
<code>REG ADD "HKLM\Software\Tailscale IPN" /v UnattendedMode /t REG_SZ /d always
|
||||||
REG ADD "HKLM\Software\Tailscale IPN" /v LoginURL /t REG_SZ /d "{{.URL}}"</code>
|
REG ADD "HKLM\Software\Tailscale IPN" /v LoginURL /t REG_SZ /d "{{.URL}}"</code>
|
||||||
</pre>
|
</pre>
|
||||||
<p>Restart Tailscale and log in.</p>
|
<p>Or using Powershell</p>
|
||||||
|
<p>
|
||||||
|
Open Powershell with Administrator rights. Issue the following commands to
|
||||||
|
add the required registry entries:
|
||||||
|
</p>
|
||||||
|
<pre>
|
||||||
|
<code>New-ItemProperty -Path 'HKLM:\Software\Tailscale IPN' -Name UnattendedMode -PropertyType String -Value always
|
||||||
|
New-ItemProperty -Path 'HKLM:\Software\Tailscale IPN' -Name LoginURL -PropertyType String -Value "{{.URL}}"</code>
|
||||||
|
</pre>
|
||||||
|
<p>Finally, restart Tailscale and log in.</p>
|
||||||
|
|
||||||
<p></p>
|
<p></p>
|
||||||
</body>
|
</body>
|
||||||
|
Reference in New Issue
Block a user